test(ci): align source preservation and catalog assertions

This commit is contained in:
morluto committed 2026-10-27 02:35:54 +08:00
1 parent b4b5c37ab3
commit 741f2de751
4 files changed
+102 -15

No files matched your search

+1 -4
View File
@@ -26,10 +26,7 @@ import {
locateApkSigningBlock,
parseZipTail,
} from "../dist/domain/apkSignature/apkSigningBlock.js";
import {
mcpTextValue,
requireMcpOutput,
} from "./lib/mcp-verifier-results.mjs";
import { mcpTextValue, requireMcpOutput } from "./lib/mcp-verifier-results.mjs";
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
const execute = promisify(execFile);
@@ -27,7 +27,15 @@ const processRow = (
pid: number,
ppid: number | null,
rss: number | null,
) => ({ user: "shell", pid, ppid, rss, state: "S", name });
) => ({
user: "shell",
pid,
ppid,
rss,
state: "S",
// The final NAME column retains the exact suffix emitted by processLine.
name: `${name} \t`,
});
const processListing = (variant: Variant) => {
const boundary = Number.MAX_SAFE_INTEGER;
@@ -1,3 +1,4 @@
import { createHash } from "node:crypto";
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it, onTestFinished, vi } from "vitest";
@@ -55,6 +56,10 @@ import { LocalWebModuleArtifacts } from "../../../src/browser/modules/WebModuleA
import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js";
import { AnalysisCancelledError } from "../../../src/domain/analysisErrorCore.js";
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
import {
webArtifactAcquisitionSchema,
type WebArtifactAcquisition,
} from "../../../src/domain/webArtifactAcquisition.js";
import { primaryAnalysisError } from "../../../src/domain/analysisErrorBase.js";
import { webModuleArtifactsFixture } from "../../fixtures/webModuleTrace.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
@@ -65,15 +70,24 @@ const sourceFixture = async (source: string) => {
const manifestPath = join(root, "manifest.json");
const sourcePath = join(root, "files", "modules", "main.js");
await mkdir(join(root, "files", "modules"), { recursive: true });
await writeFile(sourcePath, source);
await writeFile(manifestPath, JSON.stringify(fixture.manifest));
const sourceBytes = Buffer.from(source);
const manifestBytes = Buffer.from(JSON.stringify(fixture.manifest));
await writeFile(sourcePath, sourceBytes);
await writeFile(manifestPath, manifestBytes);
return {
input: { manifest_path: manifestPath, script_index: 0 },
sourcePath,
fixture,
sourceBytes,
manifestBytes,
};
};
const fileIdentity = (path: string, bytes: Buffer) => ({
path,
sha256: createHash("sha256").update(bytes).digest("hex"),
bytes: bytes.length,
});
it("retains cancelled source read ownership until service close retries it", async () => {
onTestFinished(() => {
injected.sourcePath = undefined;
@@ -85,7 +99,8 @@ it("retains cancelled source read ownership until service close retries it", asy
injected.closeAttempts = 0;
injected.sourceHandle = undefined;
});
const { input, sourcePath } = await sourceFixture('import "./dep.js";');
const { input, sourcePath, manifestBytes } =
await sourceFixture('import "./dep.js";');
injected.sourcePath = sourcePath;
injected.failClose = true;
@@ -109,6 +124,23 @@ it("retains cancelled source read ownership until service close retries it", asy
const cancelled = await service.trace(input, { signal: controller.signal });
expect(cancelled.ok).toBe(false);
if (cancelled.ok) throw new Error("Expected cancelled source read");
const expectedAcquisition: WebArtifactAcquisition = {
kind: "web-artifact-acquisition",
analysis: "not-performed",
files: [
{
role: "manifest",
file: fileIdentity(input.manifest_path, manifestBytes),
admission: "manifest-validated",
},
],
};
expect(
webArtifactAcquisitionSchema.parse(cancelled.error.partialObservation),
).toEqual(expectedAcquisition);
expect(
projectAnalysisError(cancelled.error).details?.partial_observation,
).toEqual(expectedAcquisition);
expect(cancelled.error.cleanupIncomplete).toBe(true);
expect(cancelled.error.cleanupResources).toContain(sourcePath);
expect(resolver.resolve).not.toHaveBeenCalled();
@@ -158,6 +190,13 @@ it("retains cancelled source read ownership until service close retries it", asy
expect(injected.closeAttempts).toBeGreaterThan(attemptsBeforeRetry);
expect(resolver.close).toHaveBeenCalledTimes(3);
expect(injected.sourceHandle?.fd).toBe(-1);
expect(primaryAnalysisError(cancelled.error).partialObservation).toEqual(
expectedAcquisition,
);
expect(
projectAnalysisError(primaryAnalysisError(cancelled.error)).details
?.partial_observation,
).toEqual(expectedAcquisition);
for (const error of [cancelled.error, blocked.error, cleanupFailure]) {
expect(error).toMatchObject({
cleanupIncomplete: false,
@@ -176,9 +215,8 @@ it.each([
])(
"keeps physical source read failure and exact cleanup ownership %j",
async ({ readFails, closeFails, cancel }) => {
const { input, sourcePath, fixture } = await sourceFixture(
"export const value = 1;",
);
const { input, sourcePath, sourceBytes, manifestBytes } =
await sourceFixture("export const value = 1;");
const physical = Object.assign(new Error("fixture descriptor EIO"), {
code: "EIO",
syscall: "read",
@@ -212,7 +250,10 @@ it.each([
if (!readFails && !cancel) {
if (!result.ok) throw result.error;
expect(result.value.normalized_result).toMatchObject({
source: { path: sourcePath, sha256: fixture.sourceFile.sha256 },
source: {
path: sourcePath,
sha256: fileIdentity(sourcePath, sourceBytes).sha256,
},
manifest: { path: input.manifest_path },
});
await service.close();
@@ -229,7 +270,34 @@ it.each([
});
else expect(primary).toBeInstanceOf(AnalysisCancelledError);
expect(primary.cause).toBe(readFails ? physical : reason);
expect(result.error.partialObservation).toBeUndefined();
const expectedAcquisition: WebArtifactAcquisition = {
kind: "web-artifact-acquisition",
analysis: "not-performed",
files: [
{
role: "manifest",
file: fileIdentity(input.manifest_path, manifestBytes),
admission: "manifest-validated",
},
// Descriptor-finalization cancellation precedes selected-source digest/length admission.
...(!readFails
? [
{
role: "source" as const,
file: fileIdentity(sourcePath, sourceBytes),
admission: "read" as const,
},
]
: []),
],
};
expect(
webArtifactAcquisitionSchema.parse(result.error.partialObservation),
).toEqual(expectedAcquisition);
expect(primary.partialObservation).toEqual(expectedAcquisition);
expect(
projectAnalysisError(result.error).details?.partial_observation,
).toEqual(expectedAcquisition);
expect(result.error.cleanupIncomplete).toBe(closeFails);
if (closeFails) {
expect(result.error.cleanupResources).toEqual([sourcePath]);
@@ -242,6 +310,10 @@ it.each([
expect(result.error.cleanupIncomplete).toBe(false);
expect(result.error.cleanupResources).toEqual([]);
expect(primaryAnalysisError(result.error)).toBe(primary);
expect(primary.partialObservation).toEqual(expectedAcquisition);
expect(projectAnalysisError(primary).details?.partial_observation).toEqual(
expectedAcquisition,
);
expect(projectAnalysisError(result.error)).toMatchObject({
code: readFails ? "artifact_operation_failed" : "cancelled",
...(readFails
@@ -61,10 +61,20 @@ it("verifies canonical names, converted schemas and session metadata together",
}))
.filter((group) => group.names.length > 0);
toolContract("binary_session").outputSchema.parse(body);
const matchingOmission = {
const disagreeingOmission = {
...status,
content: [{ type: "text" as const, text: JSON.stringify(body) }],
};
const matchingOmission = {
...disagreeingOmission,
structuredContent: body,
};
await expect(
verifyCompleteToolCatalog({
...client,
callTool: async () => disagreeingOmission,
}),
).rejects.toThrow("binary_session text and structured results disagree");
// Equal inventories are insufficient when both omit the same canonical tool.
await expect(
verifyCompleteToolCatalog({