| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- --[[
- tests/ctl.lua — Unit tests for ctl.lua
- Pure helpers (build_request, validation, rendering) + file-IPC round trip
- in a temp dir. No daemon required.
- ]]
- package.path = package.path .. ";./src/?.lua"
- local ctl = require("balancerlite.ctl")
- local os_tmp = os.tmpname()
- local passed = 0
- local failed = 0
- local function check(name, ok, detail)
- if ok then
- passed = passed + 1
- print(" PASS " .. name)
- else
- failed = failed + 1
- print(" FAIL " .. name .. (detail and (" " .. detail) or ""))
- end
- end
- -- Make a temp state dir for IPC tests
- local function mkdtemp()
- local p = "/tmp/bltest_" .. tostring(os.time()) .. "_" ..
- tostring(math.random(1000000))
- os.execute("mkdir -p " .. p)
- return p
- end
- -- -----------------------------------------------------------------
- -- TEST 1: build_request
- -- -----------------------------------------------------------------
- print("\n[1] build_request")
- do
- local r = ctl.build_request("switch", { target = "wan-b" })
- check("kind set", r.kind == "switch")
- check("target carried", r.target == "wan-b")
- check("ts is a number", type(r.ts) == "number" and r.ts > 0)
- end
- -- -----------------------------------------------------------------
- -- TEST 2: validate_switch_target
- -- -----------------------------------------------------------------
- print("\n[2] validate_switch_target")
- do
- local ok, _ = ctl.validate_switch_target("wan-a")
- check("wan-a accepted", ok == true)
- ok, _ = ctl.validate_switch_target("wan-b")
- check("wan-b accepted", ok == true)
- ok, err = ctl.validate_switch_target("wan-z")
- check("wan-z rejected", ok == false and type(err) == "string" and err:match("wan.z"))
- ok, _ = ctl.validate_switch_target(nil)
- check("nil rejected", ok == false)
- end
- -- -----------------------------------------------------------------
- -- TEST 3: validate_days
- -- -----------------------------------------------------------------
- print("\n[3] validate_days")
- do
- local n, err = ctl.validate_days(nil)
- check("nil -> default 30", n == 30 and err == nil)
- n, err = ctl.validate_days("")
- check("empty -> default 30", n == 30)
- n, err = ctl.validate_days("14")
- check("14 -> 14", n == 14)
- n, err = ctl.validate_days("14.7")
- check("14.7 -> 14 (floor)", n == 14)
- n, err = ctl.validate_days("0")
- check("0 rejected", n == nil and type(err) == "string")
- n, err = ctl.validate_days("notanumber")
- check("garbage rejected", n == nil)
- n, err = ctl.validate_days("99999")
- check("99999 rejected (>3650)", n == nil)
- end
- -- -----------------------------------------------------------------
- -- TEST 4: render_status with empty status
- -- -----------------------------------------------------------------
- print("\n[4] render_status empty")
- do
- local out = ctl.render_status(nil)
- check("contains 'no status file'", type(out) == "string" and out:match("no status file"))
- end
- -- -----------------------------------------------------------------
- -- TEST 5: render_status with real status
- -- -----------------------------------------------------------------
- print("\n[5] render_status happy path")
- do
- local st = { daemon=true, state="WAN_A_PRIMARY", active_wan="wan-a",
- host="rtr1", cycles=42, last_event_ts=os.time()-60, updated_ts=os.time() }
- local out = ctl.render_status(st)
- check("contains 'alive'", out:match("alive"))
- check("contains WAN_A_PRIMARY", out:match("WAN_A_PRIMARY"))
- check("contains active_wan=wan-a", out:match("wan%-a"))
- check("contains cycles 42", out:match("42"))
- end
- -- -----------------------------------------------------------------
- -- TEST 6: render_event
- -- -----------------------------------------------------------------
- print("\n[6] render_event")
- do
- local ev = { ts=os.time(), type="failover.switch",
- from_state="WAN_A_PRIMARY", to_state="WAN_B_PRIMARY",
- active_wan="wan-b", reason="primary down" }
- local out = ctl.render_event(ev)
- check("contains type", out:match("failover%.switch"))
- check("contains WAN_A -> WAN_B", out:match("WAN_A_PRIMARY") and out:match("WAN_B_PRIMARY"))
- check("contains active=wan-b", out:match("wan%-b"))
- check("contains reason", out:match("primary down"))
- end
- -- -----------------------------------------------------------------
- -- TEST 7: write_control / read_control round trip
- -- -----------------------------------------------------------------
- print("\n[7] write/read control round trip")
- do
- local dir = mkdtemp()
- local req = ctl.build_request("switch", { target = "wan-a" })
- local ok = ctl.write_control(dir, req)
- check("write ok", ok == true)
- check("control file exists",
- io.open(dir .. "/control.json", "r") ~= nil)
- local got = ctl.read_control(dir)
- check("read returns table", type(got) == "table")
- check("kind preserved", got.kind == "switch")
- check("target preserved", got.target == "wan-a")
- ctl.clear_control(dir)
- check("clear removes file",
- io.open(dir .. "/control.json", "r") == nil)
- os.execute("rm -rf " .. dir)
- end
- -- -----------------------------------------------------------------
- -- TEST 8: read_status on missing file returns nil
- -- -----------------------------------------------------------------
- print("\n[8] read_status missing")
- do
- local dir = mkdtemp()
- check("returns nil on missing status", ctl.read_status(dir) == nil)
- os.execute("rm -rf " .. dir)
- end
- -- -----------------------------------------------------------------
- -- TEST 9: cmd_switch writes valid JSON request
- -- -----------------------------------------------------------------
- print("\n[9] cmd_switch writes valid request")
- do
- local dir = mkdtemp()
- local rc = ctl.cmd_switch(dir, "wan-b")
- check("rc=0", rc == 0)
- local got = ctl.read_control(dir)
- check("kind=switch", got and got.kind == "switch")
- check("target=wan-b", got and got.target == "wan-b")
- ctl.clear_control(dir)
- os.execute("rm -rf " .. dir)
- end
- -- -----------------------------------------------------------------
- -- TEST 10: cmd_switch bad target exits 1
- -- -----------------------------------------------------------------
- print("\n[10] cmd_switch bad target")
- do
- local dir = mkdtemp()
- local rc = ctl.cmd_switch(dir, "wan-z")
- check("rc=1", rc == 1)
- check("no control file written",
- io.open(dir .. "/control.json", "r") == nil)
- os.execute("rm -rf " .. dir)
- end
- -- -----------------------------------------------------------------
- -- TEST 11: cmd_compact
- -- -----------------------------------------------------------------
- print("\n[11] cmd_compact")
- do
- local dir = mkdtemp()
- local rc = ctl.cmd_compact(dir, "7")
- check("rc=0", rc == 0)
- local got = ctl.read_control(dir)
- check("kind=compact", got and got.kind == "compact")
- check("days=7", got and got.days == 7)
- ctl.clear_control(dir)
- local rc2 = ctl.cmd_compact(dir, "garbage")
- check("bad days rc=1", rc2 == 1)
- os.execute("rm -rf " .. dir)
- end
- -- -----------------------------------------------------------------
- -- TEST 12: cmd_verify
- -- -----------------------------------------------------------------
- print("\n[12] cmd_verify")
- do
- local dir = mkdtemp()
- local rc = ctl.cmd_verify(dir)
- check("rc=0", rc == 0)
- local got = ctl.read_control(dir)
- check("kind=verify", got and got.kind == "verify")
- ctl.clear_control(dir)
- os.execute("rm -rf " .. dir)
- end
- -- -----------------------------------------------------------------
- -- TEST 13: cmd_status with no daemon returns rc=1
- -- -----------------------------------------------------------------
- print("\n[13] cmd_status no daemon")
- do
- local dir = mkdtemp()
- local rc = ctl.cmd_status(dir)
- check("rc=1 when no status file", rc == 1)
- os.execute("rm -rf " .. dir)
- end
- -- -----------------------------------------------------------------
- -- Done
- -- -----------------------------------------------------------------
- print(string.format("\n=== ctl: %d/%d passed ===", passed, passed + failed))
- if failed > 0 then os.exit(1) end
|