mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
org member remove and public/private exited 0 without a word for users who are not members, and member list --public showed concealed members, because Gitea lists them as public to administrators. Membership changes now check the membership first: removing a non-member warns, publicizing one fails, and a change to the current visibility warns. Member list checks each membership and gains a VISIBILITY column. org list and member list get a header and context line through writeTable, org view shows the description, website, location and member, team and repository counts, mutations print ✓ lines, and org delete asks to type the organization's name after refusing organizations that still own repositories. public and private become publicize and conceal, the old names stay as aliases, and org edit --visibility no longer shows the create default. Every command has examples, and organizations and members complete. Signed-off-by: bircni <bircni@icloud.com>
98 lines
4.3 KiB
Go
98 lines
4.3 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"gitea.dev/tea/internal/clierr"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestOrganizationMemberChangesCheckMembershipFirst(t *testing.T) {
|
|
var changes []string
|
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method + " " + r.URL.Path {
|
|
case "GET /api/v1/orgs/acme/members/bob":
|
|
w.WriteHeader(http.StatusNotFound)
|
|
case "GET /api/v1/orgs/acme/members/carol", "GET /api/v1/orgs/acme/public_members/carol":
|
|
w.WriteHeader(http.StatusNoContent)
|
|
default:
|
|
changes = append(changes, r.Method+" "+r.URL.Path)
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
deps, _, diagnostics := serverDeps(t, server.URL)
|
|
deps.Streams.OutTTY = true
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"org", "member", "remove", "acme", "bob", "--yes"}))
|
|
require.Equal(t, "! bob is not a member of acme\n", withoutANSI(diagnostics.String()))
|
|
err := Execute(context.Background(), deps, []string{"org", "member", "publicize", "acme", "bob"})
|
|
require.EqualError(t, err, "bob is not a member of acme")
|
|
diagnostics.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"org", "member", "public", "acme", "carol"}))
|
|
require.Equal(t, "! carol's membership of acme is already public\n", withoutANSI(diagnostics.String()))
|
|
require.Empty(t, changes)
|
|
}
|
|
|
|
func TestOrganizationDeleteAsksToTypeTheName(t *testing.T) {
|
|
var deleted []string
|
|
repos := "0"
|
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method + " " + r.URL.Path {
|
|
case "GET /api/v1/orgs/acme":
|
|
_, _ = w.Write([]byte(`{"id":4,"name":"acme"}`))
|
|
case "GET /api/v1/orgs/acme/repos":
|
|
w.Header().Set("X-Total-Count", repos)
|
|
_, _ = w.Write([]byte(`[]`))
|
|
case "GET /api/v1/orgs/acme/members", "GET /api/v1/orgs/acme/teams":
|
|
_, _ = w.Write([]byte(`[]`))
|
|
case "DELETE /api/v1/orgs/acme":
|
|
deleted = append(deleted, r.URL.Path)
|
|
w.WriteHeader(http.StatusNoContent)
|
|
default:
|
|
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
deps, _, _ := serverDeps(t, server.URL)
|
|
deps.Streams.InTTY, deps.Streams.ErrTTY = true, true
|
|
const prompt = "Delete organization acme and its teams? This cannot be undone. Type acme to confirm:"
|
|
prompter := &scriptedPrompter{inputs: map[string]string{prompt: "yes"}, accept: true}
|
|
deps.Prompter = prompter
|
|
err := Execute(context.Background(), deps, []string{"org", "delete", "acme"})
|
|
require.Equal(t, clierr.Canceled, clierr.ExitCode(err), "%v", err)
|
|
require.Equal(t, []string{prompt}, prompter.asked)
|
|
require.Empty(t, prompter.confirmations)
|
|
prompter.inputs[prompt] = "acme"
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"org", "delete", "acme"}))
|
|
require.Equal(t, []string{"/api/v1/orgs/acme"}, deleted)
|
|
|
|
repos = "2"
|
|
err = Execute(context.Background(), deps, []string{"org", "delete", "acme", "--yes"})
|
|
require.EqualError(t, err, "organization acme still owns repositories; transfer or delete them first (see tea repo list acme)")
|
|
require.Len(t, deleted, 1)
|
|
}
|
|
|
|
func TestOrganizationViewShowsDetailsAndCounts(t *testing.T) {
|
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/api/v1/orgs/acme":
|
|
_, _ = w.Write([]byte(`{"id":4,"name":"acme","full_name":"ACME Corp","description":"Tools","website":"https://acme.example","visibility":"public"}`))
|
|
case "/api/v1/orgs/acme/members", "/api/v1/orgs/acme/teams", "/api/v1/orgs/acme/repos":
|
|
w.Header().Set("X-Total-Count", map[string]string{"members": "2", "teams": "3", "repos": "1"}[r.URL.Path[len("/api/v1/orgs/acme/"):]])
|
|
_, _ = w.Write([]byte(`[]`))
|
|
default:
|
|
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
deps, out, _ := serverDeps(t, server.URL)
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"org", "view", "acme"}))
|
|
require.Equal(t, "title:\tACME Corp\nname:\tacme\nvisibility:\tpublic\nwebsite:\thttps://acme.example\nlocation:\t\n"+
|
|
"repo admins manage team access:\tno\nmembers:\t2\nteams:\t3\nrepositories:\t1\nurl:\t"+server.URL+"/acme\n--\nTools\n", out.String())
|
|
}
|