Files
asepharyana 1cdb82a76f Sync config from arch
- hypr/apps.lua
- hypr/autostart.lua
- hypr/envs.lua
- hypr/hyprland.lua
- hypr/hyprsunset.conf
- hypr/input.lua
- hypr/looknfeel.lua
- hypr/omasettings.lua
- hypr/xdph.conf
- omarchy/branding/about.txt
- omarchy/branding/screensaver.txt
- omarchy/extensions/omarchy-menu.jsonc
- omarchy/hooks/battery-low.d/play-warning-sound.sample
- omarchy/hooks/font-set.d/show-font-notification.sample
- omarchy/hooks/post-boot.d/weather.sample
- omarchy/hooks/post-update.d/install-voxtype.hook
- omarchy/hooks/post-update.d/setup-agent.hook
- omarchy/hooks/post-update.d/setup-fingerprint.hook
- omarchy/hooks/post-update.d/show-update-notification.sample
- omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample
- omarchy/hooks/theme-set.d/show-theme-notification.sample
- omarchy/shell.json
- omarchy/shell.toml
- omarchy/theme.name
- omarchy/themes/azure-glow/README.md
- omarchy/themes/azure-glow/alacritty.toml
- omarchy/themes/azure-glow/btop.theme
- omarchy/themes/azure-glow/hyprland.conf
- omarchy/themes/azure-glow/hyprlock.conf
- omarchy/themes/azure-glow/icons.theme
- … 269 more
2026-09-23 15:19:12 +07:00

253 lines
14 KiB
JavaScript

#!/usr/bin/env node
// Tests for the generator's option handling. Generation itself is `bw generate`;
// what is worth testing is that we never hand it a combination it rejects.
//
// node tests/generator.test.js
const fs = require("fs")
const path = require("path")
const panelSrc = fs.readFileSync(path.join(__dirname, "..", "Panel.qml"), "utf8")
const bodyOf = (name) => {
const start = panelSrc.indexOf(`function ${name}(`)
if (start === -1) return ""
let depth = 0
for (let i = panelSrc.indexOf("{", start); i < panelSrc.length; i++) {
if (panelSrc[i] === "{") depth++
else if (panelSrc[i] === "}" && --depth === 0) return panelSrc.slice(start, i + 1)
}
return ""
}
const Model = {}
new Function("exports", fs.readFileSync(path.join(__dirname, "..", "BitwardenModel.js"), "utf8")
.replace(/^\.pragma library\s*$/m, "") + `
exports.generateCommand = generateCommand
exports.generateServeUrl = generateServeUrl
exports.generateServeRequestCommand = generateServeRequestCommand
exports.parseServeGenerated = parseServeGenerated
exports.generateServeCommand = generateServeCommand
exports.normalizeGeneratorOptions = normalizeGeneratorOptions
exports.generatorDefaults = generatorDefaults
exports.generatorStrength = generatorStrength
exports.generatorPortIsForeign = generatorPortIsForeign
exports.generatorServeExitAction = generatorServeExitAction
`)(Model)
let pass = 0
const failures = []
const check = (l, ok, d) => ok ? pass++ : failures.push(`${l}\n ${d}`)
const args = o => Model.generateCommand(o).join(" ")
// `bw generate` errors if every character set is off; fall back rather than fail.
const none = Model.normalizeGeneratorOptions({ uppercase: false, lowercase: false, numbers: false, special: false })
check("all character sets off falls back to lowercase",
none.lowercase === true, JSON.stringify(none))
// Requiring more special/numeric characters than the length allows is impossible.
const tight = Model.normalizeGeneratorOptions({ length: 5, numbers: true, minNumber: 9, special: true, minSpecial: 9 })
check("length grows to fit the required character minimums",
tight.length >= tight.minNumber + tight.minSpecial, JSON.stringify(tight))
// Minimums for a disabled set would be rejected by bw.
const noNums = Model.normalizeGeneratorOptions({ numbers: false, minNumber: 5, special: false, minSpecial: 5 })
check("minimums are zeroed for disabled character sets",
noNums.minNumber === 0 && noNums.minSpecial === 0, JSON.stringify(noNums))
check("disabled sets emit no minimum flags",
!args({ numbers: false, special: false }).includes("--minNumber")
&& !args({ numbers: false, special: false }).includes("--minSpecial"),
args({ numbers: false, special: false }))
// Clamping to the documented CLI limits.
for (const [k, v, lo, hi] of [["length", 1, 5, 128], ["length", 999, 5, 128],
["words", 1, 3, 20], ["words", 99, 3, 20],
["minNumber", -3, 0, 9], ["minSpecial", 99, 0, 9]]) {
const got = Model.normalizeGeneratorOptions({ [k]: v, numbers: true, special: true })[k]
check(`${k}=${v} clamps into [${lo}, ${hi}]`, got >= lo && got <= hi, `got ${got}`)
}
// Passphrase mode must not leak password-only flags, and vice versa.
const pp = args({ type: "passphrase", words: 5, capitalize: true, includeNumber: true })
check("passphrase passes --passphrase and word options",
pp.includes("--passphrase") && pp.includes("--words 5") && pp.includes("--capitalize") && pp.includes("--includeNumber"), pp)
check("passphrase omits password-only flags",
!pp.includes("--length") && !pp.includes("--minNumber") && !pp.includes("--uppercase"), pp)
const pw = args({ type: "password" })
check("password omits passphrase-only flags",
!pw.includes("--passphrase") && !pw.includes("--words") && !pw.includes("--capitalize"), pw)
check("an empty separator falls back rather than producing a bare flag",
Model.normalizeGeneratorOptions({ separator: "" }).separator === "-",
Model.normalizeGeneratorOptions({ separator: "" }).separator)
// Strength must move in the right direction, or the meter misleads.
const s = o => Model.generatorStrength(o).bits
check("longer passwords score higher", s({ length: 32 }) > s({ length: 8 }), `${s({length:32})} vs ${s({length:8})}`)
check("more character sets score higher",
s({ length: 16, special: true }) > s({ length: 16, special: false }),
`${s({length:16,special:true})} vs ${s({length:16,special:false})}`)
check("more words score higher", s({ type: "passphrase", words: 8 }) > s({ type: "passphrase", words: 3 }),
`${s({type:"passphrase",words:8})} vs ${s({type:"passphrase",words:3})}`)
check("strength fraction stays within 0..1",
[{}, { length: 128, special: true }, { length: 5 }].every(o => {
const f = Model.generatorStrength(o).fraction; return f >= 0 && f <= 1 }), "out of range")
check("defaults are a fresh object each call",
Model.generatorDefaults() !== Model.generatorDefaults(), "same reference returned")
// --- the same options over `bw serve` ---------------------------------------
// `bw generate` spends ~2.9s on CLI bootstrap and service init before it
// generates anything; the serve API answers the same request in ~2ms. These
// URLs were verified against a live locked `bw serve`.
const url = (o) => Model.generateServeUrl(o)
check("the server is addressed on loopback only",
url({}).startsWith("http://127.0.0.1:"), url({}))
check("a password request carries the character sets and length",
url({ length: 20, uppercase: true, lowercase: true, numbers: true, special: true })
.includes("length=20") && url({ length: 20, special: true }).includes("special=true"),
url({ length: 20, uppercase: true, lowercase: true, numbers: true, special: true }))
check("a disabled set is omitted rather than sent false",
!url({ special: false, numbers: false }).includes("special=")
&& !url({ special: false, numbers: false }).includes("number="),
url({ special: false, numbers: false }))
check("minimums ride along only when their set is on",
url({ numbers: true, minNumber: 3 }).includes("minNumber=3")
&& !url({ numbers: false, minNumber: 3 }).includes("minNumber"),
url({ numbers: true, minNumber: 3 }))
check("a passphrase request switches shape entirely",
url({ type: "passphrase", words: 5 }).includes("passphrase=true")
&& url({ type: "passphrase", words: 5 }).includes("words=5")
&& !url({ type: "passphrase", words: 5 }).includes("length="),
url({ type: "passphrase", words: 5 }))
check("a separator that means something in a URL is encoded",
url({ type: "passphrase", separator: "&" }).includes("separator=%26"),
url({ type: "passphrase", separator: "&" }))
check("the serve options are clamped the same way the CLI ones are",
url({ length: 9999 }).includes("length=128") && url({ length: 1 }).includes("length=5"),
url({ length: 9999 }) + " / " + url({ length: 1 }))
// The server is started without a session on purpose: a loopback port has no
// authentication, so it must never hold an unlocked vault.
check("the serve command binds loopback and names no session",
JSON.stringify(Model.generateServeCommand()) ===
JSON.stringify(["bw", "serve", "--hostname", "127.0.0.1", "--port", "8087"]),
JSON.stringify(Model.generateServeCommand()))
const serveReq = Model.generateServeRequestCommand({ length: 20, special: true })
check("the serve request command targets the generated url with timeout and stream cap",
serveReq[2].includes("curl -q -s -S") && serveReq[2].includes("http://127.0.0.1:8087/generate")
&& serveReq[2].includes("--max-time 2") && serveReq[2].includes("head -c 65536"),
serveReq[2])
check("loopback generator requests ignore proxy variables and curl config",
serveReq[2].includes("curl -q ") && serveReq[2].includes("--noproxy '*'"), serveReq[2])
check("a successful response yields the value",
Model.parseServeGenerated('{"success":true,"data":{"object":"string","data":"abc123"}}') === "abc123",
Model.parseServeGenerated('{"success":true,"data":{"object":"string","data":"abc123"}}'))
check("a failed response yields nothing, so the caller falls back",
Model.parseServeGenerated('{"success":false,"message":"locked"}') === "", "expected empty")
check("garbage yields nothing rather than throwing",
Model.parseServeGenerated("<html>not json</html>") === "", "expected empty")
check("an empty body yields nothing", Model.parseServeGenerated("") === "", "expected empty")
// --- the loopback server is not trusted just because it answers --------------
//
// `bw serve` has no authentication and a loopback port is reachable by every
// account on the machine, so an HTTP 200 is not evidence that the process which
// sent it is ours. The only answer that leaves the port free for our own server
// is a refused connection.
check("a refused connection is the one answer that frees the port",
Model.generatorPortIsForeign(0) === false, "status 0 was treated as occupied")
for (const status of [200, 404, 500, 401, 302]) {
check(`an HTTP ${status} means someone else is already bound`,
Model.generatorPortIsForeign(status) === true, `status ${status} was trusted`)
}
check("a status arriving as a string is still not mistaken for silence",
Model.generatorPortIsForeign("200") === true, "string status was trusted")
// --- what our own server exiting means ---------------------------------------
const stopped = Model.generatorServeExitAction({ stopping: true, wasReady: true, busy: false,
onGeneratorScreen: false })
check("a shutdown we asked for is not a bind failure",
stopped.giveUp === false && stopped.dropValue === false && stopped.useCli === false,
JSON.stringify(stopped))
// Our bind failing is what a squatted port looks like from here, so a value the
// ready-poll already accepted cannot be left on screen to be copied.
const stranded = Model.generatorServeExitAction({ stopping: false, wasReady: true, busy: false,
onGeneratorScreen: true })
check("a value delivered before our server died is dropped, not left to be copied",
stranded.dropValue === true && stranded.giveUp === true && stranded.useCli === true,
JSON.stringify(stranded))
const neverBound = Model.generatorServeExitAction({ stopping: false, wasReady: false, busy: true,
onGeneratorScreen: true })
check("a server that never bound gives up the port and falls back to the CLI",
neverBound.giveUp === true && neverBound.dropValue === false && neverBound.useCli === true,
JSON.stringify(neverBound))
const offScreen = Model.generatorServeExitAction({ stopping: false, wasReady: true, busy: false,
onGeneratorScreen: false })
check("nothing is regenerated for a screen the user has already left",
offScreen.useCli === false && offScreen.dropValue === true, JSON.stringify(offScreen))
const idle = Model.generatorServeExitAction({ stopping: false, wasReady: false, busy: false,
onGeneratorScreen: true })
check("an idle failure gives up the port without generating anything",
idle.giveUp === true && idle.useCli === false, JSON.stringify(idle))
// A process already answering an older option set must not have its callback
// relabelled as the newest request. Queue one regeneration and discard the old
// result; the follow-up reads the latest root.genOpts.
const regenerate = bodyOf("regenerate")
const generated = bodyOf("onGenerated")
check("rapid option changes queue behind the active generator operation",
/if\s*\(genBusy\)[\s\S]*genRegeneratePending\s*=\s*true/.test(regenerate), regenerate)
check("a queued regeneration discards the old value before rerunning",
/genRegeneratePending[\s\S]*regenerate\(\)/.test(generated)
&& generated.indexOf("genRegeneratePending") < generated.indexOf("genValue = v"),
generated)
check("a value from the previous option set cannot be copied while regeneration is busy",
/if\s*\(genBusy\s*\|\|\s*!genValue\)\s*return/.test(bodyOf("copyGenerated"))
&& /if\s*\(!generatorFeedsForm\s*\|\|\s*genBusy\s*\|\|\s*!genValue\)\s*return/.test(bodyOf("useGeneratedPassword"))
&& /enabled:\s*!root\.genBusy\s*&&\s*root\.genValue\s*!==\s*""/.test(panelSrc),
bodyOf("copyGenerated") + "\n" + bodyOf("useGeneratedPassword"))
const stopGenerator = bodyOf("stopGeneratorServe")
check("canceling an in-flight generator request cannot leave generation wedged busy",
/genBusy\s*=\s*false/.test(stopGenerator)
&& /genRegeneratePending\s*=\s*false/.test(stopGenerator)
&& /genRequestSignature\s*=\s*""/.test(stopGenerator),
stopGenerator)
check("leaving during CLI fallback cancels the late result instead of restarting off-screen",
/cancelCliGeneration\s*=\s*genBusy\s*&&\s*generateProc\.running/.test(stopGenerator)
&& /generateCliStopping\s*=\s*true[\s\S]*generateProc\.running\s*=\s*false/.test(stopGenerator),
stopGenerator)
const generateProcBlock = panelSrc.slice(panelSrc.indexOf("id: generateProc"),
panelSrc.indexOf("id: generateServeProc"))
check("a canceled CLI result is discarded and a quick reopen restarts only after exit",
/if\s*\(generateCliStopping\)[\s\S]*genRegeneratePending\s*=\s*true[\s\S]*return/.test(regenerate)
&& /generateCliStopping[\s\S]*currentScreen\s*===\s*"generator"[\s\S]*Qt\.callLater\(root\.regenerate\)[\s\S]*return/.test(generateProcBlock),
regenerate + "\n" + generateProcBlock)
const serveRequest = bodyOf("generatorRequest")
const resumeServeRequest = bodyOf("resumePendingGeneratorRequest")
const serveRequestProcBlock = panelSrc.slice(panelSrc.indexOf("id: generateServeRequestProc"),
panelSrc.indexOf("id: generateServePoll"))
check("a quick reopen cannot attach a new callback to the request being canceled",
/generateServeRequestStopping\s*\|\|\s*generateServeRequestProc\.running/.test(serveRequest)
&& /generateServeRequestPendingCallback\s*=\s*done/.test(serveRequest)
&& /generateServeRequestStopping\s*=\s*true[\s\S]*generateServeRequestProc\.running\s*=\s*false/.test(stopGenerator)
&& /resumePendingGeneratorRequest\(\)[\s\S]*return/.test(serveRequestProcBlock),
serveRequest + "\n" + stopGenerator + "\n" + serveRequestProcBlock)
check("a deferred generator request restarts only if the generator is still open",
/root\.opened\s*&&\s*root\.currentScreen\s*===\s*"generator"[\s\S]*generatorRequest\(pendingOptions,\s*pendingCallback\)/.test(
resumeServeRequest),
resumeServeRequest)
console.log(`${pass} passed, ${failures.length} failed`)
if (failures.length) { console.error("\nFAILURES:\n " + failures.join("\n ")); process.exit(1) }