#!/usr/bin/env nu # Tests for reflection/modules/secrets.nu — the 15 named errors that form the # helper contract. Each test builds a minimal fixture (project_root with # .ontoref/project.ncl and/or .ontology/manifest.ncl) and asserts the helper # raises the expected `[]` prefix. # # Run: nu reflection/tests/test_secrets.nu # Exit: 0 if all pass, 1 if any fail. use ../modules/secrets.nu * const ONTOREF_ROOT = "/Users/Akasha/Development/ontoref" # Self-contained import path — the secrets module reads $env.NICKEL_IMPORT_PATH # when invoking `nickel export`. Each fixture's manifest.ncl imports # `ontology/defaults/manifest.ncl`, which lives under $ONTOREF_ROOT/ontology. # We append (rather than overwrite) so a caller's existing path still applies. $env.NICKEL_IMPORT_PATH = ( [ ($env.NICKEL_IMPORT_PATH? | default "") $ONTOREF_ROOT $"($ONTOREF_ROOT)/ontology" ($env.HOME | path join ".config" "ontoref" "schemas") ] | where { |s| ($s | str length) > 0 } | str join (char esep) ) # ── Test runner ────────────────────────────────────────────────────────────── mut TOTAL = 0 mut PASSED = 0 mut FAILED = [] def expect-error [ name: string code: string # e.g. "kage-not-resolvable" body: closure ]: nothing -> record { let r = (try { do $body { kind: "no-error", msg: "" } } catch { |e| { kind: "error", msg: $e.msg } }) let prefix = $"[($code)]" if $r.kind == "no-error" { { ok: false, msg: $"expected error ($prefix) but body returned without error" } } else if ($r.msg | str starts-with $prefix) { { ok: true, msg: "" } } else { let snippet = ($r.msg | str substring 0..200) { ok: false, msg: $"expected ($prefix) got: ($snippet)" } } } # ── Fixture builders ───────────────────────────────────────────────────────── def write-project-ncl [ root: string body: string # the body of make_project { ... } ]: nothing -> nothing { mkdir ($root | path join ".ontoref") let schema_dir = ($env.HOME | path join ".config" "ontoref" "schemas") let import = $'let s = import "ontoref-project.ncl" in ' let content = $"($import) s.make_project { ($body) }" $content | save --force ($root | path join ".ontoref" "project.ncl") } def write-manifest-ncl [ root: string body: string # additional fields beyond the required boilerplate ]: nothing -> nothing { mkdir ($root | path join ".ontology") # Minimum required fields by ontology/defaults/manifest.ncl::make_manifest. let boilerplate = ([ ' project = "fixture",', (" repo_kind = " + (char single_quote) + "Mixed,"), ' description = "test fixture",', ' consumption_modes = [],', ] | str join (char newline)) let content = ([ 'let m = import "ontology/defaults/manifest.ncl" in', '', 'm.make_manifest {', $boilerplate, $body, '}', ] | str join (char newline)) $content | save --force ($root | path join ".ontology" "manifest.ncl") } def fixture-base-project [root: string, sops_block: string]: nothing -> nothing { write-project-ncl $root $' slug = "fixture", root = "($root)", nickel_import_paths = ["($root)", "($ONTOREF_ROOT)", "($ONTOREF_ROOT)/ontology"], keys = [], ($sops_block)' } # ── Tests ──────────────────────────────────────────────────────────────────── let TMP = (mktemp -d -t ontoref-tests.XXXXXX) print $"# fixtures in ($TMP)" print "" # Each test runs in its own subdir of $TMP so fixtures don't collide. def run-one [name: string, code: string, body: closure]: nothing -> record { let r = (expect-error $name $code $body) if $r.ok { print $" (ansi green)✓(ansi reset) ($name) [($code)]" } else { print $" (ansi red)✗(ansi reset) ($name) [($code)] → ($r.msg)" } $r } # --- 1. [invalid-op] ------------------------------------------------------------ let r1 = (run-one "invalid op" "invalid-op" { let f = ($TMP | path join "t1") fixture-base-project $f 'sops = { enabled = false }' write-manifest-ncl $f '' resolve-registry-credential $f primary --op badname }) $TOTAL = $TOTAL + 1; if $r1.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "invalid-op") } # --- 2. [project-ncl-missing] --------------------------------------------------- # Use assert-actor-authorized which reads project.ncl first; # resolve-registry-credential reads manifest first and would shadow this case. let r2 = (run-one "project-ncl missing" "project-ncl-missing" { let f = ($TMP | path join "t2-empty") mkdir $f assert-actor-authorized $f pull }) $TOTAL = $TOTAL + 1; if $r2.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "project-ncl-missing") } # --- 3. [manifest-ncl-missing] -------------------------------------------------- let r3 = (run-one "manifest.ncl missing" "manifest-ncl-missing" { let f = ($TMP | path join "t3") fixture-base-project $f 'sops = { enabled = true, vault_id = "fixture", master_key_path = "/dev/null" }' resolve-registry-credential $f primary --op pull }) $TOTAL = $TOTAL + 1; if $r3.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "manifest-ncl-missing") } # --- 4. [registry-provides-missing] -------------------------------------------- let r4 = (run-one "registry_provides missing" "registry-provides-missing" { let f = ($TMP | path join "t4") fixture-base-project $f 'sops = { enabled = true, vault_id = "fixture", master_key_path = "/dev/null" }' write-manifest-ncl $f '' resolve-registry-credential $f primary --op pull }) $TOTAL = $TOTAL + 1; if $r4.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "registry-provides-missing") } # --- 5. [registry-id-unknown] -------------------------------------------------- let r5 = (run-one "registry id unknown" "registry-id-unknown" { let f = ($TMP | path join "t5") fixture-base-project $f 'sops = { enabled = true, vault_id = "fixture", master_key_path = "/dev/null" }' write-manifest-ncl $f ' registry_provides = m.make_registry_provides { participant = "fixture", registries = m.make_registries_config { default = "primary", registries = [ m.make_registry_entry { id = "primary", endpoint = "x", credential_sops = "registry/ro.sops.yaml" } ], }, },' resolve-registry-credential $f does-not-exist --op pull }) $TOTAL = $TOTAL + 1; if $r5.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "registry-id-unknown") } # --- 6. [credential-sops-missing] ---------------------------------------------- let r6 = (run-one "credential_sops missing for op" "credential-sops-missing" { let f = ($TMP | path join "t6") fixture-base-project $f 'sops = { enabled = true, vault_id = "fixture", master_key_path = "/dev/null" }' write-manifest-ncl $f ' registry_provides = m.make_registry_provides { participant = "fixture", registries = m.make_registries_config { default = "primary", registries = [ m.make_registry_entry { id = "primary", endpoint = "x", credential_sops = "registry/ro.sops.yaml" } ], }, },' # credential_sops_rw is absent, op=push → should fail resolve-registry-credential $f primary --op push }) $TOTAL = $TOTAL + 1; if $r6.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "credential-sops-missing") } # --- 7. [sops-file-not-found] -------------------------------------------------- let r7 = (run-one "sops file not on disk" "sops-file-not-found" { let f = ($TMP | path join "t7") fixture-base-project $f $'sops = { enabled = true, vault_id = "fixture-t7-($r6.ok)", master_key_path = "/dev/null" }' write-manifest-ncl $f ' registry_provides = m.make_registry_provides { participant = "fixture", registries = m.make_registries_config { default = "primary", registries = [ m.make_registry_entry { id = "primary", endpoint = "x", credential_sops = "registry/ro.sops.yaml" } ], }, },' # Vault dir for "fixture-t7-..." does not exist → sops file not found resolve-registry-credential $f primary --op pull }) $TOTAL = $TOTAL + 1; if $r7.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "sops-file-not-found") } # --- 8. [kage-not-resolvable] (master_key_path file missing) ------------------- let r8 = (run-one "kage not resolvable" "kage-not-resolvable" { let f = ($TMP | path join "t8") let bogus_kage = ($TMP | path join "non-existent.kage") fixture-base-project $f $'sops = { enabled = true, vault_id = "fixture-t8", master_key_path = "($bogus_kage)" }' write-manifest-ncl $f ' registry_provides = m.make_registry_provides { participant = "fixture", registries = m.make_registries_config { default = "primary", registries = [ m.make_registry_entry { id = "primary", endpoint = "x", credential_sops = "registry/ro.sops.yaml" } ], }, },' # Pre-create the sops file so we get past sops-file-not-found. let vault_dir = ($env.HOME | path join ".config" "ontoref" "vaults" "fixture-t8" "src-vault" "registry") mkdir $vault_dir "fake" | save --force ($vault_dir | path join "ro.sops.yaml") resolve-registry-credential $f primary --op pull }) $TOTAL = $TOTAL + 1; if $r8.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "kage-not-resolvable") } # Cleanup rm -rf ($env.HOME | path join ".config" "ontoref" "vaults" "fixture-t8") # --- 9. [actor-bindings-missing] ----------------------------------------------- let r9 = (run-one "actor_key_bindings empty" "actor-bindings-missing" { let f = ($TMP | path join "t9") fixture-base-project $f 'sops = { enabled = true, vault_id = "fixture-t9", master_key_path = "/dev/null", actor_key_bindings = {} }' write-manifest-ncl $f '' assert-actor-authorized $f pull }) $TOTAL = $TOTAL + 1; if $r9.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "actor-bindings-missing") } # --- 10. [actor-not-bound] ----------------------------------------------------- let r10 = (run-one "actor not in bindings" "actor-not-bound" { let f = ($TMP | path join "t10") fixture-base-project $f 'sops = { enabled = true, vault_id = "fixture-t10", master_key_path = "/dev/null", actor_key_bindings = { admin = "admin" } }' write-manifest-ncl $f '' with-env { ONTOREF_ACTOR: "alien" } { assert-actor-authorized $f pull } }) $TOTAL = $TOTAL + 1; if $r10.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "actor-not-bound") } # --- 11. [scope-not-loaded] ---------------------------------------------------- let r11 = (run-one "scope file missing" "scope-not-loaded" { let f = ($TMP | path join "t11") fixture-base-project $f 'sops = { enabled = true, vault_id = "fixture-t11", master_key_path = "/dev/null", actor_key_bindings = { developer = "developer" } }' write-manifest-ncl $f '' # No scopes/developer.ncl in vault dir with-env { ONTOREF_ACTOR: "developer" } { assert-actor-authorized $f pull } }) $TOTAL = $TOTAL + 1; if $r11.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "scope-not-loaded") } # --- 12. [op-not-in-scope] ----------------------------------------------------- let r12 = (run-one "op not in scope.ops" "op-not-in-scope" { let f = ($TMP | path join "t12") fixture-base-project $f 'sops = { enabled = true, vault_id = "fixture-t12", master_key_path = "/dev/null", actor_key_bindings = { developer = "developer" } }' write-manifest-ncl $f '' let scope_dir = ($env.HOME | path join ".config" "ontoref" "vaults" "fixture-t12" "src-vault" "scopes") mkdir $scope_dir ("{ role = \"developer\", access = 'ro, bound_actor = [], namespaces = [\"domains/*/\"], ops = ['pull, 'verify] }" | save --force ($scope_dir | path join "developer.ncl")) with-env { ONTOREF_ACTOR: "developer" } { assert-actor-authorized $f push } }) $TOTAL = $TOTAL + 1; if $r12.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "op-not-in-scope") } rm -rf ($env.HOME | path join ".config" "ontoref" "vaults" "fixture-t12") # --- 13. [actor-not-in-bound-actor] -------------------------------------------- let r13 = (run-one "scope.bound_actor excludes actor" "actor-not-in-bound-actor" { let f = ($TMP | path join "t13") fixture-base-project $f 'sops = { enabled = true, vault_id = "fixture-t13", master_key_path = "/dev/null", actor_key_bindings = { developer = "developer" } }' write-manifest-ncl $f '' let scope_dir = ($env.HOME | path join ".config" "ontoref" "vaults" "fixture-t13" "src-vault" "scopes") mkdir $scope_dir ("{ role = \"developer\", access = 'rw, bound_actor = [\"admin\"], namespaces = [\"domains/*/\"], ops = ['pull, 'push] }" | save --force ($scope_dir | path join "developer.ncl")) with-env { ONTOREF_ACTOR: "developer" } { assert-actor-authorized $f pull } }) $TOTAL = $TOTAL + 1; if $r13.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "actor-not-in-bound-actor") } rm -rf ($env.HOME | path join ".config" "ontoref" "vaults" "fixture-t13") # --- 14. [target-not-in-scope] ------------------------------------------------- let r14 = (run-one "target not in scope.namespaces" "target-not-in-scope" { let f = ($TMP | path join "t14") fixture-base-project $f 'sops = { enabled = true, vault_id = "fixture-t14", master_key_path = "/dev/null", actor_key_bindings = { developer = "developer" } }' write-manifest-ncl $f '' let scope_dir = ($env.HOME | path join ".config" "ontoref" "vaults" "fixture-t14" "src-vault" "scopes") mkdir $scope_dir ("{ role = \"developer\", access = 'rw, bound_actor = [], namespaces = [\"domains/foo/\"], ops = ['pull, 'push] }" | save --force ($scope_dir | path join "developer.ncl")) with-env { ONTOREF_ACTOR: "developer" } { assert-target-in-scope $f "modes/lian-build/" } }) $TOTAL = $TOTAL + 1; if $r14.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "target-not-in-scope") } rm -rf ($env.HOME | path join ".config" "ontoref" "vaults" "fixture-t14") # --- 15. [sops-decrypt-failed] ------------------------------------------------ # Generate a real age keypair (key-A), encrypt a sops file with key-A's pubkey, # then attempt resolve-registry-credential with a DIFFERENT master key (key-B). # sops returns non-zero because key-B is not a recipient. let r15 = (run-one "sops decrypt failed (wrong master_key)" "sops-decrypt-failed" { let f = ($TMP | path join "t15") let kage_a = ($TMP | path join "key-a.kage") let kage_b = ($TMP | path join "key-b.kage") ^age-keygen -o $kage_a out+err> /dev/null ^age-keygen -o $kage_b out+err> /dev/null chmod 0400 $kage_a chmod 0400 $kage_b let pub_a = (open --raw $kage_a | lines | where { |l| $l | str contains "public key" } | first | split row " " | last) fixture-base-project $f $'sops = { enabled = true, vault_id = "fixture-t15", master_key_path = "($kage_b)" }' write-manifest-ncl $f ' registry_provides = m.make_registry_provides { participant = "fixture", registries = m.make_registries_config { default = "primary", registries = [ m.make_registry_entry { id = "primary", endpoint = "x", credential_sops = "registry/ro.sops.yaml" } ], }, },' # Pre-create the sops file encrypted ONLY for key-A (key-B cannot decrypt). let vault_dir = ($env.HOME | path join ".config" "ontoref" "vaults" "fixture-t15" "src-vault" "registry") mkdir $vault_dir let sops_path = ($vault_dir | path join "ro.sops.yaml") let plain = ($vault_dir | path join "_plain.yaml") "username: u\npassword: p\n" | save --force $plain let enc = (^sops --age $pub_a --input-type yaml --encrypt $plain) $enc | save --force $sops_path rm $plain # Now resolve with key-B as master — sops decrypt must fail. resolve-registry-credential $f primary --op pull }) $TOTAL = $TOTAL + 1; if $r15.ok { $PASSED = $PASSED + 1 } else { $FAILED = ($FAILED | append "sops-decrypt-failed") } rm -rf ($env.HOME | path join ".config" "ontoref" "vaults" "fixture-t15") # ── Report ─────────────────────────────────────────────────────────────────── print "" let bar = if ($FAILED | is-empty) { (ansi green) } else { (ansi red) } print $"($bar)── ($PASSED)/($TOTAL) tests passed(ansi reset)" if not ($FAILED | is-empty) { print $" failed: ($FAILED | str join ', ')" rm -rf $TMP exit 1 } # Cleanup fixtures rm -rf $TMP