Files
mcr/internal/grpcserver/registry_test.go
Kyle Isom 185b68ff6d Phase 10: gRPC admin API with interceptor chain
Proto definitions for 4 services (RegistryService, PolicyService,
AuditService, AdminService) with hand-written Go stubs using JSON
codec until protobuf tooling is available.

Interceptor chain: logging (method, peer IP, duration, never logs
auth metadata) → auth (bearer token via MCIAS, Health bypasses) →
admin (role check for GC, policy, delete, audit RPCs).

All RPCs share business logic with REST handlers via internal/db
and internal/gc packages. TLS 1.3 minimum on gRPC listener.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 20:46:21 -07:00

145 lines
3.6 KiB
Go

package grpcserver
import (
"context"
"testing"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
pb "git.wntrmute.dev/kyle/mcr/gen/mcr/v1"
"git.wntrmute.dev/kyle/mcr/internal/auth"
)
func adminDeps(t *testing.T) Deps {
t.Helper()
return Deps{
DB: openTestDB(t),
Validator: &fakeValidator{
claims: &auth.Claims{Subject: "admin-uuid", AccountType: "human", Roles: []string{"admin"}},
},
}
}
func adminCtx() context.Context {
return withAuth(context.Background(), "admin-token")
}
func TestListRepositoriesEmpty(t *testing.T) {
deps := adminDeps(t)
cc := startTestServer(t, deps)
client := pb.NewRegistryServiceClient(cc)
resp, err := client.ListRepositories(adminCtx(), &pb.ListRepositoriesRequest{})
if err != nil {
t.Fatalf("ListRepositories: %v", err)
}
if len(resp.GetRepositories()) != 0 {
t.Fatalf("expected 0 repos, got %d", len(resp.Repositories))
}
}
func TestGetRepositoryNotFound(t *testing.T) {
deps := adminDeps(t)
cc := startTestServer(t, deps)
client := pb.NewRegistryServiceClient(cc)
_, err := client.GetRepository(adminCtx(), &pb.GetRepositoryRequest{Name: "nonexistent"})
if err == nil {
t.Fatal("expected error for nonexistent repo")
}
st, ok := status.FromError(err)
if !ok {
t.Fatalf("expected gRPC status, got %v", err)
}
if st.Code() != codes.NotFound {
t.Fatalf("code: got %v, want NotFound", st.Code())
}
}
func TestGetRepositoryEmptyName(t *testing.T) {
deps := adminDeps(t)
cc := startTestServer(t, deps)
client := pb.NewRegistryServiceClient(cc)
_, err := client.GetRepository(adminCtx(), &pb.GetRepositoryRequest{})
if err == nil {
t.Fatal("expected error for empty name")
}
st, ok := status.FromError(err)
if !ok {
t.Fatalf("expected gRPC status, got %v", err)
}
if st.Code() != codes.InvalidArgument {
t.Fatalf("code: got %v, want InvalidArgument", st.Code())
}
}
func TestDeleteRepositoryNotFound(t *testing.T) {
deps := adminDeps(t)
cc := startTestServer(t, deps)
client := pb.NewRegistryServiceClient(cc)
_, err := client.DeleteRepository(adminCtx(), &pb.DeleteRepositoryRequest{Name: "nonexistent"})
if err == nil {
t.Fatal("expected error for nonexistent repo")
}
st, ok := status.FromError(err)
if !ok {
t.Fatalf("expected gRPC status, got %v", err)
}
if st.Code() != codes.NotFound {
t.Fatalf("code: got %v, want NotFound", st.Code())
}
}
func TestDeleteRepositoryEmptyName(t *testing.T) {
deps := adminDeps(t)
cc := startTestServer(t, deps)
client := pb.NewRegistryServiceClient(cc)
_, err := client.DeleteRepository(adminCtx(), &pb.DeleteRepositoryRequest{})
if err == nil {
t.Fatal("expected error for empty name")
}
st, ok := status.FromError(err)
if !ok {
t.Fatalf("expected gRPC status, got %v", err)
}
if st.Code() != codes.InvalidArgument {
t.Fatalf("code: got %v, want InvalidArgument", st.Code())
}
}
func TestGCStatusInitial(t *testing.T) {
deps := adminDeps(t)
cc := startTestServer(t, deps)
client := pb.NewRegistryServiceClient(cc)
resp, err := client.GetGCStatus(adminCtx(), &pb.GetGCStatusRequest{})
if err != nil {
t.Fatalf("GetGCStatus: %v", err)
}
if resp.Running {
t.Fatal("expected running=false on startup")
}
if resp.LastRun != nil {
t.Fatal("expected no last_run on startup")
}
}
func TestGarbageCollectTrigger(t *testing.T) {
deps := adminDeps(t)
cc := startTestServer(t, deps)
client := pb.NewRegistryServiceClient(cc)
// Trigger GC without a collector (no-op but should return an ID).
resp, err := client.GarbageCollect(adminCtx(), &pb.GarbageCollectRequest{})
if err != nil {
t.Fatalf("GarbageCollect: %v", err)
}
if resp.GetId() == "" {
t.Fatal("expected non-empty GC ID")
}
}