// WHAT THE PHONE REMEMBERS (mobile/src/pocket/store.ts) and HOW ITS GIT // BYTES TRAVEL (mobile/src/pocket/transport.ts), at unit level. // // The store is Capacitor Preferences; off a phone, Preferences is its own web // implementation over `localStorage`, which a Map stands in for here. The // transport's native half is a bridge a fake answers: what is pinned is the // base64 round trip, that a request body gathered from isomorphic-git's // chunks arrives whole, and that a body-less request sends no body at all. import assert from "node:assert/strict"; import { before, describe, it } from "node:test"; import { createGitHttp, fromBase64, toBase64, type GitBridge } from "../mobile/src/pocket/transport.ts"; const held = new Map(); const g = globalThis as Record; let store: typeof import("../mobile/src/pocket/store.ts"); before(async () => { g.localStorage = { getItem: (k: string) => (held.has(k) ? held.get(k)! : null), setItem: (k: string, v: string) => void held.set(k, String(v)), removeItem: (k: string) => void held.delete(k), clear: () => held.clear(), key: (i: number) => [...held.keys()][i] ?? null, get length() { return held.size; }, }; g.window ??= globalThis; store = await import("../mobile/src/pocket/store.ts"); }); describe("the store", () => { it("keeps the token, and a sign-out forgets the token and the account but not the repository", async () => { assert.equal(await store.readToken(), null); await store.writeToken("gho_x"); await store.writeUser({ login: "owner", name: null, email: null }); await store.writeRepo({ fullName: "owner/vault", branch: "main", cloneUrl: "https://github.com/owner/vault.git" }); assert.equal(await store.readToken(), "gho_x"); assert.deepEqual(await store.readUser(), { login: "owner", name: null, email: null }); await store.forgetSignIn(); assert.equal(await store.readToken(), null); assert.equal(await store.readUser(), null); assert.deepEqual(await store.readRepo(), { fullName: "owner/vault", branch: "main", cloneUrl: "https://github.com/owner/vault.git" }); await store.forgetRepo(); assert.equal(await store.readRepo(), null); }); it("remembers the base commit and the door, and reads an unknown door as none", async () => { await store.writeBase("abc123"); assert.equal(await store.readBase(), "abc123"); assert.equal(await store.readDoor(), null); await store.writeDoor("pocket"); assert.equal(await store.readDoor(), "pocket"); await store.writeDoor("instance"); assert.equal(await store.readDoor(), "instance"); const doorKey = [...held.keys()].find((k) => k.endsWith("pocket.door"))!; held.set(doorKey, "somewhere"); assert.equal(await store.readDoor(), null); }); it("a half-written preference reads as nothing rather than a blank screen", async () => { await store.writeUser({ login: "owner", name: null, email: null }); const userKey = [...held.keys()].find((k) => k.endsWith("pocket.user"))!; held.set(userKey, "{not json"); assert.equal(await store.readUser(), null); }); it("the device state is one JSON document of keys, and never the repository's", async () => { const state = store.createPocketStore(); assert.equal(await state.get("workspace"), null); await state.set("workspace", { open: ["Welcome.md"] }); await state.set("prefs", { theme: "dark" }); assert.deepEqual(await state.get("workspace"), { open: ["Welcome.md"] }); assert.deepEqual(await state.get("prefs"), { theme: "dark" }); const stateKey = [...held.keys()].find((k) => k.endsWith("pocket.state"))!; assert.deepEqual(Object.keys(JSON.parse(held.get(stateKey)!)).sort(), ["prefs", "workspace"]); }); }); describe("the transport", () => { it("base64 round-trips every byte, past the chunk size", () => { const bytes = new Uint8Array(0x8000 * 2 + 17).map((_, i) => (i * 31) % 256); assert.deepEqual(fromBase64(toBase64(bytes)), bytes); assert.equal(toBase64(new Uint8Array()), ""); assert.equal(toBase64(new TextEncoder().encode("git")), "Z2l0"); }); async function* chunks(...parts: string[]): AsyncIterableIterator { for (const p of parts) yield new TextEncoder().encode(p); } const drain = async (body: AsyncIterableIterator | undefined): Promise => { let out = ""; for await (const c of body ?? []) out += new TextDecoder().decode(c); return out; }; it("gathers a packfile from its chunks and sends it as one base64 body", async () => { const seen: Parameters[0][] = []; const bridge: GitBridge = { async gitRequest(options) { seen.push(options); return { status: 200, statusText: "OK", headers: { "content-type": "application/x-git-receive-pack-result" }, bodyBase64: toBase64(new TextEncoder().encode("0000")) }; }, }; const http = createGitHttp(bridge); const answer = await http.request({ url: "https://github.com/o/v.git/git-receive-pack", method: "POST", headers: { Authorization: "Basic x" }, body: chunks("PACK", "…bytes…") }); assert.equal(seen.length, 1); assert.equal(new TextDecoder().decode(fromBase64(seen[0].bodyBase64!)), "PACK…bytes…"); assert.equal(seen[0].headers.Authorization, "Basic x"); assert.equal(answer.statusCode, 200); assert.equal(answer.statusMessage, "OK"); assert.equal(answer.headers?.["content-type"], "application/x-git-receive-pack-result"); assert.equal(await drain(answer.body), "0000"); }); it("a GET carries no body and defaults its method", async () => { const seen: Parameters[0][] = []; const http = createGitHttp({ async gitRequest(options) { seen.push(options); return { status: 401, statusText: "Unauthorized", headers: {}, bodyBase64: "" }; }, }); const answer = await http.request({ url: "https://github.com/o/v.git/info/refs?service=git-upload-pack" }); assert.equal(seen[0].method, "GET"); assert.ok(!("bodyBase64" in seen[0])); assert.deepEqual(seen[0].headers, {}); assert.equal(answer.statusCode, 401); assert.equal(await drain(answer.body), ""); }); });