Skip to content
Closed
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
162 changes: 162 additions & 0 deletions packages/cli/src/deploy-command.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtemp, writeFile } from 'node:fs/promises';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The temporary directories created by mkdtemp in writeOneClickFixture are never cleaned up, which can clutter the system's temporary directory over time. Consider importing rm from node:fs/promises and cleaning up the created directory in a finally block within each test.

Suggested change
import { mkdtemp, writeFile } from 'node:fs/promises';
import { mkdtemp, writeFile, rm } from 'node:fs/promises';

import { tmpdir } from 'node:os';
import path from 'node:path';
import {
configureDeployCommandForTest,
parseDeployArgs,
runDeploy,
runLogin,
runLogout
} from './deploy-command.js';
Expand Down Expand Up @@ -54,6 +57,58 @@ function trapExit(throwOnExit = true): ExitTrap {
return trap;
}

async function writeOneClickFixture(options: {
manifestInputs?: Record<string, string>;
} = {}): Promise<{ dir: string; manifestPath: string; personaPath: string }> {
const dir = await mkdtemp(path.join(tmpdir(), 'workforce-one-click-'));
const personaPath = path.join(dir, 'persona.json');
const manifestPath = path.join(dir, 'agent.manifest.json');
await writeFile(
personaPath,
JSON.stringify({
id: 'issue-triage',
intent: 'documentation',
description: 'Triage incoming issues',
skills: [],
harness: 'claude',
model: 'anthropic/claude-3-5-sonnet',
systemPrompt: 'Triage issues for ${TEAM}.',
harnessSettings: { reasoning: 'medium', timeoutSeconds: 300 },
cloud: true,
integrations: {
github: {
triggers: [{ on: 'issues.opened' }, { on: 'issues.edited' }]
}
},
schedules: [{ name: 'daily', cron: '0 9 * * *', tz: 'UTC' }],
inputs: {
TEAM: { description: 'Team name' },
OPTIONAL_NOTE: { optional: true }
},
onEvent: './agent.ts'
}),
'utf8'
);
await writeFile(
manifestPath,
JSON.stringify({
schema: 'agent-manifest/v1',
name: 'Issue Triage',
persona: './persona.json',
workspace: 'manifest-workspace',
integrations: {
github: { required: true, reason: 'watches repository issues' }
},
secrets: {
NangoSecretKey: { required: true, reason: 'Layer-B only' }
},
...(options.manifestInputs ? { inputs: options.manifestInputs } : {})
}),
'utf8'
);
return { dir, manifestPath, personaPath };
}

test('runLogin uses cloud SDK auth, picks a workspace, and writes the active pointer (no token mint)', async () => {
const calls: string[] = [];
const writes: unknown[] = [];
Expand Down Expand Up @@ -104,6 +159,113 @@ test('runLogin uses cloud SDK auth, picks a workspace, and writes the active poi
}
});

test('runDeploy --one-click --dry-run prints the Layer-A plan without deploying', async () => {
const { manifestPath } = await writeOneClickFixture({ manifestInputs: { TEAM: 'platform' } });
const deployCalls: unknown[] = [];
const restoreDeps = configureDeployCommandForTest({
createTerminalIO: () => createBufferedIO(),
deploy: async (opts) => {
deployCalls.push(opts);
throw new Error('deploy should not be called during one-click dry-run');
}
});
const trap = trapExit(false);
try {
await runDeploy(['--one-click', manifestPath, '--dry-run']);
assert.deepEqual(trap.exits, [0]);
assert.deepEqual(deployCalls, []);
assert.match(trap.stdout, /one-click deploy plan/);
assert.match(trap.stdout, /persona: issue-triage/);
assert.match(trap.stdout, /workspace: manifest-workspace/);
assert.match(trap.stdout, /connect integrations:/);
assert.match(trap.stdout, /github \(required\): issues\.opened, issues\.edited/);
assert.match(trap.stdout, /watches repository issues/);
assert.match(trap.stdout, /required inputs: none/);
assert.match(trap.stdout, /platform secrets: none required \(shared platform\)/);
assert.match(trap.stdout, /fires on: github:issues\.opened, github:issues\.edited, schedule:daily/);
} finally {
trap.restore();
restoreDeps();
}
});

test('runDeploy --one-click --dry-run treats CLI-supplied inputs as provided in the plan', async () => {
const { manifestPath } = await writeOneClickFixture();
const restoreDeps = configureDeployCommandForTest({
createTerminalIO: () => createBufferedIO()
});
const trap = trapExit(false);
try {
await runDeploy(['--one-click', manifestPath, '--dry-run', '--input', 'TEAM=cli']);
assert.deepEqual(trap.exits, [0]);
assert.match(trap.stdout, /required inputs: none/);
assert.match(trap.stdout, /provided inputs: TEAM/);
} finally {
trap.restore();
restoreDeps();
}
});

test('runDeploy --one-click deploys the resolved persona in cloud update mode', async () => {
const { manifestPath, personaPath } = await writeOneClickFixture({ manifestInputs: { TEAM: 'platform' } });
const deployCalls: unknown[] = [];
const restoreDeps = configureDeployCommandForTest({
createTerminalIO: () => createBufferedIO(),
deploy: async (opts) => {
deployCalls.push(opts);
return {
deploymentId: 'dep_123',
mode: 'cloud',
workspace: opts.workspace ?? 'default',
bundleDir: '/tmp/bundle',
connectedIntegrations: ['github'],
schedules: ['daily'],
warnings: [],
runHandle: {
id: 'agent_123',
agentId: 'agent_123',
deploymentId: 'dep_123',
status: 'ready',
stop: async () => {},
done: Promise.resolve({ code: 0 })
}
};
}
});
const trap = trapExit(false);
try {
await runDeploy([
'--one-click',
manifestPath,
'--yes',
'--workspace',
'cli-workspace',
'--cloud-url',
'https://cloud.example.test/',
'--input',
'TEAM=cli'
]);
assert.deepEqual(trap.exits, [0]);
assert.equal(deployCalls.length, 1);
const deployOpts = deployCalls[0] as Record<string, unknown>;
assert.equal(deployOpts.personaPath, personaPath);
assert.equal(deployOpts.mode, 'cloud');
assert.equal(deployOpts.workspace, 'cli-workspace');
assert.equal(deployOpts.cloudUrl, 'https://cloud.example.test/');
assert.equal(deployOpts.noPrompt, true);
assert.equal(deployOpts.noConnect, true);
assert.equal(deployOpts.onExists, 'update');
assert.deepEqual(deployOpts.inputs, { TEAM: 'cli' });
assert.equal(typeof deployOpts.io, 'object');
assert.match(trap.stdout, /ok: dep_123 \(mode=cloud, workspace=cli-workspace\)/);
assert.match(trap.stdout, /agent: agent_123/);
assert.match(trap.stdout, /fires on: github:issues\.opened, github:issues\.edited, schedule:daily/);
} finally {
trap.restore();
restoreDeps();
}
});

test('runLogin with --workspace skips the workspaces list, skips token mint, writes active pointer', async () => {
const calls: string[] = [];
const writes: unknown[] = [];
Expand Down
Loading