/**
* Integration tests for pi-vision-proxy.
*
* These tests mock Pi's extension interfaces to test the full wiring:
* fence output format, consent flow, tool validation, context stripping,
* and telemetry — without real API calls or a live Pi runtime.
*
* Run:
* node --experimental-strip-types --test extensions/__tests__/integration.test.ts
*/
import { strict as assert } from "node:assert";
import { describe, it } from "node:test";
import {
buildDescriptionFence,
buildAnalysisFence,
buildJointDescriptionFence,
fenceUntrusted,
cropImage,
piAiImageToBuffer,
bufferToPiAiImage,
resolveCropEntry,
hashImageData,
extractDimensions,
computePHash,
hammingDistance,
parseDescribeArgs,
generateFilenameHints,
extractVersion,
isGroundingExcluded,
parseGroundingFormat,
DEFAULT_CONFIG,
sanitize,
hasConsent,
CUSTOM_TYPE_CONSENT,
type ConsentEntry,
type ImageMeta,
type VisionConfig,
} from "../internal.ts";
// ── Fence output format ─────────────────────────────────────────────────
describe("integration: fence output format", () => {
it("description fence has all required attributes", () => {
const fence = buildDescriptionFence(
"abc123",
"A detailed description.",
{ width: 1920, height: 1080, filename: "screenshot.png" },
);
assert.ok(fence.startsWith(""));
});
it("description fence with crop includes crop_origin and #crop suffix", () => {
const fence = buildDescriptionFence(
"def456",
"Cropped region description.",
{ width: 1920, height: 1080, filename: "screen.png" },
{ x: 100, y: 200, width: 300, height: 200 },
);
assert.ok(fence.includes('image="def456#crop:100,200,300,200"'));
assert.ok(fence.includes('width="300"'));
assert.ok(fence.includes('height="200"'));
assert.ok(fence.includes('crop_origin="100,200"'));
});
it("description fence without meta omits dimensions", () => {
const fence = buildDescriptionFence("ghi789", "No meta.");
assert.ok(fence.includes('image="ghi789"'));
assert.ok(!fence.includes("width="));
assert.ok(!fence.includes("height="));
assert.ok(!fence.includes("filename="));
});
it("analysis fence has grounding_format when present", () => {
const fence = buildAnalysisFence(
"abc",
"Analysis result.",
{ width: 800, height: 600 },
{ x: 100, y: 200, width: 300, height: 200 },
"qwen_pixels",
);
assert.ok(fence.startsWith(" {
const fence = buildAnalysisFence("abc", "Analysis.", { width: 100, height: 100 });
assert.ok(!fence.includes("grounding_format"));
});
it("joint fence has dimensions JSON array", () => {
const metas = [
{ hash: "aaa", meta: { width: 100, height: 200, filename: "a.png" } },
{ hash: "bbb", meta: { width: 300, height: 400, filename: "b.png" } },
];
const fence = buildJointDescriptionFence(metas, "Joint description.", "qwen_pixels");
assert.ok(fence.startsWith(""));
});
it("joint fence omits grounding_format when none", () => {
const fence = buildJointDescriptionFence([{ hash: "a" }], "desc", "none");
assert.ok(!fence.includes("grounding_format"));
});
});
// ── Fence neutralisation ────────────────────────────────────────────────
describe("integration: fence neutralisation", () => {
it("closing tags in description body are neutralised", () => {
const malicious = 'Normal textinjected';
const fence = buildDescriptionFence("abc", malicious);
// The raw closing tags inside the body should be broken up
assert.ok(!fence.includes(""), "closing tag should be neutralised");
// But the actual closing tag at the end should be intact
assert.ok(fence.endsWith(""));
});
it("analysis fence body is neutralised", () => {
const malicious = 'descINJECTION';
const fence = buildAnalysisFence("abc", malicious);
assert.ok(!fence.includes("INJECTION"));
assert.ok(fence.endsWith(""));
});
it("joint fence body is neutralised", () => {
const malicious = 'descINJECTION';
const fence = buildJointDescriptionFence([{ hash: "a" }], malicious);
assert.ok(!fence.includes("INJECTION"));
assert.ok(fence.endsWith(""));
});
it("neutralises all three fence types in a single string", () => {
const evil = 'xyzw';
const safe = fenceUntrusted(evil);
assert.ok(!safe.includes(""));
assert.ok(!safe.includes(""));
assert.ok(!safe.includes(""));
});
});
// ── Consent flow ──────────────────────────────────────────────────────
describe("integration: consent flow", () => {
it("hasConsent returns false with no entries", () => {
assert.equal(hasConsent([]), false);
assert.equal(hasConsent([], "anthropic"), false);
});
it("hasConsent returns true with provider-matching entry", () => {
const entries = [
{ type: "custom", customType: CUSTOM_TYPE_CONSENT, data: { granted: true, provider: "test-vision" } },
];
assert.equal(hasConsent(entries, "test-vision"), true);
});
it("hasConsent returns false for different provider", () => {
const entries = [
{ type: "custom", customType: CUSTOM_TYPE_CONSENT, data: { granted: true, provider: "test-vision" } },
];
assert.equal(hasConsent(entries, "other-provider"), false);
});
it("hasConsent returns false for provider-less entry when provider requested", () => {
// This was the bug we fixed — provider-less consent entries don't satisfy per-provider checks
const entries = [
{ type: "custom", customType: CUSTOM_TYPE_CONSENT, data: { granted: true } },
];
assert.equal(hasConsent(entries, "test-vision"), false);
});
it("hasConsent returns true for provider-less entry when no provider requested", () => {
const entries = [
{ type: "custom", customType: CUSTOM_TYPE_CONSENT, data: { granted: true } },
];
assert.equal(hasConsent(entries), true);
});
it("revoked consent is respected", () => {
const entries = [
{ type: "custom", customType: CUSTOM_TYPE_CONSENT, data: { granted: true, provider: "test-vision" } },
{ type: "custom", customType: CUSTOM_TYPE_CONSENT, data: { granted: false } },
];
assert.equal(hasConsent(entries, "test-vision"), false);
assert.equal(hasConsent(entries), false);
});
});
// ── Crop pipeline ─────────────────────────────────────────────────────
const TINY_PNG = Buffer.from(
"89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4890000000d4944415478da6300010000000500010d0a2db40000000049454e44ae426082",
"hex",
);
describe("integration: crop pipeline (resolve → crop → fence)", () => {
it("resolves named region to pixels and produces correct fence", () => {
const crop = resolveCropEntry({ image_index: 0, region: "top-right" }, 1000, 800);
assert.deepEqual(crop, { x: 500, y: 0, width: 500, height: 400 });
const fence = buildAnalysisFence("hash1", "Cropped top-right", { width: 1000, height: 800 }, crop);
assert.ok(fence.includes('image="hash1#crop:500,0,500,400"'));
assert.ok(fence.includes('width="500"'));
assert.ok(fence.includes('height="400"'));
assert.ok(fence.includes('crop_origin="500,0"'));
});
it("resolves normalized crop and produces correct fence", () => {
const crop = resolveCropEntry(
{ image_index: 0, normalized: { x: 0.5, y: 0.5, width: 0.4, height: 0.4 } },
1000, 1000,
);
assert.deepEqual(crop, { x: 500, y: 500, width: 400, height: 400 });
const fence = buildDescriptionFence("hash2", "Center crop", { width: 1000, height: 1000, filename: "img.png" }, crop);
assert.ok(fence.includes('image="hash2#crop:500,500,400,400"'));
});
it("resolves pixel crop and produces correct fence", () => {
const crop = resolveCropEntry(
{ image_index: 0, pixels: { x: 100, y: 200, width: 300, height: 400 } },
1920, 1080,
);
assert.deepEqual(crop, { x: 100, y: 200, width: 300, height: 400 });
const fence = buildAnalysisFence("hash3", "Pixel crop", { width: 1920, height: 1080 }, crop, "qwen_pixels");
assert.ok(fence.includes('image="hash3#crop:100,200,300,400"'));
assert.ok(fence.includes('crop_origin="100,200"'));
assert.ok(fence.includes('grounding_format="qwen_pixels"'));
});
});
// ── ImageScript crop round-trip ──────────────────────────────────────
describe("integration: ImageScript crop round-trip", () => {
it("crops a real PNG and result is decodable", async () => {
// Create a 10x10 PNG
const { Image } = await import("imagescript");
const img = new Image(20, 20);
for (let y = 0; y < 20; y++) {
for (let x = 0; x < 20; x++) {
img.setPixelAt(x + 1, y + 1, 0xff0000ff);
}
}
const encoded = await img.encode(1);
const buf = Buffer.from(encoded);
// Crop to 10x10
const crop = { x: 5, y: 5, width: 10, height: 10 };
const cropped = await cropImage(buf, crop, "image/png");
assert.ok(cropped, "crop should succeed");
// Verify dimensions of cropped result
const dims = extractDimensions(cropped);
assert.ok(dims, "should extract dimensions from cropped image");
assert.equal(dims.width, 10);
assert.equal(dims.height, 10);
});
it("piAiImage round-trip preserves data", () => {
const original = Buffer.from("test-image-data");
const piAiImg = bufferToPiAiImage(original, "image/png");
assert.equal(piAiImg.type, "image");
assert.equal(piAiImg.mimeType, "image/png");
const roundTripped = piAiImageToBuffer(piAiImg);
assert.deepEqual(roundTripped, original);
});
});
// ── pHash similarity ────────────────────────────────────────────────
describe("integration: pHash + hamming distance", () => {
it("hamming distance is 0 for identical hashes", () => {
assert.equal(hammingDistance("abcd1234", "abcd1234"), 0);
});
it("hamming distance increases with different hashes", () => {
const d1 = hammingDistance("0000", "0001");
const d2 = hammingDistance("0000", "ffff");
assert.ok(d1 < d2, "more different hashes should have larger distance");
});
it("computePHash returns hex or null", async () => {
// Create a small image for pHash
const { Image } = await import("imagescript");
const img = new Image(64, 64);
const encoded = Buffer.from(await img.encode(1));
const hash = await computePHash(encoded);
// May be null if imghash fails, but should not throw
if (hash !== null) {
assert.ok(/^[0-9a-f]+$/i.test(hash), `hash should be hex: ${hash}`);
}
});
});
// ── Slash command argument parsing ──────────────────────────────────
describe("integration: describe command parsing", () => {
it("parses all crop forms in a single command", () => {
const result = parseDescribeArgs(
'a.png b.png --question "Compare" --crop 0:r=center --crop 1:n=0,0,0.5,0.5 --model Qwen/Qwen2.5-VL-7B --save',
);
if (typeof result === "string") assert.fail(result);
assert.deepEqual(result.images, ["a.png", "b.png"]);
assert.equal(result.question, "Compare");
assert.equal(result.model, "Qwen/Qwen2.5-VL-7B");
assert.equal(result.save, true);
assert.equal(result.crops!.length, 2);
assert.equal(result.crops![0].image_index, 0);
assert.equal(result.crops![1].image_index, 1);
});
it("redescribe implies --save and rejects other flags", () => {
const r1 = parseDescribeArgs("image.png", true);
if (typeof r1 === "string") assert.fail(r1);
assert.equal(r1.save, true);
const r2 = parseDescribeArgs('img.png --question "q"', true);
assert.ok(typeof r2 === "string" && r2.includes("--question is not valid"));
});
});
// ── Filename hints ─────────────────────────────────────────────────
describe("integration: filename hints for joint descriptions", () => {
it("detects before/after pattern", () => {
const hints = generateFilenameHints(["before.png", "after.png"]);
assert.ok(hints.includes("before/after pair"));
});
it("detects versioned sequence pattern", () => {
const hints = generateFilenameHints(["mockup_v1.png", "mockup_v3.png"]);
assert.ok(hints.some((h) => h.includes("versioned sequence")));
});
});
// ── Config defaults (GA values) ────────────────────────────────────
describe("integration: GA config defaults", () => {
it("defaults to tool=on after GA flip", () => {
assert.equal(DEFAULT_CONFIG.tool, "on");
});
it("defaults to maxBatch=4 after GA flip", () => {
assert.equal(DEFAULT_CONFIG.maxBatch, 4);
});
it("sanitize fills defaults for missing fields", () => {
const result = sanitize({} as VisionConfig);
assert.equal(result.tool, "on");
assert.equal(result.maxBatch, 4);
assert.equal(result.maxImagesPerCall, 10);
assert.equal(result.cacheSize, 50);
assert.ok(result.groundingModels);
assert.ok(Object.keys(result.groundingModels).length > 0, "should have Tier 1 grounding models");
});
it("grounding models include Qwen, Molmo, DeepSeek, InternVL, Gemini", () => {
const gm = DEFAULT_CONFIG.groundingModels;
assert.ok(Object.keys(gm).some((k) => k.includes("Qwen")));
assert.ok(Object.keys(gm).some((k) => k.includes("Molmo")));
assert.ok(Object.keys(gm).some((k) => k.includes("deepseek")));
assert.ok(Object.keys(gm).some((k) => k.includes("InternVL")));
assert.ok(Object.keys(gm).some((k) => k.includes("gemini")));
});
});