diff --git a/README.md b/README.md index f09edcb4..7056d5bd 100644 --- a/README.md +++ b/README.md @@ -1198,6 +1198,53 @@ checkpoints only after stdout and every forwarding destination have accepted an event. A cursor gap stops with a nonzero exit unless `--on-gap continue` was selected. +## Trigger test events + +`polymorfa trigger` sends a signed test event for a Test number. The event +reaches your webhooks, event history, and a running `polymorfa listen` with +`source: "test"`, and does not change the Test number. Real numbers are +refused. Each project can trigger 30 test events per minute. It requires a +server API key with `sandbox:write` (`sandbox:read` for `--list`) and Test +numbers access. + +Run a listener in one terminal: + +```shell +polymorfa listen --project PROJECT_ID --forward-to http://127.0.0.1:3000/webhooks +``` + +Trigger events in another: + +```shell +polymorfa trigger --list --project PROJECT_ID +polymorfa trigger message.received --project PROJECT_ID --session my-test-number --text "hi" +polymorfa trigger message.received --project PROJECT_ID --session my-test-number \ + --override mediaType=image --override caption=look +polymorfa trigger message.ack --project PROJECT_ID --session my-test-number --override ackStatus=read +polymorfa trigger message.failed --project PROJECT_ID --session my-test-number +polymorfa trigger call.received --project PROJECT_ID --session my-test-number --override video=true +polymorfa trigger session.restriction_updated --project PROJECT_ID --session my-test-number \ + --override restrictionActive=false +polymorfa trigger template.status --project PROJECT_ID --session my-test-number \ + --override templateStatus=REJECTED --override reason=INVALID_FORMAT +polymorfa trigger bansafe.enforcement --project PROJECT_ID --session my-test-number +``` + +The trigger prints the event ID. Without `--forward-to`, the listener prints +the same line (`message.received `) when the event arrives; with +`--forward-to`, it prints nothing on success unless you add `--json` together +with `--forward-secret-out ` (JSON forwarding requires the secret file), +which writes one JSON line per event. Fixtures are +`message.received`, `message.ack`, `message.failed`, `call.received`, +`call.missed`, `call.ended`, `session.status`, `session.restriction_updated`, `template.status`, +`bansafe.enforcement`, and `bansafe.risk_changed`. `--override key=value` +sets one payload field and can be repeated; `--text` and `--from` are +shortcuts. `video` and `restrictionActive` accept only `true` or `false`; +`durationSeconds` accepts a whole number. Set `restrictionActive=false` to +test the end of a restriction. `--from-session OTHER_TEST_NUMBER` sends a real simulated text from +another Test number in the project instead, producing an ordinary +`message.received` event you can reply to. Use `--json` for one JSON document. + ## MCP listener controls The package includes a standards-based stdio MCP server for Codex and Claude: diff --git a/docs/internal/2026-09-22-trigger-fixture-parity.md b/docs/internal/2026-09-22-trigger-fixture-parity.md new file mode 100644 index 00000000..e8d12af6 --- /dev/null +++ b/docs/internal/2026-09-22-trigger-fixture-parity.md @@ -0,0 +1,80 @@ +# Trigger fixture parity + +Resolved on 2026-09-23: SDK PR #290 merged as +`2b693964305b91dc34b9092ca5774055c7e47d88` and published +`@polymorfa/sdk@0.1.0-dev.20260923093259`. CLI PR #15 now pins that exact +registry artifact in its manifest and lockfile. A clean install and the full +CLI verification suite passed on the branch merged with current `dev`. +The notes below record the earlier local repair and its then-open dependency. + +Local repair of CLI #15 at `67b1c0f43568c015c779ebf2c6475237f7b980a8`. +Not published or release-ready. The API contract checked is +`polymorfa/polymorfa@63111fec728ac3ebc9a825ea57ebc4c592abdafc`. + +The API lists `session.restriction_updated`, but the CLI's pinned SDK +`0.1.0-dev.20260922102711` omits it from the fixture union. The CLI consequently +refuses an event that `trigger --list` prints. Its override parser also treats +`restrictionActive=false` as a string. This repair types the override as a +boolean and tests both starting and ending a restriction through the command +and HTTP request. + +## SDK dependency + +Companion SDK work is based on `polymorfa/sdks@8392f66b4df0b0fee3403e27e3d2914516b0e243`, +branch `fix/test-event-fixture-contract-20260922`. It adds the fixture, +`restrictionActive?: boolean`, and the API's `call_restricted` member of +`callEndReason`. It stores the four relevant OpenAPI schemas, source commit, +and source hash in `contracts/testing-events.json` and checks the fixture list +against that snapshot. + +CLI validation uses a tarball built from that local SDK source. The package +manifest and lockfile intentionally still pin the published dependency. A +clean install of that old version cannot build this repair because its types +lack `restrictionActive`. Publish and inspect the companion SDK artifact, then +update the CLI's exact dependency and lockfile and repeat normal verification. +Do not replace the dependency with a local alias or invent a registry version. + +The three BanSafe event retirements on monorepo branch +`fix/declared-webhook-events` are not in this API baseline. When that change is +integrated, update this contract, the SDK fixture list, CLI examples, and tests +together. This repair retains the two test fixtures the pinned API accepts. + +## Runtime and authority + +The API mounts trigger and fixture-list routes under `/messaging/testing` +when a simulated Sandbox client is configured. List requires `sandbox:read`; +trigger requires `sandbox:write`. Project/team credentials are accepted by the +API; client tokens are denied. The CLI's existing command specifically uses +its server-API-key path and requires an explicit `--project`. + +Both routes check the live project and unified `test-numbers` feature. +This feature is a beta with release flag `release-test-numbers` and explicit +team enrollment. Missing enrollment, disabled release, or failed authority +does not grant access. No flag, credential, enrollment, or environment setting +changes in this repair. + +Generated events use the test publication path and cannot change number +state. A real number is rejected. `--from-session` only permits +`message.received`, text-only overrides, and a different Test number in the +same project. The API limits triggering to 30 requests per project per minute. + +Errors use the existing CLI mapping: validation/configuration exit 2, +authentication/authorization exit 3, not found/conflict exit 4, and rate limit, +network, timeout, or server failure exit 5. This command passes no idempotency +key, so the SDK does not automatically retry its POST. An uncertain response +must not be described as failed delivery. No new retry behavior is introduced. + +## Impact and release checks + +| Part | State | Evidence or remaining work | +| ---------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| API | Unaffected | Existing fixture and schema at the pinned revision; no handler, scope, or response change. | +| SDKs | Updated locally; publication blocked | Companion TypeScript catalog and override types, serializer tests, exact schema supplement, README. No new API method or other-language implementation is introduced. | +| CLI | Updated locally; dependency blocked | Boolean parser and command regression tests. Await the inspected published SDK, then update the exact pin and lockfile. | +| Docs | Updated locally | CLI README and SDK README cover the fixture and boolean input. Mintlify `docs/sandbox/test-events.mdx` already documents this existing API behavior. No shipped release announcement is added. Add CLI usage to public docs when the CLI is actually released. | +| Feature releases | Existing authority unchanged; live availability unverified | Existing Test numbers beta, targeting, enrollment and operational controls still apply. No provider or release setting was changed. | +| Admin | Unaffected | Test fixtures add no durable setting or grant. Existing Test numbers release/enrollment and webhook controls remain the authority; generated events cannot create state needing a new staff action. | + +Build and focused tests exercise a local HTTP receiver using the actual packed +SDK. They do not establish deployed beta availability, real webhook delivery, +listener delivery, or package publication. No automated reviews were requested. diff --git a/docs/internal/2026-09-23-trigger-launch-docs.md b/docs/internal/2026-09-23-trigger-launch-docs.md new file mode 100644 index 00000000..ad3e6d3b --- /dev/null +++ b/docs/internal/2026-09-23-trigger-launch-docs.md @@ -0,0 +1,13 @@ +# Prepared CLI trigger documentation + +`launch-docs/cli-trigger.mdx` is an internal insertion snippet for `docs/sandbox/test-events.mdx`, after its TypeScript SDK example and before the fixture list. The CLI is unpublished, so do not add the snippet or a CLI changelog entry to public Mintlify until a released CLI artifact includes this command, its installed behavior is checked, and the Test numbers beta audience can use the deployed API path. + +The snippet follows the CLI implementation and README at `polymorfa/cli` PR #15, SDK version `0.1.0-dev.20260923093259` from SDK merge commit `2b693964305b91dc34b9092ca5774055c7e47d88`, and the existing Test events guide. A clean CLI install resolved the exact SDK registry artifact. `npm run verify` passed on the branch merged with CLI `dev`: 635 tests in 73 files, build, typecheck, formatting, lint, pack check, and production audit. Compiled `trigger --help` confirms the snippet's flags and fixture name. The command tests exercise local HTTP requests, including typed `restrictionActive=false`, refusal paths, and no-request validation. No deployed API, webhook, event history, live listener, or released CLI install was exercised. + +At publication, verify the released CLI version and installed `trigger --help`, then insert the snippet into the existing guide. Render the page at mobile and desktop widths, run Mintlify broken-link and validation checks, and date a customer changelog entry for the available release. Confirm the `release-test-numbers` gate, team enrollment, Test number, and deployed API route for the intended audience. No flag or entitlement change is part of PR #15. + +Proposed changelog copy, with date set when customers can use the CLI: + +> The Polymorfa CLI can list Test event fixtures and trigger signed events for a Test number. It also sends a simulated message from another Test number with `--from-session`. See [Trigger test events](/sandbox/test-events). + +API: unchanged by this CLI work; the existing testing route and scopes remain authoritative. SDK: fixture and override types updated in SDK PR #290, then published under the exact version above. CLI: command, README, and tests updated in PR #15. Docs: CLI README and this internal snippet updated; public insertion remains a release gate. Feature releases: existing Test numbers beta access and enrollment remain; live availability is unverified here. Admin: unchanged because this command adds no staff control. diff --git a/docs/internal/launch-docs/cli-trigger.mdx b/docs/internal/launch-docs/cli-trigger.mdx new file mode 100644 index 00000000..90e0bb95 --- /dev/null +++ b/docs/internal/launch-docs/cli-trigger.mdx @@ -0,0 +1,14 @@ +## Trigger with the CLI + +Use a server API key with `sandbox:write` and a project with Test numbers access. Listing fixtures needs `sandbox:read`. + +```sh +polymorfa trigger --list --project PROJECT_ID +polymorfa trigger message.received --project PROJECT_ID --session my-test-number --text "hi" +polymorfa trigger session.restriction_updated --project PROJECT_ID --session my-test-number \ + --override restrictionActive=false +``` + +The command prints the event type and ID returned by the API. The event also appears in event history and reaches subscribed webhooks with `source: "test"`. A real number is refused without sending an event. + +To send a simulated message from another Test number in the same project, use `--from-session OTHER_TEST_NUMBER` with `message.received`. That sends a message you can reply to; the named fixtures above only generate events. diff --git a/package-lock.json b/package-lock.json index df915eac..31d2ef0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@modelcontextprotocol/server": "2.0.0", "@napi-rs/keyring": "1.3.0", "@oclif/core": "^4.13.5", - "@polymorfa/sdk": "0.1.0-dev.20260922115749", + "@polymorfa/sdk": "0.1.0-dev.20260923093259", "jq-wasm": "3.0.0-jq-1.8.2", "zod": "4.2.0" }, @@ -997,9 +997,9 @@ } }, "node_modules/@polymorfa/sdk": { - "version": "0.1.0-dev.20260922115749", - "resolved": "https://registry.npmjs.org/@polymorfa/sdk/-/sdk-0.1.0-dev.20260922115749.tgz", - "integrity": "sha512-ezfryhK10r6W84CUR3OFNHz1riChzWraSjKQuOZEE/pn+Okx9qMHhc34BDimorSeMWQnUW3pQKew12b2xrWyCw==", + "version": "0.1.0-dev.20260923093259", + "resolved": "https://registry.npmjs.org/@polymorfa/sdk/-/sdk-0.1.0-dev.20260923093259.tgz", + "integrity": "sha512-rmSvbH1Tp6gGEPC0asXGaZnX8swxHniDzcK9XaQLTVkMciqjRxDDP+DQeA5jVI6cI3U08Paev15ek43ziCywFQ==", "engines": { "node": ">=20" } diff --git a/package.json b/package.json index d34b4732..964991d6 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "@modelcontextprotocol/server": "2.0.0", "@napi-rs/keyring": "1.3.0", "@oclif/core": "^4.13.5", - "@polymorfa/sdk": "0.1.0-dev.20260922115749", + "@polymorfa/sdk": "0.1.0-dev.20260923093259", "jq-wasm": "3.0.0-jq-1.8.2", "zod": "4.2.0" }, diff --git a/src/commands/trigger.ts b/src/commands/trigger.ts new file mode 100644 index 00000000..ed11b31e --- /dev/null +++ b/src/commands/trigger.ts @@ -0,0 +1,170 @@ +import {Args, Flags} from '@oclif/core' +import { + PolymorfaConfigurationError, + TEST_EVENT_FIXTURES, + type TestEventFixture, + type TestEventOverrides, + type TriggerTestEventRequest, +} from '@polymorfa/sdk' +import {BaseCommand} from '../base-command.js' + +const BOOLEAN_OVERRIDES = new Set([ + 'video', + 'restrictionActive', +]) +const INTEGER_OVERRIDES = new Set(['durationSeconds']) + +export default class Trigger extends BaseCommand { + static override description = + 'Send a signed test event for a Test number to your webhooks, event history, and a running `polymorfa listen`. Real numbers are refused.' + static override args = { + event: Args.string({ + description: 'Fixture name; each name is the event type it produces.', + options: [...TEST_EVENT_FIXTURES], + required: false, + }), + } + static override flags = { + list: Flags.boolean({ + description: 'List fixtures and the overrides each accepts.', + exclusive: ['override', 'session', 'text', 'from', 'from-session'], + }), + session: Flags.string({ + description: 'Test number session name in the project.', + }), + override: Flags.string({ + description: + 'Override a payload field as key=value, for example ackStatus=read. Repeat for several fields.', + multiple: true, + }), + text: Flags.string({ + description: 'Shortcut for --override text=.', + }), + from: Flags.string({ + description: 'Shortcut for --override from=.', + }), + 'from-session': Flags.string({ + description: + 'message.received only: send a simulated message from this other Test number instead of generating an event.', + }), + } + static override examples = [ + '<%= config.bin %> trigger --list --project ', + '<%= config.bin %> trigger message.received --session my-test-number --text "hi" --project ', + '<%= config.bin %> trigger message.ack --session my-test-number --override ackStatus=read --project ', + '<%= config.bin %> trigger template.status --session my-test-number --override templateStatus=REJECTED --override reason=INVALID_FORMAT --project ', + '<%= config.bin %> trigger bansafe.enforcement --session my-test-number --override enforcementKind=temporary_ban --project ', + ] + + async run(): Promise { + const {args, flags} = await this.parse(Trigger) + if (!flags.project) + throw new PolymorfaConfigurationError( + 'Select a project with --project.', + 'project', + ) + const client = await this.messagingClient(flags, { + serverApiKeyOnly: 'Test event', + }) + + if (flags.list) { + const response = await client.testing.listEventFixtures(flags.project) + const human = response.data.fixtures + .map( + (fixture) => + `${fixture.name.padEnd(22)} ${fixture.description}${ + fixture.overrides.length > 0 + ? `\n${' '.repeat(23)}overrides: ${fixture.overrides.join(', ')}` + : '' + }`, + ) + .join('\n') + this.writeValue( + {data: response.data, metadata: response.metadata}, + human, + flags, + ) + return + } + + if (args.event === undefined) + throw new PolymorfaConfigurationError( + `Name a fixture: ${TEST_EVENT_FIXTURES.join(', ')}. Use --list to see overrides.`, + 'event', + ) + if (!flags.session) + throw new PolymorfaConfigurationError( + 'Select the Test number with --session.', + 'session', + ) + + const overrides = parseOverrides(flags.override ?? []) + for (const [key, value] of [ + ['text', flags.text], + ['from', flags.from], + ] as const) { + if (value === undefined) continue + if (key in overrides) + throw new PolymorfaConfigurationError( + `Pass ${key} once: use either --${key} or --override ${key}=..., not both.`, + 'override', + ) + overrides[key] = value + } + const request: TriggerTestEventRequest = { + session: flags.session, + event: args.event as TestEventFixture, + ...(Object.keys(overrides).length > 0 ? {overrides} : {}), + ...(flags['from-session'] === undefined + ? {} + : {fromSession: flags['from-session']}), + } + const response = await client.testing.triggerEvent(flags.project, request) + const result = response.data + const human = + result.delivery === 'generated' + ? `Triggered ${result.event} ${String(result.eventId)} on ${result.session} (source: test).\n` + + `A running \`polymorfa listen --project ${flags.project}\` prints it as: ${result.event} ${String(result.eventId)}` + : `Sent a simulated message to ${result.session} from ${String(flags['from-session'])}.\n` + + `The ${result.event} event arrives as Test number activity (source: runtime).` + this.writeValue({data: result, metadata: response.metadata}, human, flags) + } +} + +/** Parse repeated key=value overrides. Numeric and boolean fields are typed. */ +export function parseOverrides(values: readonly string[]): TestEventOverrides { + const overrides: Record = {} + for (const value of values) { + const separator = value.indexOf('=') + if (separator <= 0) + throw new PolymorfaConfigurationError( + `Override "${value}" must use key=value.`, + 'override', + ) + const key = value.slice(0, separator) as keyof TestEventOverrides + const raw = value.slice(separator + 1) + if (key in overrides) + throw new PolymorfaConfigurationError( + `Override "${key}" is set more than once.`, + 'override', + ) + if (BOOLEAN_OVERRIDES.has(key)) { + if (raw !== 'true' && raw !== 'false') + throw new PolymorfaConfigurationError( + `Override "${key}" must be true or false.`, + 'override', + ) + overrides[key] = raw === 'true' + } else if (INTEGER_OVERRIDES.has(key)) { + if (!/^\d+$/.test(raw)) + throw new PolymorfaConfigurationError( + `Override "${key}" must be a whole number.`, + 'override', + ) + overrides[key] = Number(raw) + } else { + overrides[key] = raw + } + } + return overrides +} diff --git a/test/commands/trigger.test.ts b/test/commands/trigger.test.ts new file mode 100644 index 00000000..899990ea --- /dev/null +++ b/test/commands/trigger.test.ts @@ -0,0 +1,225 @@ +import {runCommand} from '@oclif/test' +import {expect, test} from 'vitest' +import {parseOverrides} from '../../src/commands/trigger.js' +import { + startHttpSequenceServer, + withEnvironment, +} from '../helpers/http-server.js' + +const projectId = '11111111-1111-4111-8111-111111111111' +const apiKey = `pmfa_${'A'.repeat(72)}` +const eventId = '0199f1c2-7a4e-7c55-9d1e-3f0b8a2c6d10' + +async function run( + command: string, + responses: Parameters[0], +) { + const server = await startHttpSequenceServer(responses) + try { + const result = await withEnvironment( + { + POLYMORFA_API_KEY: apiKey, + POLYMORFA_BASE_URL: server.baseUrl, + POLYMORFA_CLIENT_TOKEN: undefined, + POLYMORFA_PROJECT_TOKEN: undefined, + }, + () => runCommand(command), + ) + return {result, requests: server.requests} + } finally { + await server.close() + } +} + +test('triggers message.received with text and typed overrides', async () => { + const {result, requests} = await run( + `trigger message.received --project ${projectId} --session test-a --text hi --override pushName=Ada`, + [ + { + status: 202, + body: { + event: 'message.received', + session: 'test-a', + delivery: 'generated', + eventId, + source: 'test', + }, + }, + ], + ) + expect(result.error).toBeUndefined() + expect(result.stdout).toContain( + `Triggered message.received ${eventId} on test-a (source: test).`, + ) + expect(result.stdout).toContain(`message.received ${eventId}`) + expect(requests.map(({method, url}) => `${method} ${url}`)).toEqual([ + `POST /messaging/testing/${projectId}/events`, + ]) + expect(JSON.parse(requests[0]!.body)).toEqual({ + session: 'test-a', + event: 'message.received', + overrides: {pushName: 'Ada', text: 'hi'}, + }) +}) + +test('sends a simulated message with --from-session and prints JSON', async () => { + const {result, requests} = await run( + `trigger message.received --project ${projectId} --session test-a --from-session test-b --json`, + [ + { + status: 202, + body: { + event: 'message.received', + session: 'test-a', + delivery: 'simulated', + eventId: null, + source: 'runtime', + }, + }, + ], + ) + expect(result.error).toBeUndefined() + expect(JSON.parse(result.stdout)).toMatchObject({ + data: {delivery: 'simulated', eventId: null}, + }) + expect(JSON.parse(requests[0]!.body)).toEqual({ + session: 'test-a', + event: 'message.received', + fromSession: 'test-b', + }) +}) + +test('lists fixtures', async () => { + const {result, requests} = await run( + `trigger --list --project ${projectId}`, + [ + { + body: { + fixtures: [ + { + name: 'message.ack', + description: 'Delivery or read receipt for an outbound message', + overrides: ['from', 'ackStatus', 'messageId'], + }, + ], + }, + }, + ], + ) + expect(result.error).toBeUndefined() + expect(result.stdout).toContain('message.ack') + expect(result.stdout).toContain('overrides: from, ackStatus, messageId') + expect(requests[0]!.url).toBe( + `/messaging/testing/${projectId}/events/fixtures`, + ) +}) + +test.each(['true', 'false'])( + 'triggers the listed restriction fixture with restrictionActive=%s', + async (active) => { + const fixture = 'session.restriction_updated' + const listed = await run(`trigger --list --project ${projectId}`, [ + { + body: { + fixtures: [ + { + name: fixture, + description: 'WhatsApp restriction changed', + overrides: ['restrictionActive'], + }, + ], + }, + }, + ]) + expect(listed.result.stdout).toContain(fixture) + + const {result, requests} = await run( + `trigger ${fixture} --project ${projectId} --session test-a --override restrictionActive=${active}`, + [ + { + status: 202, + body: { + event: fixture, + session: 'test-a', + delivery: 'generated', + eventId, + source: 'test', + }, + }, + ], + ) + expect(result.error, result.stderr).toBeUndefined() + expect(requests).toHaveLength(1) + expect(JSON.parse(requests[0]!.body)).toEqual({ + session: 'test-a', + event: fixture, + overrides: {restrictionActive: active === 'true'}, + }) + }, +) + +test('reports a real-session refusal without retrying', async () => { + const {result, requests} = await run( + `trigger call.received --project ${projectId} --session real-number`, + [ + { + status: 400, + body: { + error: { + type: 'invalid_request_error', + code: 'invalid_parameter', + message: + 'Test events are available only for Test numbers. This session is not a Test number in the project, or it does not exist.', + param: null, + }, + data: null, + }, + }, + ], + ) + expect(result.stderr).toMatch(/only for Test numbers/) + expect(requests).toHaveLength(1) +}) + +test('requires a session and a fixture before any request', async () => { + const noSession = await run(`trigger call.missed --project ${projectId}`, []) + expect(noSession.result.stderr).toMatch(/--session/) + expect(noSession.requests).toHaveLength(0) + const unknown = await run( + `trigger message.sent --project ${projectId} --session test-a`, + [], + ) + expect(unknown.result.error).toBeDefined() + expect(unknown.requests).toHaveLength(0) +}) + +test('parses typed overrides and rejects malformed ones', () => { + expect( + parseOverrides(['video=true', 'durationSeconds=12', 'reason=a=b']), + ).toEqual({video: true, durationSeconds: 12, reason: 'a=b'}) + expect(() => parseOverrides(['text'])).toThrow('key=value') + expect(() => parseOverrides(['video=yes'])).toThrow('true or false') + expect(() => parseOverrides(['durationSeconds=-1'])).toThrow('whole number') + expect(() => parseOverrides(['text=a', 'text=b'])).toThrow('more than once') + expect(parseOverrides(['restrictionActive=false'])).toEqual({ + restrictionActive: false, + }) + expect(() => parseOverrides(['restrictionActive=yes'])).toThrow( + 'true or false', + ) +}) + +test('rejects a shortcut that repeats an explicit override', async () => { + for (const flags of [ + '--override text=first --text second', + '--override from=+15550001111 --from +15550002222', + ]) { + const collision = await run( + `trigger message.received --project ${projectId} --session test-a ${flags}`, + [], + ) + expect(collision.result.error).toBeDefined() + expect(collision.result.stderr).toContain('not both') + expect(collision.requests).toHaveLength(0) + } +}) diff --git a/test/commands/version.test.ts b/test/commands/version.test.ts index 773d1ec4..b7cd97f6 100644 --- a/test/commands/version.test.ts +++ b/test/commands/version.test.ts @@ -1,12 +1,19 @@ +import {readFileSync} from 'node:fs' + import {runCommand} from '@oclif/test' import {describe, expect, test} from 'vitest' +const manifest = JSON.parse( + readFileSync(new URL('../../package.json', import.meta.url), 'utf8'), +) as {dependencies: {'@polymorfa/sdk': string}} +const sdkVersion = manifest.dependencies['@polymorfa/sdk'] + describe('version', () => { test('prints the CLI and SDK development versions', async () => { const result = await runCommand('version') expect(result.error).toBeUndefined() - expect(result.stdout).toBe('polymorfa/0.1.0-dev.0 sdk/0.1.0-dev.0\n') + expect(result.stdout).toBe(`polymorfa/0.1.0-dev.0 sdk/${sdkVersion}\n`) expect(result.stderr).toBe('') }) @@ -16,7 +23,7 @@ describe('version', () => { expect(result.error).toBeUndefined() expect(JSON.parse(result.stdout)).toEqual({ cli: '0.1.0-dev.0', - sdk: '0.1.0-dev.0', + sdk: sdkVersion, }) expect(result.stderr).toBe('') })