Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

238 lines
6.9 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
COMPOSE_TEXT_ACTION_ID,
COMPOSE_TEXT_VERSION,
composeTextOutputsSchema,
generateComposeTextStableKey,
renderComposeTextOutputs,
renderTemplateDocumentToMarkdown,
templateDocumentSchema,
validateComposeTextConfig,
} from '../composeText';
describe('composeText helpers', () => {
it('T002: validates compose-text config outputs and rejects missing outputs', () => {
const valid = validateComposeTextConfig({
actionId: COMPOSE_TEXT_ACTION_ID,
version: COMPOSE_TEXT_VERSION,
outputs: [
{
id: 'out-1',
label: 'Prompt',
stableKey: 'prompt',
document: {
version: 1,
blocks: [{ type: 'paragraph', children: [{ type: 'text', text: 'Hello' }] }],
},
},
],
});
const invalid = validateComposeTextConfig({
actionId: COMPOSE_TEXT_ACTION_ID,
version: COMPOSE_TEXT_VERSION,
});
expect(valid).toMatchObject({ ok: true });
expect(invalid).toMatchObject({ ok: false });
if (invalid.ok === false) {
expect(invalid.errors[0]).toContain('at least one output');
}
});
it('T003/T004: generates stable keys from freeform labels and disambiguates collisions', () => {
expect(generateComposeTextStableKey('Prompt Body')).toBe('prompt_body');
expect(generateComposeTextStableKey('123 Summary')).toBe('output_123_summary');
expect(generateComposeTextStableKey('Prompt Body', ['prompt_body'])).toBe('prompt_body_2');
});
it('T006/T007/T008: rejects empty labels plus duplicate labels and stable keys', () => {
const parsed = composeTextOutputsSchema.safeParse([
{
id: 'out-1',
label: ' ',
stableKey: 'prompt',
document: { version: 1, blocks: [] },
},
{
id: 'out-2',
label: 'Prompt',
stableKey: 'prompt',
document: { version: 1, blocks: [] },
},
{
id: 'out-3',
label: 'prompt',
stableKey: 'prompt_2',
document: { version: 1, blocks: [] },
},
]);
expect(parsed.success).toBe(false);
expect(parsed.success ? [] : parsed.error.issues.map((issue) => issue.message)).toEqual(
expect.arrayContaining([
'String must contain at least 1 character(s)',
'Output labels must be unique within the step.',
'Stable keys must be unique within the step.',
])
);
});
it('T009/T010: restricts template documents to markdown-safe nodes with simple references only', () => {
const valid = templateDocumentSchema.safeParse({
version: 1,
blocks: [
{
type: 'paragraph',
children: [
{ type: 'text', text: 'Hello', marks: ['bold'] },
{ type: 'reference', path: 'payload.ticket.id', label: 'Ticket ID' },
],
},
],
});
const invalidBlock = templateDocumentSchema.safeParse({
version: 1,
blocks: [{ type: 'table', children: [] }],
});
const invalidReference = templateDocumentSchema.safeParse({
version: 1,
blocks: [
{
type: 'paragraph',
children: [{ type: 'reference', path: 'payload.ticket.id & payload.name', label: 'Bad' }],
},
],
});
expect(valid.success).toBe(true);
expect(invalidBlock.success).toBe(false);
expect(invalidReference.success).toBe(false);
});
it('T014/T015/T016: renders paragraphs, hard breaks, lists, and inline formatting as markdown', async () => {
const markdown = await renderTemplateDocumentToMarkdown(
{
version: 1,
blocks: [
{
type: 'paragraph',
children: [{ type: 'text', text: 'Line 1\nLine 2' }],
},
{
type: 'bullet_list_item',
children: [{ type: 'text', text: 'One', marks: ['bold'] }],
},
{
type: 'bullet_list_item',
children: [{ type: 'text', text: 'Two', marks: ['italic', 'code'] }],
},
{
type: 'ordered_list_item',
children: [{ type: 'text', text: 'Three', marks: ['link'], href: 'https://example.com' }],
},
],
},
{ outputKey: 'prompt' }
);
expect(markdown).toBe(
'Line 1 \nLine 2\n\n- **One**\n- _`Two`_\n\n1. [Three](https://example.com)'
);
});
it('T017/T018/T019/T020/T021: renders multiple outputs from payload and vars references and fails on missing references', async () => {
const config = {
actionId: COMPOSE_TEXT_ACTION_ID,
version: COMPOSE_TEXT_VERSION,
outputs: [
{
id: 'out-1',
label: 'Prompt',
stableKey: 'prompt',
document: {
version: 1,
blocks: [
{
type: 'paragraph',
children: [
{ type: 'text', text: 'Ticket ' },
{ type: 'reference', path: 'payload.ticket.id', label: 'Ticket ID' },
],
},
],
},
},
{
id: 'out-2',
label: 'Summary',
stableKey: 'summary',
document: {
version: 1,
blocks: [
{
type: 'paragraph',
children: [
{ type: 'reference', path: 'vars.ticketResult.updated', label: 'Updated' },
{ type: 'text', text: ' / ' },
{ type: 'reference', path: 'vars.ticketResult.ticket_id', label: 'Ticket ID' },
],
},
],
},
},
],
};
const rendered = await renderComposeTextOutputs(config, {
payload: { ticket: { id: 'T-100' } },
vars: { ticketResult: { updated: true, ticket_id: 'T-100' } },
meta: {},
error: undefined,
});
expect(rendered).toEqual({
prompt: 'Ticket T-100',
summary: 'true / T-100',
});
await expect(
renderComposeTextOutputs(
{
...config,
outputs: [
...config.outputs,
{
id: 'out-3',
label: 'Missing',
stableKey: 'missing',
document: {
version: 1,
blocks: [
{
type: 'paragraph',
children: [{ type: 'reference', path: 'vars.ticketResult.subject', label: 'Subject' }],
},
],
},
},
],
},
{
payload: { ticket: { id: 'T-100' } },
vars: { ticketResult: { updated: true, ticket_id: 'T-100' } },
meta: {},
error: undefined,
}
)
).rejects.toMatchObject({
category: 'ValidationError',
code: 'MISSING_REFERENCE',
details: {
outputKey: 'missing',
referencePath: 'vars.ticketResult.subject',
},
});
});
});