task-write-e2e.test.mjs
179 lines 6.0 KB
Raw
sha256:baa800aedf841dbf32081aa7b2befa288ac33dfc7175ac55014c55c4d8c742f9 docs: move durable-auth freeze/evidence to local developmen… Human 10 days ago
1 /**
2 * Tier 3 — E2E: loop create → approve → materialize → get; pause blocks; cancel cascades.
3 */
4 import { describe, it, beforeEach, afterEach } from 'node:test';
5 import assert from 'node:assert/strict';
6 import fs from 'node:fs';
7 import path from 'node:path';
8 import { fileURLToPath } from 'node:url';
9
10 import {
11 handleTaskLoopProposeRequest,
12 handleTaskInstanceMaterializeRequest,
13 loopStateId,
14 } from '../lib/task/task-write.mjs';
15 import { getTask } from '../lib/task/task-store.mjs';
16 import { getTaskLoop, taskLoopForClient } from '../lib/task/task-loop-store.mjs';
17 import { createProposal } from '../hub/proposals-store.mjs';
18 import {
19 approveTaskProposal,
20 emptyTaskStarterDir,
21 sampleLoopCreatePayload,
22 visibleAll,
23 } from './fixtures/task/write-helpers.mjs';
24
25 const __dirname = path.dirname(fileURLToPath(import.meta.url));
26 const tmpRoot = path.join(__dirname, 'fixtures', 'tmp-task-write-e2e');
27
28 describe('task write — e2e lifecycle', () => {
29 const dataDir = path.join(tmpRoot, 'data');
30 const vaultId = 'default';
31 let starterDir;
32
33 beforeEach(() => {
34 fs.rmSync(tmpRoot, { recursive: true, force: true });
35 fs.mkdirSync(dataDir, { recursive: true });
36 starterDir = emptyTaskStarterDir(dataDir);
37 process.env.TASK_WRITES_ENABLED = '1';
38 });
39 afterEach(() => {
40 delete process.env.TASK_WRITES_ENABLED;
41 });
42
43 it('loop create → approve → materialize → get instance with loop_ref/occurrence_key', async () => {
44 const payload = sampleLoopCreatePayload();
45 payload.loop.loop_id = 'loop_e2e_trip';
46 const proposed = await handleTaskLoopProposeRequest({
47 dataDir,
48 vaultId,
49 visibleScopes: visibleAll,
50 proposalKind: 'task_loop_create',
51 body: payload,
52 intent: 'create series',
53 starterDir,
54 createProposal,
55 });
56 assert.equal(proposed.ok, true);
57 assert.equal(approveTaskProposal(dataDir, proposed.payload.proposal_id).ok, true);
58
59 const loop = getTaskLoop(dataDir, vaultId, 'loop_e2e_trip', { visibleScopes: visibleAll, starterDir });
60 assert.ok(loop);
61 const baseStateId = loopStateId(taskLoopForClient(loop));
62
63 const mat = await handleTaskInstanceMaterializeRequest({
64 dataDir,
65 vaultId,
66 visibleScopes: visibleAll,
67 loopId: 'loop_e2e_trip',
68 body: { loop_id: 'loop_e2e_trip', occurrence_key: '2026-W25', base_state_id: baseStateId },
69 intent: 'spawn',
70 starterDir,
71 createProposal,
72 });
73 assert.equal(mat.ok, true);
74 assert.equal(approveTaskProposal(dataDir, mat.payload.proposal_id).ok, true);
75
76 const task = getTask(dataDir, vaultId, mat.payload.task_id, { visibleScopes: visibleAll, starterDir });
77 assert.ok(task);
78 assert.equal(task.loop_ref, 'loop_e2e_trip');
79 assert.equal(task.occurrence_key, '2026-W25');
80 assert.equal(task.run_ref, null);
81 });
82
83 it('pause blocks materialize propose', async () => {
84 const payload = sampleLoopCreatePayload();
85 payload.loop.loop_id = 'loop_e2e_pause';
86 const createRes = await handleTaskLoopProposeRequest({
87 dataDir,
88 vaultId,
89 visibleScopes: visibleAll,
90 proposalKind: 'task_loop_create',
91 body: payload,
92 intent: 'create',
93 starterDir,
94 createProposal,
95 });
96 approveTaskProposal(dataDir, createRes.payload.proposal_id);
97
98 const loop = getTaskLoop(dataDir, vaultId, 'loop_e2e_pause', { visibleScopes: visibleAll, starterDir });
99 const baseStateId = loopStateId(taskLoopForClient(loop));
100
101 const pauseRes = await handleTaskLoopProposeRequest({
102 dataDir,
103 vaultId,
104 visibleScopes: visibleAll,
105 proposalKind: 'task_loop_pause',
106 body: { proposal_kind: 'task_loop_pause', loop_id: 'loop_e2e_pause', base_state_id: baseStateId },
107 intent: 'pause',
108 starterDir,
109 createProposal,
110 });
111 approveTaskProposal(dataDir, pauseRes.payload.proposal_id);
112
113 const mat = await handleTaskInstanceMaterializeRequest({
114 dataDir,
115 vaultId,
116 visibleScopes: visibleAll,
117 loopId: 'loop_e2e_pause',
118 body: { loop_id: 'loop_e2e_pause', occurrence_key: '2026-W26' },
119 intent: 'spawn',
120 starterDir,
121 createProposal,
122 });
123 assert.equal(mat.ok, false);
124 assert.equal(mat.code, 'TASK_LOOP_NOT_ACTIVE');
125 });
126
127 it('cancel cascades pending instances atomically', async () => {
128 const payload = sampleLoopCreatePayload();
129 payload.loop.loop_id = 'loop_e2e_cancel';
130 approveTaskProposal(
131 dataDir,
132 (await handleTaskLoopProposeRequest({
133 dataDir,
134 vaultId,
135 visibleScopes: visibleAll,
136 proposalKind: 'task_loop_create',
137 body: payload,
138 intent: 'create',
139 starterDir,
140 createProposal,
141 })).payload.proposal_id,
142 );
143
144 let loop = getTaskLoop(dataDir, vaultId, 'loop_e2e_cancel', { visibleScopes: visibleAll, starterDir });
145 let baseStateId = loopStateId(taskLoopForClient(loop));
146
147 const mat = await handleTaskInstanceMaterializeRequest({
148 dataDir,
149 vaultId,
150 visibleScopes: visibleAll,
151 loopId: 'loop_e2e_cancel',
152 body: { loop_id: 'loop_e2e_cancel', occurrence_key: '2026-W25', base_state_id: baseStateId },
153 intent: 'spawn',
154 starterDir,
155 createProposal,
156 });
157 approveTaskProposal(dataDir, mat.payload.proposal_id);
158
159 loop = getTaskLoop(dataDir, vaultId, 'loop_e2e_cancel', { visibleScopes: visibleAll, starterDir });
160 baseStateId = loopStateId(taskLoopForClient(loop));
161
162 const cancelRes = await handleTaskLoopProposeRequest({
163 dataDir,
164 vaultId,
165 visibleScopes: visibleAll,
166 proposalKind: 'task_loop_cancel',
167 body: { proposal_kind: 'task_loop_cancel', loop_id: 'loop_e2e_cancel', base_state_id: baseStateId },
168 intent: 'cancel series',
169 starterDir,
170 createProposal,
171 });
172 const approved = approveTaskProposal(dataDir, cancelRes.payload.proposal_id);
173 assert.equal(approved.ok, true);
174
175 const task = getTask(dataDir, vaultId, mat.payload.task_id, { visibleScopes: visibleAll, starterDir });
176 assert.equal(task.status, 'cancelled');
177 assert.equal(task.skip_reason, 'series_cancelled');
178 });
179 });
File History 1 commit
sha256:baa800aedf841dbf32081aa7b2befa288ac33dfc7175ac55014c55c4d8c742f9 docs: move durable-auth freeze/evidence to local developmen… Human 10 days ago