Skip to content
Merged
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
2 changes: 2 additions & 0 deletions src/commands/actors/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export class ActorsInfoCommand extends ApifyCommand<typeof ActorsInfoCommand> {
}

simpleLog({ message: latest.build.readme, stdout: true });
return;
}

if (input) {
Expand All @@ -164,6 +165,7 @@ export class ActorsInfoCommand extends ApifyCommand<typeof ActorsInfoCommand> {
}

simpleLog({ message: latest.build.inputSchema, stdout: true });
return;
}

const message = [
Expand Down
15 changes: 3 additions & 12 deletions src/commands/builds/log.ts
Original file line number Diff line number Diff line change
@@ -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<typeof BuildsLogCommand> {
Expand Down Expand Up @@ -32,20 +32,11 @@ export class BuildsLogCommand extends ApifyCommand<typeof BuildsLogCommand> {
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 });
}
}
22 changes: 8 additions & 14 deletions src/commands/builds/rm.ts
Original file line number Diff line number Diff line change
@@ -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<typeof BuildsRmCommand> {
Expand Down Expand Up @@ -47,8 +47,7 @@ export class BuildsRmCommand extends ApifyCommand<typeof BuildsRmCommand> {
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();
Expand Down Expand Up @@ -91,16 +90,11 @@ export class BuildsRmCommand extends ApifyCommand<typeof BuildsRmCommand> {
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,
});
}
}
54 changes: 54 additions & 0 deletions test/e2e/commands/actor/generate-schema-types.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
48 changes: 48 additions & 0 deletions test/e2e/commands/actors/info.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { randomBytes } from 'node:crypto';

import { runCli } from '../../__helpers__/run-cli.js';

describe('[e2e][api] actors info', () => {
let authEnv: Record<string, string>;

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');
});
});
41 changes: 41 additions & 0 deletions test/e2e/commands/actors/ls.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { randomBytes } from 'node:crypto';

import { runCli } from '../../__helpers__/run-cli.js';

describe('[e2e][api] actors ls', () => {
let authEnv: Record<string, string>;

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();
});
});
35 changes: 35 additions & 0 deletions test/e2e/commands/actors/search.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
38 changes: 38 additions & 0 deletions test/e2e/commands/auth/login.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading