mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
226 lines
9.3 KiB
Go
226 lines
9.3 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package services
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"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)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOrganizationPublicMembersReadLaterPages(t *testing.T) {
|
|
// The first server page holds only concealed members; a public one follows on page 2.
|
|
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.URL.Path == "/api/v1/orgs/acme/public_members" && r.URL.Query().Get("page") == "1":
|
|
var users []string
|
|
for i := range 50 {
|
|
users = append(users, fmt.Sprintf(`{"id":%d,"login":"hidden%d"}`, i+10, i))
|
|
}
|
|
w.Header().Set("Link", `<http://x/api/v1/orgs/acme/public_members?page=2>; rel="next"`)
|
|
_, _ = w.Write([]byte("[" + strings.Join(users, ",") + "]"))
|
|
case r.URL.Path == "/api/v1/orgs/acme/public_members":
|
|
_, _ = w.Write([]byte(`[{"id":1,"login":"alice"}]`))
|
|
case r.URL.Path == "/api/v1/orgs/acme/public_members/alice":
|
|
w.WriteHeader(http.StatusNoContent)
|
|
case strings.HasPrefix(r.URL.Path, "/api/v1/orgs/acme/public_members/hidden"):
|
|
w.WriteHeader(http.StatusNotFound)
|
|
default:
|
|
t.Errorf("unexpected request %s %s", r.Method, r.URL.Path)
|
|
}
|
|
}))
|
|
members, err := (Organizations{Client: client}).Members(context.Background(), "acme", MemberListOptions{Public: true, Visibility: true, Limit: 1, Page: 1})
|
|
require.NoError(t, err)
|
|
require.Equal(t, []OrganizationMember{{ID: 1, Username: "alice", Visibility: MembershipPublic}}, members.Items)
|
|
require.False(t, members.More)
|
|
}
|