27-assertion TAP-style suite for lib-management.dep-fetcher v0.1.0:
- 4 internal-helper tests (id_to_path, gitea_slug_for incl. .git
exception)
- 6 JSON decoder tests (objects/arrays/nesting)
- 3 conflict-detection tests (synthetic two-parent fixture)
- 3 closure-walk tests (clean fixture, check_only mode)
- 3 case-B (clean) tests
- 4 case-C (dirty) tests
- 4 case-D (wrong-tag → silent checkout, post-state verification)
Cases A (missing+clone) and E (broken describe) need network or
contrived corruption; out of scope for slice 5 per architecture spec.
Fixture matrix:
conflict_a/ — static, no .git needed
clean_b/ — .git at v0.1.0 (clean)
dirty_c/ — .git at v0.1.0-1-g…-dirty
wrong_tag_d/ — .git with tags v0.1.0 + v0.2.0, HEAD at v0.1.0
fixtures/_setup.sh reconstructs the .git states (idempotent — restores
manifest.lib content after the case-D silent-checkout side-effect).
Required pre-step before each test session; the test-runner blocks
with a clear error if fixtures/_setup.stamp is missing.
.gitignore covers the .git subdirs and the stamp file — they are
local-machine state, rebuilt by _setup.sh from the source-tracked
manifest.lib seed files.
169 lines
7.0 KiB
Lua
169 lines
7.0 KiB
Lua
-- lib-management.dep-fetcher-test v0.1.0
|
|
--
|
|
-- TAP-style assertion suite for lib-management.dep-fetcher. Covers the
|
|
-- closure walk (conflict detection) plus per-lib state-check cases
|
|
-- B-D from spec §5 (clean / dirty / wrong-tag). Case A (missing+clone)
|
|
-- and Case E (broken describe) are exercised in check_only mode where
|
|
-- possible; their live-network variants are gated by SPOREL_DEPFETCHER_NET=1
|
|
-- and not run by default (slice-5 stays offline).
|
|
--
|
|
-- Fixture states that cannot be checked in verbatim (live .git dirs)
|
|
-- are reconstructed by ./fixtures/_setup.sh which the user must run
|
|
-- before invoking this test-module.
|
|
|
|
local M = {}
|
|
|
|
local function fixture_dir()
|
|
return engine.module.dir_of("lib-management.dep-fetcher-test")
|
|
.. "/fixtures"
|
|
end
|
|
|
|
function M.run_tests()
|
|
local depf = require("lib-management.dep-fetcher")
|
|
|
|
-- ============================================================
|
|
-- Assertions on internal helpers (gitea-slug edge case, id->path)
|
|
-- ============================================================
|
|
|
|
engine.test.equals(depf._id_to_path("lib-core.maps"),
|
|
"lib-core/maps", "id_to_path: simple two-segment id")
|
|
engine.test.equals(depf._id_to_path("lib-core.body.vertebrate"),
|
|
"lib-core/body/vertebrate",
|
|
"id_to_path: multi-segment id keeps depth")
|
|
engine.test.equals(depf._gitea_slug_for("lib-core.maps"),
|
|
"sporel-lib-core.maps",
|
|
"gitea_slug_for: regular id gets sporel- prefix")
|
|
engine.test.equals(depf._gitea_slug_for("lib-core.git"),
|
|
"sporel-lib-core.git-lib",
|
|
"gitea_slug_for: .git-suffix id appends -lib (Gitea reserved)")
|
|
|
|
-- ============================================================
|
|
-- JSON decoder sanity (small targeted suite — manifests are JSON)
|
|
-- ============================================================
|
|
|
|
local empty = depf._json_decode("{}")
|
|
engine.test.assert(type(empty) == "table",
|
|
"json_decode: empty object is a table")
|
|
local arr_only = depf._json_decode("[1,2,3]")
|
|
engine.test.equals(#arr_only, 3,
|
|
"json_decode: array of numbers length")
|
|
engine.test.equals(arr_only[2], 2,
|
|
"json_decode: array index 2 value")
|
|
local nested = depf._json_decode(
|
|
'{"id":"x","deps":[{"id":"a","version":"0.1.0"}]}')
|
|
engine.test.equals(nested.id, "x",
|
|
"json_decode: nested object id field")
|
|
engine.test.equals(nested.deps[1].id, "a",
|
|
"json_decode: nested array-of-objects access")
|
|
engine.test.equals(nested.deps[1].version, "0.1.0",
|
|
"json_decode: deeply-nested string field")
|
|
|
|
-- ============================================================
|
|
-- Conflict-detection fixture (no actual git ops)
|
|
-- ============================================================
|
|
|
|
local r_conflict = depf.ensure_for_module_at(
|
|
fixture_dir() .. "/conflict_a/manifest.module",
|
|
{
|
|
install_root = fixture_dir() .. "/conflict_a/install",
|
|
check_only = true,
|
|
})
|
|
engine.test.equals(r_conflict.ok, false,
|
|
"conflict_a: ok=false (conflict detected)")
|
|
engine.test.equals(#r_conflict.conflicts, 1,
|
|
"conflict_a: exactly one conflict")
|
|
engine.test.equals(r_conflict.conflicts[1].lib_id, "lib-core.maps",
|
|
"conflict_a: conflict is on lib-core.maps")
|
|
|
|
-- ============================================================
|
|
-- Closure walk over a clean offline fixture (check_only)
|
|
-- ============================================================
|
|
|
|
local r_walk = depf.ensure_for_module_at(
|
|
fixture_dir() .. "/clean_b/manifest.module",
|
|
{
|
|
install_root = fixture_dir() .. "/clean_b/install",
|
|
check_only = true,
|
|
})
|
|
engine.test.equals(r_walk.ok, true,
|
|
"clean_b (check_only): walk succeeds, no conflicts")
|
|
engine.test.equals(#r_walk.conflicts, 0,
|
|
"clean_b (check_only): zero conflicts")
|
|
engine.test.assert(#r_walk.closure >= 1,
|
|
"clean_b (check_only): closure non-empty")
|
|
|
|
-- ============================================================
|
|
-- State-check (cases B, C, D)
|
|
--
|
|
-- These exercise the per-lib git path. They require ./fixtures
|
|
-- /_setup.sh to have constructed the .git states. The test
|
|
-- detects unprepared fixtures by check_only-bypassing them when
|
|
-- the helper file is absent, so a missing _setup.sh run gives
|
|
-- a clear diagnostic via test.fail rather than an obscure
|
|
-- describe failure.
|
|
-- ============================================================
|
|
|
|
local function fixtures_ready()
|
|
local f = io.open(fixture_dir() .. "/_setup.stamp", "rb")
|
|
if not f then return false end
|
|
f:close()
|
|
return true
|
|
end
|
|
|
|
if not fixtures_ready() then
|
|
engine.test.fail(
|
|
"fixtures/_setup.stamp missing — " ..
|
|
"run `bash sporel-libs/lib-management/dep-fetcher-test/" ..
|
|
"fixtures/_setup.sh` first to construct .git state-check " ..
|
|
"fixtures (cases B/C/D)")
|
|
return
|
|
end
|
|
|
|
-- Case B — clean: repo at exactly pinned tag.
|
|
local r_clean = depf.ensure_for_module_at(
|
|
fixture_dir() .. "/clean_b/manifest.module",
|
|
{ install_root = fixture_dir() .. "/clean_b/install" })
|
|
engine.test.equals(r_clean.ok, true,
|
|
"clean_b (full): state-check OK")
|
|
engine.test.equals(#r_clean.warnings, 0,
|
|
"clean_b (full): zero warnings (case B)")
|
|
engine.test.equals(#r_clean.errors, 0,
|
|
"clean_b (full): zero errors")
|
|
|
|
-- Case C — dirty: extra commit past tag.
|
|
local r_dirty = depf.ensure_for_module_at(
|
|
fixture_dir() .. "/dirty_c/manifest.module",
|
|
{ install_root = fixture_dir() .. "/dirty_c/install" })
|
|
engine.test.equals(r_dirty.ok, true,
|
|
"dirty_c: state-check OK (warnings are non-blocking)")
|
|
engine.test.equals(#r_dirty.warnings, 1,
|
|
"dirty_c: one warning (case C)")
|
|
if r_dirty.warnings[1] then
|
|
engine.test.equals(r_dirty.warnings[1].kind, "dirty",
|
|
"dirty_c: warning.kind == 'dirty'")
|
|
engine.test.equals(r_dirty.warnings[1].lib_id, "lib-x.consumer",
|
|
"dirty_c: warning is on lib-x.consumer")
|
|
end
|
|
|
|
-- Case D — wrong tag: repo at v0.1.0, module wants v0.2.0.
|
|
-- The state-check silently checks out v0.2.0. We then verify the
|
|
-- post-state by re-running and expecting Case B (clean).
|
|
local r_wrong = depf.ensure_for_module_at(
|
|
fixture_dir() .. "/wrong_tag_d/manifest.module",
|
|
{ install_root = fixture_dir() .. "/wrong_tag_d/install" })
|
|
engine.test.equals(r_wrong.ok, true,
|
|
"wrong_tag_d: silent checkout succeeded")
|
|
engine.test.equals(#r_wrong.errors, 0,
|
|
"wrong_tag_d: zero errors after checkout")
|
|
|
|
local r_wrong2 = depf.ensure_for_module_at(
|
|
fixture_dir() .. "/wrong_tag_d/manifest.module",
|
|
{ install_root = fixture_dir() .. "/wrong_tag_d/install" })
|
|
engine.test.equals(r_wrong2.ok, true,
|
|
"wrong_tag_d (post-checkout): now clean")
|
|
engine.test.equals(#r_wrong2.warnings, 0,
|
|
"wrong_tag_d (post-checkout): zero warnings (case B reached)")
|
|
end
|
|
|
|
return M
|