Files
Arah2/web/test/no-storage.test.mjs
araharah-executor 2659b7f352 feat(03-02): D-11 Mock-Telegram harness + the four requirement gates (17 cases green)
- telegram-mock.ts: fake window.Telegram.WebApp injected into the SAME window
  (never an iframe — Bot API 10.2 origin restriction, Pitfall 6); Web-Crypto
  dev signer mirroring bot/src/security/initdata.ts's two-step HMAC (async
  makeDevInitData — node:crypto stays out of the browser bundle path);
  controllable LocationManager stream, themeChanged emitter, BackButton clicks
- location-chain.test.mjs (10 cases): GEO-01 version gate / null-WebApp guard
  / null-denial / hard timeout / stale-detector + one-read nudge; GEO-02 pin
  pickup / bounded 60s poll / Desktop no-geolocation leg ' same denial keys
- no-storage.test.mjs (3 cases): GEO-03 comment-stripped grep gate over
  web/src/**/*.{ts,html}, no-SW invariant, store.ts storage-assignment probe
- theme.test.mjs (4 cases): PX-02 colorScheme + luminance fallback + setStyle
  source-introspection (Pitfall 7) + themeChanged re-apply; document stub
  installed and restored per case
- Rule 1 fix in telegram.ts: looksStale/nudgeAndRetryTelegram accept an
  omitted lastAccepted (undefined normalized to null) ' acquireFix(deps)
  first-call contract
2026-10-22 23:46:17 +08:00

80 lines
3.6 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/*
* no-storage.test.mjs — the GEO-03 grep gate (zero persistence, source-introspection).
* -----------------------------------------------------------------------------------
* Inverted form of bot/test/privacy.test.mjs (PG1 pins writes to carry TTLs; this
* pin that web/src contains NO persistence surface at all — D-03 (memory-only
* token) + D-07 (coordinates live in memory only)).
*
* The gate MUST strip comments before scanning: web/src/mock/telegram-mock.ts's
* header documents the ban in prose, and prose is not a call site. Strip block
* comments (/* … *​/) and line comments (// …), THEN assert none of the banned
* tokens appear — code occurrences only.
*/
import { strict as assert } from "node:assert";
import { readFileSync, readdirSync, statSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { test } from "./harness.mjs";
const SRC = path.join(path.dirname(fileURLToPath(new URL(import.meta.url))), "..", "src");
const BANNED = [
"localStorage", "sessionStorage", "indexedDB", "caches", "CacheStorage",
"document.cookie", "WebApp.DeviceStorage", "WebApp.SecureStorage", "WebApp.CloudStorage",
];
/* path-scrape + comment strip (the plan's contract for this gate) */
function listFiles(dir, exts, acc = []) {
for (const entry of readdirSync(dir)) {
const full = path.join(dir, entry);
if (statSync(full).isDirectory()) listFiles(full, exts, acc);
else if (exts.some((e) => entry.endsWith(e))) acc.push(full);
}
return acc;
}
function stripComments(src) {
return src
.replace(/\/\*[\s\S]*?\*\//g, " ") // block comments
.replace(/(^|[^:\\])\/\/[^\n]*/g, "$1"); // line comments (not URLs inside strings — benign over-stripping, gate is prefer-fail reading)
}
function codeText(file) {
return stripComments(readFileSync(file, "utf8"));
}
/* ————— GEO3 — the mechanical gate over every web/src source ————— */
test("GEO3 no banned storage token appears in web/src code (comments stripped, all *.ts/*.html)", () => {
const files = listFiles(SRC, [".ts", ".html"]);
assert.ok(files.length >= 12, `scanned a real tree (found ${files.length} files)`);
for (const file of files) {
const code = codeText(file);
for (const banned of BANNED) {
assert.ok(!code.includes(banned), `${path.relative(process.cwd(), file)} references ${banned} — GEO-03 violated`);
}
}
});
test("GEO3 the runtime-probe-preceding invariant: no service worker / registration call exists", () => {
// Vite emits no SW by default; this pins the "and none was added" half (RESEARCH GEO-03 §).
for (const file of listFiles(SRC, [".ts", ".html"])) {
const code = codeText(file);
for (const token of ["serviceWorker.register", "navigator.serviceWorker", "workbox"]) {
assert.ok(!code.includes(token), `${path.relative(process.cwd(), file)} references ${token} — GEO-03 surface added`);
}
}
});
test("GEO3 web/src/session/store.ts assigns nothing to any storage object (the D-03 half)", () => {
const code = codeText(path.join(SRC, "session", "store.ts"));
assert.ok(!/Storage\s*\.\s*setItem|Storage\s*\.\s*getItem/.test(code), "store.ts touches a Storage object");
assert.ok(!/indexedDB\s*\.\s*open/.test(code), "store.ts opens IndexedDB");
assert.ok(!/document\s*\.\s*cookie\s*=/.test(code), "store.ts writes a cookie");
assert.ok(!/caches\s*\.\s*open/.test(code), "store.ts opens a CacheStorage");
// ...and keeps the named TTL constant the client revalidates against (D-03).
assert.ok(code.includes("SESSION_TTL_SECONDS = 900"), "the 900s TTL mirror by name is present");
});