diff --git a/src/commands/actors/info.ts b/src/commands/actors/info.ts index 91faaf9b6..8f4e36701 100644 --- a/src/commands/actors/info.ts +++ b/src/commands/actors/info.ts @@ -142,6 +142,7 @@ export class ActorsInfoCommand extends ApifyCommand { } simpleLog({ message: latest.build.readme, stdout: true }); + return; } if (input) { @@ -164,6 +165,7 @@ export class ActorsInfoCommand extends ApifyCommand { } simpleLog({ message: latest.build.inputSchema, stdout: true }); + return; } const message = [ diff --git a/src/commands/builds/log.ts b/src/commands/builds/log.ts index 686f7eda9..5375234e7 100644 --- a/src/commands/builds/log.ts +++ b/src/commands/builds/log.ts @@ -1,6 +1,6 @@ import { ApifyCommand } from '../../lib/command-framework/apify-command.js'; import { Args } from '../../lib/command-framework/args.js'; -import { error, info } from '../../lib/outputs.js'; +import { info } from '../../lib/outputs.js'; import { getLoggedClientOrThrow, outputJobLog } from '../../lib/utils.js'; export class BuildsLogCommand extends ApifyCommand { @@ -32,20 +32,11 @@ export class BuildsLogCommand extends ApifyCommand { const build = await apifyClient.build(buildId).get(); if (!build) { - error({ message: `Build with ID "${buildId}" was not found on your account.`, stdout: true }); - return; + throw new Error(`Build with ID "${buildId}" was not found on your account.`); } info({ message: `Log for build with ID "${buildId}":\n` }); - try { - await outputJobLog({ job: build, apifyClient }); - } catch (err) { - // This should never happen... - error({ - message: `Failed to get log for build with ID "${buildId}": ${(err as Error).message}`, - stdout: true, - }); - } + await outputJobLog({ job: build, apifyClient }); } } diff --git a/src/commands/builds/rm.ts b/src/commands/builds/rm.ts index 570202c33..d69fe6c6b 100644 --- a/src/commands/builds/rm.ts +++ b/src/commands/builds/rm.ts @@ -1,11 +1,11 @@ -import type { ActorTaggedBuild, ApifyApiError } from 'apify-client'; +import type { ActorTaggedBuild } from 'apify-client'; import { ApifyCommand } from '../../lib/command-framework/apify-command.js'; import { Args } from '../../lib/command-framework/args.js'; import { YesFlag } from '../../lib/command-framework/flags.js'; import { useInputConfirmation } from '../../lib/hooks/user-confirmations/useInputConfirmation.js'; import { useYesNoConfirm } from '../../lib/hooks/user-confirmations/useYesNoConfirm.js'; -import { error, info, success } from '../../lib/outputs.js'; +import { info, success } from '../../lib/outputs.js'; import { getLoggedClientOrThrow } from '../../lib/utils.js'; export class BuildsRmCommand extends ApifyCommand { @@ -47,8 +47,7 @@ export class BuildsRmCommand extends ApifyCommand { const build = await apifyClient.build(buildId).get(); if (!build) { - error({ message: `Build with ID "${buildId}" was not found on your account.`, stdout: true }); - return; + throw new Error(`Build with ID "${buildId}" was not found on your account.`); } const actor = await apifyClient.actor(build.actId).get(); @@ -91,16 +90,11 @@ export class BuildsRmCommand extends ApifyCommand { return; } - try { - await apifyClient.build(buildId).delete(); + await apifyClient.build(buildId).delete(); - success({ - message: `Build with ID "${buildId}" was deleted.`, - stdout: true, - }); - } catch (err) { - const casted = err as ApifyApiError; - error({ message: `Failed to delete build "${buildId}".\n ${casted.message || casted}`, stdout: true }); - } + success({ + message: `Build with ID "${buildId}" was deleted.`, + stdout: true, + }); } } diff --git a/test/e2e/commands/actor/generate-schema-types.test.ts b/test/e2e/commands/actor/generate-schema-types.test.ts new file mode 100644 index 000000000..80335c3ca --- /dev/null +++ b/test/e2e/commands/actor/generate-schema-types.test.ts @@ -0,0 +1,54 @@ +import { access, writeFile } from 'node:fs/promises'; +import path from 'node:path'; + +import { runCli } from '../../__helpers__/run-cli.js'; +import { createTestActor, removeTestActor, type TestActor } from '../../__helpers__/test-actor.js'; + +describe('[e2e] actor generate-schema-types', () => { + let actor: TestActor; + + beforeAll(async () => { + actor = await createTestActor(); + + const inputSchema = { + title: 'Test input schema', + description: 'A test input schema', + type: 'object', + schemaVersion: 1, + properties: { + testField: { + title: 'Test Field', + type: 'string', + description: 'A test field', + editor: 'textfield', + }, + }, + }; + + await writeFile(path.join(actor.dir, '.actor', 'INPUT_SCHEMA.json'), JSON.stringify(inputSchema, null, 2)); + }); + + afterAll(async () => { + if (actor) await removeTestActor(actor); + }); + + it('generates TypeScript types from input schema', async () => { + const result = await runCli('apify', ['actor', 'generate-schema-types'], { cwd: actor.dir }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(result.stderr).toContain('Generated types written to'); + + await expect(access(path.join(actor.dir, 'src', '.generated', 'actor', 'input.ts'))).resolves.toBeUndefined(); + }); + + it('respects --output flag', async () => { + const customDir = path.join(actor.dir, 'custom-types'); + + const result = await runCli('apify', ['actor', 'generate-schema-types', '--output', customDir], { + cwd: actor.dir, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + await expect(access(path.join(customDir, 'input.ts'))).resolves.toBeUndefined(); + }); +}); diff --git a/test/e2e/commands/actors/info.test.ts b/test/e2e/commands/actors/info.test.ts new file mode 100644 index 000000000..5bc688ee5 --- /dev/null +++ b/test/e2e/commands/actors/info.test.ts @@ -0,0 +1,48 @@ +import { randomBytes } from 'node:crypto'; + +import { runCli } from '../../__helpers__/run-cli.js'; + +describe('[e2e][api] actors info', () => { + let authEnv: Record; + + beforeAll(async () => { + const token = process.env.TEST_USER_TOKEN; + if (!token) throw new Error('TEST_USER_TOKEN env var is required for actors info tests'); + + const authPath = `e2e-actors-info-${randomBytes(6).toString('hex')}`; + authEnv = { __APIFY_INTERNAL_TEST_AUTH_PATH__: authPath }; + + const loginResult = await runCli('apify', ['login', '--token', token], { env: authEnv }); + if (loginResult.exitCode !== 0) { + throw new Error(`Failed to login:\n${loginResult.stderr}`); + } + }); + + it('shows info for a public actor (--json)', async () => { + const result = await runCli('apify', ['actors', 'info', 'apify/web-scraper', '--json'], { env: authEnv }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const data = JSON.parse(result.stdout); + expect(data.name).toBe('web-scraper'); + }); + + it('shows README with --readme flag', async () => { + const result = await runCli('apify', ['actors', 'info', 'apify/web-scraper', '--readme'], { env: authEnv }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(result.stdout.length).toBeGreaterThan(0); + }); + + it('shows input schema with --input flag', async () => { + const result = await runCli('apify', ['actors', 'info', 'apify/rag-web-browser', '--input'], { env: authEnv }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(() => JSON.parse(result.stdout)).not.toThrow(); + }); + + it('fails with invalid actor ID', async () => { + const result = await runCli('apify', ['actors', 'info', 'nonexistent/actor-xyz'], { env: authEnv }); + + expect(result.stdout).toContain('was not found'); + }); +}); diff --git a/test/e2e/commands/actors/ls.test.ts b/test/e2e/commands/actors/ls.test.ts new file mode 100644 index 000000000..1fd624e6a --- /dev/null +++ b/test/e2e/commands/actors/ls.test.ts @@ -0,0 +1,41 @@ +import { randomBytes } from 'node:crypto'; + +import { runCli } from '../../__helpers__/run-cli.js'; + +describe('[e2e][api] actors ls', () => { + let authEnv: Record; + + beforeAll(async () => { + const token = process.env.TEST_USER_TOKEN; + if (!token) throw new Error('TEST_USER_TOKEN env var is required for actors ls tests'); + + const authPath = `e2e-actors-ls-${randomBytes(6).toString('hex')}`; + authEnv = { __APIFY_INTERNAL_TEST_AUTH_PATH__: authPath }; + + const loginResult = await runCli('apify', ['login', '--token', token], { env: authEnv }); + if (loginResult.exitCode !== 0) { + throw new Error(`Failed to login:\n${loginResult.stderr}`); + } + }); + + it('lists recent actors (--json)', async () => { + const result = await runCli('apify', ['actors', 'ls', '--json'], { env: authEnv }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(() => JSON.parse(result.stdout)).not.toThrow(); + }); + + it('lists own actors with --my flag', async () => { + const result = await runCli('apify', ['actors', 'ls', '--my', '--json'], { env: authEnv }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(() => JSON.parse(result.stdout)).not.toThrow(); + }); + + it('respects --limit flag', async () => { + const result = await runCli('apify', ['actors', 'ls', '--my', '--limit', '5', '--json'], { env: authEnv }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(() => JSON.parse(result.stdout)).not.toThrow(); + }); +}); diff --git a/test/e2e/commands/actors/search.test.ts b/test/e2e/commands/actors/search.test.ts new file mode 100644 index 000000000..aa2d1eca2 --- /dev/null +++ b/test/e2e/commands/actors/search.test.ts @@ -0,0 +1,35 @@ +import { runCli } from '../../__helpers__/run-cli.js'; + +describe.concurrent('[e2e] actors search', () => { + it('finds actors by query', async () => { + const result = await runCli('apify', ['actors', 'search', 'web scraper', '--json']); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const data = JSON.parse(result.stdout); + expect(data.items.length).toBeGreaterThan(0); + }); + + it('respects --limit flag', async () => { + const result = await runCli('apify', ['actors', 'search', 'scraper', '--limit', '3', '--json']); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const data = JSON.parse(result.stdout); + expect(data.items.length).toBeLessThanOrEqual(3); + }); + + it('filters by pricing model', async () => { + const result = await runCli('apify', ['actors', 'search', '--pricing-model', 'FREE', '--limit', '5', '--json']); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const data = JSON.parse(result.stdout); + expect(data.items.length).toBeLessThanOrEqual(5); + }); + + it('returns empty results for nonexistent query', async () => { + const result = await runCli('apify', ['actors', 'search', 'xyznonexistent12345absolutelyfake', '--json']); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const data = JSON.parse(result.stdout); + expect(data.items.length).toBe(0); + }); +}); diff --git a/test/e2e/commands/auth/login.test.ts b/test/e2e/commands/auth/login.test.ts new file mode 100644 index 000000000..4a2b97f02 --- /dev/null +++ b/test/e2e/commands/auth/login.test.ts @@ -0,0 +1,38 @@ +import { randomBytes } from 'node:crypto'; + +import { runCli } from '../../__helpers__/run-cli.js'; + +describe('[e2e][api] auth login & token', () => { + const authEnv = { __APIFY_INTERNAL_TEST_AUTH_PATH__: `e2e-login-${randomBytes(6).toString('hex')}` }; + + it('logs in with a valid token', async () => { + const token = process.env.TEST_USER_TOKEN; + if (!token) throw new Error('TEST_USER_TOKEN env var is required'); + + const result = await runCli('apify', ['login', '--token', token], { env: authEnv }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(result.stderr).toContain('You are logged in to Apify as'); + }); + + it('fails with an invalid token', async () => { + const badAuthEnv = { __APIFY_INTERNAL_TEST_AUTH_PATH__: `e2e-badlogin-${randomBytes(6).toString('hex')}` }; + + const result = await runCli('apify', ['login', '--token', 'invalid-token-abc'], { env: badAuthEnv }); + + expect(result.stderr).toContain('Login to Apify failed'); + }); + + it('prints the current token after login', async () => { + const token = process.env.TEST_USER_TOKEN; + if (!token) throw new Error('TEST_USER_TOKEN env var is required'); + + // Ensure we're logged in + await runCli('apify', ['login', '--token', token], { env: authEnv }); + + const result = await runCli('apify', ['auth', 'token'], { env: authEnv }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(result.stdout.trim().length).toBeGreaterThan(0); + }); +}); diff --git a/test/e2e/commands/builds/create-info-ls.test.ts b/test/e2e/commands/builds/lifecycle.test.ts similarity index 60% rename from test/e2e/commands/builds/create-info-ls.test.ts rename to test/e2e/commands/builds/lifecycle.test.ts index b2e0e2cb4..e7c45be89 100644 --- a/test/e2e/commands/builds/create-info-ls.test.ts +++ b/test/e2e/commands/builds/lifecycle.test.ts @@ -10,12 +10,13 @@ describe('[e2e][api] builds namespace', () => { let actor: TestActor; let authEnv: Record; let client: ApifyClient; + // A finished, non-default build reused across info/log/tag/rm tests. Deleted in the last section. + let buildId: string; beforeAll(async () => { const token = process.env.TEST_USER_TOKEN; if (!token) throw new Error('TEST_USER_TOKEN env var is required for builds tests'); - // Unique auth path so parallel runs don't collide const authPath = `e2e-builds-${randomBytes(6).toString('hex')}`; authEnv = { __APIFY_INTERNAL_TEST_AUTH_PATH__: authPath }; @@ -26,9 +27,9 @@ describe('[e2e][api] builds namespace', () => { client = new ApifyClient(getApifyClientOptions(token)); - // Create and push actor actor = await createTestActor('e2e-builds'); + // Push creates the actor's default build const pushResult = await runCli('apify', ['push'], { cwd: actor.dir, env: authEnv, @@ -37,6 +38,22 @@ describe('[e2e][api] builds namespace', () => { if (pushResult.exitCode !== 0) { throw new Error(`Failed to push actor:\n${pushResult.stderr}`); } + + // Create a build for tests — then create another so the first is not the actor's default (which cannot be deleted) + const create = await runCli('apify', ['builds', 'create', '--json'], { + cwd: actor.dir, + env: authEnv, + }); + + buildId = JSON.parse(create.stdout).id; + await client.build(buildId).waitForFinish(); + + const create2 = await runCli('apify', ['builds', 'create', '--json'], { + cwd: actor.dir, + env: authEnv, + }); + + await client.build(JSON.parse(create2.stdout).id).waitForFinish(); }); afterAll(async () => { @@ -96,25 +113,6 @@ describe('[e2e][api] builds namespace', () => { }); describe('builds info', () => { - let buildId: string; - - beforeAll(async () => { - const create = await runCli('apify', ['builds', 'create'], { - cwd: actor.dir, - env: authEnv, - }); - - const id = - create.stdout.match(/Build Started \(ID: (\w+)\)/)?.[1] ?? - create.stderr.match(/Build Started \(ID: (\w+)\)/)?.[1]; - - if (!id) { - throw new Error(`Failed to capture build ID.\nstdout: ${create.stdout}\nstderr: ${create.stderr}`); - } - - buildId = id; - }); - it('fails with invalid build ID', async () => { const result = await runCli('apify', ['builds', 'info', 'invalid-id'], { cwd: actor.dir, @@ -153,4 +151,70 @@ describe('[e2e][api] builds namespace', () => { expect(() => JSON.parse(result.stdout)).not.toThrow(); }); }); + + describe('builds log', () => { + it('prints the build log', async () => { + const result = await runCli('apify', ['builds', 'log', buildId], { + cwd: actor.dir, + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(result.stderr).toContain('Log for build with ID'); + }); + + it('fails with invalid build ID', async () => { + const result = await runCli('apify', ['builds', 'log', 'invalid-id'], { + cwd: actor.dir, + env: authEnv, + }); + + expect(result.exitCode).not.toBe(0); + }); + }); + + describe('builds add-tag / remove-tag', () => { + const tag = `e2e-tag-${randomBytes(4).toString('hex')}`; + + it('adds a tag to a build', async () => { + const result = await runCli('apify', ['builds', 'add-tag', '--build', buildId, '--tag', tag], { + cwd: actor.dir, + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(result.stdout).toContain(`Tag "${tag}"`); + }); + + it('removes the tag from the build', async () => { + const result = await runCli('apify', ['builds', 'remove-tag', '--build', buildId, '--tag', tag, '--yes'], { + cwd: actor.dir, + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(result.stdout).toContain(`Tag "${tag}"`); + }); + }); + + describe('builds rm', () => { + it('deletes a build', async () => { + const result = await runCli('apify', ['builds', 'rm', buildId, '--yes'], { + cwd: actor.dir, + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(result.stdout).toContain('was deleted'); + }); + + it('fails with invalid build ID', async () => { + const result = await runCli('apify', ['builds', 'rm', 'invalid-id', '--yes'], { + cwd: actor.dir, + env: authEnv, + }); + + expect(result.exitCode).not.toBe(0); + }); + }); }); diff --git a/test/e2e/commands/info.test.ts b/test/e2e/commands/info.test.ts new file mode 100644 index 000000000..7feded1a9 --- /dev/null +++ b/test/e2e/commands/info.test.ts @@ -0,0 +1,33 @@ +import { randomBytes } from 'node:crypto'; + +import { runCli } from '../__helpers__/run-cli.js'; + +describe('[e2e][api] account info', () => { + const authEnv = { __APIFY_INTERNAL_TEST_AUTH_PATH__: `e2e-info-${randomBytes(6).toString('hex')}` }; + + beforeAll(async () => { + const token = process.env.TEST_USER_TOKEN; + if (!token) throw new Error('TEST_USER_TOKEN env var is required for info tests'); + + const loginResult = await runCli('apify', ['login', '--token', token], { env: authEnv }); + if (loginResult.exitCode !== 0) { + throw new Error(`Failed to login:\n${loginResult.stderr}`); + } + }); + + it('prints account details', async () => { + const result = await runCli('apify', ['info'], { env: authEnv }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(result.stdout).toContain('username:'); + expect(result.stdout).toContain('userId:'); + }); + + it('fails when not logged in', async () => { + const noAuthEnv = { __APIFY_INTERNAL_TEST_AUTH_PATH__: `e2e-noauth-${randomBytes(6).toString('hex')}` }; + + const result = await runCli('apify', ['info'], { env: noAuthEnv }); + + expect(result.exitCode).not.toBe(0); + }); +}); diff --git a/test/e2e/commands/runs/lifecycle.test.ts b/test/e2e/commands/runs/lifecycle.test.ts new file mode 100644 index 000000000..9c40111aa --- /dev/null +++ b/test/e2e/commands/runs/lifecycle.test.ts @@ -0,0 +1,177 @@ +import { randomBytes } from 'node:crypto'; +import { access, rm } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { ApifyClient } from 'apify-client'; + +import { getApifyClientOptions } from '../../../../src/lib/utils.js'; +import { runCli } from '../../__helpers__/run-cli.js'; +import { createTestActor, removeTestActor, type TestActor } from '../../__helpers__/test-actor.js'; + +const TestTmpRoot = fileURLToPath(new URL('../../../../tmp/', import.meta.url)); + +describe('[e2e][api] runs lifecycle', () => { + let actor: TestActor; + let authEnv: Record; + let client: ApifyClient; + let actorFullName: string; + let runId: string; + + beforeAll(async () => { + const token = process.env.TEST_USER_TOKEN; + if (!token) throw new Error('TEST_USER_TOKEN env var is required for runs tests'); + + const authPath = `e2e-runs-${randomBytes(6).toString('hex')}`; + authEnv = { __APIFY_INTERNAL_TEST_AUTH_PATH__: authPath }; + + const loginResult = await runCli('apify', ['login', '--token', token], { env: authEnv }); + if (loginResult.exitCode !== 0) { + throw new Error(`Failed to login:\n${loginResult.stderr}`); + } + + client = new ApifyClient(getApifyClientOptions(token)); + const me = await client.user('me').get(); + + actor = await createTestActor('e2e-runs'); + actorFullName = `${me.username}/${actor.name}`; + + const pushResult = await runCli('apify', ['push'], { + cwd: actor.dir, + env: authEnv, + }); + + if (pushResult.exitCode !== 0) { + throw new Error(`Push failed:\n${pushResult.stderr}\n${pushResult.stdout}`); + } + }, 300_000); + + afterAll(async () => { + if (actorFullName && client) { + try { + await client.actor(actorFullName).delete(); + } catch { + // Do nothing + } + } + + if (actor) await removeTestActor(actor); + }); + + it('actors start — starts a run', async () => { + const result = await runCli('apify', ['actors', 'start', actorFullName, '--json'], { + cwd: actor.dir, + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const data = JSON.parse(result.stdout); + expect(data.id).toBeTruthy(); + runId = data.id; + }); + + it('runs ls — lists runs', async () => { + const result = await runCli('apify', ['runs', 'ls', actorFullName, '--json'], { + cwd: actor.dir, + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const data = JSON.parse(result.stdout); + expect(data).toHaveProperty('items'); + const found = data.items.some((r: { id: string }) => r.id === runId); + expect(found).toBe(true); + }); + + it('runs info — shows run details', async () => { + await client.run(runId).waitForFinish(); + + const result = await runCli('apify', ['runs', 'info', runId, '--json'], { + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const data = JSON.parse(result.stdout); + expect(data).toHaveProperty('status'); + }); + + it('runs log — prints the run log', async () => { + const result = await runCli('apify', ['runs', 'log', runId], { + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(result.stdout).toContain('Log for run with ID'); + }); + + it('runs resurrect — resurrects a finished run', async () => { + const result = await runCli('apify', ['runs', 'resurrect', runId, '--json'], { + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const data = JSON.parse(result.stdout); + expect(data.id).toBe(runId); + }); + + it('runs abort — aborts a running run', async () => { + const result = await runCli('apify', ['runs', 'abort', runId, '--json'], { + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const data = JSON.parse(result.stdout); + expect(data.id).toBe(runId); + }); + + it('runs rm — deletes a run', async () => { + // Wait for abort to settle before deleting + await client.run(runId).waitForFinish(); + + const result = await runCli('apify', ['runs', 'rm', runId, '--yes'], { + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(result.stderr).toContain('was deleted'); + }); + + it('actors call — calls an actor and waits for completion', async () => { + const result = await runCli('apify', ['actors', 'call', actorFullName, '--json', '--input', '{"test":true}'], { + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const data = JSON.parse(result.stdout); + expect(data.status).toBe('SUCCEEDED'); + }, 120_000); + + it('pull — downloads actor source', async () => { + const dirName = `e2e-pull-${randomBytes(6).toString('hex')}`; + const pullDir = path.join(TestTmpRoot, dirName); + + try { + const result = await runCli('apify', ['pull', actorFullName, '--dir', dirName], { + cwd: TestTmpRoot, + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + await expect(access(path.join(pullDir, '.actor', 'actor.json'))).resolves.toBeUndefined(); + } finally { + await rm(pullDir, { recursive: true, force: true }); + } + }); + + it('actors rm — deletes the actor', async () => { + const result = await runCli('apify', ['actors', 'rm', actorFullName, '--yes'], { + env: authEnv, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + expect(result.stderr).toContain('was deleted'); + + // Clear so afterAll doesn't double-delete + actorFullName = ''; + }); +});