@ryanfw/prompt-orchestration-pipeline 1.3.2 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/http-api.md +80 -4
- package/package.json +1 -4
- package/src/api/__tests__/index.test.ts +443 -32
- package/src/api/index.ts +108 -127
- package/src/cli/__tests__/analyze-task.test.ts +40 -7
- package/src/cli/__tests__/index.test.ts +337 -17
- package/src/cli/__tests__/types.test.ts +0 -18
- package/src/cli/__tests__/update-pipeline-json.test.ts +19 -9
- package/src/cli/analyze-task.ts +3 -14
- package/src/cli/index.ts +73 -61
- package/src/cli/types.ts +0 -13
- package/src/cli/update-pipeline-json.ts +3 -14
- package/src/config/__tests__/paths.test.ts +46 -1
- package/src/config/__tests__/pipeline-registry.test.ts +767 -0
- package/src/config/__tests__/sse-events.test.ts +470 -0
- package/src/config/__tests__/statuses.test.ts +41 -0
- package/src/config/paths.ts +11 -2
- package/src/config/pipeline-registry.ts +258 -0
- package/src/config/sse-events.ts +122 -0
- package/src/config/statuses.ts +20 -1
- package/src/core/__tests__/agent-step.test.ts +115 -0
- package/src/core/__tests__/config.test.ts +545 -105
- package/src/core/__tests__/core-boot-resolution.test.ts +181 -0
- package/src/core/__tests__/job-concurrency.test.ts +260 -0
- package/src/core/__tests__/job-submission.test.ts +396 -0
- package/src/core/__tests__/job-view.test.ts +1341 -0
- package/src/core/__tests__/json-file.test.ts +111 -0
- package/src/core/__tests__/lifecycle-policy.test.ts +81 -7
- package/src/core/__tests__/logger.test.ts +22 -0
- package/src/core/__tests__/orchestrator-no-deprecated-exports.test.ts +41 -0
- package/src/core/__tests__/orchestrator.test.ts +615 -2
- package/src/core/__tests__/pipeline-runner.test.ts +946 -9
- package/src/core/__tests__/redact.test.ts +153 -0
- package/src/core/__tests__/runner-liveness.test.ts +910 -0
- package/src/core/__tests__/seed-naming.test.ts +57 -0
- package/src/core/__tests__/single-derivation.test.ts +159 -0
- package/src/core/__tests__/task-runner.test.ts +594 -3
- package/src/core/__tests__/task-telemetry.test.ts +241 -0
- package/src/core/__tests__/workspace-root-resolution-guard.test.ts +62 -0
- package/src/core/agent-step.ts +4 -1
- package/src/core/agent-types.ts +5 -4
- package/src/core/config.ts +134 -222
- package/src/core/control.ts +3 -0
- package/src/core/job-concurrency.ts +56 -20
- package/src/core/job-submission.ts +133 -0
- package/src/core/job-view.ts +473 -0
- package/src/core/json-file.ts +45 -0
- package/src/core/lifecycle-policy.ts +23 -8
- package/src/core/logger.ts +0 -29
- package/src/core/orchestrator.ts +200 -74
- package/src/core/pipeline-runner.ts +85 -53
- package/src/core/redact.ts +40 -0
- package/src/core/runner-liveness.ts +280 -0
- package/src/core/seed-naming.ts +9 -0
- package/src/core/status-writer.ts +27 -33
- package/src/core/task-runner.ts +356 -319
- package/src/core/task-telemetry.ts +107 -0
- package/src/harness/__tests__/subprocess.test.ts +112 -1
- package/src/harness/subprocess.ts +55 -16
- package/src/llm/__tests__/index.test.ts +684 -33
- package/src/llm/index.ts +310 -67
- package/src/providers/__tests__/alibaba.test.ts +67 -6
- package/src/providers/__tests__/anthropic.test.ts +35 -14
- package/src/providers/__tests__/base.test.ts +62 -0
- package/src/providers/__tests__/claude-code.test.ts +99 -14
- package/src/providers/__tests__/deepseek.test.ts +16 -6
- package/src/providers/__tests__/gemini.test.ts +47 -25
- package/src/providers/__tests__/moonshot.test.ts +27 -0
- package/src/providers/__tests__/openai.test.ts +262 -74
- package/src/providers/__tests__/opencode.test.ts +77 -0
- package/src/providers/__tests__/request-timeout-contract.test.ts +226 -0
- package/src/providers/__tests__/stream-accumulator.test.ts +52 -0
- package/src/providers/__tests__/types.test.ts +85 -0
- package/src/providers/__tests__/zero-fill-guard.test.ts +62 -0
- package/src/providers/__tests__/zhipu.test.ts +27 -14
- package/src/providers/alibaba.ts +20 -39
- package/src/providers/anthropic.ts +23 -11
- package/src/providers/base.ts +19 -0
- package/src/providers/claude-code.ts +27 -18
- package/src/providers/deepseek.ts +9 -28
- package/src/providers/gemini.ts +20 -58
- package/src/providers/moonshot.ts +15 -11
- package/src/providers/openai.ts +79 -61
- package/src/providers/opencode.ts +16 -33
- package/src/providers/stream-accumulator.ts +27 -21
- package/src/providers/types.ts +29 -4
- package/src/providers/zhipu.ts +15 -44
- package/src/task-analysis/__tests__/analyzer.test.ts +0 -0
- package/src/task-analysis/__tests__/enrichers-schema-deducer.test.ts +34 -2
- package/src/task-analysis/__tests__/no-ast-imports.test.ts +52 -0
- package/src/task-analysis/__tests__/repository.test.ts +65 -0
- package/src/task-analysis/__tests__/types.test.ts +63 -130
- package/src/task-analysis/analyzer.ts +91 -0
- package/src/task-analysis/enrichers/schema-deducer.ts +13 -2
- package/src/task-analysis/index.ts +2 -36
- package/src/task-analysis/repository.ts +45 -0
- package/src/task-analysis/types.ts +42 -58
- package/src/ui/client/__tests__/api.test.ts +143 -1
- package/src/ui/client/__tests__/bootstrap.test.ts +178 -62
- package/src/ui/client/__tests__/job-adapter.test.ts +203 -2
- package/src/ui/client/__tests__/load-state.test.ts +70 -0
- package/src/ui/client/__tests__/types.test.ts +66 -3
- package/src/ui/client/__tests__/useJobDetailWithUpdates.test.ts +390 -77
- package/src/ui/client/__tests__/useJobList.test.ts +198 -23
- package/src/ui/client/__tests__/useJobListWithUpdates.test.ts +218 -7
- package/src/ui/client/adapters/__tests__/job-adapter.test.ts +186 -0
- package/src/ui/client/adapters/job-adapter.ts +41 -16
- package/src/ui/client/api.ts +38 -15
- package/src/ui/client/bootstrap.ts +19 -14
- package/src/ui/client/hooks/useJobDetailWithUpdates.ts +73 -97
- package/src/ui/client/hooks/useJobList.ts +26 -31
- package/src/ui/client/hooks/useJobListWithUpdates.ts +42 -76
- package/src/ui/client/load-state.ts +20 -0
- package/src/ui/client/reducers/__tests__/job-events.test.ts +568 -0
- package/src/ui/client/reducers/job-events.ts +137 -0
- package/src/ui/client/types.ts +16 -20
- package/src/ui/components/DAGGrid.tsx +6 -1
- package/src/ui/components/JobDetail.tsx +12 -4
- package/src/ui/components/JobTable.tsx +41 -13
- package/src/ui/components/StageTimeline.tsx +8 -20
- package/src/ui/components/TaskAnalysisDisplay.tsx +48 -6
- package/src/ui/components/__tests__/DAGGrid.test.tsx +36 -0
- package/src/ui/components/__tests__/JobDetail.test.tsx +112 -0
- package/src/ui/components/__tests__/JobTable.test.tsx +137 -1
- package/src/ui/components/__tests__/StageTimeline.test.tsx +5 -6
- package/src/ui/components/__tests__/TaskAnalysisDisplay.test.tsx +64 -0
- package/src/ui/components/types.ts +35 -15
- package/src/ui/dist/assets/{index--RH3sAt3.js → index-DN3-zvtP.js} +4324 -263
- package/src/ui/dist/assets/index-DN3-zvtP.js.map +1 -0
- package/src/ui/dist/assets/style-CtZBnjlR.css +2 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/pages/PipelineDetail.tsx +60 -4
- package/src/ui/pages/PromptPipelineDashboard.tsx +59 -7
- package/src/ui/pages/__tests__/PromptPipelineDashboard.test.tsx +114 -32
- package/src/ui/pages/__tests__/pages.test.tsx +236 -42
- package/src/ui/server/__tests__/concurrency-endpoint.test.ts +54 -0
- package/src/ui/server/__tests__/config-bridge-node.test.ts +34 -1
- package/src/ui/server/__tests__/config-bridge.test.ts +9 -1
- package/src/ui/server/__tests__/embedded-assets.test.ts +66 -0
- package/src/ui/server/__tests__/file-endpoints.test.ts +183 -5
- package/src/ui/server/__tests__/gate-endpoints.test.ts +55 -0
- package/src/ui/server/__tests__/index.test.ts +63 -0
- package/src/ui/server/__tests__/job-control-endpoints.test.ts +512 -2
- package/src/ui/server/__tests__/job-endpoints.test.ts +158 -2
- package/src/ui/server/__tests__/read-static-path-guard.test.ts +94 -0
- package/src/ui/server/__tests__/router-root-isolation.test.ts +204 -0
- package/src/ui/server/__tests__/router-threading.test.ts +148 -0
- package/src/ui/server/__tests__/router.test.ts +104 -0
- package/src/ui/server/__tests__/sse-broadcast.test.ts +44 -0
- package/src/ui/server/__tests__/sse-enhancer.test.ts +70 -0
- package/src/ui/server/config-bridge-node.ts +8 -26
- package/src/ui/server/config-bridge.ts +3 -4
- package/src/ui/server/embedded-assets-imports.d.ts +44 -0
- package/src/ui/server/embedded-assets.ts +13 -2
- package/src/ui/server/endpoints/__tests__/meta-endpoint.test.ts +1 -0
- package/src/ui/server/endpoints/__tests__/pipeline-analysis-endpoint.test.ts +167 -0
- package/src/ui/server/endpoints/__tests__/schema-file-endpoint.test.ts +104 -0
- package/src/ui/server/endpoints/__tests__/task-analysis-endpoint.test.ts +103 -0
- package/src/ui/server/endpoints/__tests__/upload-endpoints.test.ts +162 -96
- package/src/ui/server/endpoints/concurrency-endpoint.ts +2 -1
- package/src/ui/server/endpoints/create-pipeline-endpoint.ts +14 -29
- package/src/ui/server/endpoints/file-endpoints.ts +15 -10
- package/src/ui/server/endpoints/gate-endpoints.ts +2 -6
- package/src/ui/server/endpoints/job-control-endpoints.ts +129 -141
- package/src/ui/server/endpoints/job-endpoints.ts +7 -0
- package/src/ui/server/endpoints/meta-endpoint.ts +1 -1
- package/src/ui/server/endpoints/pipeline-analysis-endpoint.ts +99 -11
- package/src/ui/server/endpoints/schema-file-endpoint.ts +11 -3
- package/src/ui/server/endpoints/task-analysis-endpoint.ts +21 -5
- package/src/ui/server/endpoints/task-save-endpoint.ts +1 -2
- package/src/ui/server/endpoints/upload-endpoints.ts +5 -40
- package/src/ui/server/index.ts +19 -14
- package/src/ui/server/job-reader.ts +14 -2
- package/src/ui/server/router.ts +33 -10
- package/src/ui/server/sse-broadcast.ts +14 -3
- package/src/ui/server/sse-enhancer.ts +12 -2
- package/src/ui/state/__tests__/schema-loader.test.ts +11 -1
- package/src/ui/state/__tests__/snapshot.test.ts +120 -14
- package/src/ui/state/__tests__/types.test.ts +104 -5
- package/src/ui/state/snapshot.ts +2 -2
- package/src/ui/state/transformers/__tests__/list-transformer.test.ts +85 -2
- package/src/ui/state/transformers/__tests__/status-transformer.test.ts +250 -22
- package/src/ui/state/transformers/list-transformer.ts +13 -3
- package/src/ui/state/transformers/status-transformer.ts +36 -170
- package/src/ui/state/types.ts +15 -48
- package/src/utils/__tests__/path-containment.test.ts +160 -0
- package/src/{ui/server/utils → utils}/path-containment.ts +21 -0
- package/src/task-analysis/__tests__/enrichers-analysis-writer.test.ts +0 -86
- package/src/task-analysis/__tests__/enrichers-artifact-resolver.test.ts +0 -124
- package/src/task-analysis/__tests__/extractors-artifacts.test.ts +0 -133
- package/src/task-analysis/__tests__/extractors-llm-calls.test.ts +0 -46
- package/src/task-analysis/__tests__/extractors-stages.test.ts +0 -52
- package/src/task-analysis/__tests__/index.test.ts +0 -143
- package/src/task-analysis/__tests__/parser.test.ts +0 -41
- package/src/task-analysis/__tests__/utils-ast.test.ts +0 -82
- package/src/task-analysis/enrichers/analysis-writer.ts +0 -75
- package/src/task-analysis/enrichers/artifact-resolver.ts +0 -89
- package/src/task-analysis/extractors/artifacts.ts +0 -143
- package/src/task-analysis/extractors/llm-calls.ts +0 -117
- package/src/task-analysis/extractors/stages.ts +0 -45
- package/src/task-analysis/parser.ts +0 -20
- package/src/task-analysis/utils/ast.ts +0 -45
- package/src/ui/dist/assets/index--RH3sAt3.js.map +0 -1
- package/src/ui/dist/assets/style-CSSKuMOe.css +0 -2
- package/src/ui/embedded-assets.js +0 -12
- package/src/ui/server/__tests__/path-containment.test.ts +0 -54
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
2
|
import { mkdtemp, rm, mkdir, readdir } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
@@ -29,8 +29,11 @@ async function scaffoldWorkspace(tmpDir: string): Promise<void> {
|
|
|
29
29
|
await Bun.write(
|
|
30
30
|
join(tmpDir, "pipeline-config", "registry.json"),
|
|
31
31
|
JSON.stringify({
|
|
32
|
+
version: 1,
|
|
32
33
|
pipelines: {
|
|
33
34
|
"test-pipeline": {
|
|
35
|
+
name: "Test Pipeline",
|
|
36
|
+
description: "test pipeline",
|
|
34
37
|
configDir: join(tmpDir, "pipeline-config", "test-pipeline"),
|
|
35
38
|
tasksDir: join(tmpDir, "pipeline-config", "test-pipeline", "tasks"),
|
|
36
39
|
},
|
|
@@ -60,7 +63,7 @@ describe("submitJobWithValidation", () => {
|
|
|
60
63
|
|
|
61
64
|
it("writes seed file to pending and returns success", async () => {
|
|
62
65
|
const result = await submitJobWithValidation({
|
|
63
|
-
|
|
66
|
+
root: tmpDir,
|
|
64
67
|
seedObject: { pipeline: "test-pipeline" },
|
|
65
68
|
});
|
|
66
69
|
|
|
@@ -79,7 +82,7 @@ describe("submitJobWithValidation", () => {
|
|
|
79
82
|
|
|
80
83
|
it("returns seed name as jobName when provided", async () => {
|
|
81
84
|
const result = await submitJobWithValidation({
|
|
82
|
-
|
|
85
|
+
root: tmpDir,
|
|
83
86
|
seedObject: { pipeline: "test-pipeline", name: "my-job" },
|
|
84
87
|
});
|
|
85
88
|
|
|
@@ -87,74 +90,88 @@ describe("submitJobWithValidation", () => {
|
|
|
87
90
|
expect((result as SubmitSuccessResult).jobName).toBe("my-job");
|
|
88
91
|
});
|
|
89
92
|
|
|
90
|
-
it("rejects non-object seed with message
|
|
93
|
+
it("rejects non-object seed with exact message", async () => {
|
|
91
94
|
const result = await submitJobWithValidation({
|
|
92
|
-
|
|
95
|
+
root: tmpDir,
|
|
93
96
|
seedObject: "not an object",
|
|
94
97
|
});
|
|
95
98
|
|
|
96
99
|
expect(result.success).toBe(false);
|
|
97
|
-
expect((result as SubmitFailureResult).message).
|
|
100
|
+
expect((result as SubmitFailureResult).message).toBe("seed must be a JSON object");
|
|
98
101
|
});
|
|
99
102
|
|
|
100
|
-
it("rejects array seed with message
|
|
103
|
+
it("rejects array seed with exact message", async () => {
|
|
101
104
|
const result = await submitJobWithValidation({
|
|
102
|
-
|
|
105
|
+
root: tmpDir,
|
|
103
106
|
seedObject: [1, 2, 3],
|
|
104
107
|
});
|
|
105
108
|
|
|
106
109
|
expect(result.success).toBe(false);
|
|
107
|
-
expect((result as SubmitFailureResult).message).
|
|
110
|
+
expect((result as SubmitFailureResult).message).toBe("seed must be a JSON object");
|
|
108
111
|
});
|
|
109
112
|
|
|
110
|
-
it("rejects null seed with message
|
|
113
|
+
it("rejects null seed with exact message", async () => {
|
|
111
114
|
const result = await submitJobWithValidation({
|
|
112
|
-
|
|
115
|
+
root: tmpDir,
|
|
113
116
|
seedObject: null,
|
|
114
117
|
});
|
|
115
118
|
|
|
116
119
|
expect(result.success).toBe(false);
|
|
117
|
-
expect((result as SubmitFailureResult).message).
|
|
120
|
+
expect((result as SubmitFailureResult).message).toBe("seed must be a JSON object");
|
|
118
121
|
});
|
|
119
122
|
|
|
120
|
-
it("rejects seed missing pipeline field with message
|
|
123
|
+
it("rejects seed missing pipeline field with exact message", async () => {
|
|
121
124
|
const result = await submitJobWithValidation({
|
|
122
|
-
|
|
125
|
+
root: tmpDir,
|
|
123
126
|
seedObject: { name: "my-job" },
|
|
124
127
|
});
|
|
125
128
|
|
|
126
129
|
expect(result.success).toBe(false);
|
|
127
|
-
expect((result as SubmitFailureResult).message).
|
|
130
|
+
expect((result as SubmitFailureResult).message).toBe("seed.pipeline must be a non-empty string");
|
|
128
131
|
});
|
|
129
132
|
|
|
130
133
|
it("rejects seed with empty pipeline string", async () => {
|
|
131
134
|
const result = await submitJobWithValidation({
|
|
132
|
-
|
|
135
|
+
root: tmpDir,
|
|
133
136
|
seedObject: { pipeline: "" },
|
|
134
137
|
});
|
|
135
138
|
|
|
136
139
|
expect(result.success).toBe(false);
|
|
137
|
-
expect((result as SubmitFailureResult).message).
|
|
140
|
+
expect((result as SubmitFailureResult).message).toBe("seed.pipeline must be a non-empty string");
|
|
138
141
|
});
|
|
139
142
|
|
|
140
|
-
it("rejects non-
|
|
143
|
+
it("rejects non-string pipeline value", async () => {
|
|
141
144
|
const result = await submitJobWithValidation({
|
|
142
|
-
|
|
145
|
+
root: tmpDir,
|
|
146
|
+
seedObject: { pipeline: 123 },
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
expect(result.success).toBe(false);
|
|
150
|
+
expect((result as SubmitFailureResult).message).toBe("seed.pipeline must be a non-empty string");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("rejects non-existent pipeline slug with exact message", async () => {
|
|
154
|
+
const result = await submitJobWithValidation({
|
|
155
|
+
root: tmpDir,
|
|
143
156
|
seedObject: { pipeline: "does-not-exist" },
|
|
144
157
|
});
|
|
145
158
|
|
|
146
159
|
expect(result.success).toBe(false);
|
|
147
|
-
expect((result as SubmitFailureResult).message).
|
|
160
|
+
expect((result as SubmitFailureResult).message).toBe(
|
|
161
|
+
"Pipeline 'does-not-exist' not found in registry",
|
|
162
|
+
);
|
|
148
163
|
});
|
|
149
164
|
|
|
150
165
|
it("rejects seed with empty name string", async () => {
|
|
151
166
|
const result = await submitJobWithValidation({
|
|
152
|
-
|
|
167
|
+
root: tmpDir,
|
|
153
168
|
seedObject: { pipeline: "test-pipeline", name: "" },
|
|
154
169
|
});
|
|
155
170
|
|
|
156
171
|
expect(result.success).toBe(false);
|
|
157
|
-
expect((result as SubmitFailureResult).message).
|
|
172
|
+
expect((result as SubmitFailureResult).message).toBe(
|
|
173
|
+
"seed.name must be a non-empty string if provided",
|
|
174
|
+
);
|
|
158
175
|
});
|
|
159
176
|
});
|
|
160
177
|
|
|
@@ -196,7 +213,7 @@ describe("PipelineOrchestrator.getStatus", () => {
|
|
|
196
213
|
}),
|
|
197
214
|
);
|
|
198
215
|
|
|
199
|
-
const orch = new PipelineOrchestrator({ autoStart: false });
|
|
216
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
200
217
|
const record = await orch.getStatus(jobId);
|
|
201
218
|
|
|
202
219
|
expect(record.jobId).toBe(jobId);
|
|
@@ -214,17 +231,18 @@ describe("PipelineOrchestrator.getStatus", () => {
|
|
|
214
231
|
id: jobId,
|
|
215
232
|
name: "done-job",
|
|
216
233
|
pipeline: "test-pipeline",
|
|
217
|
-
state: "
|
|
234
|
+
state: "done",
|
|
218
235
|
createdAt: "2026-01-01T00:00:00.000Z",
|
|
219
236
|
tasks: {},
|
|
220
237
|
}),
|
|
221
238
|
);
|
|
222
239
|
|
|
223
|
-
const orch = new PipelineOrchestrator({ autoStart: false });
|
|
240
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
224
241
|
const record = await orch.getStatus(jobId);
|
|
225
242
|
|
|
226
243
|
expect(record.jobId).toBe(jobId);
|
|
227
244
|
expect(record.jobName).toBe("done-job");
|
|
245
|
+
expect(record.state).toBe("complete");
|
|
228
246
|
});
|
|
229
247
|
|
|
230
248
|
it("finds job by name scan fallback", async () => {
|
|
@@ -243,7 +261,7 @@ describe("PipelineOrchestrator.getStatus", () => {
|
|
|
243
261
|
}),
|
|
244
262
|
);
|
|
245
263
|
|
|
246
|
-
const orch = new PipelineOrchestrator({ autoStart: false });
|
|
264
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
247
265
|
const record = await orch.getStatus("named-job");
|
|
248
266
|
|
|
249
267
|
expect(record.jobId).toBe(jobId);
|
|
@@ -257,7 +275,7 @@ describe("PipelineOrchestrator.getStatus", () => {
|
|
|
257
275
|
JSON.stringify({ pipeline: "test-pipeline", name: "pending-job" }),
|
|
258
276
|
);
|
|
259
277
|
|
|
260
|
-
const orch = new PipelineOrchestrator({ autoStart: false });
|
|
278
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
261
279
|
const record = await orch.getStatus(jobId);
|
|
262
280
|
|
|
263
281
|
expect(record.jobId).toBe(jobId);
|
|
@@ -267,11 +285,78 @@ describe("PipelineOrchestrator.getStatus", () => {
|
|
|
267
285
|
expect(record.createdAt).toBeTruthy();
|
|
268
286
|
});
|
|
269
287
|
|
|
288
|
+
it("returns pending record for non-UUID seed filename parsed by parseSeedFilename", async () => {
|
|
289
|
+
const jobId = "my-job";
|
|
290
|
+
await Bun.write(
|
|
291
|
+
join(tmpDir, "pipeline-data", "pending", `${jobId}-seed.json`),
|
|
292
|
+
JSON.stringify({ pipeline: "test-pipeline", name: "my-job" }),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
296
|
+
const record = await orch.getStatus(jobId);
|
|
297
|
+
|
|
298
|
+
expect(record.jobId).toBe(jobId);
|
|
299
|
+
expect(record.jobName).toBe("my-job");
|
|
300
|
+
expect(record.state).toBe("pending");
|
|
301
|
+
expect(record.pipeline).toBe("test-pipeline");
|
|
302
|
+
});
|
|
303
|
+
|
|
270
304
|
it("throws for nonexistent job with message containing 'not found'", async () => {
|
|
271
|
-
const orch = new PipelineOrchestrator({ autoStart: false });
|
|
305
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
272
306
|
|
|
273
307
|
await expect(orch.getStatus("nonexistent-id")).rejects.toThrow("not found");
|
|
274
308
|
});
|
|
309
|
+
|
|
310
|
+
it("returns unreadable placeholder for corrupt current job instead of throwing", async () => {
|
|
311
|
+
const jobId = "33333333-4444-5555-6666-777777777777";
|
|
312
|
+
const jobDir = join(tmpDir, "pipeline-data", "current", jobId);
|
|
313
|
+
await mkdir(jobDir, { recursive: true });
|
|
314
|
+
await Bun.write(join(jobDir, "tasks-status.json"), "{{{not valid json");
|
|
315
|
+
|
|
316
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
317
|
+
const record = await orch.getStatus(jobId);
|
|
318
|
+
|
|
319
|
+
expect(record.jobId).toBe(jobId);
|
|
320
|
+
expect(record.state).toBe("failed");
|
|
321
|
+
expect(record.readable).toBe(false);
|
|
322
|
+
expect(record.error).toBeDefined();
|
|
323
|
+
const error = record.error as { code: string; message: string; path: string };
|
|
324
|
+
expect(error.code).toBe("STATUS_CORRUPT");
|
|
325
|
+
expect(error.path).toBe(join(jobDir, "tasks-status.json"));
|
|
326
|
+
expect(error.message).toContain("Repair or delete");
|
|
327
|
+
expect(error.message).toContain(jobId);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("skips unexpected current read errors and falls back to complete lookup", async () => {
|
|
331
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
332
|
+
const jobId = "33333333-4444-5555-6666-888888888888";
|
|
333
|
+
const currentDir = join(tmpDir, "pipeline-data", "current", jobId);
|
|
334
|
+
await mkdir(currentDir, { recursive: true });
|
|
335
|
+
await mkdir(join(currentDir, "tasks-status.json"));
|
|
336
|
+
|
|
337
|
+
const completeDir = join(tmpDir, "pipeline-data", "complete", jobId);
|
|
338
|
+
await mkdir(completeDir, { recursive: true });
|
|
339
|
+
await Bun.write(
|
|
340
|
+
join(completeDir, "tasks-status.json"),
|
|
341
|
+
JSON.stringify({
|
|
342
|
+
id: jobId,
|
|
343
|
+
name: "complete-fallback",
|
|
344
|
+
pipeline: "test-pipeline",
|
|
345
|
+
state: "done",
|
|
346
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
347
|
+
tasks: {},
|
|
348
|
+
}),
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
352
|
+
const record = await orch.getStatus(jobId);
|
|
353
|
+
|
|
354
|
+
expect(record.jobId).toBe(jobId);
|
|
355
|
+
expect(record.jobName).toBe("complete-fallback");
|
|
356
|
+
expect(record.state).toBe("complete");
|
|
357
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining(jobId));
|
|
358
|
+
warn.mockRestore();
|
|
359
|
+
});
|
|
275
360
|
});
|
|
276
361
|
|
|
277
362
|
// ─── PipelineOrchestrator.listJobs ───────────────────────────────────────────
|
|
@@ -330,13 +415,13 @@ describe("PipelineOrchestrator.listJobs", () => {
|
|
|
330
415
|
id: completeId,
|
|
331
416
|
name: "complete-one",
|
|
332
417
|
pipeline: "test-pipeline",
|
|
333
|
-
state: "
|
|
418
|
+
state: "done",
|
|
334
419
|
createdAt: "2026-01-01T00:00:00.000Z",
|
|
335
420
|
tasks: {},
|
|
336
421
|
}),
|
|
337
422
|
);
|
|
338
423
|
|
|
339
|
-
const orch = new PipelineOrchestrator({ autoStart: false });
|
|
424
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
340
425
|
const jobs = await orch.listJobs();
|
|
341
426
|
|
|
342
427
|
expect(jobs.length).toBe(3);
|
|
@@ -354,7 +439,7 @@ describe("PipelineOrchestrator.listJobs", () => {
|
|
|
354
439
|
});
|
|
355
440
|
|
|
356
441
|
it("returns empty array when all directories are empty", async () => {
|
|
357
|
-
const orch = new PipelineOrchestrator({ autoStart: false });
|
|
442
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
358
443
|
const jobs = await orch.listJobs();
|
|
359
444
|
|
|
360
445
|
expect(jobs).toEqual([]);
|
|
@@ -364,9 +449,335 @@ describe("PipelineOrchestrator.listJobs", () => {
|
|
|
364
449
|
// Remove all pipeline-data directories
|
|
365
450
|
await rm(join(tmpDir, "pipeline-data"), { recursive: true, force: true });
|
|
366
451
|
|
|
367
|
-
const orch = new PipelineOrchestrator({ autoStart: false });
|
|
452
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
453
|
+
const jobs = await orch.listJobs();
|
|
454
|
+
|
|
455
|
+
expect(jobs).toEqual([]);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("surfaces corrupt current job as unreadable placeholder alongside valid and pending jobs", async () => {
|
|
459
|
+
const pendingId = "bbbb-1111-2222-3333-0001";
|
|
460
|
+
await Bun.write(
|
|
461
|
+
join(tmpDir, "pipeline-data", "pending", `${pendingId}-seed.json`),
|
|
462
|
+
JSON.stringify({ pipeline: "test-pipeline", name: "pending-one" }),
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
const validCurrentId = "bbbb-1111-2222-3333-0002";
|
|
466
|
+
const validCurrentDir = join(tmpDir, "pipeline-data", "current", validCurrentId);
|
|
467
|
+
await mkdir(validCurrentDir, { recursive: true });
|
|
468
|
+
await Bun.write(
|
|
469
|
+
join(validCurrentDir, "tasks-status.json"),
|
|
470
|
+
JSON.stringify({
|
|
471
|
+
id: validCurrentId,
|
|
472
|
+
name: "valid-current",
|
|
473
|
+
pipeline: "test-pipeline",
|
|
474
|
+
state: "running",
|
|
475
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
476
|
+
tasks: {},
|
|
477
|
+
}),
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
const corruptCurrentId = "bbbb-1111-2222-3333-0003";
|
|
481
|
+
const corruptCurrentDir = join(tmpDir, "pipeline-data", "current", corruptCurrentId);
|
|
482
|
+
await mkdir(corruptCurrentDir, { recursive: true });
|
|
483
|
+
await Bun.write(join(corruptCurrentDir, "tasks-status.json"), "{{{not valid json");
|
|
484
|
+
|
|
485
|
+
const completeId = "bbbb-1111-2222-3333-0004";
|
|
486
|
+
const completeDir = join(tmpDir, "pipeline-data", "complete", completeId);
|
|
487
|
+
await mkdir(completeDir, { recursive: true });
|
|
488
|
+
await Bun.write(
|
|
489
|
+
join(completeDir, "tasks-status.json"),
|
|
490
|
+
JSON.stringify({
|
|
491
|
+
id: completeId,
|
|
492
|
+
name: "complete-one",
|
|
493
|
+
pipeline: "test-pipeline",
|
|
494
|
+
state: "done",
|
|
495
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
496
|
+
tasks: {},
|
|
497
|
+
}),
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
501
|
+
const jobs = await orch.listJobs();
|
|
502
|
+
|
|
503
|
+
expect(jobs.length).toBe(4);
|
|
504
|
+
|
|
505
|
+
const ids = jobs.map((j) => j.jobId);
|
|
506
|
+
expect(ids).toContain(pendingId);
|
|
507
|
+
expect(ids).toContain(validCurrentId);
|
|
508
|
+
expect(ids).toContain(corruptCurrentId);
|
|
509
|
+
expect(ids).toContain(completeId);
|
|
510
|
+
|
|
511
|
+
const corrupt = jobs.find((j) => j.jobId === corruptCurrentId)!;
|
|
512
|
+
expect(corrupt.state).toBe("failed");
|
|
513
|
+
expect(corrupt.readable).toBe(false);
|
|
514
|
+
const corruptError = corrupt.error as { code: string; message: string; path: string };
|
|
515
|
+
expect(corruptError.code).toBe("STATUS_CORRUPT");
|
|
516
|
+
expect(corruptError.path).toBe(join(corruptCurrentDir, "tasks-status.json"));
|
|
517
|
+
expect(corruptError.message).toContain("Repair or delete");
|
|
518
|
+
expect(corruptError.message).toContain(corruptCurrentId);
|
|
519
|
+
|
|
520
|
+
const valid = jobs.find((j) => j.jobId === validCurrentId)!;
|
|
521
|
+
expect(valid.readable).toBeUndefined();
|
|
522
|
+
expect(valid.error).toBeUndefined();
|
|
523
|
+
expect(valid.jobName).toBe("valid-current");
|
|
524
|
+
|
|
525
|
+
const pending = jobs.find((j) => j.jobId === pendingId)!;
|
|
526
|
+
expect(pending.state).toBe("pending");
|
|
527
|
+
expect(pending.readable).toBeUndefined();
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("skips current job whose status file is missing", async () => {
|
|
531
|
+
const missingId = "cccc-1111-2222-3333-0001";
|
|
532
|
+
const missingDir = join(tmpDir, "pipeline-data", "current", missingId);
|
|
533
|
+
await mkdir(missingDir, { recursive: true });
|
|
534
|
+
|
|
535
|
+
const validId = "cccc-1111-2222-3333-0002";
|
|
536
|
+
const validDir = join(tmpDir, "pipeline-data", "current", validId);
|
|
537
|
+
await mkdir(validDir, { recursive: true });
|
|
538
|
+
await Bun.write(
|
|
539
|
+
join(validDir, "tasks-status.json"),
|
|
540
|
+
JSON.stringify({
|
|
541
|
+
id: validId,
|
|
542
|
+
name: "valid-current",
|
|
543
|
+
pipeline: "test-pipeline",
|
|
544
|
+
state: "running",
|
|
545
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
546
|
+
tasks: {},
|
|
547
|
+
}),
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
551
|
+
const jobs = await orch.listJobs();
|
|
552
|
+
|
|
553
|
+
const ids = jobs.map((j) => j.jobId);
|
|
554
|
+
expect(ids).toContain(validId);
|
|
555
|
+
expect(ids).not.toContain(missingId);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it("skips current job with unexpected status read error without throwing", async () => {
|
|
559
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
560
|
+
const unreadableId = "dddd-1111-2222-3333-0001";
|
|
561
|
+
const unreadableDir = join(tmpDir, "pipeline-data", "current", unreadableId);
|
|
562
|
+
await mkdir(unreadableDir, { recursive: true });
|
|
563
|
+
await mkdir(join(unreadableDir, "tasks-status.json"));
|
|
564
|
+
|
|
565
|
+
const validId = "dddd-1111-2222-3333-0002";
|
|
566
|
+
const validDir = join(tmpDir, "pipeline-data", "current", validId);
|
|
567
|
+
await mkdir(validDir, { recursive: true });
|
|
568
|
+
await Bun.write(
|
|
569
|
+
join(validDir, "tasks-status.json"),
|
|
570
|
+
JSON.stringify({
|
|
571
|
+
id: validId,
|
|
572
|
+
name: "valid-current",
|
|
573
|
+
pipeline: "test-pipeline",
|
|
574
|
+
state: "running",
|
|
575
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
576
|
+
tasks: {},
|
|
577
|
+
}),
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
581
|
+
const jobs = await orch.listJobs();
|
|
582
|
+
|
|
583
|
+
const ids = jobs.map((j) => j.jobId);
|
|
584
|
+
expect(ids).toContain(validId);
|
|
585
|
+
expect(ids).not.toContain(unreadableId);
|
|
586
|
+
expect(warn).toHaveBeenCalledWith(expect.stringContaining(unreadableId));
|
|
587
|
+
warn.mockRestore();
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
it("getStatus and listJobs return identical state for the same fixture", async () => {
|
|
591
|
+
const jobId = "cccc-5555-6666-7777-0001";
|
|
592
|
+
const jobDir = join(tmpDir, "pipeline-data", "current", jobId);
|
|
593
|
+
await mkdir(jobDir, { recursive: true });
|
|
594
|
+
await Bun.write(
|
|
595
|
+
join(jobDir, "tasks-status.json"),
|
|
596
|
+
JSON.stringify({
|
|
597
|
+
id: jobId,
|
|
598
|
+
name: "cross-surface",
|
|
599
|
+
pipeline: "test-pipeline",
|
|
600
|
+
state: "running",
|
|
601
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
602
|
+
tasks: {
|
|
603
|
+
a: { state: "running" },
|
|
604
|
+
b: { state: "pending" },
|
|
605
|
+
},
|
|
606
|
+
}),
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
610
|
+
const getStatusRecord = await orch.getStatus(jobId);
|
|
611
|
+
const listJobsRecords = await orch.listJobs();
|
|
612
|
+
const listJobsRecord = listJobsRecords.find((j) => j.jobId === jobId)!;
|
|
613
|
+
|
|
614
|
+
expect(getStatusRecord.state).toBe("running");
|
|
615
|
+
expect(listJobsRecord.state).toBe("running");
|
|
616
|
+
expect(getStatusRecord.state).toBe(listJobsRecord.state);
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it("getStatus returns canonical 'complete' for persisted 'done' state", async () => {
|
|
620
|
+
const jobId = "cccc-5555-6666-7777-0002";
|
|
621
|
+
const jobDir = join(tmpDir, "pipeline-data", "complete", jobId);
|
|
622
|
+
await mkdir(jobDir, { recursive: true });
|
|
623
|
+
await Bun.write(
|
|
624
|
+
join(jobDir, "tasks-status.json"),
|
|
625
|
+
JSON.stringify({
|
|
626
|
+
id: jobId,
|
|
627
|
+
name: "done-canonical",
|
|
628
|
+
pipeline: "test-pipeline",
|
|
629
|
+
state: "done",
|
|
630
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
631
|
+
tasks: { a: { state: "done" } },
|
|
632
|
+
}),
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
636
|
+
const record = await orch.getStatus(jobId);
|
|
637
|
+
|
|
638
|
+
expect(record.state).toBe("complete");
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it("listJobs returns canonical 'complete' for persisted 'done' state without force", async () => {
|
|
642
|
+
const jobId = "cccc-5555-6666-7777-0003";
|
|
643
|
+
const jobDir = join(tmpDir, "pipeline-data", "complete", jobId);
|
|
644
|
+
await mkdir(jobDir, { recursive: true });
|
|
645
|
+
await Bun.write(
|
|
646
|
+
join(jobDir, "tasks-status.json"),
|
|
647
|
+
JSON.stringify({
|
|
648
|
+
id: jobId,
|
|
649
|
+
name: "done-list",
|
|
650
|
+
pipeline: "test-pipeline",
|
|
651
|
+
state: "done",
|
|
652
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
653
|
+
tasks: { a: { state: "done" } },
|
|
654
|
+
}),
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
368
658
|
const jobs = await orch.listJobs();
|
|
659
|
+
const record = jobs.find((j) => j.jobId === jobId)!;
|
|
660
|
+
|
|
661
|
+
expect(record.state).toBe("complete");
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
it("getStatus maps persisted 'error' legacy status to canonical 'failed'", async () => {
|
|
665
|
+
const jobId = "cccc-5555-6666-7777-0004";
|
|
666
|
+
const jobDir = join(tmpDir, "pipeline-data", "current", jobId);
|
|
667
|
+
await mkdir(jobDir, { recursive: true });
|
|
668
|
+
await Bun.write(
|
|
669
|
+
join(jobDir, "tasks-status.json"),
|
|
670
|
+
JSON.stringify({
|
|
671
|
+
id: jobId,
|
|
672
|
+
name: "legacy-error",
|
|
673
|
+
pipeline: "test-pipeline",
|
|
674
|
+
state: "unknown-state",
|
|
675
|
+
status: "error",
|
|
676
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
677
|
+
tasks: { a: { state: "failed" } },
|
|
678
|
+
}),
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
682
|
+
const record = await orch.getStatus(jobId);
|
|
683
|
+
|
|
684
|
+
expect(record.state).toBe("failed");
|
|
685
|
+
});
|
|
369
686
|
|
|
687
|
+
it("listJobs preserves JobView extras (costs)", async () => {
|
|
688
|
+
const jobId = "cccc-5555-6666-7777-0005";
|
|
689
|
+
const jobDir = join(tmpDir, "pipeline-data", "current", jobId);
|
|
690
|
+
await mkdir(jobDir, { recursive: true });
|
|
691
|
+
await Bun.write(
|
|
692
|
+
join(jobDir, "tasks-status.json"),
|
|
693
|
+
JSON.stringify({
|
|
694
|
+
id: jobId,
|
|
695
|
+
name: "with-extras",
|
|
696
|
+
pipeline: "test-pipeline",
|
|
697
|
+
state: "running",
|
|
698
|
+
progress: 42,
|
|
699
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
700
|
+
costs: { usd: 1.23 },
|
|
701
|
+
tasks: { a: { state: "running" } },
|
|
702
|
+
}),
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
706
|
+
const jobs = await orch.listJobs();
|
|
707
|
+
const record = jobs.find((j) => j.jobId === jobId)!;
|
|
708
|
+
|
|
709
|
+
expect(record.state).toBe("running");
|
|
710
|
+
expect(record.progress).toBe(0);
|
|
711
|
+
expect(record.costs).toEqual({ usd: 1.23 });
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
// ─── AC-4: API strict root resolution ─────────────────────────────────────────
|
|
716
|
+
|
|
717
|
+
describe("API strict root resolution (AC-4)", () => {
|
|
718
|
+
let tmpDir: string;
|
|
719
|
+
let savedPoRoot: string | undefined;
|
|
720
|
+
|
|
721
|
+
beforeEach(async () => {
|
|
722
|
+
tmpDir = await mkdtemp(join(tmpdir(), "pop-api-strict-test-"));
|
|
723
|
+
await scaffoldWorkspace(tmpDir);
|
|
724
|
+
savedPoRoot = process.env["PO_ROOT"];
|
|
725
|
+
delete process.env["PO_ROOT"];
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
afterEach(async () => {
|
|
729
|
+
if (savedPoRoot === undefined) {
|
|
730
|
+
delete process.env["PO_ROOT"];
|
|
731
|
+
} else {
|
|
732
|
+
process.env["PO_ROOT"] = savedPoRoot;
|
|
733
|
+
}
|
|
734
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it("PipelineOrchestrator throws when neither root nor PO_ROOT is set", () => {
|
|
738
|
+
expect(() => new PipelineOrchestrator({ autoStart: false })).toThrow(
|
|
739
|
+
"Workspace root is required",
|
|
740
|
+
);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
it("PipelineOrchestrator succeeds when root is set explicitly", async () => {
|
|
744
|
+
const orch = new PipelineOrchestrator({ autoStart: false, root: tmpDir });
|
|
745
|
+
const jobs = await orch.listJobs();
|
|
746
|
+
expect(jobs).toEqual([]);
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it("PipelineOrchestrator succeeds when PO_ROOT is set and root is absent", async () => {
|
|
750
|
+
process.env["PO_ROOT"] = tmpDir;
|
|
751
|
+
const orch = new PipelineOrchestrator({ autoStart: false });
|
|
752
|
+
const jobs = await orch.listJobs();
|
|
370
753
|
expect(jobs).toEqual([]);
|
|
371
754
|
});
|
|
755
|
+
|
|
756
|
+
it("submitJobWithValidation throws when neither root nor PO_ROOT is set", async () => {
|
|
757
|
+
await expect(
|
|
758
|
+
submitJobWithValidation({ seedObject: { pipeline: "test-pipeline" } }),
|
|
759
|
+
).rejects.toThrow("Workspace root is required");
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
it("submitJobWithValidation succeeds when root is set explicitly", async () => {
|
|
763
|
+
const result = await submitJobWithValidation({
|
|
764
|
+
root: tmpDir,
|
|
765
|
+
seedObject: { pipeline: "test-pipeline" },
|
|
766
|
+
});
|
|
767
|
+
expect(result.success).toBe(true);
|
|
768
|
+
const pendingFiles = await readdir(join(tmpDir, "pipeline-data", "pending"));
|
|
769
|
+
const seedFile = pendingFiles.find((f) => f.endsWith("-seed.json"));
|
|
770
|
+
expect(seedFile).toBeDefined();
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
it("submitJobWithValidation succeeds when PO_ROOT is set and root is absent", async () => {
|
|
774
|
+
process.env["PO_ROOT"] = tmpDir;
|
|
775
|
+
const result = await submitJobWithValidation({
|
|
776
|
+
seedObject: { pipeline: "test-pipeline" },
|
|
777
|
+
});
|
|
778
|
+
expect(result.success).toBe(true);
|
|
779
|
+
const pendingFiles = await readdir(join(tmpDir, "pipeline-data", "pending"));
|
|
780
|
+
const seedFile = pendingFiles.find((f) => f.endsWith("-seed.json"));
|
|
781
|
+
expect(seedFile).toBeDefined();
|
|
782
|
+
});
|
|
372
783
|
});
|