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
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/);
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
Loading