26254fe51f
Co-authored-by: nova28 <17953305+nova28@users.noreply.github.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
104 lines
3.4 KiB
Go
104 lines
3.4 KiB
Go
package gitlab
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/kandev/kandev/internal/task/repository/repoerrors"
|
|
)
|
|
|
|
// denyOnly returns a workspace authorizer that allows every workspace except
|
|
// the named ones, which it denies with the same ErrWorkspaceNotFound the real
|
|
// task-service authorizer returns. Mirrors internal/jira/scope_test.go.
|
|
func denyOnly(denied ...string) func(context.Context, string) error {
|
|
blocked := make(map[string]bool, len(denied))
|
|
for _, ws := range denied {
|
|
blocked[ws] = true
|
|
}
|
|
return func(_ context.Context, ws string) error {
|
|
if blocked[ws] {
|
|
return repoerrors.ErrWorkspaceNotFound
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func newScopeWatchFixture(t *testing.T) (*Service, *Store) {
|
|
t.Helper()
|
|
store := newTestStore(t)
|
|
svc := NewService("", nil, AuthMethodNone, nil, newTestLogger(t))
|
|
svc.SetStore(store)
|
|
return svc, store
|
|
}
|
|
|
|
func seedIssueWatch(t *testing.T, store *Store, workspaceID string) *IssueWatch {
|
|
t.Helper()
|
|
w := &IssueWatch{
|
|
WorkspaceID: workspaceID, WorkflowID: "wf", WorkflowStepID: "step",
|
|
AgentProfileID: "agent", ExecutorProfileID: "exec", Enabled: true,
|
|
}
|
|
if err := store.CreateIssueWatch(context.Background(), w); err != nil {
|
|
t.Fatalf("seed issue watch: %v", err)
|
|
}
|
|
return w
|
|
}
|
|
|
|
// TestListAllIssueWatches_FiltersToAccessibleWorkspaces is the regression for
|
|
// the gap noted in AGENTS.md's "Per-user scoping" section: ListAllIssueWatches
|
|
// was a bare passthrough to the store with no workspace filter.
|
|
func TestListAllIssueWatches_FiltersToAccessibleWorkspaces(t *testing.T) {
|
|
svc, store := newScopeWatchFixture(t)
|
|
ctx := context.Background()
|
|
seedIssueWatch(t, store, "ws-allowed")
|
|
seedIssueWatch(t, store, "ws-allowed")
|
|
seedIssueWatch(t, store, "ws-denied")
|
|
svc.SetWorkspaceAuthorizer(denyOnly("ws-denied"))
|
|
|
|
watches, err := svc.ListAllIssueWatches(ctx)
|
|
if err != nil {
|
|
t.Fatalf("ListAllIssueWatches: %v", err)
|
|
}
|
|
if len(watches) != 2 {
|
|
t.Fatalf("watches = %+v, want both of ws-allowed's watches", watches)
|
|
}
|
|
for _, w := range watches {
|
|
if w.WorkspaceID != "ws-allowed" {
|
|
t.Fatalf("watches = %+v, want only ws-allowed's watches", watches)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestListAllIssueWatches_NilAuthorizerReturnsEveryWorkspace preserves the
|
|
// identity-less internal caller behaviour: poller.go's ListAllIssueWatches
|
|
// call must keep seeing every watch when no authorizer is wired.
|
|
func TestListAllIssueWatches_NilAuthorizerReturnsEveryWorkspace(t *testing.T) {
|
|
svc, store := newScopeWatchFixture(t)
|
|
ctx := context.Background()
|
|
seedIssueWatch(t, store, "ws-1")
|
|
seedIssueWatch(t, store, "ws-2")
|
|
|
|
watches, err := svc.ListAllIssueWatches(ctx)
|
|
if err != nil {
|
|
t.Fatalf("ListAllIssueWatches: %v", err)
|
|
}
|
|
if len(watches) != 2 {
|
|
t.Fatalf("watches = %+v, want 2 (nil authorizer must stay unscoped for internal callers like the poller)", watches)
|
|
}
|
|
}
|
|
|
|
// TestListAllIssueWatches_PropagatesAuthorizerError is the regression for a
|
|
// non-denial authorizer error (e.g. a transient DB failure): it must surface,
|
|
// not be treated as a silent per-watch denial that returns a truncated list.
|
|
func TestListAllIssueWatches_PropagatesAuthorizerError(t *testing.T) {
|
|
svc, store := newScopeWatchFixture(t)
|
|
ctx := context.Background()
|
|
seedIssueWatch(t, store, "ws-1")
|
|
boom := errors.New("authorizer backend down")
|
|
svc.SetWorkspaceAuthorizer(func(context.Context, string) error { return boom })
|
|
|
|
if _, err := svc.ListAllIssueWatches(ctx); !errors.Is(err, boom) {
|
|
t.Fatalf("expected the authorizer error to propagate, got %v", err)
|
|
}
|
|
}
|