1// @effect-diagnostics nodeBuiltinImport:off - expectations mirror the host path joins the script under test performs.
2import { assert, it } from "@effect/vitest";
3import { PNG } from "pngjs";
4
5import type { ShowcaseStoreAssetSpec } from "./mobile-showcase.config.ts";
6import {
7 encodeAndroidPairingUrls,
8 normalizeStorePng,
9 parsePairingCredentialOutput,
10 readPngMetadata,
11 validateStoreAsset,
12} from "./mobile-showcase.ts";
13
14function rgbaPng(width: number, height: number): Buffer {
15 const png = new PNG({ width, height });
16 png.data.fill(255);
17 return PNG.sync.write(png);
18}
19
20it("converts simulator RGBA captures to upload-safe 24-bit RGB PNGs", () => {
21 const normalized = normalizeStorePng(rgbaPng(2, 3));
22 assert.deepStrictEqual(readPngMetadata(normalized), {
23 width: 2,
24 height: 3,
25 bitDepth: 8,
26 colorType: 2,
27 hasAlpha: false,
28 });
29});
30
31const storeSpec: ShowcaseStoreAssetSpec = {
32 store: "apple",
33 directory: "fixture",
34 width: 2,
35 height: 3,
36 minimumUploadCount: 1,
37 maximumUploadCount: 10,
38};
39
40it("validates a complete RGB PNG from an offset byte view", () => {
41 const bytes = normalizeStorePng(rgbaPng(2, 3));
42 const padded = Buffer.concat([Buffer.from([0]), bytes, Buffer.from([0])]);
43 assert.deepStrictEqual(validateStoreAsset(storeSpec, padded.subarray(1, -1)), {
44 width: 2,
45 height: 3,
46 bitDepth: 8,
47 colorType: 2,
48 hasAlpha: false,
49 });
50});
51
52it("rejects PNG metadata without image data during store validation", () => {
53 const bytes = normalizeStorePng(rgbaPng(2, 3));
54 assert.throws(
55 () => validateStoreAsset(storeSpec, bytes.subarray(0, 26), "threads.png"),
56 /threads\.png is not a valid PNG/,
57 );
58 assert.throws(() => validateStoreAsset(storeSpec, bytes.subarray(0, 33)), /is not a valid PNG/);
59});
60
61it("rejects a PNG truncated inside its image data", () => {
62 const bytes = normalizeStorePng(rgbaPng(2, 3));
63 const imageData = bytes.indexOf("IDAT");
64 assert.notEqual(imageData, -1);
65 assert.throws(
66 () => validateStoreAsset(storeSpec, bytes.subarray(0, imageData + 5)),
67 /is not a valid PNG/,
68 );
69});
70
71it("rejects a PNG with a corrupt image-data checksum", () => {
72 const bytes = normalizeStorePng(rgbaPng(2, 3));
73 const imageData = bytes.indexOf("IDAT");
74 assert.notEqual(imageData, -1);
75 const checksum = imageData + 4 + bytes.readUInt32BE(imageData - 4);
76 bytes[checksum] = bytes.readUInt8(checksum) ^ 1;
77 assert.throws(() => validateStoreAsset(storeSpec, bytes), /is not a valid PNG/);
78});
79
80it("reads multiline JSON from the pairing CLI", () => {
81 assert.equal(
82 parsePairingCredentialOutput('server log\n{\n "credential": "PAIR-ME"\n}\n'),
83 "PAIR-ME",
84 );
85});
86
87it("encodes Android pairing URLs without shell-sensitive JSON quotes", () => {
88 const urls = ["http://10.0.2.2:65164/#token=ONE", "http://10.0.2.2:65198/#token=TWO"];
89 const encoded = encodeAndroidPairingUrls(urls);
90 assert.equal(encoded.startsWith("json-uri:"), true);
91 assert.deepStrictEqual(JSON.parse(decodeURIComponent(encoded.slice("json-uri:".length))), urls);
92 assert.equal(encoded.includes('"'), false);
93});