Files
gitea-tea/internal/services/organizations_test.go
T

197 lines
7.9 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package services
import (
"context"
"errors"
"net/http"
"testing"
"gitea.dev/tea/internal/transport"
"github.com/stretchr/testify/require"
)
func TestOrganizationEditsPreserveOmittedFields(t *testing.T) {
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPatch, r.Method)
require.Equal(t, "/api/v1/orgs/team", r.URL.Path)
body := requestJSON(t, r)
require.Len(t, body, 2)
require.Equal(t, "", body["description"])
require.Equal(t, false, body["repo_admin_change_team_access"])
_, _ = w.Write([]byte(`{"id":1,"username":"team","visibility":"private"}`))
}))
description := ""
access := false
service := Organizations{Client: client}
item, err := service.Edit(context.Background(), "team", OrganizationOptions{Description: &description, RepoAdminChangeTeamAccess: &access})
require.NoError(t, err)
require.Equal(t, "team", item.Name)
require.Equal(t, "private", item.Visibility)
}
func TestOrganizationLookupTreatsMissingOrganizationAsUser(t *testing.T) {
for name, tc := range map[string]struct {
status int
body string
wantID int64
wantOrg bool
wantStatusErr int
}{
"organization": {status: http.StatusOK, body: `{"id":9,"name":"acme"}`, wantID: 9, wantOrg: true},
"user namespace": {status: http.StatusNotFound, body: `{"message":"not found"}`},
"server failure": {status: http.StatusInternalServerError, body: `{}`, wantStatusErr: http.StatusInternalServerError},
} {
t.Run(name, func(t *testing.T) {
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/api/v1/orgs/acme", r.URL.Path)
w.WriteHeader(tc.status)
_, _ = w.Write([]byte(tc.body))
}))
id, isOrg, err := organization(context.Background(), client, "acme")
if tc.wantStatusErr != 0 {
var failure *transport.APIError
require.True(t, errors.As(err, &failure))
require.Equal(t, tc.wantStatusErr, failure.Status)
return
}
require.NoError(t, err)
require.Equal(t, tc.wantID, id)
require.Equal(t, tc.wantOrg, isOrg)
})
}
}
func TestOrganizationListSelectsScopeAndPage(t *testing.T) {
for name, tc := range map[string]struct {
mine bool
username string
path string
}{
"public": {path: "/api/v1/orgs"},
"mine": {mine: true, path: "/api/v1/user/orgs"},
"user": {username: "alice", path: "/api/v1/users/alice/orgs"},
} {
t.Run(name, func(t *testing.T) {
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, tc.path, r.URL.Path)
require.Equal(t, "2", r.URL.Query().Get("limit"))
require.Equal(t, "3", r.URL.Query().Get("page"))
w.Header().Set("X-Total-Count", "7")
_, _ = w.Write([]byte(`[{"id":1,"username":"legacy"},{"id":2,"name":"acme"}]`))
}))
items, err := (Organizations{Client: client}).List(context.Background(), tc.mine, tc.username, 2, 3)
require.NoError(t, err)
require.True(t, items.More)
require.Equal(t, 7, items.Total)
require.Equal(t, []string{"legacy", "acme"}, []string{items.Items[0].Name, items.Items[1].Name})
})
}
}
func TestOrganizationCreateSendsSuppliedPreferences(t *testing.T) {
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, http.MethodPost, r.Method)
require.Equal(t, "/api/v1/orgs", r.URL.Path)
body := requestJSON(t, r)
require.Equal(t, "acme", body["username"])
require.Equal(t, "limited", body["visibility"])
require.Equal(t, true, body["repo_admin_change_team_access"])
w.WriteHeader(http.StatusCreated)
_, _ = w.Write([]byte(`{"id":5,"name":"acme","visibility":"limited","repo_admin_change_team_access":true}`))
}))
name, visibility, access := "acme", "limited", true
item, err := (Organizations{Client: client}).Create(context.Background(), OrganizationOptions{Name: &name, Visibility: &visibility, RepoAdminChangeTeamAccess: &access})
require.NoError(t, err)
require.Equal(t, Organization{ID: 5, Name: "acme", Visibility: "limited", RepoAdminChangeTeamAccess: true}, item)
}
func TestOrganizationMembershipChecksReportWhatGiteaRefuses(t *testing.T) {
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v1/orgs/acme/members/bob":
http.Redirect(w, r, "/org/acme/public_members", http.StatusSeeOther)
case "/api/v1/orgs/acme/members/bbo":
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"message":"user does not exist [uid: 0, name: bbo]"}`))
default:
w.WriteHeader(http.StatusNotFound)
}
}))
service := Organizations{Client: client}
_, err := service.IsMember(context.Background(), "acme", "bob")
require.True(t, IsForbidden(err), "%v", err)
_, err = service.IsMember(context.Background(), "acme", "bbo")
require.EqualError(t, err, `user "bbo" not found`)
member, err := service.IsMember(context.Background(), "acme", "carol")
require.NoError(t, err)
require.False(t, member)
}
func TestOrganizationCreateNamesTheTakenName(t *testing.T) {
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnprocessableEntity)
_, _ = w.Write([]byte(`{"message":"user already exists [name: bob]"}`))
}))
name := "bob"
_, err := (Organizations{Client: client}).Create(context.Background(), OrganizationOptions{Name: &name})
require.EqualError(t, err, `name "bob" already exists`)
}
func TestOrganizationMembershipRequests(t *testing.T) {
var requests []string
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requests = append(requests, r.Method+" "+r.URL.Path)
w.WriteHeader(http.StatusNoContent)
}))
service := Organizations{Client: client}
member, err := service.IsMember(context.Background(), "acme", "bob")
require.NoError(t, err)
require.True(t, member)
require.NoError(t, service.SetMemberPublic(context.Background(), "acme", "bob", true))
require.NoError(t, service.RemoveMember(context.Background(), "acme", "bob"))
require.Equal(t, []string{
"GET /api/v1/orgs/acme/members/bob", "PUT /api/v1/orgs/acme/public_members/bob", "DELETE /api/v1/orgs/acme/members/bob",
}, requests)
}
func TestOrganizationMembersCheckEachMembership(t *testing.T) {
// Gitea lists concealed members among the public ones to administrators; only the check of
// each membership tells them apart.
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v1/orgs/acme/members", "/api/v1/orgs/acme/public_members":
w.Header().Set("X-Total-Count", "2")
_, _ = w.Write([]byte(`[{"id":1,"login":"alice"},{"id":2,"login":"bob","full_name":"Bob"}]`))
case "/api/v1/orgs/acme/public_members/alice":
w.WriteHeader(http.StatusNoContent)
case "/api/v1/orgs/acme/public_members/bob":
w.WriteHeader(http.StatusNotFound)
default:
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
}
}))
alice, bob := OrganizationMember{ID: 1, Username: "alice"}, OrganizationMember{ID: 2, Username: "bob", FullName: "Bob"}
public, concealed := alice, bob
public.Visibility, concealed.Visibility = MembershipPublic, MembershipConcealed
for name, tc := range map[string]struct {
opts MemberListOptions
want []OrganizationMember
counted bool
}{
"names": {MemberListOptions{}, []OrganizationMember{alice, bob}, true},
"visibility": {MemberListOptions{Visibility: true}, []OrganizationMember{public, concealed}, true},
"public": {MemberListOptions{Public: true}, []OrganizationMember{public}, false},
} {
t.Run(name, func(t *testing.T) {
tc.opts.Limit, tc.opts.Page = 10, 1
members, err := (Organizations{Client: client}).Members(context.Background(), "acme", tc.opts)
require.NoError(t, err)
require.Equal(t, tc.want, members.Items)
require.Equal(t, tc.counted, members.Counted)
})
}
}