Files
rea/tests/acceptance/tools/protobufDescriptorIdentity.test.ts
T

267 lines
8.1 KiB
TypeScript

import { create, toBinary } from "@bufbuild/protobuf";
import {
FieldDescriptorProtoSchema,
FileDescriptorSetSchema,
OneofDescriptorProtoSchema,
} from "@bufbuild/protobuf/wkt";
import { createHash } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { z } from "zod";
import { analysisViewResultSchema } from "../../../src/domain/analysisView/analysisView.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { webNetworkCaptureSchema } from "../../../src/domain/webNetworkCapture.js";
import { protobufHar } from "../../fixtures/protobufCapture.js";
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
const sha256 = (bytes: string | Uint8Array) =>
createHash("sha256").update(bytes).digest("hex");
// Two occurrences of first=1, second=3, then child { value=4 }.
const body = Buffer.from("0801080210031a020804", "hex");
const descriptorDefinition = () =>
create(FileDescriptorSetSchema, {
file: [
{
name: "identity.proto",
package: "capture",
syntax: "proto3",
messageType: [
{
name: "Message",
field: [
{ name: "first", number: 1, type: 5, label: 3 },
{ name: "second", number: 2, type: 5, label: 1 },
{
name: "child",
number: 3,
type: 11,
typeName: ".capture.Message.Child",
label: 1,
},
],
nestedType: [
{
name: "Child",
field: [{ name: "value", number: 1, type: 5, label: 1 }],
},
],
},
],
},
],
});
const retainedDetailsSchema = z.object({
partial_observation: z.strictObject({
kind: z.literal("retained-evidence"),
evidence_id: z.string(),
}),
});
cliTest(
"refuses ambiguous descriptor field identities through CLI/MCP while retaining source Evidence",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-protobuf-field-identity-");
const capture = join(root, "capture.har");
const text = protobufHar(body, "application/x-protobuf");
await writeFile(capture, text);
const call = await cli.sharedMcpCall!();
for (const variant of ["forward", "reverse", "nested", "oneof"]) {
const definition = descriptorDefinition();
const message = definition.file[0]!.messageType[0]!;
if (variant === "nested") {
message.nestedType[0]!.field.push(
create(FieldDescriptorProtoSchema, {
name: "alias",
number: 1,
type: 5,
label: 1,
}),
);
} else if (variant === "oneof") {
message.oneofDecl.push(
create(OneofDescriptorProtoSchema, { name: "choice" }),
);
message.field.push(
create(FieldDescriptorProtoSchema, {
name: "alias",
number: 1,
type: 5,
label: 1,
oneofIndex: 0,
}),
);
} else {
message.field[1]!.number = 1;
if (variant === "reverse") message.field.reverse();
}
const descriptor = join(root, `${variant}.pb`);
const bytes = toBinary(FileDescriptorSetSchema, definition);
await writeFile(descriptor, bytes);
const output = await cli.sharedCliRun!({
arguments: [
"inspect-web-network-capture",
capture,
"har",
"--record",
"0",
"--protobuf-schema",
descriptor,
"--protobuf-message",
"capture.Message",
"--json",
],
});
const expected = {
code: "invalid_request",
details: {
issues: [
{
path: ["protobuf", "schema", "descriptor_set_path"],
reason: "invalid_format",
},
],
},
};
expect(output.exitCode, variant).toBe(1);
expect(output.json, variant).toMatchObject(expected);
const response = await call("inspect_web_network_capture", {
capture_path: capture,
format: "har",
record_ordinals: [0],
protobuf: {
schema: {
descriptor_set_path: descriptor,
message_type: "capture.Message",
},
},
});
const error = parseMcpToolError(response).error;
expect(error, variant).toMatchObject(expected);
const source = retainedDetailsSchema.parse(
error.details,
).partial_observation;
const retained = await call("inspect_analysis_view", {
source,
view: { kind: "summary" },
});
expect(retained.isError, JSON.stringify(retained)).not.toBe(true);
expect(
analysisViewResultSchema.parse(
parseEvidence(retained.structuredContent).normalized_result,
),
).toMatchObject({
summary: {
artifact: {
path: capture,
bytes: Buffer.byteLength(text),
sha256: sha256(text),
},
retained_records: 1,
protobuf_coverage: { status: "partial", inspected_records: 0 },
},
});
expect(
Buffer.compare(await readFile(descriptor), Buffer.from(bytes)),
).toBe(0);
}
expect(await readFile(capture, "utf8")).toBe(text);
},
);
cliTest(
"keeps repeated wire occurrences and message-local number reuse through CLI/MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-protobuf-field-control-");
const capture = join(root, "capture.har");
const descriptor = join(root, "valid.pb");
const definition = toBinary(
FileDescriptorSetSchema,
descriptorDefinition(),
);
await writeFile(capture, protobufHar(body, "application/x-protobuf"));
await writeFile(descriptor, definition);
const output = await cli.sharedCliRun!({
arguments: [
"inspect-web-network-capture",
capture,
"har",
"--record",
"0",
"--protobuf-schema",
descriptor,
"--protobuf-message",
"capture.Message",
"--json",
],
});
expect(output.exitCode, output.stdout + output.stderr).toBe(0);
const evidence = parseEvidence(output.json);
const report = webNetworkCaptureSchema.parse(evidence.normalized_result);
expect(report.protobuf_coverage?.status).toBe("complete");
const decoded = report.records[0]?.protobuf?.[0];
expect(decoded?.schema).toMatchObject({
descriptor_set_path: descriptor,
descriptor_set_sha256: sha256(definition),
descriptor_set_bytes: definition.length,
message_type: "capture.Message",
});
expect(decoded?.fields).toMatchObject([
{
field_id: "offset:0",
number: 1,
schema_name: "first",
schema_value: "1",
},
{
field_id: "offset:2",
number: 1,
schema_name: "first",
schema_value: "2",
},
{
field_id: "offset:4",
number: 2,
schema_name: "second",
schema_value: "3",
},
{
field_id: "offset:6",
number: 3,
schema_name: "child",
schema_type: "capture.Message.Child",
},
{
field_id: "offset:8",
number: 1,
schema_name: "value",
schema_value: "4",
},
]);
expect(decoded?.fields).toHaveLength(5);
const call = await cli.sharedMcpCall!();
const response = await call("inspect_web_network_capture", {
capture_path: capture,
format: "har",
record_ordinals: [0],
protobuf: {
schema: {
descriptor_set_path: descriptor,
message_type: "capture.Message",
},
},
});
expect(response.isError, JSON.stringify(response)).not.toBe(true);
expect(parseEvidence(response.structuredContent).evidence_id).toBe(
evidence.evidence_id,
);
expect(
Buffer.compare(await readFile(descriptor), Buffer.from(definition)),
).toBe(0);
},
);