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
140 changes: 140 additions & 0 deletions packages/cli/src/deploy-command.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { join } from 'node:path';
import {
configureDeployCommandForTest,
parseDeployArgs,
runDeploy,
runLogin,
runLogout
} from './deploy-command.js';
Expand Down Expand Up @@ -336,6 +340,142 @@ test('parseDeployArgs: malformed --input exits with clean error', () => {
}
});

test('runDeploy --one-click reads persona.json, renders plan, and delegates cloud update deploy', async () => {
const root = mkdtempSync(join(tmpdir(), 'aw-one-click-json-'));
const personaPath = join(root, 'persona.json');
const agentPath = join(root, 'agent.ts');
writeFileSync(agentPath, 'export async function onEvent() {}\n', 'utf8');
writeFileSync(
personaPath,
JSON.stringify({
id: 'issue-triage',
intent: 'review',
tags: ['review'],
description: 'Triage incoming issues.',
cloud: true,
integrations: {
github: { triggers: [{ on: 'issues.opened' }] },
slack: { optional: true, triggers: [{ on: 'message.channels' }] }
},
schedules: [{ name: 'daily', cron: '0 9 * * *', tz: 'UTC' }],
inputs: {
LEAD: { description: 'Owner to mention', env: 'TRIAGE_LEAD' },
OPTIONAL_NOTE: { optional: true }
},
onEvent: './agent.ts',
harnessSettings: { reasoning: 'medium', timeoutSeconds: 60 }
}, null, 2),
'utf8'
);
const deployCalls: unknown[] = [];
const restoreDeps = configureDeployCommandForTest({
deploy: async (opts: unknown) => {
deployCalls.push(opts);
return {
deploymentId: 'issue-triage',
mode: 'cloud',
workspace: 'ws-1',
bundleDir: root,
connectedIntegrations: ['github'],
schedules: ['daily'],
warnings: []
};
}
});
const trap = trapExit(false);
try {
await runDeploy([
'--one-click',
personaPath,
'--workspace',
'ws-1',
'--input',
'LEAD=alice',
'--cloud-url',
'https://cloud.example.test',
'--no-prompt'
]);

assert.deepEqual(trap.exits, [0]);
assert.equal(deployCalls.length, 1);
assert.deepEqual(deployCalls[0], {
personaPath,
mode: 'cloud',
onExists: 'update',
workspace: 'ws-1',
inputs: { LEAD: 'alice' },
cloudUrl: 'https://cloud.example.test',
noConnect: true,
noPrompt: true
});
assert.match(trap.stdout, /One-click deploy plan/);
assert.match(trap.stdout, /Fires on:\n- github:issues\.opened\n- slack:message\.channels\n- schedule:daily/);
assert.match(trap.stdout, /Integrations:\n- github \(required\): issues\.opened\n- slack \(optional\): message\.channels/);
assert.match(trap.stdout, /Required inputs: none/);
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

Update the test assertion to match the improved plan output that lists the resolved required inputs.

Suggested change
assert.match(trap.stdout, /Required inputs: none/);
assert.match(trap.stdout, /Required inputs:\n- LEAD=alice env=TRIAGE_LEAD - Owner to mention/);

assert.match(trap.stdout, /Platform secrets: none required \(shared platform\)/);
assert.match(trap.stdout, /ok: issue-triage \(mode=cloud, workspace=ws-1\)/);
} finally {
trap.restore();
restoreDeps();
rmSync(root, { recursive: true, force: true });
}
});

test('runDeploy --one-click compiles persona.ts and dry-runs the plan without deploy side effects', async () => {
const root = mkdtempSync(join(tmpdir(), 'aw-one-click-ts-'));
const personaPath = join(root, 'persona.ts');
writeFileSync(join(root, 'agent.ts'), 'export async function onEvent() {}\n', 'utf8');
writeFileSync(
personaPath,
`import { definePersona } from '@agentworkforce/persona-kit';

export default definePersona({
id: 'compiled-one-click',
intent: 'review',
tags: ['review'],
description: 'Compiled one-click persona.',
cloud: true,
integrations: {
linear: { triggers: [{ on: 'issue.created' }] }
},
inputs: {
TEAM: { description: 'Team key' }
},
onEvent: './agent.ts',
harnessSettings: { reasoning: 'medium', timeoutSeconds: 60 }
});
`,
'utf8'
);
const deployCalls: unknown[] = [];
const restoreDeps = configureDeployCommandForTest({
deploy: async (opts: unknown) => {
deployCalls.push(opts);
throw new Error('deploy should not run for --one-click --dry-run');
}
});
const trap = trapExit(false);
try {
await runDeploy([
'--one-click',
personaPath,
'--input',
'TEAM=eng',
'--dry-run'
]);

assert.deepEqual(trap.exits, [0]);
assert.deepEqual(deployCalls, []);
assert.match(trap.stdout, /One-click deploy plan/);
assert.match(trap.stdout, /linear \(required\): issue\.created/);
assert.match(trap.stdout, /--dry-run: plan rendered; exiting before deploy/);
} finally {
trap.restore();
restoreDeps();
rmSync(root, { recursive: true, force: true });
}
});

test('runLogin canonicalizes origin.agentrelay.cloud apiUrl before persisting active.json', async () => {
// ensureAuthenticated occasionally returns auth.apiUrl pointing at the
// SST origin-bypass hostname. If we persist that, every subsequent API
Expand Down
Loading