Files
bootnode/console/lib/brand.test.ts
zeekayandHanzo Dev 7d293212cb refactor: web/ is now console/ — bootnode/bootnode is the backend, bootnode/console the frontend
Names the two halves for what they are. The repo already holds both (12407e9 merged
bootnode/bootnode into this history); this makes the frontend directory say so, so the layout
reads the way the owner describes the system: backend and frontend separate, one repo.

git mv throughout — 187 renames, history preserved. Every path reference follows: hanzo.yml
build context and dockerfile, compose.yml, compose.prod.yml, start.sh, LLM.md,
infra/k8s/web-deployments.yaml, infra/cloud/deploy.sh, and the Dockerfile's own COPY paths.
Image and container names (bootnode-web, the -web tag suffix) are deliberately NOT renamed here:
those are a deploy contract the App CR and ingress bind to on port 3001, and breaking them is a
separate, staged change.

Console builds green after the move; the Go tree builds.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 17:47:51 -07:00

111 lines
4.9 KiB
TypeScript

// The white-label contract, as executable assertions.
//
// Run: `pnpm test` (node:test + native TypeScript stripping — no test
// framework, no new dependency).
//
// Every expectation here was verified against the live IAM before it was
// written down; see the probe table at the top of lib/brand.ts. This file is
// what stops it drifting: change a client id or a host and the build's test
// step says so, instead of a customer discovering it at the login screen.
import { strict as assert } from "node:assert"
import { describe, it } from "node:test"
import { getBrand, getBrandKey } from "./brand.ts"
// host → [brand key, IAM issuer, OIDC client id]. The five hosts this one
// image must serve, plus the aliases that ride along with them.
const CONTRACT: [string, string, string, string][] = [
["bootno.de", "bootnode", "https://id.bootno.de", "bootnode-platform"],
["web3.hanzo.ai", "hanzo", "https://hanzo.id", "hanzo-web3"],
["lux.cloud", "lux", "https://lux.id", "lux-cloud"],
["zoo.cloud", "zoo", "https://zoolabs.id", "zoo-cloud"],
["cloud.zoo.network", "zoo", "https://zoolabs.id", "zoo-cloud"],
// Aliases that must inherit their parent's brand.
["www.bootno.de", "bootnode", "https://id.bootno.de", "bootnode-platform"],
["nodes.lux.cloud", "lux", "https://lux.id", "lux-cloud"],
["console.zoo.cloud", "zoo", "https://zoolabs.id", "zoo-cloud"],
["cloud.pars.network", "pars", "https://pars.id", "pars-app"],
]
describe("white-label by hostname", () => {
for (const [host, key, issuer, clientId] of CONTRACT) {
it(`${host} → ${key} / ${clientId}`, () => {
const brand = getBrand(host)
assert.equal(getBrandKey(host), key)
assert.equal(brand.key, key)
assert.equal(brand.iam.url, issuer)
assert.equal(brand.iam.clientId, clientId)
})
}
it("does not regress the one login that works today", () => {
// lux.cloud + lux-cloud + https://lux.cloud/auth/callback is the triple
// that answers 302 on lux.id and lands a real user on /dashboard. If this
// assertion ever fails, the merge broke a working production login.
const lux = getBrand("lux.cloud")
assert.equal(lux.iam.url, "https://lux.id")
assert.equal(lux.iam.clientId, "lux-cloud")
})
it("resolves unknown hosts and localhost to the neutral default", () => {
for (const host of ["localhost", "127.0.0.1", "example.com", ""]) {
assert.equal(getBrandKey(host), "bootnode")
}
})
it("matches on exact host or dot-suffix, never on substring", () => {
// The old resolver used `hostname.includes(match)`, which would hand a
// real brand's OIDC client id — and therefore its login — to any host an
// attacker can name. These must all fall through to the default.
for (const host of [
"lux.cloud.attacker.example",
"zoo.cloudy.example",
"notbootno.de.example",
"web3.hanzo.ai.evil.test",
"xlux.cloud",
]) {
assert.equal(getBrandKey(host), "bootnode", host)
}
})
it("gives every brand its own mark, wordmark and favicon", () => {
// One image, five identities: a shared /logo.svg for all of them would
// mean bootno.de's mark showing up on zoo.cloud.
const seen = new Set<string>()
for (const host of ["bootno.de", "web3.hanzo.ai", "lux.cloud", "zoo.cloud", "cloud.pars.network"]) {
const brand = getBrand(host)
assert.match(brand.logoIcon, new RegExp(`/logo/${brand.key}-icon\\.svg$`))
assert.match(brand.logoWhite, new RegExp(`/logo/${brand.key}-logo-white\\.svg$`))
assert.equal(brand.favicon, brand.logoIcon)
assert.ok(!seen.has(brand.name), `duplicate brand name ${brand.name}`)
seen.add(brand.name)
}
assert.equal(seen.size, 5)
})
it("flags exactly the marks that cannot paint themselves", () => {
// Measured from public/logo/*-icon.svg: bootnode is fill="currentColor"
// and lux is fill="white" — both need the current-text-colour mask, or one
// is invisible on black and the other on white. The rest ship a palette.
assert.equal(getBrand("bootno.de").monoMark, true)
assert.equal(getBrand("lux.cloud").monoMark, true)
assert.equal(getBrand("zoo.cloud").monoMark, false)
assert.equal(getBrand("web3.hanzo.ai").monoMark, false)
assert.equal(getBrand("cloud.pars.network").monoMark, false)
})
it("carries a complete, non-empty config for every brand", () => {
for (const host of ["bootno.de", "web3.hanzo.ai", "lux.cloud", "zoo.cloud", "cloud.pars.network"]) {
const b = getBrand(host)
for (const field of ["name", "shortName", "tagline", "domain", "apiUrl", "statusUrl", "networkName"] as const) {
assert.ok(b[field], `${host}: ${field} is empty`)
}
// Partial catalog entries used to produce `undefined` colours and crash
// rendering, which is why no catalog was ever shipped.
assert.ok(b.colors.accent && b.colors.accentEnd)
assert.ok(b.iam.idLabel)
assert.match(b.apiUrl, /^https:\/\//)
}
})
})