package app import ( "context" "errors" "testing" "github.com/dtoro/oikos/internal/core/domain" "github.com/dtoro/oikos/internal/core/ports" "github.com/dtoro/oikos/internal/core/ports/portstest" "github.com/google/uuid" ) func TestApprovalDecideValidDecisions(t *testing.T) { for _, tc := range []struct { decision string want string }{ {"approve", "approved"}, {"deny", "denied"}, {"revoke", "revoked"}, } { t.Run(tc.decision, func(t *testing.T) { repo := portstest.NewApprovalRepo() repo.Result = ports.ApprovalDecideResult{ Approval: domain.Approval{EntityID: "some-id", Status: tc.want}, } svc := NewApprovalService(repo) aid := uuid.New() got, err := svc.Decide(context.Background(), ApprovalDecideCmd{ ApprovalID: domain.UUID(aid.String()), Decision: tc.decision, Actor: "operator", }) if err != nil { t.Fatalf("Decide(%s): %v", tc.decision, err) } if len(repo.Decided) != 1 { t.Fatalf("Decide called %d times, want 1", len(repo.Decided)) } in := repo.Decided[0] if in.Status != tc.want { t.Errorf("repo status = %q, want %q", in.Status, tc.want) } if in.Actor != "operator" || string(in.ApprovalID) != aid.String() { t.Errorf("repo input = %+v", in) } if got.Approval.Status != tc.want { t.Errorf("result approval status = %q, want %q", got.Approval.Status, tc.want) } }) } } func TestApprovalDecideInvalidDecision(t *testing.T) { repo := portstest.NewApprovalRepo() svc := NewApprovalService(repo) _, err := svc.Decide(context.Background(), ApprovalDecideCmd{ ApprovalID: domain.UUID(uuid.New().String()), Decision: "maybe", Actor: "operator", }) if !errors.Is(err, domain.ErrInvalidInput) { t.Fatalf("err = %v, want ErrInvalidInput", err) } if len(repo.Decided) != 0 { t.Error("repo must not be called on an invalid decision") } } func TestApprovalDecideRepoErrorPropagates(t *testing.T) { repo := portstest.NewApprovalRepo() repo.ErrStub = domain.ErrNotFound svc := NewApprovalService(repo) _, err := svc.Decide(context.Background(), ApprovalDecideCmd{ ApprovalID: domain.UUID(uuid.New().String()), Decision: "approve", }) if !errors.Is(err, domain.ErrNotFound) { t.Fatalf("err = %v, want ErrNotFound", err) } } func TestApprovalList(t *testing.T) { repo := portstest.NewApprovalRepo() repo.Pending = []domain.Approval{{EntityID: "a", Status: "pending"}} svc := NewApprovalService(repo) got, err := svc.List(context.Background(), "e1", 10) if err != nil { t.Fatalf("List: %v", err) } if len(got) != 1 || got[0].Status != "pending" { t.Fatalf("got %+v", got) } } func TestApprovalDecideTokenForwarded(t *testing.T) { repo := portstest.NewApprovalRepo() svc := NewApprovalService(repo) aid := uuid.New() _, err := svc.Decide(context.Background(), ApprovalDecideCmd{ ApprovalID: domain.UUID(aid.String()), Token: "hmac-token", Decision: "approve", Actor: "operator", }) if err != nil { t.Fatalf("Decide: %v", err) } if repo.Decided[0].Token != "hmac-token" { t.Errorf("token = %q, want forwarded", repo.Decided[0].Token) } }