Files
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

100 lines
3.9 KiB
TypeScript

// Phase 7-8 entry slice / spec §11.5.1 — handoff atom helper.
import { describe, expect, it } from 'vitest';
import type { ArtifactManifest } from '@open-design/contracts';
import { isDeployableAppEligible, recordHandoff } from '../src/plugins/atoms/handoff.js';
const baseManifest = (extra: Partial<ArtifactManifest> = {}): ArtifactManifest => ({
version: 1,
kind: 'html',
title: 'Fixture',
entry: 'index.html',
renderer: 'html',
exports: [],
...extra,
});
describe('recordHandoff — append-only contracts', () => {
it('appends a new exportTargets entry', () => {
const out = recordHandoff({
manifest: baseManifest(),
exportTarget: { surface: 'cli', target: '/workspace/od/x.html', exportedAt: 1000 },
});
expect(out.changed).toContain('exportTargets');
expect(out.manifest.exportTargets).toEqual([
{ surface: 'cli', target: '/workspace/od/x.html', exportedAt: 1000 },
]);
});
it('is idempotent on identical (surface, target) pairs', () => {
const first = recordHandoff({
manifest: baseManifest(),
exportTarget: { surface: 'cli', target: '/p/a.html', exportedAt: 1 },
});
const second = recordHandoff({
manifest: first.manifest,
exportTarget: { surface: 'cli', target: '/p/a.html', exportedAt: 5 },
});
expect(second.changed).toEqual([]);
expect(second.manifest.exportTargets?.length).toBe(1);
});
it('appends deployTargets independently', () => {
const out = recordHandoff({
manifest: baseManifest(),
deployTarget: { provider: 'aws', location: 'arn:aws:ecs:...', deployedAt: 2 },
});
expect(out.changed).toEqual(['deployTargets']);
expect(out.manifest.deployTargets?.[0]?.provider).toBe('aws');
});
});
describe('recordHandoff — handoffKind monotonicity', () => {
it('records the first handoffKind unconditionally', () => {
const out = recordHandoff({
manifest: baseManifest(),
handoffKind: 'design-only',
});
expect(out.manifest.handoffKind).toBe('design-only');
expect(out.changed).toContain('handoffKind');
});
it('promotes handoffKind along the axis design-only → implementation-plan → patch → deployable-app', () => {
const a = recordHandoff({ manifest: baseManifest({ handoffKind: 'design-only' }), handoffKind: 'patch' });
expect(a.manifest.handoffKind).toBe('patch');
const b = recordHandoff({ manifest: a.manifest, handoffKind: 'deployable-app' });
expect(b.manifest.handoffKind).toBe('deployable-app');
});
it('refuses to downgrade when enforceMonotonicHandoff is on (default)', () => {
const a = recordHandoff({ manifest: baseManifest({ handoffKind: 'patch' }), handoffKind: 'design-only' });
expect(a.manifest.handoffKind).toBe('patch');
expect(a.changed).not.toContain('handoffKind');
});
it('allows downgrade when enforceMonotonicHandoff is false (rollback path)', () => {
const a = recordHandoff({
manifest: baseManifest({ handoffKind: 'deployable-app' }),
handoffKind: 'patch',
enforceMonotonicHandoff: false,
});
expect(a.manifest.handoffKind).toBe('patch');
});
});
describe('isDeployableAppEligible', () => {
it('requires both build + tests passing', () => {
const m = baseManifest({ exportTargets: [{ surface: 'docker', target: 'ghcr.io/od/x:1', exportedAt: 1 }] });
expect(isDeployableAppEligible({ manifest: m, buildPassing: true, testsPassing: true })).toBe(true);
expect(isDeployableAppEligible({ manifest: m, buildPassing: false, testsPassing: true })).toBe(false);
expect(isDeployableAppEligible({ manifest: m, buildPassing: true, testsPassing: false })).toBe(false);
});
it('requires at least one docker or cli exportTarget', () => {
const m = baseManifest({
exportTargets: [{ surface: 'figma', target: 'file/abc', exportedAt: 1 }],
});
expect(isDeployableAppEligible({ manifest: m, buildPassing: true, testsPassing: true })).toBe(false);
});
});