Release Package Tools 0.2.0
Verify / verify (push) Canceled after 0s

This commit is contained in:
2026-09-02 11:29:24 +02:00
parent e42943e689
commit 01c7a8c372
32 changed files with 1316 additions and 85 deletions
+10 -1
View File
@@ -92,6 +92,15 @@ test("compares two local package inventories", async ({ page }) => {
await expect(
page.getByRole("row", { name: /changed word\/document\.xml/u }),
).toBeVisible();
await page
.getByRole("button", { name: "Verify comparable contents" })
.click();
await expect(
page.getByText(/Verified \d+ comparable entries/u),
).toBeVisible();
await expect(
page.getByRole("row", { name: /changed word\/document\.xml/u }),
).toContainText(/[0-9a-f]{12} [0-9a-f]{12}/u);
});
test("integrates help, theme, PWA identity and hardened headers", async ({
@@ -123,7 +132,7 @@ test("integrates help, theme, PWA identity and hardened headers", async ({
const manifest = await request.get("/deep/nested/package/toolbox-app.json");
await expect(manifest.json()).resolves.toMatchObject({
id: "de.add-ideas.package-tools",
version: "0.1.0",
version: "0.2.0",
privacy: { processing: "local", telemetry: false },
});
});
+18
View File
@@ -0,0 +1,18 @@
import { expect, test } from "@playwright/test";
test("keeps the primary workspace inside a narrow viewport", async ({
page,
}) => {
await page.goto("/deep/nested/package/");
await expect(page.locator("main").first()).toBeVisible();
await expect(
page.locator("main .loading, main .workbench-loading"),
).toHaveCount(0);
const widths = await page.evaluate(() => ({
content: document.documentElement.scrollWidth,
viewport: document.documentElement.clientWidth,
}));
expect(widths.viewport).toBeLessThanOrEqual(430);
expect(widths.content).toBeLessThanOrEqual(widths.viewport + 1);
});
+37 -1
View File
@@ -104,7 +104,19 @@ describe("compound package analysis", () => {
const jar = await analyzePackage(
archive("tool.jar", {
"META-INF/MANIFEST.MF": strToU8(
"Manifest-Version: 1.0\r\nMain-Class: example.Main\r\n",
"Manifest-Version: 1.0\r\nMain-Class: example.Main\r\nClass-Path: lib/a.jar lib/b.jar\r\n",
),
"META-INF/LOCAL.SF": strToU8(
"Signature-Version: 1.0\r\nSHA-256-Digest-Manifest: abc\r\n",
),
"META-INF/LOCAL.RSA": new Uint8Array([1, 2, 3]),
"META-INF/LICENSE.txt": strToU8("Example licence evidence"),
"package.json": strToU8(
JSON.stringify({
dependencies: { alpha: "^1.2.0" },
devDependencies: { beta: "2.0.0" },
license: "MIT",
}),
),
"example/Main.class": new Uint8Array([0xca, 0xfe, 0xba, 0xbe]),
}),
@@ -115,6 +127,30 @@ describe("compound package analysis", () => {
name: "Main-Class",
value: "example.Main",
});
expect(jar.dependencies).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: "alpha", scope: "runtime" }),
expect.objectContaining({
name: "lib/a.jar",
scope: "JAR Class-Path declaration",
}),
]),
);
expect(jar.licenses).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: "MIT", source: "package.json" }),
expect.objectContaining({ source: "META-INF/LICENSE.txt" }),
]),
);
expect(jar.signatures).toEqual(
expect.arrayContaining([
expect.objectContaining({
path: "META-INF/LOCAL.SF",
state: "complete-pair",
declaredDigests: ["SHA-256"],
}),
]),
);
});
it("adapts APK and both WebExtension identities with bounded inventories", async () => {
+17 -1
View File
@@ -45,7 +45,23 @@ describe("inventory comparison", () => {
changed: "changed",
left: "only-left",
right: "only-right",
same: "same",
same: "unverified",
});
});
it("only calls file contents equal after matching SHA-256 evidence", () => {
const left = document([entry("same-size", 4, "declared")]);
const right = document([entry("same-size", 4, "declared")]);
expect(compareInventories(left, right)[0]?.status).toBe("unverified");
expect(
compareInventories(left, right, {
"same-size": { leftSha256: "abc", rightSha256: "abc" },
})[0]?.status,
).toBe("same");
expect(
compareInventories(left, right, {
"same-size": { leftSha256: "abc", rightSha256: "def" },
})[0]?.status,
).toBe("changed");
});
});
+58
View File
@@ -0,0 +1,58 @@
import { describe, expect, it } from "vitest";
import { strToU8, zipSync } from "fflate";
import { analyzePackage } from "../../src/package/analyze";
import { compareSemanticEntry } from "../../src/package/content-compare";
import { verifyPackageContents } from "../../src/package/content-compare";
const bytes = (value: string) => new TextEncoder().encode(value);
describe("bounded semantic package comparison", () => {
it("hashes verified decompressed bytes instead of ZIP representation", async () => {
const contents = { "same.txt": strToU8("same decompressed value") };
const left = await analyzePackage(
new File([zipSync(contents, { level: 0 })], "left.zip"),
);
const right = await analyzePackage(
new File([zipSync(contents, { level: 9 })], "right.zip"),
);
const evidence = await verifyPackageContents(left, right);
expect(evidence["same.txt"]?.leftSha256).toMatch(/^[0-9a-f]{64}$/u);
expect(evidence["same.txt"]?.leftSha256).toBe(
evidence["same.txt"]?.rightSha256,
);
});
it("recognizes equivalent JSON serialization and reports value paths", () => {
expect(
compareSemanticEntry(
"manifest.json",
bytes('{"name":"demo","items":[1,2]}'),
bytes('{\n "items": [1, 2], "name": "demo"\n}'),
),
).toMatchObject({ kind: "json", equivalent: true });
expect(
compareSemanticEntry(
"manifest.json",
bytes('{"name":"before"}'),
bytes('{"name":"after"}'),
)?.differences,
).toContain("$.name: value changed");
});
it("normalizes safe XML attributes but rejects active declarations", () => {
expect(
compareSemanticEntry(
"content.xml",
bytes('<root a="1" b="2"><item>value</item></root>'),
bytes('<root b="2" a="1">\n<item>value</item>\n</root>'),
),
).toMatchObject({ kind: "xml", equivalent: true });
expect(
compareSemanticEntry(
"content.xml",
bytes("<!DOCTYPE root><root/>"),
bytes("<root/>"),
)?.summary,
).toMatch(/could not/u);
});
});