Newer
Older
gnexus-creds / extensions / extension / tools / test-pack-shared.js
#!/usr/bin/env node
/**
 * Plain-node test suite for src/pack-shared.js (no DOM, no deps).
 * Usage: node tools/test-pack-shared.js
 */
const assert = require("assert");
const pack = require("../src/pack-shared.js");
const detect = require("../src/field-detect.js");

let passed = 0;
let failed = 0;

function check(name, actual, expected) {
  try {
    assert.deepStrictEqual(actual, expected);
    passed++;
  } catch (e) {
    failed++;
    console.error(`FAIL ${name}\n  expected: ${JSON.stringify(e.expected)}\n  actual:   ${JSON.stringify(e.actual)}`);
  }
}

// --- resolveConfig: whitelist + clamps --------------------------------------
{
  const cfg = pack.resolveConfig({
    timings: { scanDebounceMs: 900, cardExitMs: 10, unknown: 1 },
    strings: { use: " Apply ", dismiss: "", selectAccount: "Чей аккаунт?" },
    unknownTop: "junk",
  });
  check("timings clamped", cfg.timings, { scanDebounceMs: 900, cardExitMs: 50 });
  check("strings sanitized", cfg.strings, {
    use: "Apply", dismiss: "Dismiss", selectAccount: "Чей аккаунт?",
  });
  check("defaults on junk config", pack.resolveConfig("not an object").timings, { scanDebounceMs: 300, cardExitMs: 280 });
  check("junk string falls back", pack.resolveConfig({ strings: { use: 42 } }).strings.use, "Use");
  check("oversized string falls back", pack.resolveConfig({ strings: { use: "x".repeat(61) } }).strings.use.length, "Use".length);
}

// --- resolveTables: additive merge over bundled defaults ---------------------
{
  const bundled = detect.keywordTables;
  const merged = pack.resolveTables({

    usernameKeywords: [["user_name", 9], ["brandlogin", 7], [null, 1], ["x*y", 3], ["brandlogin", 6]],
    passwordKeywords: [["пароль2", 8]],
    antiKeywords: ["brandotp"],
    autocompleteTokens: { brandlogin: { username: 9, password: 0 }, "cc_": { username: 5 } },
    unknownKey: true,
  }, detect.keywordTables);
  const username = Object.fromEntries(merged.usernameKeywords);
  check("existing keyword reweighted", username["user_name"], 9);
  check("new keyword added", username["brandlogin"], 6);
  check("invalid keyword dropped", username["x*y"], undefined);
  check("new russian keyword added", Object.fromEntries(merged.passwordKeywords)["пароль2"], 8);
  check("anti set-union", merged.antiKeywords.includes("brandotp") && merged.antiKeywords.includes("cc"), true);
  check("token replaced", merged.autocompleteTokens.brandlogin, { username: 9, password: 0 });
  check("token with partial weights completed", merged.autocompleteTokens["cc_"], { username: 5, password: 0 });
  check("bundled password rows preserved", Object.fromEntries(merged.passwordKeywords)["password"], 8);
  const snapshot = detect.keywordTables;
  pack.resolveTables({ usernameKeywords: [["zz", 1]] });
  check("bundled tables stay frozen-ish", JSON.stringify(detect.keywordTables), JSON.stringify(snapshot));
}

// --- isCompatible ------------------------------------------------------------
check("own newer than pack min", pack.isCompatible("0.1.1", "0.2.0"), true);
check("own equals pack min", pack.isCompatible("0.1.1", "0.1.1"), true);
check("own older than pack min", pack.isCompatible("0.2.0", "0.1.1"), false);
check("partial versions compare", pack.isCompatible("0.2", "0.1.9"), false);
check("junk stays incompatible", pack.isCompatible("latest", "0.2.0"), false);

// --- validateStoredPack ------------------------------------------------------
const validStore = {
  packVersion: 2,
  minExtensionVersion: "0.1.1",
  maxExtensionVersion: null,
  files: { "content.css": { sha256: "a".repeat(64), size: 100 } },
  texts: { "content.css": ".a{color:red}" },
};
check("valid store accepted", (pack.validateStoredPack(validStore, "0.1.1") || {}).packVersion, 2);
check("older extension rejected", pack.validateStoredPack(validStore, "0.1.0"), null);
check("unknown file name rejected", pack.validateStoredPack(
  { ...validStore, files: { "evil.js": { sha256: "a".repeat(64) } }, texts: { "evil.js": "alert(1)" } },
  "0.1.1"), null);
check("oversized file rejected", pack.validateStoredPack(
  { ...validStore, texts: { "content.css": "a".repeat(300000) } },
  "0.1.1"), null);

// --- load(): no storage access -> bundled behavior ---------------------------
{
  global.chrome = undefined;
  pack.load().then((loaded) => {
    check("load without storage returns no pack", loaded.pack, null);
    check("load config = defaults", loaded.config, pack.DEFAULT_CONFIG);
    console.log(`\n${passed} passed, ${failed} failed`);
    process.exit(failed ? 1 : 0);
  });
}