package ontology import ( "context" "errors" "testing" "github.com/dtoro/oikos/internal/domain" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) // The lifecycle precondition checks split into a pure attribute/type guard // and a DB query. These cover the pure guards at 0%: the entity-type skip // rules and the attribute presence/absence semantics. The DB-backed checks // (health, edges, backups, docs) are exercised by make test-db. // // ctx/pool/entityID are unused by the pure guards, so nil is safe here. var ( noCtx = context.Background() noPool *pgxpool.Pool // nil: the pure guards never touch the pool noID = uuid.New() ) func TestCheckAgeKeyEnrolled(t *testing.T) { cases := []struct { name string entityType string attrs map[string]any wantErr bool }{ {"workstation with age key", "workstation", map[string]any{"age_pubkey": "age1abc"}, false}, {"workstation missing age key", "workstation", map[string]any{}, true}, {"server needs a key too", "server", map[string]any{}, true}, {"lxc is exempt", "lxc", map[string]any{}, false}, {"vm is exempt", "vm", map[string]any{}, false}, {"docker-container is exempt", "docker-container", map[string]any{}, false}, {"nil attrs on a workstation", "workstation", nil, true}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { err := checkAgeKeyEnrolled(noCtx, noPool, noID, c.entityType, c.attrs) if c.wantErr && !errors.Is(err, domain.ErrInvalidTransition) { t.Errorf("want ErrInvalidTransition, got %v", err) } if !c.wantErr && err != nil { t.Errorf("want nil, got %v", err) } }) } } func TestCheckMeshJoined(t *testing.T) { cases := []struct { name string entityType string attrs map[string]any wantErr bool }{ {"workstation with mesh_ip", "workstation", map[string]any{"mesh_ip": "10.0.0.5"}, false}, {"workstation missing mesh_ip", "workstation", map[string]any{}, true}, {"server missing mesh_ip", "server", map[string]any{}, true}, {"lxc is exempt", "lxc", map[string]any{}, false}, {"vm is exempt", "vm", map[string]any{}, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { err := checkMeshJoined(noCtx, noPool, noID, c.entityType, c.attrs) if c.wantErr && !errors.Is(err, domain.ErrInvalidTransition) { t.Errorf("want ErrInvalidTransition, got %v", err) } if !c.wantErr && err != nil { t.Errorf("want nil, got %v", err) } }) } } func TestCheckSecretsRevoked(t *testing.T) { // checkSecretsRevoked treats an ABSENT age_pubkey as "secrets revoked" // (the inverse of checkAgeKeyEnrolled). It is type-agnostic. cases := []struct { name string attrs map[string]any wantErr bool }{ {"age key gone → revoked", map[string]any{}, false}, {"age key still present → blocked", map[string]any{"age_pubkey": "age1abc"}, true}, {"nil attrs → revoked", nil, false}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { err := checkSecretsRevoked(noCtx, noPool, noID, "workstation", c.attrs) if c.wantErr && !errors.Is(err, domain.ErrInvalidTransition) { t.Errorf("want ErrInvalidTransition, got %v", err) } if !c.wantErr && err != nil { t.Errorf("want nil, got %v", err) } }) } }