refactor(grant): share revoke routing; test granteeName + grant rows · Entire

refactor(grant): share revoke routing; test granteeName + grant rows

374ad1d·

toothbrush·2w ago·2 files·+106 added/-29 removed

Extract the ULID-vs-handle revoke routing and message formatting shared by revokeProjectGrantee/revokeRepoGrantee into revokeGrantee. Add tests for granteeName's name/ULID fallback and the project/repo grant row builders.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

2a9100f0081fView transcript

Changes

2

300 unmodified lines

}
// revokeProjectGrantee revokes a grantee (provider:handle or account ULID) from
// a resolved project. A ULID grantee takes the typed-id route directly; a
// handle is resolved to its provider account first and takes the by-provider
// route. projectRef is the user's original (pre-resolution) project ref, used
// only for the success message.
func revokeProjectGrantee(ctx context.Context, cmd *cobra.Command, c *coreapi.Client, projID, projectRef, grantee string) error {
    if looksLikeULID(grantee) {
        return revokeGrant(cmd, "Revoked", fmt.Sprintf("account %s from project %s", grantee, projectRef), func() error {
            return revokeGrantee(ctx, cmd, c, "project", projectRef, grantee,
                func() error {
                    return c.RevokeProjectAccess(ctx, coreapi.RevokeProjectAccessParams{
                        ProjectId:   projID,
                        GranteeType: "account",
                        GranteeId:   grantee,
                    })
                },
                func(provider, providerUserID string) error {
                    return c.RevokeProjectAccessByProvider(ctx, coreapi.RevokeProjectAccessByProviderParams{
                        ProjectId:      projID,
                        Provider:       provider,
                        ProviderUserId: providerUserID,
                    })
                })
    }
}

// revokeGrantee performs the shared grantee-revocation routing for projects and
// repos: a ULID grantee takes the typed-id route (revokeByID); a provider:handle
// is resolved to its provider account first and takes the by-provider route
// (revokeByProvider). target ("project"/"repo") and ref name the grant in the
// success message.
func revokeGrantee(
    ctx context.Context,
    cmd *cobra.Command,
    c *coreapi.Client,
    target, ref, grantee string,
    revokeByID func() error,
    revokeByProvider func(provider, providerUserID string) error,
) error {
    if looksLikeULID(grantee) {
        return revokeGrant(cmd, "Revoked", fmt.Sprintf("account %s from %s %s", grantee, target, ref), revokeByID)
    }
    provider, providerUserID, err := resolveGranteeProvider(ctx, c, grantee)
    if err != nil {
        return err
    }
    return revokeGrant(cmd, "Revoked", fmt.Sprintf("%s from project %s", grantee, projectRef), func() error {
        return c.RevokeProjectAccessByProvider(ctx, coreapi.RevokeProjectAccessByProviderParams{
            ProjectId:      projID,
            Provider:       provider,
            ProviderUserId: providerUserID,
        })
    })
}

// revokeRepoGrantee mirrors revokeProjectGrantee for repos: a ULID grantee
// takes the typed-id revoke route, a provider:handle is resolved first and takes
// the by-provider route. repoRef is the user's original repo ref, for messaging.
func revokeRepoGrantee(ctx context.Context, cmd *cobra.Command, c *coreapi.Client, repoID, repoRef, grantee string) error {
    if looksLikeULID(grantee) {
        return revokeGrant(cmd, "Revoked", fmt.Sprintf("account %s from repo %s", grantee, repoRef), func() error {
            return revokeGrantee(ctx, cmd, c, "repo", repoRef, grantee,
                func() error {
                    return c.RevokeRepoAccess(ctx, coreapi.RevokeRepoAccessParams{
                        RepoId:      repoID,
                        GranteeType: "account",
                        GranteeId:   grantee,
                    })
                },
                func(provider, providerUserID string) error {
                    return c.RevokeRepoAccessByProvider(ctx, coreapi.RevokeRepoAccessByProviderParams{
                        RepoId:         repoID,
                        Provider:       provider,
                        ProviderUserId: providerUserID,
                    })
                })
        }
    }
}

func TestGranteeName(t *testing.T) {
    t.Parallel()
    const ulid = "01HZX0000000000000000000AB"
    tests := []struct {
        name string
        in   coreapi.OptString
        id   string
        want string
    }{
        {name: "friendly name wins", in: coreapi.NewOptString("github:alice"), id: ulid, want: "github:alice"},
        {name: "unset falls back to ULID", in: coreapi.OptString{}, id: ulid, want: ulid},
        {name: "empty string falls back to ULID", in: coreapi.NewOptString(""), id: ulid, want: ulid},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel()
            if got := granteeName(tt.in, tt.id); got != tt.want {
                t.Errorf("granteeName(%v, %q) = %q, want %q", tt.in, tt.id, got, tt.want)
            }
        })
    }
}

func TestGrantRows(t *testing.T) {
    t.Parallel()
    const ulid = "01HZX0000000000000000000AB"

// grantColumns and the row builders must stay in lockstep — same width,
    // same column order — or the table header and cells misalign.
    if got, want := len(grantColumns), 5; got != want {
        t.Fatalf("grantColumns has %d columns, want %d", got, want)
    }

t.Run("project resolved name", func(t *testing.T) {
        t.Parallel()
        row := projectGrantRow(coreapi.ProjectGrant{
            GranteeId:   ulid,
            GranteeName: coreapi.NewOptString("github:alice"),
            GranteeType: "account",
            Role:        "writer",
            Source:      "direct",
        })
        want := []string{"account", "github:alice", ulid, "writer", "direct"}
        if !slices.Equal(row, want) {
            t.Errorf("projectGrantRow = %v, want %v", row, want)
        }
    })

t.Run("repo unresolved name falls back to ULID", func(t *testing.T) {
        t.Parallel()
        row := repoGrantRow(coreapi.RepoGrant{
            GranteeId:   ulid,
            GranteeName: coreapi.OptString{},
            GranteeType: "team",
            Role:        "reader",
            Source:      "inherited",
        })
        want := []string{"team", ulid, ulid, "reader", "inherited"}
        if !slices.Equal(row, want) {
            t.Errorf("repoGrantRow = %v, want %v", row, want)
        }
    })
}

func TestParseOrgRole(t *testing.T) {
    t.Parallel()