mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
test(contracts): align plist and UI finalization regressions
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).
This commit is contained in:
1 parent
32f0e51209
commit
d1adc60795
4 files changed
+97
-44
No files matched your search
@@ -1013,12 +1013,12 @@ describe("bounded Interface Builder archive decoding", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("decodes an archive whose date the decoder cannot represent", async () => {
|
||||
it("decodes an archive with a date year beyond four digits", async () => {
|
||||
const root = await createTestTempDirectory("rea-ib-test-");
|
||||
const bundle = join(root, "Example.app");
|
||||
const resources = join(bundle, "Contents", "Resources");
|
||||
await mkdir(resources, { recursive: true });
|
||||
// plutil writes years past 9999 unsigned, which the decoder rejects.
|
||||
// XML property-list dates use an extended ISO year in the public schema.
|
||||
await writeFile(
|
||||
join(resources, "Far.nib"),
|
||||
'<?xml version="1.0"?><plist version="1.0"><dict><key>$archiver</key><string>NSKeyedArchiver</string><key>$objects</key><array><string>$null</string><date>11507-08-17T05:20:00Z</date></array><key>$top</key><dict/></dict></plist>',
|
||||
@@ -1030,7 +1030,10 @@ describe("bounded Interface Builder archive decoding", () => {
|
||||
});
|
||||
|
||||
expect(result.documents).toHaveLength(1);
|
||||
expect(result.limitations).toContainEqual(
|
||||
expect(result.graph.coverage).toContainEqual(
|
||||
expect.objectContaining({ facet: "archive_decode", status: "complete" }),
|
||||
);
|
||||
expect(result.limitations).not.toContainEqual(
|
||||
expect.stringMatching(
|
||||
/^Contents\/Resources\/Far\.nib: 1 date value\(s\)/u,
|
||||
),
|
||||
@@ -1039,7 +1042,9 @@ describe("bounded Interface Builder archive decoding", () => {
|
||||
expect.stringContaining("could not be decoded"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Xcode-compiled Interface Builder archives", () => {
|
||||
it.skipIf(!xcodeIbtoolAvailable)(
|
||||
"decodes an Xcode-compiled storyboard NIB and recovers its UI routes",
|
||||
async () => {
|
||||
|
||||
@@ -10,11 +10,11 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest(
|
||||
"built CLI reports a date the decoder cannot represent as unknown",
|
||||
"built CLI preserves dates with years beyond four digits",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-keyed-archive-date-");
|
||||
const path = join(root, "archive.plist");
|
||||
// plutil writes years past 9999 unsigned, which the decoder rejects.
|
||||
// XML property-list dates use an extended ISO year in the public schema.
|
||||
const xml = build({
|
||||
$archiver: "NSKeyedArchiver",
|
||||
$version: 100000,
|
||||
@@ -37,15 +37,19 @@ cliTest(
|
||||
result.objects.map(({ id, kind, value }) => ({ id, kind, value })),
|
||||
).toEqual([
|
||||
{ id: 0, kind: "nil", value: "$null" },
|
||||
{ id: 1, kind: "scalar", value: { $plist_type: "date", iso8601: null } },
|
||||
{
|
||||
id: 1,
|
||||
kind: "scalar",
|
||||
value: { $plist_type: "date", iso8601: "+011507-08-17T05:20:00.000Z" },
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
kind: "scalar",
|
||||
value: { $plist_type: "date", iso8601: "2020-01-01T00:00:00.000Z" },
|
||||
},
|
||||
]);
|
||||
expect(result.limitations).toContainEqual(
|
||||
expect.stringContaining("1 date value(s)"),
|
||||
expect(result.limitations).not.toContainEqual(
|
||||
expect.stringContaining("date value(s)"),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -76,13 +76,15 @@ it.each([false, true])(
|
||||
});
|
||||
expect(parseMcpToolError(result)).toMatchObject({
|
||||
error: {
|
||||
code: fails ? "cleanup_incomplete" : "cancelled",
|
||||
code: fails ? "cleanup_incomplete" : "provider_timeout",
|
||||
details: {
|
||||
cleanup: fails ? "incomplete" : "complete",
|
||||
...(fails
|
||||
? {
|
||||
cleanup: "incomplete",
|
||||
execution_failure: "execution_failure",
|
||||
diagnostics: { primary_error: { code: "cancelled" } },
|
||||
diagnostics: {
|
||||
primary_error: { code: "provider_timeout" },
|
||||
},
|
||||
resources: ["controlled-ui-runtime"],
|
||||
}
|
||||
: {}),
|
||||
@@ -94,6 +96,7 @@ it.each([false, true])(
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(cleanupLease.released).toBe(!fails);
|
||||
allowClose = true;
|
||||
const closed = await client.callTool({
|
||||
name: "close_binary",
|
||||
|
||||
+74
-33
@@ -11,8 +11,17 @@ 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,
|
||||
});
|
||||
|
||||
/** Accepted decoder representations whose supplemental metadata needs care. */
|
||||
/** Boundary cases that retain exact integers or explicit unknown literals. */
|
||||
export const mixedArchiveNumberElements = [
|
||||
"<integer>9007199254740992<!-- split -->3</integer>",
|
||||
"<integer>9007199254740992<![CDATA[3]]></integer>",
|
||||
@@ -23,8 +32,19 @@ export const mixedArchiveNumberElements = [
|
||||
"<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 = [
|
||||
export const archiveNumberMetadataCases: readonly MetadataCase[] = [
|
||||
...[
|
||||
{
|
||||
element: "<real>9007199254740992<![CDATA[0]]></real>",
|
||||
@@ -39,15 +59,23 @@ export const archiveNumberMetadataCases = [
|
||||
source: { first: "__INTEGER__", other: "__OTHER__" },
|
||||
element,
|
||||
expected: { first: EXACT_INTEGER, other: value },
|
||||
incomplete: false,
|
||||
malformedReference: false,
|
||||
})),
|
||||
...mixedArchiveNumberElements.map((element) => ({
|
||||
name: element,
|
||||
source: { first: "__INTEGER__", other: "__OTHER__" },
|
||||
element,
|
||||
expected: { first: ROUNDED, other: ROUNDED },
|
||||
incomplete: true,
|
||||
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) => [
|
||||
@@ -56,7 +84,6 @@ export const archiveNumberMetadataCases = [
|
||||
source: { [key]: { amount: "__INTEGER__" } },
|
||||
element: "",
|
||||
expected: { [key]: { amount: EXACT_INTEGER } },
|
||||
incomplete: false,
|
||||
malformedReference: false,
|
||||
},
|
||||
{
|
||||
@@ -64,7 +91,6 @@ export const archiveNumberMetadataCases = [
|
||||
source: { [key]: ["__INTEGER__"] },
|
||||
element: "",
|
||||
expected: { [key]: [EXACT_INTEGER] },
|
||||
incomplete: false,
|
||||
malformedReference: false,
|
||||
},
|
||||
{
|
||||
@@ -72,30 +98,23 @@ export const archiveNumberMetadataCases = [
|
||||
source: { [key]: "__INTEGER__" },
|
||||
element: "",
|
||||
expected: { [key]: ROUNDED },
|
||||
incomplete: false,
|
||||
malformedReference: true,
|
||||
},
|
||||
]),
|
||||
{
|
||||
name: "entity-encoded integer text",
|
||||
// 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: EXACT_INTEGER },
|
||||
incomplete: false,
|
||||
expected: {
|
||||
amount: unknownInteger("9007199254740993", "invalid-literal"),
|
||||
},
|
||||
knownIntegerCount: 0,
|
||||
unknownScalarCount: 1,
|
||||
malformedReference: false,
|
||||
integerElement: "<integer>9007199254740993</integer>",
|
||||
},
|
||||
] satisfies readonly {
|
||||
name: string;
|
||||
source: JsonValue;
|
||||
element: string;
|
||||
expected: JsonValue;
|
||||
incomplete: boolean;
|
||||
malformedReference: boolean;
|
||||
integerElement?: string;
|
||||
}[];
|
||||
|
||||
type MetadataCase = (typeof archiveNumberMetadataCases)[number];
|
||||
];
|
||||
|
||||
/** Write one inert archive and retain the original byte digest for assertions. */
|
||||
export const keyedArchiveNumberMetadataFixture = async (item: MetadataCase) => {
|
||||
@@ -108,9 +127,7 @@ export const keyedArchiveNumberMetadataFixture = async (item: MetadataCase) => {
|
||||
})
|
||||
.replace(
|
||||
"<string>__INTEGER__</string>",
|
||||
"integerElement" in item
|
||||
? item.integerElement
|
||||
: "<integer>9007199254740993</integer>",
|
||||
item.integerElement ?? "<integer>9007199254740993</integer>",
|
||||
)
|
||||
.replace("<string>__OTHER__</string>", item.element);
|
||||
const bytes = Buffer.from(xml);
|
||||
@@ -145,14 +162,26 @@ export const expectKeyedArchiveNumberMetadata = (
|
||||
status: "resolved",
|
||||
raw: { CF$UID: 1 },
|
||||
});
|
||||
if (item.incomplete) {
|
||||
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),
|
||||
);
|
||||
expect(
|
||||
result.limitations.some((note) => note.includes("unambiguous integer")),
|
||||
).toBe(false);
|
||||
} else if (item.malformedReference) {
|
||||
} 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: [],
|
||||
@@ -162,8 +191,20 @@ export const expectKeyedArchiveNumberMetadata = (
|
||||
});
|
||||
} else {
|
||||
expect(result.references).toHaveLength(1);
|
||||
expect(result.limitations).toContainEqual(
|
||||
expect.stringMatching(/^1 unambiguous integer value/u),
|
||||
);
|
||||
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",
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user