- 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
184 lines
6.7 KiB
QML
184 lines
6.7 KiB
QML
import QtQuick
|
|
import QtTest
|
|
import "../../BitwardenModel.js" as Model
|
|
|
|
// The supervision logic runs inside QML's own JavaScript engine, not Node's.
|
|
// These cases re-prove the properties the panel depends on -- the inert
|
|
// default, the one transition that opens the signing gate, and the bounded
|
|
// failure paths -- against that engine, and check that the reducer never asks
|
|
// the caller to wait for anything.
|
|
TestCase {
|
|
name: "SshAgent"
|
|
|
|
readonly property string readyLine: JSON.stringify({
|
|
v: 1, type: "ready", socketPath: "/run/user/1000/qs-bitwarden-cli/ssh-agent.sock",
|
|
fifoPath: "/run/user/1000/qs-bitwarden-cli/ssh-keys.fifo", agentVersion: "0.1.0"
|
|
})
|
|
|
|
function drive(state, events) {
|
|
var actions = []
|
|
for (var i = 0; i < events.length; i++) {
|
|
var step = Model.sshAgentReduce(state, events[i])
|
|
state = step.state
|
|
actions.push(step.action)
|
|
}
|
|
return { state: state, actions: actions, last: actions[actions.length - 1] }
|
|
}
|
|
|
|
function ready() {
|
|
return drive(Model.sshAgentInitialState(), [
|
|
{ kind: "enabled", value: true, nowMs: 0 },
|
|
{ kind: "started", nowMs: 1 },
|
|
{ kind: "line", line: readyLine, nowMs: 2 }
|
|
])
|
|
}
|
|
|
|
function test_disabled_supervisor_starts_nothing() {
|
|
var run = drive(Model.sshAgentInitialState(), [
|
|
{ kind: "started", nowMs: 0 },
|
|
{ kind: "line", line: readyLine, nowMs: 1 },
|
|
{ kind: "restartTimer", nowMs: 2 }
|
|
])
|
|
compare(run.state.phase, "disabled")
|
|
compare(run.state.gateOpen, false)
|
|
for (var i = 0; i < run.actions.length; i++) {
|
|
verify(!run.actions[i].start)
|
|
verify(!run.actions[i].writeHello)
|
|
}
|
|
}
|
|
|
|
function test_handshake_opens_the_signing_gate() {
|
|
var run = ready()
|
|
compare(run.state.phase, "ready")
|
|
compare(run.state.gateOpen, true)
|
|
compare(run.state.socketPath, "/run/user/1000/qs-bitwarden-cli/ssh-agent.sock")
|
|
compare(run.state.agentVersion, "0.1.0")
|
|
}
|
|
|
|
function test_reductions_never_ask_qml_to_wait() {
|
|
var run = ready()
|
|
for (var i = 0; i < run.actions.length; i++) {
|
|
verify(run.actions[i].wait === undefined)
|
|
verify(run.actions[i].waitMs === undefined)
|
|
verify(typeof run.actions[i].restartInMs === "number")
|
|
}
|
|
}
|
|
|
|
function test_helper_is_launched_by_absolute_plugin_path() {
|
|
var dir = Model.pluginDirFromUrl("file:///home/u/.config/omarchy/plugins/bw/")
|
|
compare(dir, "/home/u/.config/omarchy/plugins/bw")
|
|
// The source comes from the bundle inspection; the command follows it
|
|
// rather than guessing which binary to run.
|
|
var cmd = Model.sshAgentHelperCommand(dir, "bundled")
|
|
compare(cmd.length, 1)
|
|
compare(cmd[0].charAt(0), "/")
|
|
verify(cmd[0].indexOf("/home/u/.config/omarchy/plugins/bw/") === 0)
|
|
compare(Model.sshAgentHelperCommand(Model.pluginDirFromUrl("file:///opt/bw/../etc/"), "bundled").length, 0)
|
|
compare(Model.sshAgentHelperCommand(dir, "").length, 0)
|
|
}
|
|
|
|
function test_helper_environment_is_minimal() {
|
|
var env = Model.sshAgentHelperEnv("/run/user/1000")
|
|
var keys = []
|
|
for (var k in env) keys.push(k)
|
|
compare(keys.length, 1)
|
|
compare(keys[0], "XDG_RUNTIME_DIR")
|
|
compare(Model.sshAgentHelperEnv("relative/dir"), null)
|
|
}
|
|
|
|
function test_bad_output_closes_the_gate() {
|
|
var cases = [
|
|
{ line: "not json", code: "MALFORMED" },
|
|
{ line: '{"v":2,"type":"locked","epoch":1}', code: "VERSION_MISMATCH" },
|
|
{ line: '{"v":1,"type":"exec"}', code: "UNKNOWN_TYPE" },
|
|
{ line: readyLine, code: "PROTOCOL" }
|
|
]
|
|
for (var i = 0; i < cases.length; i++) {
|
|
var run = drive(ready().state, [{ kind: "line", line: cases[i].line, nowMs: 100 }])
|
|
compare(run.state.errorCode, cases[i].code)
|
|
compare(run.state.gateOpen, false)
|
|
compare(run.last.stop, true)
|
|
}
|
|
}
|
|
|
|
function test_overlong_output_is_rejected_by_bytes() {
|
|
var filler = new Array(Model.sshAgentMaxLineBytes()).join("é")
|
|
var run = drive(ready().state, [{
|
|
kind: "line", nowMs: 100,
|
|
line: '{"v":1,"type":"error","code":"X","message":"' + filler + '"}'
|
|
}])
|
|
compare(run.state.errorCode, "LINE_TOO_LONG")
|
|
compare(run.state.gateOpen, false)
|
|
}
|
|
|
|
function test_blank_output_is_ignored() {
|
|
var run = drive(ready().state, [{ kind: "line", line: "", nowMs: 100 }])
|
|
compare(run.state.phase, "ready")
|
|
compare(run.state.gateOpen, true)
|
|
}
|
|
|
|
function test_stalled_handshake_is_bounded() {
|
|
var run = drive(Model.sshAgentInitialState(), [
|
|
{ kind: "enabled", value: true, nowMs: 0 },
|
|
{ kind: "started", nowMs: 1 },
|
|
{ kind: "handshakeTimeout", nowMs: Model.sshAgentHandshakeTimeoutMs() }
|
|
])
|
|
compare(run.state.errorCode, "HANDSHAKE_TIMEOUT")
|
|
compare(run.state.gateOpen, false)
|
|
compare(run.last.stop, true)
|
|
}
|
|
|
|
function test_eof_closes_the_gate_and_backs_off() {
|
|
var run = drive(ready().state, [{ kind: "exited", exitCode: 0, nowMs: 100 }])
|
|
compare(run.state.gateOpen, false)
|
|
compare(run.state.phase, "backoff")
|
|
compare(run.last.restartInMs, Model.sshAgentRestartDelayMs(1))
|
|
}
|
|
|
|
function test_crash_loop_stops_restarting() {
|
|
var state = Model.sshAgentReduce(Model.sshAgentInitialState(),
|
|
{ kind: "enabled", value: true, nowMs: 0 }).state
|
|
var scheduled = 0
|
|
var clock = 0
|
|
for (var i = 0; i < Model.sshAgentMaxRestarts() + 2; i++) {
|
|
clock += 10
|
|
state = Model.sshAgentReduce(state, { kind: "started", nowMs: clock }).state
|
|
clock += 10
|
|
var step = Model.sshAgentReduce(state, { kind: "exited", exitCode: 101, nowMs: clock })
|
|
state = step.state
|
|
if (step.action.restartInMs >= 0) scheduled++
|
|
if (state.phase !== "backoff") break
|
|
clock += 10
|
|
state = Model.sshAgentReduce(state, { kind: "restartTimer", nowMs: clock }).state
|
|
}
|
|
compare(state.phase, "failed")
|
|
compare(state.errorCode, "CRASH_LOOP")
|
|
compare(state.gateOpen, false)
|
|
compare(scheduled, Model.sshAgentMaxRestarts())
|
|
}
|
|
|
|
function test_backoff_is_capped() {
|
|
verify(Model.sshAgentRestartDelayMs(1) < Model.sshAgentRestartDelayMs(2))
|
|
compare(Model.sshAgentRestartDelayMs(99), Model.sshAgentRestartDelayMs(100))
|
|
}
|
|
|
|
function test_disabling_stops_everything() {
|
|
var run = drive(ready().state, [{ kind: "enabled", value: false, nowMs: 100 }])
|
|
compare(run.state.phase, "disabled")
|
|
compare(run.state.gateOpen, false)
|
|
compare(run.last.stop, true)
|
|
compare(run.last.cancelRestart, true)
|
|
}
|
|
|
|
function test_reenabling_clears_a_crash_loop() {
|
|
var failed = Model.sshAgentInitialState()
|
|
failed.phase = "failed"
|
|
failed.errorCode = "CRASH_LOOP"
|
|
failed.failures = Model.sshAgentMaxRestarts() + 1
|
|
var run = drive(failed, [{ kind: "enabled", value: true, nowMs: 0 }])
|
|
compare(run.state.phase, "starting")
|
|
compare(run.state.errorCode, "")
|
|
compare(run.last.start, true)
|
|
}
|
|
}
|