diff --git a/tests/boundary/filesystem/interfaceBuilderBundle.test.ts b/tests/boundary/filesystem/interfaceBuilderBundle.test.ts index a418c8f0bd..73f9425d04 100644 --- a/tests/boundary/filesystem/interfaceBuilderBundle.test.ts +++ b/tests/boundary/filesystem/interfaceBuilderBundle.test.ts @@ -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"), '$archiverNSKeyedArchiver$objects$null11507-08-17T05:20:00Z$top', @@ -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 () => { diff --git a/tests/boundary/filesystem/keyedArchiveDateRange.test.ts b/tests/boundary/filesystem/keyedArchiveDateRange.test.ts index 235b402d66..fd5b984a98 100644 --- a/tests/boundary/filesystem/keyedArchiveDateRange.test.ts +++ b/tests/boundary/filesystem/keyedArchiveDateRange.test.ts @@ -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)"), ); }, ); diff --git a/tests/boundary/mcp/nativeUiFinalizationMcp.test.ts b/tests/boundary/mcp/nativeUiFinalizationMcp.test.ts index 51c7c587bd..2a389e1554 100644 --- a/tests/boundary/mcp/nativeUiFinalizationMcp.test.ts +++ b/tests/boundary/mcp/nativeUiFinalizationMcp.test.ts @@ -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", diff --git a/tests/fixtures/keyedArchiveNumberMetadata.ts b/tests/fixtures/keyedArchiveNumberMetadata.ts index 9369692c93..79ba543e3a 100644 --- a/tests/fixtures/keyedArchiveNumberMetadata.ts +++ b/tests/fixtures/keyedArchiveNumberMetadata.ts @@ -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 = [ "90071992547409923", "9007199254740992", @@ -23,8 +32,19 @@ export const mixedArchiveNumberElements = [ "9007199254740992x", ] 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: "9007199254740992", @@ -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("".length, -"".length), + element.endsWith("x") + ? "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: "9007199254740993", }, -] 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( "__INTEGER__", - "integerElement" in item - ? item.integerElement - : "9007199254740993", + item.integerElement ?? "9007199254740993", ) .replace("__OTHER__", 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", + ), + ), + ); + } } };