Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/lib/commands/run-on-cloud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ export async function* runActorOrTaskOnCloud(apifyClient: ApifyClient, options:
throw new Error(`${type} ${actorOrTaskData.userFriendlyId} (${actorOrTaskData.id}) not found!`);
}

if (err.type === 'full-permission-actor-not-approved') {
const approvalUrl: string | undefined = err.data?.approvalUrl;
const lines = [
`${type} ${actorOrTaskData.userFriendlyId} requires full access to your Apify account and has not been approved yet.`,
];
if (approvalUrl) {
lines.push('', `Approve here: ${chalk.blue(approvalUrl)}`);
}
throw new Error(lines.join('\n'));
}

throw err;
}

Expand Down
57 changes: 57 additions & 0 deletions test/local/lib/run-on-cloud.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { ApifyClient } from 'apify-client';

import { runActorOrTaskOnCloud } from '../../../src/lib/commands/run-on-cloud.js';

const fakeClientThatThrows = (error: unknown) =>
({
actor: () => ({
start: async () => {
throw error;
},
}),
}) as unknown as ApifyClient;

const callAndCatch = async (apiError: unknown) => {
const iterator = runActorOrTaskOnCloud(fakeClientThatThrows(apiError), {
actorOrTaskData: { id: 'abc', userFriendlyId: 'apify/test-actor' },
runOptions: {},
type: 'Actor',
silent: true,
});

try {
for await (const _ of iterator) {
// drain
}
} catch (err) {
return err as Error;
}

throw new Error('Expected runActorOrTaskOnCloud to throw');
};

describe('runActorOrTaskOnCloud', () => {
it('surfaces approval URL when Actor requires full account access', async () => {
const approvalUrl = 'https://console.apify.com/actors/abc?approvePermissions=true';
const apiError = Object.assign(new Error('This Actor requires full access to your account.'), {
type: 'full-permission-actor-not-approved',
data: { approvalUrl },
});

const err = await callAndCatch(apiError);

expect(err.message).toMatch(/has not been approved yet/);
expect(err.message).toContain(approvalUrl);
});

it('falls back to bare message when API response has no approvalUrl', async () => {
const apiError = Object.assign(new Error('This Actor requires full access to your account.'), {
type: 'full-permission-actor-not-approved',
});

const err = await callAndCatch(apiError);

expect(err.message).toMatch(/has not been approved yet/);
expect(err.message).not.toMatch(/Approve here/);
});
});
Loading