mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-10 14:59:00 +00:00
auth-bundle-1 follow-on: close coverage gaps to clear Phase 12 floors
CI run #486 (post-Bundle-1 merge + Go 1.25.10 bump) failed three coverage-threshold gates: internal/api/handler 74.7% < floor 75 (-0.3pp) internal/auth 66.3% < floor 85 (-18.7pp) internal/service/auth 51.1% < floor 85 (-33.9pp) The Phase 12 gate file's "85% with negative-test coverage" claim turned out to be aspirational — the read-side and Update-path methods on RoleService / PermissionService / ActorRoleService had zero unit-test coverage, and internal/auth's keystore + HasPermission helper had zero tests. This commit closes the gap without lowering the gate. Per-package CI-style averages after this commit (per scripts/check-coverage-thresholds.sh's per-function-mean): internal/api/handler 76.1% (+1.4pp, margin +1.1pp) internal/auth 90.5% (+24.2pp, margin +5.5pp) internal/service/auth 93.7% (+42.6pp, margin +8.7pp) Tests added: internal/service/auth/service_test.go (+18 tests, +518 LOC): PermissionService.List, PermissionService.GetByName, RoleService.Get (4 paths), RoleService.List (system caller), RoleService.Update (4 paths), RoleService.ListPermissions (3 paths), RoleService.AddPermission/RemovePermission round-trip + gate paths, RoleService.Delete (success + nil-caller + no-perm + audit), RoleService.Create (nil-caller), ActorRoleService.ListForActor (self-bypass + cross-actor + nil-caller + system + with-perm), ActorRoleService.Effective- Permissions (same shape), ActorRoleService.ListKeys (3 paths + system bypass), ActorRoleService.Revoke (4 paths), Authorizer edge cases (empty actorID short-circuit, empty tenantID default, scoped-grant-without-scope-id no-match invariant, repo-error wrap-and-return, HoldsAnyOf early-exit), recordAudit nil-arm short-circuits. internal/auth/keystore_test.go (NEW, +175 LOC): StaticKeyStore.Len, StaticKeyStore.LookupByHash hit + miss, MutableKeyStore seeded lookup + Len, Add registers new key, AddHashed registers from precomputed hash, AddHashed replaces on duplicate hash (idempotent boot-loader contract), HasPermission no-actor / default-actor-type / checker-error / scoped-check threading. internal/auth/bootstrap/service_test.go (+36 LOC): Service.Available nil-receiver/nil-strategy short-circuit, Service.Available delegates to Strategy when configured. internal/api/handler/auth_test.go (+208 LOC): GetRole returns role + permissions, GetRole 404 + 401, UpdateRole 200 + invalid-JSON-400 + 401, ListKeys returns actor list + 401, RemoveRolePermission 204 (global + scoped) + 401, rolePermToResponse scope encoding pin via GetRole. Verified: gofmt -l . clean (touched files only). go vet ./internal/auth/... ./internal/service/auth/... ./internal/api/handler/ rc=0. go test -count=1 -short on the four packages green. CI-style per-function averages computed via the live scripts/check-coverage-thresholds.sh arithmetic — all three gated packages clear their floors with margin. Per CLAUDE.md "complete path" + "do not lower the gate to make CI green": gate file unchanged. The 85/85/75 floors stand.
This commit is contained in:
@@ -53,6 +53,42 @@ type fakeKeyStore struct {
|
||||
added []addedEntry
|
||||
}
|
||||
|
||||
// TestService_Available_NilServiceOrStrategyReturnsErrDisabled pins the
|
||||
// no-strategy short-circuit on the Available probe. The HTTP handler
|
||||
// uses Available to decide between 410 Gone (consumed/disabled) and
|
||||
// proceeding to read the request body. A nil service or nil strategy
|
||||
// means the bootstrap path was not configured at all; both arms return
|
||||
// ErrDisabled so the operator-facing surface is identical.
|
||||
func TestService_Available_NilServiceOrStrategyReturnsErrDisabled(t *testing.T) {
|
||||
var svc *Service // nil receiver
|
||||
if ok, err := svc.Available(context.Background()); ok || !errors.Is(err, ErrDisabled) {
|
||||
t.Errorf("nil service: Available = (%v, %v); want (false, ErrDisabled)", ok, err)
|
||||
}
|
||||
// non-nil service but nil strategy
|
||||
svc = &Service{}
|
||||
if ok, err := svc.Available(context.Background()); ok || !errors.Is(err, ErrDisabled) {
|
||||
t.Errorf("nil strategy: Available = (%v, %v); want (false, ErrDisabled)", ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestService_Available_DelegatesToStrategy pins the happy-path
|
||||
// delegation: when an admin already exists, the strategy probe returns
|
||||
// `exists=true` and Available reports false (one-shot path closes once
|
||||
// any admin lands).
|
||||
func TestService_Available_DelegatesToStrategy(t *testing.T) {
|
||||
strategy := NewEnvTokenStrategy("the-token", func(_ context.Context) (bool, error) {
|
||||
return true, nil // admin exists → bootstrap path closed
|
||||
})
|
||||
svc := NewService(strategy, &fakeMinter{}, &fakeGranter{}, &fakeAudit{}, &fakeKeyStore{}, sha)
|
||||
ok, err := svc.Available(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("Available err: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Errorf("admin-exists probe should yield Available=false; got true")
|
||||
}
|
||||
}
|
||||
|
||||
type addedEntry struct {
|
||||
name string
|
||||
hash string
|
||||
|
||||
Reference in New Issue
Block a user