Files
forge/cmd/admin_user_delete_access_token_test.go
zeekayandHanzo Dev acf5712fa7 Say "the forge" where services, routers and cmd said Gitea
Comments, log and error text, local identifiers and self-contained test
fixtures across services/, routers/ and cmd/ still named the upstream
product when they meant this one. 211 lines now read as the forge.

Three families stay, because the word is load-bearing rather than
decorative:

Upstream Gitea as a real thing. The migration downloader talks to Gitea
servers, so GiteaDownloader, structs.GiteaService and the "gitea" service
string parsed in cmd/dump_repo.go and services/convert/utils.go name the
product you migrate FROM. Links to go-gitea/gitea issues and PRs resolve
to pages that still explain the code they sit above. Recorded diffs,
mock HTTP responses and captured git errors are inputs, not prose.

Values something else matches. The distributed lock key
gitea_versioned_migration must agree across instances or a rolling
upgrade migrates twice. The rpm repository id [gitea-<owner>] is asserted
in tests/integration. Basic realm="Gitea" is how git-credential-manager
selects its builtin OAuth2 application — the adjacent else branch already
uses a different realm precisely to avoid that. The forbidden secret
prefix ^GIT(EA|HUB)_ keeps a workflow from shadowing a runner env var.
gitea-actions stays reserved so no account squats the old bot name.
IsGiteaActions, ParseGiteaActionsTargetURL and the notification setting
keys are declared under models/. X-GITEA-OTP is generated into
templates/swagger from a Go comment, so it moves in one commit or none.

Copyright headers. MIT requires the attribution be retained.

Two comments in services/actions/context.go named a gitea.* expression
namespace. modules/actions/runner declares the context as what act
evaluates github.* against, and nothing registers gitea.* anywhere, so
they now say github.event and github.event_name — de-branded and correct.

Two stray blank lines in routers/web/web.go and services/auth/iam.go were
already failing gofmt on main; they are gone so the touched-file check is
clean.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 19:56:08 -07:00

120 lines
4.5 KiB
Go

// Copyright 2026 Hanzo AI, Inc. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"strconv"
"testing"
auth_model "github.com/hanzoai/git/models/auth"
"github.com/hanzoai/git/models/db"
"github.com/hanzoai/git/models/unittest"
user_model "github.com/hanzoai/git/models/user"
"github.com/stretchr/testify/require"
)
func tokensOf(t *testing.T, username string) []*auth_model.AccessToken {
t.Helper()
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: username})
tokens, err := db.Find[auth_model.AccessToken](t.Context(), auth_model.ListAccessTokensOptions{UserID: u.ID})
require.NoError(t, err)
return tokens
}
func TestAdminUserDeleteAccessToken(t *testing.T) {
ctx := t.Context()
defer func() {
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.EmailAddress{}))
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{}))
}()
setup := func(t *testing.T, username, tokenName string) {
t.Helper()
require.NoError(t, microcmdUserCreate().Run(t.Context(),
[]string{"create", "--username", username, "--email", username + "@forge.local", "--random-password"}))
require.NoError(t, newUserGenerateAccessTokenCommand().Run(t.Context(),
[]string{"generate-access-token", "--username", username, "--token-name", tokenName, "--scopes", "read:user"}))
}
t.Run("list runs and the token is visible to the model", func(t *testing.T) {
setup(t, "tokenuser", "kept")
require.NoError(t, newUserListAccessTokensCommand().Run(ctx, []string{"list-access-tokens", "--username", "tokenuser"}))
require.Len(t, tokensOf(t, "tokenuser"), 1)
})
t.Run("delete by name", func(t *testing.T) {
require.NoError(t, newUserDeleteAccessTokenCommand().Run(ctx,
[]string{"delete-access-token", "--username", "tokenuser", "--token-name", "kept"}))
require.Empty(t, tokensOf(t, "tokenuser"))
})
t.Run("delete by id", func(t *testing.T) {
require.NoError(t, newUserGenerateAccessTokenCommand().Run(ctx,
[]string{"generate-access-token", "--username", "tokenuser", "--token-name", "byid", "--scopes", "read:user"}))
tokens := tokensOf(t, "tokenuser")
require.Len(t, tokens, 1)
require.NoError(t, newUserDeleteAccessTokenCommand().Run(ctx,
[]string{"delete-access-token", "--username", "tokenuser", "--id", strconv.FormatInt(tokens[0].ID, 10)}))
require.Empty(t, tokensOf(t, "tokenuser"))
})
// The reason this command exists instead of a hand-written DELETE: one
// table holds every user's tokens, so the id must be scoped by owner.
t.Run("an id belonging to another user is not deleted", func(t *testing.T) {
setup(t, "victim", "victimtoken")
victim := tokensOf(t, "victim")
require.Len(t, victim, 1)
err := newUserDeleteAccessTokenCommand().Run(ctx,
[]string{"delete-access-token", "--username", "tokenuser", "--id", strconv.FormatInt(victim[0].ID, 10)})
require.Error(t, err)
require.Contains(t, err.Error(), "access token not found")
require.Len(t, tokensOf(t, "victim"), 1)
})
}
func TestAdminUserDeleteAccessTokenFailure(t *testing.T) {
defer func() {
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.User{}))
require.NoError(t, db.TruncateBeans(t.Context(), &user_model.EmailAddress{}))
require.NoError(t, db.TruncateBeans(t.Context(), &auth_model.AccessToken{}))
}()
require.NoError(t, microcmdUserCreate().Run(t.Context(),
[]string{"create", "--username", "tokenuser", "--email", "[email protected]", "--random-password"}))
for _, tc := range []struct {
name, expectedErr string
args []string
}{
{
name: "no username",
args: []string{"delete-access-token", "--token-name", "x"},
expectedErr: "you must provide a username",
},
{
name: "neither name nor id",
args: []string{"delete-access-token", "--username", "tokenuser"},
expectedErr: "exactly one of --token-name or --id",
},
{
name: "both name and id",
args: []string{"delete-access-token", "--username", "tokenuser", "--token-name", "x", "--id", "1"},
expectedErr: "exactly one of --token-name or --id",
},
{
name: "unknown token name",
args: []string{"delete-access-token", "--username", "tokenuser", "--token-name", "nosuchtoken"},
expectedErr: `has no access token named "nosuchtoken"`,
},
} {
t.Run(tc.name, func(t *testing.T) {
err := newUserDeleteAccessTokenCommand().Run(t.Context(), tc.args)
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr)
})
}
}