Files
data-tools/tests/core/query-convert.test.ts
2026-09-01 02:47:11 +02:00

118 lines
3.8 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { convertDocument } from "../../src/core/convert";
import { DATA_LIMITS } from "../../src/core/limits";
import { parseDataDocument } from "../../src/core/parse";
import {
flattenRows,
queryPointer,
querySafePath,
tableFromNode,
} from "../../src/core/query";
const document = parseDataDocument({
source:
'{"a/b":{"~key":7},"people":[{"name":"Ada","role":"math"},{"name":"Grace","role":"code"}]}',
format: "json",
});
describe("safe data projections", () => {
it("resolves RFC 6901 escaping", () => {
expect(queryPointer(document.root, "/a~1b/~0key")[0]?.node).toMatchObject({
type: "number",
raw: "7",
});
});
it("supports a deliberately small non-evaluating path syntax", () => {
expect(querySafePath(document.root, "$.people[*].name")).toHaveLength(2);
expect(
querySafePath(document.root, "$['a/b']['~key']")[0]?.node,
).toMatchObject({ raw: "7" });
expect(() => querySafePath(document.root, "$..name")).toThrow(
/unsupported path syntax/iu,
);
expect(() => querySafePath(document.root, "$.people[?(@.role)]")).toThrow(
/filters/iu,
);
expect(() =>
queryPointer(
document.root,
`/${"a".repeat(DATA_LIMITS.maxQueryCharacters)}`,
),
).toThrow(/Query character count/iu);
});
it("projects record arrays and flattened scalar leaves", () => {
const people = queryPointer(document.root, "/people")[0]?.node;
expect(people && tableFromNode(people)).toMatchObject({
columns: ["name", "role"],
rows: [
["Ada", "math"],
["Grace", "code"],
],
});
expect(flattenRows(document.root).rows).toEqual(
expect.arrayContaining([["/a~1b/~0key", "number", "7"]]),
);
});
});
describe("explicit conversions", () => {
it("neutralises spreadsheet formula cells only when requested", () => {
const csv = parseDataDocument({
source: 'name,value\ncalc,"=2+2"\nsafe,text',
format: "csv",
});
const protectedOutput = convertDocument(csv, "csv", {
spreadsheetSafe: true,
});
expect(protectedOutput.text).toContain("'=2+2");
expect(protectedOutput.events).toEqual(
expect.arrayContaining([
expect.objectContaining({ code: "csv.formula-neutralized" }),
]),
);
const raw = convertDocument(csv, "csv", { spreadsheetSafe: false });
expect(raw.text).toContain("=2+2");
expect(raw.events).toEqual(
expect.arrayContaining([
expect.objectContaining({ code: "csv.formula-unchecked" }),
]),
);
});
it("turns unsafe decimals into strings rather than silently rounding", () => {
const precise = parseDataDocument({
source: '{"value":0.1234567890123456789012345}',
format: "json",
});
const yaml = convertDocument(precise, "yaml");
expect(yaml.events).toEqual(
expect.arrayContaining([
expect.objectContaining({ code: "yaml.unsafe-number" }),
]),
);
expect(yaml.text).toContain("0.1234567890123456789012345");
});
it("reports null and root coercion when targeting TOML", () => {
const value = parseDataDocument({ source: "null", format: "json" });
const result = convertDocument(value, "toml");
expect(result.text).toContain('value = ""');
expect(result.events.map((event) => event.code)).toEqual(
expect.arrayContaining(["toml.null", "toml.root-table"]),
);
});
it("converts ordinary data to XML using a disclosed stable convention", () => {
const result = convertDocument(document, "xml");
expect(result.text).toContain("<people>");
expect(result.text).toContain("<name>Ada</name>");
expect(result.events).toEqual(
expect.arrayContaining([
expect.objectContaining({ code: "xml.mapping" }),
]),
);
});
});