import { describe, expect, it } from "vitest";
import { convertDocument } from "../../src/core/convert";
import { DATA_LIMITS } from "../../src/core/limits";
import { objectValue } from "../../src/core/model";
import { parseDataDocument } from "../../src/core/parse";
import { DataToolsError } from "../../src/core/types";
describe("bounded format parsers", () => {
it("preserves JSON number lexemes exactly", () => {
const source =
'{"integer":9007199254740993123456789,"decimal":0.123456789012345678901}';
const document = parseDataDocument({ source, format: "json" });
expect(objectValue(document.root, "integer")).toMatchObject({
type: "number",
raw: "9007199254740993123456789",
representation: "exact",
});
expect(convertDocument(document, "json").text).toContain(
"0.123456789012345678901",
);
});
it("rejects duplicate and prototype-affecting JSON keys", () => {
expect(() =>
parseDataDocument({ source: '{"a":1,"a":2}', format: "json" }),
).toThrow(DataToolsError);
expect(() =>
parseDataDocument({ source: '{"__proto__":1}', format: "json" }),
).toThrow(/dangerous object key/iu);
});
it("uses YAML 1.2 core semantics and rejects custom tags and merges", () => {
const document = parseDataDocument({
source: "answer: yes\nactual: true\ninteger: 9007199254740993\n",
format: "yaml",
});
expect(objectValue(document.root, "answer")).toEqual({
type: "string",
value: "yes",
});
expect(objectValue(document.root, "integer")).toMatchObject({
type: "number",
raw: "9007199254740993",
});
expect(() =>
parseDataDocument({ source: "value: !custom test", format: "yaml" }),
).toThrow(/tag/iu);
expect(() =>
parseDataDocument({
source: "base: &base\n a: 1\ncopy:\n <<: *base\n",
format: "yaml",
}),
).toThrow(/merge/iu);
});
it("keeps TOML integers exact and models date/time values", () => {
const document = parseDataDocument({
source:
'title = "example"\nlarge = 9007199254740993\ncreated = 2026-09-01T10:15:30Z\n',
format: "toml",
});
expect(objectValue(document.root, "large")).toMatchObject({
type: "number",
raw: "9007199254740993",
representation: "bigint",
});
expect(objectValue(document.root, "created")).toMatchObject({
type: "date",
dateKind: "offset-date-time",
});
});
it("rejects XML declarations with active resolution semantics", () => {
expect(() =>
parseDataDocument({
source: ']>&x;',
format: "xml",
}),
).toThrow(/DOCTYPE/iu);
expect(() =>
parseDataDocument({
source:
'',
format: "xml",
}),
).toThrow(/XInclude/iu);
expect(() =>
parseDataDocument({
source:
'',
format: "xml",
}),
).toThrow(/XInclude/iu);
});
it("preserves XML namespaces, attributes and ordered children explicitly", () => {
const document = parseDataDocument({
source:
'beforeoneafter',
format: "xml",
});
expect(document.model).toBe("xml-explicit");
expect(objectValue(document.root, "name")).toEqual({
type: "string",
value: "n:root",
});
expect(objectValue(document.root, "namespace")).toEqual({
type: "string",
value: "urn:test",
});
expect(convertDocument(document, "xml").text).toContain(
"one",
);
});
it("parses delimited data as strings, including quoted fields", () => {
const document = parseDataDocument({
source: 'name,value\r\nAda,"1,200"\r\nZero,001',
format: "csv",
});
expect(document.model).toBe("tabular");
expect(document.root).toMatchObject({ type: "array" });
expect(document.diagnostics).toEqual(
expect.arrayContaining([
expect.objectContaining({ code: "delimited.strings" }),
]),
);
});
it("preflights delimited dimensions before allocating the parsed table", () => {
const row = new Array(DATA_LIMITS.maxColumns).fill("x").join(",");
const source = new Array(
Math.floor(DATA_LIMITS.maxCells / DATA_LIMITS.maxColumns) + 2,
)
.fill(row)
.join("\n");
expect(() => parseDataDocument({ source, format: "csv" })).toThrow(
/Cell count/iu,
);
});
it("reports an exact NDJSON line and ignores blank lines", () => {
const document = parseDataDocument({
source: '{"id":1}\n\n{"id":2}\n',
format: "ndjson",
});
expect(document.root).toMatchObject({ type: "array", items: [{}, {}] });
expect(document.diagnostics).toEqual(
expect.arrayContaining([
expect.objectContaining({ code: "ndjson.blank-lines" }),
]),
);
expect(() =>
parseDataDocument({
source: '{"id":1}\n{"id":}\n',
format: "ndjson",
}),
).toThrow(/line 2/iu);
});
it("detects by content or filename and discloses a mismatch", () => {
expect(
parseDataDocument({
source: '{"ok":true}\n{"ok":false}',
format: "auto",
}).format,
).toBe("ndjson");
const mismatch = parseDataDocument({
source: '{"looks":"json"}',
format: "auto",
filename: "values.yaml",
});
expect(mismatch.format).toBe("yaml");
expect(mismatch.diagnostics).toEqual(
expect.arrayContaining([
expect.objectContaining({ code: "format.possible-mismatch" }),
]),
);
});
it("rejects inputs deeper than the structural cap before modelling", () => {
const source = `${"[".repeat(DATA_LIMITS.maxDepth + 1)}0${"]".repeat(DATA_LIMITS.maxDepth + 1)}`;
expect(() => parseDataDocument({ source, format: "json" })).toThrow(
/depth/iu,
);
});
});