Update public-boundary expectations after the explicit XML unknown-scalar and native UI deadline provenance changes. Retain raw malformed literals, independent exact integers, expanded-year ISO dates, and partial UI reports. Assert successful cleanup releases its lease and failed cleanup retains it until close retries. Keep malformed-input and true date-range coverage. The shared number fixture owns CLI/filesystem/MCP expectations. Native plutil controls confirm integer entities are invalid plist literals, even though they are valid XML. No runtime behavior or CI gates change. Validation: 187 source cases, 138 focused public cases, final 97-case rerun after fixture typing/group changes, full static/architecture checks, and test discovery (1591 files).
211 lines
6.3 KiB
TypeScript
211 lines
6.3 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { build } from "plist";
|
|
import { expect } from "vitest";
|
|
|
|
import { keyedArchiveResultSchema } from "../../src/domain/apple/keyedArchive.js";
|
|
import type { JsonValue } from "../../src/domain/jsonValue.js";
|
|
import { createTestTempDirectory } from "./temporaryDirectory.js";
|
|
|
|
const EXACT_INTEGER = { $plist_type: "integer", decimal: "9007199254740993" };
|
|
const ROUNDED = 9007199254740992;
|
|
const unknownInteger = (
|
|
literal: string,
|
|
reason: "invalid-literal" | "unsupported-markup",
|
|
) => ({
|
|
$plist_type: "unknown",
|
|
element: "integer",
|
|
literal,
|
|
reason,
|
|
});
|
|
|
|
/** Boundary cases that retain exact integers or explicit unknown literals. */
|
|
export const mixedArchiveNumberElements = [
|
|
"<integer>9007199254740992<!-- split -->3</integer>",
|
|
"<integer>9007199254740992<![CDATA[3]]></integer>",
|
|
"<integer><![CDATA[9007199254740992]]>3</integer>",
|
|
"<integer><![CDATA[90071992547409923]]></integer>",
|
|
"<integer>9007199254740992<?rea split?>3</integer>",
|
|
"<integer>9007199254740992<string>3</string></integer>",
|
|
"<integer>9007199254740992x</integer>",
|
|
] as const;
|
|
|
|
interface MetadataCase {
|
|
readonly name: string;
|
|
readonly source: JsonValue;
|
|
readonly element: string;
|
|
readonly expected: JsonValue;
|
|
readonly knownIntegerCount?: number;
|
|
readonly unknownScalarCount?: number;
|
|
readonly malformedReference: boolean;
|
|
readonly integerElement?: string;
|
|
}
|
|
|
|
/** Ordinary dictionaries and malformed numeric references at the same boundary. */
|
|
export const archiveNumberMetadataCases: readonly MetadataCase[] = [
|
|
...[
|
|
{
|
|
element: "<real>9007199254740992<![CDATA[0]]></real>",
|
|
value: 90071992547409920,
|
|
},
|
|
{
|
|
element: "<real><![CDATA[1]]>9007199254740992</real>",
|
|
value: Number("19007199254740992"),
|
|
},
|
|
].map(({ element, value }) => ({
|
|
name: element,
|
|
source: { first: "__INTEGER__", other: "__OTHER__" },
|
|
element,
|
|
expected: { first: EXACT_INTEGER, other: value },
|
|
malformedReference: false,
|
|
})),
|
|
...mixedArchiveNumberElements.map((element) => ({
|
|
name: element,
|
|
source: { first: "__INTEGER__", other: "__OTHER__" },
|
|
element,
|
|
expected: {
|
|
first: EXACT_INTEGER,
|
|
other: unknownInteger(
|
|
element.slice("<integer>".length, -"</integer>".length),
|
|
element.endsWith("x</integer>")
|
|
? "invalid-literal"
|
|
: "unsupported-markup",
|
|
),
|
|
},
|
|
knownIntegerCount: 1,
|
|
unknownScalarCount: 1,
|
|
malformedReference: false,
|
|
})),
|
|
...(["UID", "CF$UID"] as const).flatMap((key) => [
|
|
{
|
|
name: `${key} dictionary data`,
|
|
source: { [key]: { amount: "__INTEGER__" } },
|
|
element: "",
|
|
expected: { [key]: { amount: EXACT_INTEGER } },
|
|
malformedReference: false,
|
|
},
|
|
{
|
|
name: `${key} array data`,
|
|
source: { [key]: ["__INTEGER__"] },
|
|
element: "",
|
|
expected: { [key]: [EXACT_INTEGER] },
|
|
malformedReference: false,
|
|
},
|
|
{
|
|
name: `${key} malformed numeric reference`,
|
|
source: { [key]: "__INTEGER__" },
|
|
element: "",
|
|
expected: { [key]: ROUNDED },
|
|
malformedReference: true,
|
|
},
|
|
]),
|
|
{
|
|
// Native plist integer grammar reads raw text, unlike real elements.
|
|
name: "entity-encoded integer rejected by native plist grammar",
|
|
source: { amount: "__INTEGER__" },
|
|
element: "",
|
|
expected: {
|
|
amount: unknownInteger("9007199254740993", "invalid-literal"),
|
|
},
|
|
knownIntegerCount: 0,
|
|
unknownScalarCount: 1,
|
|
malformedReference: false,
|
|
integerElement: "<integer>9007199254740993</integer>",
|
|
},
|
|
];
|
|
|
|
/** Write one inert archive and retain the original byte digest for assertions. */
|
|
export const keyedArchiveNumberMetadataFixture = async (item: MetadataCase) => {
|
|
const root = await createTestTempDirectory("rea-keyed-number-metadata-");
|
|
const xml = build({
|
|
$archiver: "NSKeyedArchiver",
|
|
$version: 100000,
|
|
$objects: ["$null", item.source],
|
|
$top: { root: { CF$UID: 1 } },
|
|
})
|
|
.replace(
|
|
"<string>__INTEGER__</string>",
|
|
item.integerElement ?? "<integer>9007199254740993</integer>",
|
|
)
|
|
.replace("<string>__OTHER__</string>", item.element);
|
|
const bytes = Buffer.from(xml);
|
|
const path = join(root, "archive.plist");
|
|
await writeFile(path, bytes);
|
|
return {
|
|
root,
|
|
path,
|
|
digest: createHash("sha256").update(bytes).digest("hex"),
|
|
};
|
|
};
|
|
|
|
/** Require exact ordinary values and honest limitations for incomplete metadata. */
|
|
export const expectKeyedArchiveNumberMetadata = (
|
|
value: unknown,
|
|
item: MetadataCase,
|
|
digest: string,
|
|
) => {
|
|
const result = keyedArchiveResultSchema.parse(value);
|
|
expect(result.archive_sha256).toBe(digest);
|
|
expect(result.roots).toEqual({ root: { CF$UID: 1 } });
|
|
expect(result.objects).toHaveLength(2);
|
|
expect(result.objects[1]).toMatchObject({
|
|
id: 1,
|
|
kind: "dictionary",
|
|
value: item.expected,
|
|
});
|
|
expect(result.references).toContainEqual({
|
|
source: null,
|
|
path: ["root"],
|
|
target: 1,
|
|
status: "resolved",
|
|
raw: { CF$UID: 1 },
|
|
});
|
|
if (item.unknownScalarCount !== undefined) {
|
|
expect(result.limitations).toContainEqual(
|
|
expect.stringMatching(
|
|
new RegExp(
|
|
`^${String(item.unknownScalarCount)} XML scalar value\\(s\\) could not be established`,
|
|
"u",
|
|
),
|
|
),
|
|
);
|
|
expect(result.limitations).not.toContainEqual(
|
|
expect.stringMatching(/metadata.*completely observed.*precision/u),
|
|
);
|
|
} else {
|
|
expect(result.limitations).not.toContainEqual(
|
|
expect.stringMatching(
|
|
/^\d+ XML scalar value\(s\) could not be established/u,
|
|
),
|
|
);
|
|
}
|
|
if (item.malformedReference) {
|
|
expect(result.references).toContainEqual({
|
|
source: 1,
|
|
path: [],
|
|
target: null,
|
|
status: "malformed",
|
|
raw: item.expected,
|
|
});
|
|
} else {
|
|
expect(result.references).toHaveLength(1);
|
|
const knownIntegerCount = item.knownIntegerCount ?? 1;
|
|
if (knownIntegerCount === 0) {
|
|
expect(result.limitations).not.toContainEqual(
|
|
expect.stringMatching(/^\d+ unambiguous integer value/u),
|
|
);
|
|
} else {
|
|
expect(result.limitations).toContainEqual(
|
|
expect.stringMatching(
|
|
new RegExp(
|
|
`^${String(knownIntegerCount)} unambiguous integer value`,
|
|
"u",
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
};
|