banca / client / test / publicbanks.test.mjs
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
// The resident bots' public banks — the lobby's bundled bootstrap set.
//
// Two pure decisions ship with it and both are driven here, black-box through
// the testlib façade (never src/):
//
//   · `validPublicBank` — the shape gate the lobby runs over the constant
//     before rendering a row. The six bundled entries must pass it, and the
//     ways an entry can rot (half a link, a bare secret, an unbounded string)
//     must each fail it — so a validator that failed open and data that
//     drifted both go red.
//
//   · `publicBanksAvailable` — the already-joined filter. A public bank the
//     user holds lives in "banks you use" like any saved bank; offering it a
//     second time in the public section would claim the section is a
//     different kind of thing than a link, and it is not.
//
// The tokens are public capability URLs BY DESIGN (the bots approve everyone
// and gift a small start), so pinning them in a test leaks nothing.

import test from "node:test";
import assert from "node:assert/strict";
import * as L from "../test-dist/testlib.js";

const BANKS = [...L.PUBLIC_BANKS];

test("the bundled set is the six resident banks, every one a valid entry", () => {
  assert.equal(BANKS.length, 6);
  for (const b of BANKS) {
    assert.ok(L.validPublicBank(b), `${b.name} must validate`);
  }
  assert.deepEqual(BANKS.map((b) => b.code), ["DUH", "MOR", "STR", "IEL", "ZMU", "BAL"]);
  assert.deepEqual(BANKS.map((b) => b.name),
                   ["Banca Duhului", "Banca Moroiului", "Banca Strigoiului",
                    "Banca Ielelor", "Banca Zmeului", "Banca Balaurului"]);
  // six DIFFERENT banks: distinct tokens, distinct bankers
  assert.equal(new Set(BANKS.map((b) => b.s)).size, 6);
  assert.equal(new Set(BANKS.map((b) => L.linkBanker(b.s))).size, 6);
});

test("every bundled token is a WHOLE bank link — secret half and banker half", () => {
  // §1: a link without the banker suffix is not a bank. A truncated paste of
  // one of these constants must never survive a copy-edit of this file.
  for (const b of BANKS) {
    assert.ok(L.isBankLink(b.s), `${b.code} carries a banker half`);
    const p = L.parseLink(b.s);
    assert.equal(p.secret.length, 43);
    assert.equal(p.banker.length, 43);
  }
});

test("the validator refuses every way an entry can rot", () => {
  const good = BANKS[0];
  const okBut = (patch) => L.validPublicBank({ ...good, ...patch });
  assert.ok(L.validPublicBank({ ...good }), "the control copy must pass");

  // not an entry at all
  assert.equal(L.validPublicBank(null), false);
  assert.equal(L.validPublicBank(undefined), false);

  // the token: missing, half a link (a bare secret is NOT a bank), garbage
  assert.equal(okBut({ s: undefined }), false);
  assert.equal(okBut({ s: L.linkSecret(good.s) }), false, "a bare secret must fail");
  assert.equal(okBut({ s: good.s.slice(0, -1) }), false, "a truncated banker half must fail");
  assert.equal(okBut({ s: "not a link" }), false);

  // the display strings: absent, empty, unbounded (name 40, code 8, sym 8 —
  // mirroring lib/protocol's MAX_NAME / MAX_SYM)
  assert.equal(okBut({ name: undefined }), false);
  assert.equal(okBut({ name: "" }), false);
  assert.equal(okBut({ name: "n".repeat(41) }), false);
  assert.ok(okBut({ name: "n".repeat(40) }), "40 is within bounds");
  assert.equal(okBut({ code: "" }), false);
  assert.equal(okBut({ code: "C".repeat(9) }), false);
  assert.equal(okBut({ sym: "" }), false);
  assert.equal(okBut({ sym: "s".repeat(9) }), false);
  assert.equal(okBut({ sym: 7 }), false, "a non-string must fail");
});

test("available: joined banks leave the section, order and strangers untouched", () => {
  const room = (s) => ({ s, label: "x", ts: 1 });
  const all = [...L.publicBanksAvailable(L.PUBLIC_BANKS, [])];
  assert.deepEqual(all.map((b) => b.code), ["DUH", "MOR", "STR", "IEL", "ZMU", "BAL"],
                   "an empty directory offers all six, in bundle order");

  // joining two removes exactly those two — matched on the WHOLE token
  const joined = [room(BANKS[1].s), room(BANKS[4].s)];
  const left = [...L.publicBanksAvailable(L.PUBLIC_BANKS, joined)];
  assert.deepEqual(left.map((b) => b.code), ["DUH", "STR", "IEL", "BAL"]);

  // a directory full of OTHER banks removes nothing; malformed rows are inert
  const strangers = [room("A".repeat(43) + "." + "B".repeat(43)), null, { label: "no s" }];
  assert.equal(L.publicBanksAvailable(L.PUBLIC_BANKS, strangers).length, 6);

  // half a link is a DIFFERENT bank (or none), never a match for the whole
  assert.equal(L.publicBanksAvailable(L.PUBLIC_BANKS, [room(L.linkSecret(BANKS[0].s))]).length, 6,
               "a bare secret in the directory must not shadow the bank");

  // all six joined: the section has nothing left to offer
  assert.equal(L.publicBanksAvailable(L.PUBLIC_BANKS, BANKS.map((b) => room(b.s))).length, 0);

  // and the filter validates on the way out, so a rotten entry is dropped,
  // not drawn — the lobby renders this function's answer verbatim
  const rotten = [{ ...BANKS[0], name: "" }, BANKS[1]];
  assert.deepEqual([...L.publicBanksAvailable(rotten, [])].map((b) => b.code), ["MOR"]);

  // non-arrays answer emptiness, not a throw (the lobby must paint offline)
  assert.equal(L.publicBanksAvailable(null, []).length, 0);
});

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/banca.git