This repository has been archived on 2026-06-01. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
pulse/packages/pulse-workflows/src/workflows/coding-tdd.test.ts
T
xiaomo 7993ecc6d6
CI / test (push) Has been cancelled
feat: extract @uncaged/pulse-local package, remove createStore from core exports
Phase 2 of PulseDatabase abstraction:
- Create @uncaged/pulse-local with bun:sqlite implementation
- Core @uncaged/pulse now exports types only for store (no createStore/createScopedStore)
- Update pulse-workflows, upulse to import factories from @uncaged/pulse-local
- All tests passing (267 core, 39 pulse-workflows)
2026-04-20 01:34:46 +00:00

494 lines
12 KiB
TypeScript

/**
* coding-tdd WorkflowType tests — TDD pipeline + rejection loops.
*
* 小橘 🍊 (NEKO Team)
*/
import { describe, expect, it } from 'bun:test';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createStore, type PulseStore } from '@uncaged/pulse-local';
import { createTddCodingWorkflow } from './coding-tdd.js';
import { createWorkflowRule } from '@uncaged/pulse';
import { END, START } from '@uncaged/pulse';
describe('coding-tdd WorkflowType', () => {
let store: PulseStore;
let tmpDir: string;
function setup() {
tmpDir = mkdtempSync(join(tmpdir(), 'coding-tdd-'));
store = createStore({
eventsDbPath: join(tmpDir, 'test.db'),
objectsDir: join(tmpDir, 'objects'),
});
}
async function cleanup() {
try {
await store?.close();
} catch {}
if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
}
async function trigger(topicId: string, title: string, description: string) {
const hash = await store.putObject(description);
await store.appendEvent({
occurredAt: Date.now(),
kind: 'coding-tdd.__start__',
key: topicId,
hash,
meta: JSON.stringify({ title, repoDir: '/tmp/r' }),
});
}
const ruleOpts = {
maxTicksPerTopic: 200,
maxTicksPerWindow: 500,
cooldownMs: 0,
} as const;
it('limits.maxRounds is 25', () => {
const wf = createTddCodingWorkflow();
expect(wf.limits?.maxRounds).toBe(25);
});
it('exports createTddCodingWorkflow', () => {
expect(typeof createTddCodingWorkflow).toBe('function');
});
it('full happy path via adapter: all roles until END', async () => {
setup();
try {
const wf = createTddCodingWorkflow();
const rule = createWorkflowRule(wf, store, undefined, ruleOpts);
await trigger('t1', 'Feature', 'desc');
const order: string[] = [];
for (let i = 0; i < 30; i++) {
const r = await rule.tick();
if (r.executed.length === 0) break;
order.push(...r.executed.map((x) => x.role));
}
expect(order).toEqual([
'test-planner',
'test-reviewer',
'test-coder',
'coder',
'auto-tester',
'manual-tester',
'reviewer',
]);
const r = await rule.tick();
expect(r.executed).toEqual([]);
} finally {
await cleanup();
}
});
it('moderator: START → test-planner', () => {
const wf = createTddCodingWorkflow();
expect(wf.moderator({ role: START, meta: null }, 'x')).toBe('test-planner');
});
it('moderator: test-planner → test-reviewer', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'test-planner',
meta: {
testPlan: '# p',
scenarios: ['a'],
},
},
'x',
),
).toBe('test-reviewer');
});
it('moderator: test-reviewer approved → test-coder', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{ role: 'test-reviewer', meta: { verdict: 'approved', feedback: '' } },
'x',
),
).toBe('test-coder');
});
it('moderator: test-reviewer rejected → test-planner', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'test-reviewer',
meta: { verdict: 'rejected', feedback: 'no' },
},
'x',
10,
),
).toBe('test-planner');
});
it('moderator: test-reviewer rejected + emergency → END', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'test-reviewer',
meta: { verdict: 'rejected', feedback: 'no' },
},
'x',
1,
),
).toBe(END);
});
it('moderator: test-coder → coder', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'test-coder',
meta: { testFiles: ['a.test.ts'], testCount: 1 },
},
'x',
),
).toBe('coder');
});
it('moderator: coder → auto-tester', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'coder',
meta: {
filesChanged: ['src/foo.ts'],
deploymentGuide: 'bun test',
},
},
'x',
),
).toBe('auto-tester');
});
it('moderator: auto-tester pass → manual-tester', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'auto-tester',
meta: { pass: true, failedTests: [], output: 'ok' },
},
'x',
),
).toBe('manual-tester');
});
it('moderator: auto-tester fail → coder', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'auto-tester',
meta: { pass: false, failedTests: ['x'], output: 'fail' },
},
'x',
10,
),
).toBe('coder');
});
it('moderator: auto-tester fail + emergency → END', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'auto-tester',
meta: { pass: false, failedTests: ['x'], output: 'fail' },
},
'x',
1,
),
).toBe(END);
});
it('moderator: manual-tester pass → reviewer', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{ role: 'manual-tester', meta: { pass: true, issues: [] } },
'x',
),
).toBe('reviewer');
});
it('moderator: manual-tester fail → coder', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'manual-tester',
meta: { pass: false, issues: ['ui broken'] },
},
'x',
10,
),
).toBe('coder');
});
it('moderator: manual-tester fail + emergency → END', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'manual-tester',
meta: { pass: false, issues: ['ui broken'] },
},
'x',
1,
),
).toBe(END);
});
it('moderator: reviewer approved → END', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'reviewer',
meta: {
verdict: 'approved',
comments: '',
codeQuality: 'a',
testQuality: 'a',
},
},
'x',
),
).toBe(END);
});
it('moderator: reviewer approved + emergency → END', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'reviewer',
meta: {
verdict: 'approved',
comments: '',
codeQuality: 'a',
testQuality: 'a',
},
},
'x',
1,
),
).toBe(END);
});
it('moderator: reviewer rejected → coder', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'reviewer',
meta: {
verdict: 'rejected',
comments: 'fix',
codeQuality: 'c',
testQuality: 'b',
},
},
'x',
10,
),
).toBe('coder');
});
it('moderator: reviewer rejected + emergency → END', () => {
const wf = createTddCodingWorkflow();
expect(
wf.moderator(
{
role: 'reviewer',
meta: {
verdict: 'rejected',
comments: 'fix',
codeQuality: 'c',
testQuality: 'b',
},
},
'x',
1,
),
).toBe(END);
});
it('loop: test-reviewer rejects once then approves', async () => {
setup();
try {
let trRound = 0;
const wf = createTddCodingWorkflow({
testReviewerFn: async () => {
trRound++;
return {
content: `review ${trRound}`,
meta:
trRound === 1
? { verdict: 'rejected' as const, feedback: 'add cases' }
: { verdict: 'approved' as const, feedback: 'ok' },
};
},
});
const rule = createWorkflowRule(wf, store, undefined, ruleOpts);
await trigger('loop-tr', 'L', 'd');
const roles: string[] = [];
for (let i = 0; i < 40; i++) {
const r = await rule.tick();
if (r.executed.length === 0) break;
roles.push(...r.executed.map((x) => x.role));
}
expect(roles.slice(0, 4)).toEqual([
'test-planner',
'test-reviewer',
'test-planner',
'test-reviewer',
]);
expect(roles).toContain('test-coder');
} finally {
await cleanup();
}
});
it('loop: auto-tester fails once then passes', async () => {
setup();
try {
let autoRound = 0;
const wf = createTddCodingWorkflow({
autoTesterFn: async () => {
autoRound++;
return {
content: 'out',
meta:
autoRound === 1
? {
pass: false,
failedTests: ['t1'],
output: 'fail',
}
: {
pass: true,
failedTests: [],
output: 'ok',
},
};
},
});
const rule = createWorkflowRule(wf, store, undefined, ruleOpts);
await trigger('loop-auto', 'L', 'd');
const roles: string[] = [];
for (let i = 0; i < 40; i++) {
const r = await rule.tick();
if (r.executed.length === 0) break;
roles.push(...r.executed.map((x) => x.role));
}
const firstCoder = roles.indexOf('coder');
const firstAuto = roles.indexOf('auto-tester');
const secondCoder = roles.indexOf('coder', firstCoder + 1);
expect(firstAuto).toBeGreaterThan(firstCoder);
expect(secondCoder).toBeGreaterThan(firstAuto);
} finally {
await cleanup();
}
});
it('loop: manual-tester fails once then passes', async () => {
setup();
try {
let manRound = 0;
const wf = createTddCodingWorkflow({
manualTesterFn: async () => {
manRound++;
return {
content: 'notes',
meta:
manRound === 1
? { pass: false, issues: ['bug'] }
: { pass: true, issues: [] },
};
},
});
const rule = createWorkflowRule(wf, store, undefined, ruleOpts);
await trigger('loop-man', 'L', 'd');
const roles: string[] = [];
for (let i = 0; i < 40; i++) {
const r = await rule.tick();
if (r.executed.length === 0) break;
roles.push(...r.executed.map((x) => x.role));
}
const idxReviewer1 = roles.indexOf('reviewer');
expect(idxReviewer1).toBeGreaterThan(-1);
expect(roles.lastIndexOf('coder')).toBeGreaterThan(
roles.indexOf('manual-tester'),
);
} finally {
await cleanup();
}
});
it('loop: reviewer rejects once then approves', async () => {
setup();
try {
let revRound = 0;
const wf = createTddCodingWorkflow({
reviewerFn: async () => {
revRound++;
return {
content: `rev ${revRound}`,
meta:
revRound === 1
? {
verdict: 'rejected' as const,
comments: 'fix',
codeQuality: 'c',
testQuality: 'c',
}
: {
verdict: 'approved' as const,
comments: 'ok',
codeQuality: 'a',
testQuality: 'a',
},
};
},
});
const rule = createWorkflowRule(wf, store, undefined, ruleOpts);
await trigger('loop-rev', 'L', 'd');
const roles: string[] = [];
for (let i = 0; i < 40; i++) {
const r = await rule.tick();
if (r.executed.length === 0) break;
roles.push(...r.executed.map((x) => x.role));
}
expect(roles.filter((r) => r === 'reviewer').length).toBe(2);
} finally {
await cleanup();
}
});
});