--[[ tests/smoke.lua — End-to-end smoke test Boots the daemon in dry-run mode with a fake UCI config; runs 2 cycles, verifies status.json + control.json round-trip via the ctl module. ]] package.path = package.path .. ";/usr/lib/lua/5.1/?.lua;./src/?.lua" local config = require("balancerlite.config") local ctl = require("balancerlite.ctl") 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 -- ----------------------------------------------------------------- -- Setup: write a minimal UCI config to a temp dir. -- ----------------------------------------------------------------- local dir = "/tmp/bl_smoke_" .. tostring(os.time()) .. "_" .. tostring(math.random(1000000)) os.execute("mkdir -p " .. dir) local cfg_path = dir .. "/balancerlite" local f = io.open(cfg_path, "w") f:write([[ config general 'general' option host 'smoke-test' option dry_run '1' option log_level 'info' config health 'health' option probe_interval '1s' option window_size '5' option down_threshold '2' option up_threshold '3' option icmp_timeout '1s' option tcp_timeout '2s' option dns_timeout '2s' option dns_probe_domain 'example.com' option icmp_enabled '0' option tcp_enabled '0' option dns_enabled '0' config wan 'wan_a' option interface 'lo' option address '127.0.0.1/32' option gateway '127.0.0.1' config wan 'wan_b' option interface 'lo' option address '127.0.0.1/32' option gateway '127.0.0.1' config routing 'routing' option table_a '100' option table_b '101' option mark_active_a '0x1' option mark_active_b '0x2' config wireguard 'wg' option enabled '0' option interface 'wg0' config notifier 'notifier' option webhook_url '' option webhook_secret '' option batch_window '0s' option max_attempts '1' option initial_backoff '1s' option max_backoff '1s' option outbox_capacity '100' option cb_threshold '5' option cb_cooldown '60s' config flap 'flap' option switch_count '3' option window '60s' option recovery_grace '60s' config features 'features' option hot_reload '0' option trace_ids '0' config store 'store' option state_dir ']] .. dir .. [[' option retention_days '30' ]]) f:close() -- ----------------------------------------------------------------- -- TEST 1: config loads -- ----------------------------------------------------------------- print("\n[1] config load") local cfg, err = config.load(cfg_path) check("config loads", cfg ~= nil, tostring(err)) check("dry_run=true", cfg.general.dry_run == true) check("host=smoke-test", cfg.general.host == "smoke-test") check("state_dir matches", cfg.store.state_dir == dir) check("icmp disabled", cfg.health.icmp_enabled == false) check("tcp disabled", cfg.health.tcp_enabled == false) -- ----------------------------------------------------------------- -- TEST 2: ctl round-trip in our temp dir -- ----------------------------------------------------------------- print("\n[2] ctl round-trip in " .. dir) local rc = ctl.cmd_switch(dir, "wan-b") check("ctl switch rc=0", rc == 0) local req = ctl.read_control(dir) check("control file written", req ~= nil and req.kind == "switch" and req.target == "wan-b") ctl.clear_control(dir) check("control file cleared", io.open(dir .. "/control.json", "r") == nil) -- status file should NOT exist yet (daemon hasn't been started) check("no status yet", io.open(dir .. "/status.json", "r") == nil) -- Write a fake status file (as the daemon would) local sf = io.open(dir .. "/status.json", "w") sf:write('{"daemon":true,"state":"WAN_A_PRIMARY","active_wan":"wan-a",' .. '"host":"smoke-test","cycles":3,"updated_ts":' .. tostring(os.time()) .. '}\n') sf:close() local st = ctl.read_status(dir) check("status readable", st ~= nil and st.daemon == true) check("status.state=WAN_A_PRIMARY", st.state == "WAN_A_PRIMARY") check("status.active_wan=wan-a", st.active_wan == "wan-a") local rendered = ctl.render_status(st) check("rendered contains WAN_A_PRIMARY", rendered:match("WAN_A_PRIMARY")) check("rendered contains 'alive'", rendered:match("alive")) -- ----------------------------------------------------------------- -- TEST 3: ctl cmd_status returns rc=0 when status file exists -- ----------------------------------------------------------------- print("\n[3] cmd_status with status file") local rc2 = ctl.cmd_status(dir) check("rc=0", rc2 == 0) -- ----------------------------------------------------------------- -- TEST 4: ctl cmd_events with no events -- ----------------------------------------------------------------- print("\n[4] cmd_events with empty store") local rc3 = ctl.cmd_events(dir, 10) check("rc=0", rc3 == 0) -- ----------------------------------------------------------------- -- Cleanup -- ----------------------------------------------------------------- os.execute("rm -rf " .. dir) print(string.format("\n=== smoke: %d/%d passed ===", passed, passed + failed)) if failed > 0 then os.exit(1) end