Testing
ZERG maintains a comprehensive test suite across all components. This guide covers running tests, writing new tests, and understanding test conventions.
Test Suite Overview
| Component | Tests | Files | Status |
|---|---|---|---|
| Sol server (Erlang) | 4,600+ | 433 | 0 failures |
| Luna client (Lua) | ~2,500+ | 486 | 0 failures |
| Mango auth (Python) | 433 | 49 | 0 failures |
| ZERG CLI (LuaJIT) | 504 assertions | 12 | 0 failures |
| Limon dashboard (Vue.js) | 379 | 71 | 0 failures (11 pre-existing known) |
| Total | ~6,100+ | ~1,053 | 0 failures |
Running Tests
All Server Tests
bash
cd server
rebar3 eunitSpecific Module Tests
bash
rebar3 eunit --module sol_http_conversations
rebar3 eunit --module sol_scheduler
rebar3 eunit --module sol_healthAll Luna Tests
bash
cd client
for f in tests/test_*.lua; do luajit "$f"; doneSingle Luna Test File
bash
luajit client/tests/test_redaction.lua
luajit client/tests/test_shell_quoting.lua
luajit client/tests/test_permission_checker.luaMango Tests
bash
cd mango
python3 -m pytest tests/ -vCLI Tests
bash
cd zerg-cli
bash tests/run.shLimon Tests
bash
cd limon
npm testWriting Erlang eunit Tests
Test File Structure
Test files live in server/test/ and follow the naming convention sol_<module>_tests.erl:
erlang
-module(sol_http_memory_tests).
-include_lib("eunit/include/eunit.hrl").
memory_store_ok_test() ->
meck:new(sol_memory_pg, [passthrough]),
meck:expect(sol_memory_pg, store, fun(_, _, _, _) -> ok end),
Req = make_fake_req(#{<<"key">> => <<"k1">>, <<"value">> => <<"v1">>}),
{Code, _} = sol_http_memory:handle(memory_store, Req),
?assertEqual(200, Code),
meck:unload(sol_memory_pg).
memory_store_missing_key_test() ->
Req = make_fake_req(#{<<"value">> => <<"v1">>}),
{Code, _} = sol_http_memory:handle(memory_store, Req),
?assertEqual(400, Code).Conventions
- Use
meckfor mocking gen_server calls - Test both success and error paths
- Use
?assertEqual,?assertMatch,?assertErrorassertions - Always unload mocks in the test (or use
meck:unload/0in a fixture) - Export test functions with
_test()suffix for automatic discovery
Test Fixtures
For setup/teardown, use eunit fixtures:
erlang
memory_test_() ->
{foreach,
fun setup/0,
fun cleanup/1,
[
fun store_and_retrieve/1,
fun delete_missing/1,
fun search_by_query/1
]
}.
setup() ->
application:ensure_started(mnesia),
ok.
cleanup(_) ->
mnesia:clear_table(sol_memory),
ok.Writing Lua Tests
Test Helper Pattern
Luna tests follow a consistent pattern with ok and check helpers:
lua
local N = 0
local PASS = 0
local FAIL = 0
local function ok(name, cond)
N = N + 1
if cond then PASS = PASS + 1 else
FAIL = FAIL + 1
print(" FAIL: " .. name)
end
end
local function check(name, fn)
N = N + 1
local success, result = pcall(fn)
if success and result then PASS = PASS + 1 else
FAIL = FAIL + 1
print(" FAIL: " .. name .. (not success and (" -- " .. tostring(result)) or ""))
end
endExample Test File
lua
package.path = "./?.lua;./?/init.lua;./vendor/?.lua;" .. package.path
local N = 0
local PASS = 0
local FAIL = 0
local function ok(name, cond)
N = N + 1
if cond then PASS = PASS + 1 else
FAIL = FAIL + 1
print(" FAIL: " .. name)
end
end
print("=== My Module Tests ===")
local my_module = require("core.my_module")
ok("basic operation works", my_module.process("input") == "output")
ok("handles nil gracefully", my_module.process(nil) == nil)
print(string.format("Results: %d/%d passed", PASS, N))
os.exit(FAIL > 0 and 1 or 0)Conventions
- Set
package.pathat the top to resolve module imports - Print a test suite header with
=== - Exit with code 1 if any test fails:
os.exit(FAIL > 0 and 1 or 0) - Use
okfor simple boolean assertions - Use
checkfor function calls that may throw errors - Report pass/fail summary at the end
Test Categories
Security Tests
| Test File | Tests | Coverage |
|---|---|---|
test_redaction.lua | 30 | 10 secret patterns, has_secrets, edge cases |
test_danger_detector.lua | 48 | 41 patterns, 3 severity levels |
test_permission_checker.lua | 26 | 5 modes, rule_matches, check chain |
test_shell_quoting.lua | 30 | Single-quote wrapping, bash-verified |
test_url_encoding.lua | 35 | Injection attempts in web_search |
Integration Tests
| Test File | Tests | Coverage |
|---|---|---|
test_integration_zmq.lua | 13+10 | ZMQ mock + live conditional |
test_zmq_protocol_builders.lua | 41 | spawn, event, team, result messages |
test_e2e_zmq.lua | -- | End-to-end ZMQ flow |
test_mcp_oauth_complete_flow.lua | 28 | PKCE OAuth flow |
Server Admin Tests
| Test Area | Tests | Coverage |
|---|---|---|
| Admin gate tests | 31 | 403 for non-admin on 24 endpoints |
| Password security tests | 9 | Ownership rejection, plaintext stripped |
| RBAC logging tests | -- | Permission check audit trail |
CI Integration
Tests are designed to run without external dependencies:
- Server tests use
meckmocks, no running services needed - Luna tests use
package.pathmanipulation for local imports - Mango tests use SQLite in-memory databases
- Integration tests use conditional live checks with fallback to mocks
Test Stability
All tests pass with 0 failures across all components. Do not introduce new test failures.