145 lines · 4.9 KB · TypeScript
RawDownload
import * as Cause from "effect/Cause";import * as Exit from "effect/Exit";import * as Result from "effect/Result";import * as Schema from "effect/Schema";import { describe, expect, it } from "vite-plus/test"; import {  decodeJsonResult,  extractJsonObject,  formatSchemaError,  fromLenientJson,} from "./schemaJson.ts"; const decodeLenientJson = Schema.decodeUnknownSync(fromLenientJson(Schema.Unknown)); describe("schemaJson helpers", () => {  it("extracts a balanced JSON object from surrounding text", () => {    expect(      extractJsonObject(`Sure, here is the JSON:\`\`\`json{  "subject": "Update README",  "body": ""}\`\`\`Done.`),    ).toBe(`{  "subject": "Update README",  "body": ""}`);  });   it("ignores braces inside strings while finding the object boundary", () => {    expect(      extractJsonObject('prefix {"message":"literal } brace","nested":{"ok":true}} suffix'),    ).toBe('{"message":"literal } brace","nested":{"ok":true}}');  });   it("returns trimmed input when no JSON object starts", () => {    expect(extractJsonObject("  no structured output  ")).toBe("no structured output");  });   it("decodes JSON with comments and trailing commas", () => {    expect(      decodeLenientJson(`{        // Comments are valid in settings files.        "enabled": true,        "values": [1, 2,],      }`),    ).toEqual({      enabled: true,      values: [1, 2],    });  });   it("rejects malformed JSON after lenient preprocessing", () => {    expect(() => decodeLenientJson('{ "enabled": true,, }')).toThrow();  });   it("formats schema failures with paths without exposing invalid values", () => {    const decodeCredential = decodeJsonResult(Schema.Struct({ token: Schema.Number }));    const decoded = decodeCredential('{"token":"credential=secret-value"}');     expect(Result.isFailure(decoded)).toBe(true);    if (Result.isFailure(decoded)) {      expect(formatSchemaError(decoded.failure)).toBe('Invalid type\n  at ["token"]');    }  });   it("preserves nested paths reported by schema filters", () => {    const decode = decodeJsonResult(      Schema.String.check(        Schema.makeFilter(() => ({          path: ["session", "token"],          issue: "credential is invalid",        })),      ),    );    const decoded = decode('"credential=secret-value"');     expect(Result.isFailure(decoded)).toBe(true);    if (Result.isFailure(decoded)) {      const diagnostic = formatSchemaError(decoded.failure);      expect(diagnostic).toBe('Invalid value\n  at ["session"]["token"]');      expect(diagnostic).not.toContain("credential=secret-value");    }  });   it("does not expose malformed lenient JSON input in diagnostics", () => {    const decode = Schema.decodeUnknownExit(fromLenientJson(Schema.Unknown));    const exit = decode('{"token":"credential=secret-value",,}');     expect(Exit.isFailure(exit)).toBe(true);    if (Exit.isFailure(exit)) {      const diagnostic = formatSchemaError(exit.cause);      expect(diagnostic).toBe("Invalid value");      expect(diagnostic).not.toContain("credential=secret-value");    }  });   it("summarizes unexpected defects without serializing their messages", () => {    const diagnostic = formatSchemaError(Cause.die(new Error("credential=secret-value")));     expect(diagnostic).toBe(      "Schema validation failed (failureCount=0, defectCount=1, interruptionCount=0).",    );  });   it("bounds the number of formatted schema issues", () => {    const decode = decodeJsonResult(Schema.Struct({ token: Schema.Number }));    const failures: Array<Cause.Cause<Schema.SchemaError>> = [];    for (let index = 0; index < 10; index += 1) {      const decoded = decode(`{"token":"credential=secret-value-${index}"}`);      if (Result.isFailure(decoded)) {        failures.push(decoded.failure);      }    }     const cause = Cause.fromReasons(failures.flatMap((cause) => cause.reasons));    const diagnostic = formatSchemaError(cause);    expect(diagnostic.match(/Invalid type/g)).toHaveLength(8);    expect(diagnostic).toContain("... and 2 more issue(s)");  });   it("retains the omitted issue count when bounding long diagnostics", () => {    const longPath = Array.from({ length: 16 }, (_, index) => `${index}-${"segment".repeat(16)}`);    const decode = decodeJsonResult(      Schema.String.check(        Schema.makeFilter(() => ({ path: longPath, issue: "credential is invalid" })),      ),    );    const failures: Array<Cause.Cause<Schema.SchemaError>> = [];    for (let index = 0; index < 10; index += 1) {      const decoded = decode(`"credential=secret-value-${index}"`);      if (Result.isFailure(decoded)) {        failures.push(decoded.failure);      }    }     const cause = Cause.fromReasons(failures.flatMap((cause) => cause.reasons));    const diagnostic = formatSchemaError(cause);    expect(diagnostic.length).toBeLessThanOrEqual(2_048);    expect(diagnostic.endsWith("\n... and 2 more issue(s)")).toBe(true);  });});