Skip to content

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

ComponentTestsFilesStatus
Sol server (Erlang)4,600+4330 failures
Luna client (Lua)~2,500+4860 failures
Mango auth (Python)433490 failures
ZERG CLI (LuaJIT)504 assertions120 failures
Limon dashboard (Vue.js)379710 failures (11 pre-existing known)
Total~6,100+~1,0530 failures

Running Tests

All Server Tests

bash
cd server
rebar3 eunit

Specific Module Tests

bash
rebar3 eunit --module sol_http_conversations
rebar3 eunit --module sol_scheduler
rebar3 eunit --module sol_health

All Luna Tests

bash
cd client
for f in tests/test_*.lua; do luajit "$f"; done

Single Luna Test File

bash
luajit client/tests/test_redaction.lua
luajit client/tests/test_shell_quoting.lua
luajit client/tests/test_permission_checker.lua

Mango Tests

bash
cd mango
python3 -m pytest tests/ -v

CLI Tests

bash
cd zerg-cli
bash tests/run.sh

Limon Tests

bash
cd limon
npm test

Writing 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 meck for mocking gen_server calls
  • Test both success and error paths
  • Use ?assertEqual, ?assertMatch, ?assertError assertions
  • Always unload mocks in the test (or use meck:unload/0 in 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
end

Example 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.path at 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 ok for simple boolean assertions
  • Use check for function calls that may throw errors
  • Report pass/fail summary at the end

Test Categories

Security Tests

Test FileTestsCoverage
test_redaction.lua3010 secret patterns, has_secrets, edge cases
test_danger_detector.lua4841 patterns, 3 severity levels
test_permission_checker.lua265 modes, rule_matches, check chain
test_shell_quoting.lua30Single-quote wrapping, bash-verified
test_url_encoding.lua35Injection attempts in web_search

Integration Tests

Test FileTestsCoverage
test_integration_zmq.lua13+10ZMQ mock + live conditional
test_zmq_protocol_builders.lua41spawn, event, team, result messages
test_e2e_zmq.lua--End-to-end ZMQ flow
test_mcp_oauth_complete_flow.lua28PKCE OAuth flow

Server Admin Tests

Test AreaTestsCoverage
Admin gate tests31403 for non-admin on 24 endpoints
Password security tests9Ownership rejection, plaintext stripped
RBAC logging tests--Permission check audit trail

CI Integration

Tests are designed to run without external dependencies:

  • Server tests use meck mocks, no running services needed
  • Luna tests use package.path manipulation 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.

Released under the MIT License.