Showing the changes to apps/server/src/git/Layers/OpenCodeTextGeneration.test.ts (1 of 4 files). Show all
Unified · Split
| @@ -17,7 +17,9 @@ const runtimeMock = vi.hoisted(() => { · 16 unmodified lines | |||
| 17 | promptUrls: [] as string[], | 17 | promptUrls: [] as string[], |
| 18 | authHeaders: [] as Array<string | null>, | 18 | authHeaders: [] as Array<string | null>, |
| 19 | closeCalls: [] as string[], | 19 | closeCalls: [] as string[], |
| 20 | promptResult: undefined as { data?: { info?: { structured?: unknown } } } | undefined, | 20 | promptResult: undefined as |
| 21 | | { data?: { info?: { error?: unknown }; parts?: Array<{ type: string; text?: string }> } } | ||
| 22 | | undefined, | ||
| 21 | }; | 23 | }; |
| 22 | 24 | ||
| 23 | return { | 25 | return { |
| @@ -63,12 +65,15 @@ vi.mock("../../provider/opencodeRuntime.ts", async () => { · 39 unmodified lines | |||
| 63 | return ( | 65 | return ( |
| 64 | runtimeMock.state.promptResult ?? { | 66 | runtimeMock.state.promptResult ?? { |
| 65 | data: { | 67 | data: { |
| 66 | info: { | 68 | parts: [ |
| 67 | structured: { | 69 | { |
| 68 | subject: "Improve OpenCode reuse", | 70 | type: "text", |
| 69 | body: "Reuse one server for the full action.", | 71 | text: JSON.stringify({ |
| 72 | subject: "Improve OpenCode reuse", | ||
| 73 | body: "Reuse one server for the full action.", | ||
| 74 | }), | ||
| 70 | }, | 75 | }, |
| 71 | }, | 76 | ], |
| 72 | }, | 77 | }, |
| 73 | } | 78 | } |
| 74 | ); | 79 | ); |
| @@ -198,7 +203,7 @@ it.layer(OpenCodeTextGenerationTestLayer)("OpenCodeTextGenerationLive", (it) => · 123 unmodified lines | |||
| 198 | }).pipe(Effect.provide(TestClock.layer())), | 203 | }).pipe(Effect.provide(TestClock.layer())), |
| 199 | ); | 204 | ); |
| 200 | 205 | ||
| 201 | it.effect("returns a typed missing-output error when OpenCode omits info.structured", () => | 206 | it.effect("returns a typed empty-output error when OpenCode returns no text parts", () => |
| 202 | Effect.gen(function* () { | 207 | Effect.gen(function* () { |
| 203 | runtimeMock.state.promptResult = { data: {} }; | 208 | runtimeMock.state.promptResult = { data: {} }; |
| 204 | const textGeneration = yield* TextGeneration; | 209 | const textGeneration = yield* TextGeneration; |
| @@ -213,7 +218,67 @@ it.layer(OpenCodeTextGenerationTestLayer)("OpenCodeTextGenerationLive", (it) => · 8 unmodified lines | |||
| 213 | }) | 218 | }) |
| 214 | .pipe(Effect.flip); | 219 | .pipe(Effect.flip); |
| 215 | 220 | ||
| 216 | expect(error.message).toContain("OpenCode returned no structured output."); | 221 | expect(error.message).toContain("OpenCode returned empty output."); |
| 222 | }), | ||
| 223 | ); | ||
| 224 | |||
| 225 | it.effect("parses JSON returned as plain text output", () => | ||
| 226 | Effect.gen(function* () { | ||
| 227 | runtimeMock.state.promptResult = { | ||
| 228 | data: { | ||
| 229 | parts: [ | ||
| 230 | { | ||
| 231 | type: "text", | ||
| 232 | text: 'Here is the result:\n{"subject":"Tighten OpenCode parsing","body":"Handle JSON text output locally."}', | ||
| 233 | }, | ||
| 234 | ], | ||
| 235 | }, | ||
| 236 | }; | ||
| 237 | const textGeneration = yield* TextGeneration; | ||
| 238 | |||
| 239 | const result = yield* textGeneration.generateCommitMessage({ | ||
| 240 | cwd: process.cwd(), | ||
| 241 | branch: "feature/opencode-reuse", | ||
| 242 | stagedSummary: "M README.md", | ||
| 243 | stagedPatch: "diff --git a/README.md b/README.md", | ||
| 244 | modelSelection: DEFAULT_TEST_MODEL_SELECTION, | ||
| 245 | }); | ||
| 246 | |||
| 247 | expect(result).toEqual({ | ||
| 248 | subject: "Tighten OpenCode parsing", | ||
| 249 | body: "Handle JSON text output locally.", | ||
| 250 | }); | ||
| 251 | }), | ||
| 252 | ); | ||
| 253 | |||
| 254 | it.effect("surfaces the upstream OpenCode structured-output error message", () => | ||
| 255 | Effect.gen(function* () { | ||
| 256 | runtimeMock.state.promptResult = { | ||
| 257 | data: { | ||
| 258 | info: { | ||
| 259 | error: { | ||
| 260 | name: "StructuredOutputError", | ||
| 261 | data: { | ||
| 262 | message: "Model did not produce structured output", | ||
| 263 | retries: 2, | ||
| 264 | }, | ||
| 265 | }, | ||
| 266 | }, | ||
| 267 | }, | ||
| 268 | }; | ||
| 269 | const textGeneration = yield* TextGeneration; | ||
| 270 | |||
| 271 | const error = yield* textGeneration | ||
| 272 | .generateCommitMessage({ | ||
| 273 | cwd: process.cwd(), | ||
| 274 | branch: "feature/opencode-reuse", | ||
| 275 | stagedSummary: "M README.md", | ||
| 276 | stagedPatch: "diff --git a/README.md b/README.md", | ||
| 277 | modelSelection: DEFAULT_TEST_MODEL_SELECTION, | ||
| 278 | }) | ||
| 279 | .pipe(Effect.flip); | ||
| 280 | |||
| 281 | expect(error.message).toContain("Model did not produce structured output"); | ||
| 217 | }), | 282 | }), |
| 218 | ); | 283 | ); |
| 219 | }); | 284 | }); |