import { expect, test, vi } from "vitest" import { JsonModeResponseParser, extractJsonBlock } from "#Source/aio/index.ts" import { Logger } from "#Source/log/index.ts" import type { Standard } from "#Source/validation/index.ts" interface TestJsonOutput { value: number label?: string | undefined } const createSyncSchema = ( options: { validateCount?: { current: number } | undefined } = {}, ): Standard.Schema => { const validateCount = options.validateCount return { "~standard": { version: 1 as const, vendor: "mobius-aio-json-sync-spec", validate: (value: unknown): Standard.Schema.Result => { if (validateCount !== undefined) { validateCount.current = validateCount.current + 1 } if (typeof value !== "object" || value === null) { return { issues: [{ message: "value must be an object" }], } } const record = value as Record if (typeof record["value"] !== "number") { return { issues: [{ message: "value must be a number" }], } } const output: TestJsonOutput = { value: record["value"], } if (typeof record["label"] === "string") { output.label = record["label"] } return { value: output, } }, }, } } const createAsyncSchema = (): Standard.Schema => { return { "~standard": { version: 1 as const, vendor: "mobius-aio-json-async-spec", validate: async (_value: unknown): Promise> => { await Promise.resolve() return { value: { value: 1, }, } }, }, } } test("extractJsonBlock returns the first fenced json block and throws when none exists", () => { const response = [ "before", "```json", '{"value":1}', "```", "```json", '{"value":2}', "```", ].join("\n") expect(extractJsonBlock(response)).toBe(["```json", '{"value":1}', "```"].join("\n")) expect(() => extractJsonBlock("plain text only")).toThrow( "Failed to extract json block from response", ) }) test("JsonModeResponseParser.extractJsonContent strips fences, repairs json-like content and falls back to raw text", () => { const parser = new JsonModeResponseParser({ outputSchema: createSyncSchema(), }) const repairedFromFence = parser.extractJsonContent( ["header", "```json", "{value:1,}", "```"].join("\n"), ) const repairedFromRawText = parser.extractJsonContent("{label:'mobius',value:2,}") expect(repairedFromFence).toBe('\n{"value":1}\n') expect(repairedFromRawText).toBe('{"label":"mobius","value":2}') }) test("JsonModeResponseParser.parse validates sync output, caches by source text and throws for invalid or async schemas", () => { const validateCount = { current: 0 } const parser = new JsonModeResponseParser({ outputSchema: createSyncSchema({ validateCount }), }) const first = parser.parse('{"value":1,"label":"ok"}') const second = parser.parse('{"value":1,"label":"ok"}') expect(first).toEqual({ value: 1, label: "ok" }) expect(second).toBe(first) expect(validateCount.current).toBe(1) const invalidParser = new JsonModeResponseParser({ outputSchema: createSyncSchema(), }) expect(() => invalidParser.parse('{"label":"missing-value"}')).toThrow( "Variable validation failed", ) const asyncParser = new JsonModeResponseParser({ outputSchema: createAsyncSchema(), }) expect(() => asyncParser.parse('{"value":1}')).toThrow("Validation result is a promise") }) test("JsonModeResponseParser.check returns false and logs the failure context when parsing fails", () => { const logger = new Logger({ name: "AioJsonTest", configs: { enabled: false, }, }) const logSpy = vi.spyOn(logger, "log") const parser = new JsonModeResponseParser({ logger, outputSchema: createSyncSchema(), }) expect(parser.check('{"value":3}')).toBe(true) expect(parser.check('{"label":"missing-value"}')).toBe(false) expect(logSpy).toHaveBeenCalledTimes(2) expect(logSpy.mock.calls[0]?.[0]).toBe("check error:") expect(logSpy.mock.calls[1]).toEqual(["error response:", '{"label":"missing-value"}']) })