Files
reverse-engineering-anything/src/application/apiContract/ApiContractService.test.ts
T
gensokyo-dev 91d73a113e feat(contracts): inventory offline OpenAPI operation declarations (#9104)
* feat(contracts): inventory offline OpenAPI operation declarations

* docs(contracts): link fixture provenance outside the docs site

* fix(contracts): describe the shared API document byte allowance

* fix(api-contract): preserve safe diagnostics and completed source facts
2026-11-06 15:13:51 +08:00

322 lines
11 KiB
TypeScript

import { createHash } from "node:crypto";
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it, vi } from "vitest";
import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
import { apiContractSchema } from "../../domain/apiContract/apiContract.js";
import { AnalysisResourceConstraintError } from "../../domain/analysisErrorCore.js";
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
import { inspectApiContractEvidence } from "./ApiContractService.js";
import * as readOwner from "./ApiContractRead.js";
import * as parser from "../../domain/apiContract/parseApiContract.js";
const fixture = new URL(
"../../../tests/fixtures/openapi/petstore.json.txt",
import.meta.url,
);
const inputFile = async (bytes: Uint8Array | string) => {
const root = await createTestTempDirectory("rea-api-contract-");
const path = join(root, "contract.json");
await writeFile(path, bytes);
return { root, path };
};
it("binds the published source bytes to complete Evidence independently of reply selection", async () => {
const bytes = await readFile(fixture);
const { path } = await inputFile(bytes);
const resources = new ArtifactResourceScope();
try {
const results = await Promise.all([
inspectApiContractEvidence({ path }, resources),
inspectApiContractEvidence({ path, detail: "complete" }, resources),
inspectApiContractEvidence(
{
path,
view: {
kind: "api-contract",
collection: "operations",
fields: [],
limit: 1,
},
},
resources,
),
]);
for (const result of results) expect(result.ok).toBe(true);
const [summary, complete, page] = results;
if (!summary?.ok || !complete?.ok || !page?.ok)
throw new Error("Expected verified imports");
expect(summary.value.evidence_id).toBe(complete.value.evidence_id);
expect(page.value.evidence_id).toBe(complete.value.evidence_id);
const value = apiContractSchema.parse(complete.value.normalized_result);
expect(value.artifact).toEqual({
path,
bytes: bytes.length,
sha256: createHash("sha256").update(bytes).digest("hex"),
});
expect(value.summary.declared).toBe(19);
expect(complete.value.operation).toBe("inspect_api_contract");
expect(complete.value.parameters).not.toHaveProperty("detail");
expect(complete.value.parameters).not.toHaveProperty("view");
expect((await readFile(path)).equals(bytes)).toBe(true);
} finally {
await resources.close();
}
});
it.each([
[Buffer.from([255]), "not valid UTF-8"],
[Buffer.from("{broken"), "JSON"],
[Buffer.from("\uFEFF{}"), "byte-order mark"],
])(
"reports malformed UTF-8/JSON without rewriting source %#",
async (bytes, reason) => {
const { path } = await inputFile(bytes);
const resources = new ArtifactResourceScope();
try {
const result = await inspectApiContractEvidence({ path }, resources);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error._tag).toBe("AnalysisInputError");
expect(JSON.stringify(result.error)).toContain(reason);
expect(result.error.partialObservation).toEqual({
kind: "file-snapshot-hash",
path,
bytes: bytes.length,
sha256: createHash("sha256").update(bytes).digest("hex"),
});
}
expect((await readFile(path)).equals(bytes)).toBe(true);
} finally {
await resources.close();
}
},
);
it("rejects the selected physical byte budget before parsing", async () => {
const { path } = await inputFile("{bad json}");
const resources = new ArtifactResourceScope();
try {
const result = await inspectApiContractEvidence(
{ path, max_file_bytes: 4 },
resources,
);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error._tag).toBe("AnalysisResourceConstraintError");
if (result.error instanceof AnalysisResourceConstraintError)
expect(result.error.operation).toBe("inspect_api_contract");
}
} finally {
await resources.close();
}
});
it("bounds nesting even in schema regions omitted from the inventory", async () => {
let nested: unknown = {};
for (let depth = 0; depth < 1001; depth++) nested = { child: nested };
const { path } = await inputFile(
JSON.stringify({
openapi: "3.1.2",
info: { title: "Nested", version: "1" },
paths: {},
components: nested,
}),
);
const resources = new ArtifactResourceScope();
try {
const result = await inspectApiContractEvidence({ path }, resources);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error._tag).toBe("AnalysisInputError");
expect(JSON.stringify(result.error)).toContain("maximum nesting depth");
expect(result.error.partialObservation).toMatchObject({
kind: "file-snapshot-hash",
path,
});
}
} finally {
await resources.close();
}
});
it("rejects relative, missing and non-regular selections at the input owner", async () => {
const { root } = await inputFile("{}");
const directory = join(root, "directory");
await mkdir(directory);
const resources = new ArtifactResourceScope();
try {
for (const path of [
"relative.json",
join(root, "missing.json"),
directory,
]) {
const result = await inspectApiContractEvidence({ path }, resources);
expect(result.ok).toBe(false);
if (!result.ok) expect(result.error._tag).toBe("AnalysisInputError");
}
} finally {
await resources.close();
}
});
it.skipIf(process.platform === "win32")(
"preserves an actionable regular-file retry for a selected symlink",
async () => {
const { root, path } = await inputFile("{}");
const link = join(root, "linked.json");
await symlink(path, link);
const resources = new ArtifactResourceScope();
try {
const result = await inspectApiContractEvidence(
{ path: link },
resources,
);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error._tag).toBe("AnalysisInputError");
expect(JSON.stringify(result.error)).toContain(path);
}
} finally {
await resources.close();
}
},
);
it("honors pre-cancellation with an opaque caller reason", async () => {
const controller = new AbortController();
const reason = { _tag: "ArtifactReaderFailure", reason: "integrity" };
controller.abort(reason);
const resources = new ArtifactResourceScope();
try {
const result = await inspectApiContractEvidence(
{ path: "/unused.json" },
resources,
{ signal: controller.signal },
);
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error._tag).toBe("AnalysisCancelledError");
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBeUndefined();
}
} finally {
await resources.close();
}
});
it("keeps native JSON source excerpts internal while preserving the failure cause", async () => {
const bytes = '{"$ref":"https://u:p@h","":NaN}';
const { path } = await inputFile(bytes);
const resources = new ArtifactResourceScope();
try {
const result = await inspectApiContractEvidence({ path }, resources);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected a syntax refusal");
expect(result.error.cause).toBeInstanceOf(SyntaxError);
expect(JSON.stringify(result.error)).not.toContain("u:p@h");
expect(result.error.partialObservation).toEqual({
kind: "file-snapshot-hash",
path,
bytes: Buffer.byteLength(bytes),
sha256: createHash("sha256").update(bytes).digest("hex"),
});
} finally {
await resources.close();
}
});
it.each([
[{ openapi: "2.0" }, "AnalysisUnsupportedTargetError"],
[
{
openapi: "3.1.2",
info: { title: "a".repeat(3 * 1024 * 1024), version: "1" },
},
"AnalysisResourceConstraintError",
],
])(
"keeps a completed parser failure ahead of queued cancellation %#",
async (document, tag) => {
const bytes = JSON.stringify(document);
const { path } = await inputFile(bytes);
const resources = new ArtifactResourceScope();
const controller = new AbortController();
const realParse = parser.parseApiContractSteps;
const scheduled = vi
.spyOn(parser, "parseApiContractSteps")
.mockImplementationOnce(function* (...args) {
const result = yield* realParse(...args);
expect(result.ok).toBe(false);
setImmediate(() => controller.abort("after-parser-failure"));
return result;
});
try {
const result = await inspectApiContractEvidence({ path }, resources, {
signal: controller.signal,
});
await new Promise<void>((resolve) => setImmediate(resolve));
expect(controller.signal.aborted).toBe(true);
expect(result.ok).toBe(false);
if (result.ok) throw new Error("Expected the parser failure");
expect(result.error._tag).toBe(tag);
expect(result.error.partialObservation).toEqual({
kind: "file-snapshot-hash",
path,
bytes: Buffer.byteLength(bytes),
sha256: createHash("sha256").update(bytes).digest("hex"),
});
} finally {
scheduled.mockRestore();
await resources.close();
}
},
);
it("cancels a cooperative document traversal after the actual verified file read", async () => {
const paths = Object.fromEntries(
Array.from({ length: 3000 }, (_, index) => [
`/items/${index}`,
{ get: { operationId: String(index) } },
]),
);
const bytes = JSON.stringify({
openapi: "3.1.2",
info: { title: "Cancellation", version: "1" },
paths,
});
const { path } = await inputFile(bytes);
const controller = new AbortController();
const reason = { stage: "after-source-read" };
const realRead = readOwner.readApiContract;
const scheduled = vi
.spyOn(readOwner, "readApiContract")
.mockImplementationOnce(async (...args) => {
const loaded = await realRead(...args);
expect(loaded.ok).toBe(true);
setImmediate(() => controller.abort(reason));
return loaded;
});
const resources = new ArtifactResourceScope();
try {
const result = await inspectApiContractEvidence({ path }, resources, {
signal: controller.signal,
});
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error._tag).toBe("AnalysisCancelledError");
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toEqual({
kind: "file-snapshot-hash",
path,
bytes: Buffer.byteLength(bytes),
sha256: createHash("sha256").update(bytes).digest("hex"),
});
}
expect(await readFile(path, "utf8")).toBe(bytes);
} finally {
scheduled.mockRestore();
await resources.close();
}
});