// Copyright 2026 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package services import ( "context" "net/http" "testing" gitea "gitea.dev/sdk" "github.com/stretchr/testify/require" ) func TestTeamRepositoryPolicyPreservesUnitPermissions(t *testing.T) { mutations := 0 client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Equal(t, "/api/v1/teams/1", r.URL.Path) if r.Method == http.MethodGet { _, _ = w.Write([]byte(`{"id":1,"permission":"read","units_map":{"repo.code":"write","repo.issues":"read"}}`)) return } mutations++ body := requestJSON(t, r) require.Equal(t, "read", body["permission"]) require.Equal(t, false, body["includes_all_repositories"]) require.Equal(t, map[string]any{"repo.code": "write", "repo.issues": "read"}, body["units_map"]) require.NotContains(t, body, "name") require.NotContains(t, body, "description") _, _ = w.Write([]byte(`{"id":1,"permission":"read","organization":{"username":"acme"}}`)) })) service := Teams{Client: client} allRepos := false item, err := service.Edit(context.Background(), 1, TeamOptions{AllRepositories: &allRepos}) require.NoError(t, err) require.Equal(t, "acme", item.Organization) require.Equal(t, 1, mutations) } func TestTeamEditMergesUnitsUnlessReplaced(t *testing.T) { for _, test := range []struct { replace bool reads int want map[string]any }{ {false, 1, map[string]any{"repo.code": "write", "repo.issues": "read", "repo.wiki": "write"}}, {true, 0, map[string]any{"repo.wiki": "write"}}, } { reads := 0 client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { require.Equal(t, "/api/v1/teams/5", r.URL.Path) if r.Method == http.MethodGet { reads++ _, _ = w.Write([]byte(`{"id":5,"permission":"write","units_map":{"repo.code":"write","repo.issues":"read","repo.wiki":"none"}}`)) return } body := requestJSON(t, r) require.Equal(t, test.want, body["units_map"]) require.NotContains(t, body, "permission") _, _ = w.Write([]byte(`{"id":5}`)) })) _, err := (Teams{Client: client}).Edit(context.Background(), 5, TeamOptions{Units: map[string]string{"repo.wiki": "write"}, ReplaceUnits: test.replace}) require.NoError(t, err) require.Equal(t, test.reads, reads) } } func TestTeamCreateSendsPermissionAndUnits(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/acme/teams", r.URL.Path) body := requestJSON(t, r) require.Equal(t, "Developers", body["name"]) require.Equal(t, "write", body["permission"]) require.Equal(t, true, body["can_create_org_repo"]) require.Equal(t, false, body["includes_all_repositories"]) require.Equal(t, map[string]any{"repo.code": "write"}, body["units_map"]) w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte(`{"id":3,"name":"Developers","permission":"write","can_create_org_repo":true,"units_map":{"repo.code":"write"},"organization":{"name":"acme"}}`)) })) name, permission, createRepo, allRepos := "Developers", "write", true, false item, err := (Teams{Client: client}).Create(context.Background(), "acme", TeamOptions{ Name: &name, Permission: &permission, CanCreateRepo: &createRepo, AllRepositories: &allRepos, Units: map[string]string{"repo.code": "write"}, }) require.NoError(t, err) require.Equal(t, Team{ID: 3, Name: "Developers", Organization: "acme", Permission: "write", CanCreateRepo: true, Units: map[string]string{"repo.code": "write"}}, item) } func TestTeamAccessRequests(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.RequestURI()) switch { case r.URL.Path == "/api/v1/teams/3/members": _, _ = w.Write([]byte(`[{"id":2,"login":"bob"}]`)) case r.URL.Path == "/api/v1/teams/3/repos": _, _ = w.Write([]byte(`[{"id":4,"full_name":"acme/project","html_url":"https://gitea.test/acme/project"}]`)) default: w.WriteHeader(http.StatusNoContent) } })) service := Teams{Client: client} members, err := service.Members(context.Background(), 3, 5, 2) require.NoError(t, err) require.Equal(t, []OrganizationMember{{ID: 2, Username: "bob"}}, members.Items) repos, err := service.Repositories(context.Background(), 3, 5, 1) require.NoError(t, err) require.Equal(t, "https://gitea.test/acme/project", repos.Items[0].URL) require.NoError(t, service.SetMember(context.Background(), 3, "bob", false)) require.NoError(t, service.SetRepository(context.Background(), 3, "acme/project", true)) require.Equal(t, []string{ "GET /api/v1/teams/3/members?limit=5&page=2", "GET /api/v1/teams/3/repos?limit=5&page=1", "DELETE /api/v1/teams/3/members/bob", "PUT /api/v1/teams/3/repos/acme/project", }, requests) } func TestTeamFromKeepsAbsentValuesEmpty(t *testing.T) { item := teamFrom(decode[gitea.Team](t, `{"id":1,"name":"Owners","permission":"owner","organization":null,"units_map":null}`)) require.Equal(t, Team{ID: 1, Name: "Owners", Permission: "owner", Units: map[string]string{}}, item) } func TestTeamAccessSummarizesUnits(t *testing.T) { for _, tc := range []struct { permission string units map[string]string want string }{ {"owner", UnitPolicy("write"), "owner"}, {"admin", nil, "admin"}, {"none", UnitPolicy("write"), "write"}, {"none", UnitPolicy("read"), "read"}, {"none", map[string]string{"repo.code": "write", "repo.issues": "write", "repo.pulls": "write", "repo.wiki": "none", "repo.ext_wiki": "read"}, "write (code, issues, pulls)"}, {"none", map[string]string{"repo.code": "write", "repo.wiki": "read"}, "custom"}, {"none", map[string]string{"repo.ext_issues": "read"}, "none"}, } { require.Equal(t, tc.want, Team{Permission: tc.permission, Units: tc.units}.Access(), "%v", tc.units) } } func TestTeamFindSearchesTheOrganization(t *testing.T) { var queries []string client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/api/v1/orgs/nope/teams/search" { w.WriteHeader(http.StatusNotFound) return } require.Equal(t, "/api/v1/orgs/acme/teams/search", r.URL.Path) queries = append(queries, r.URL.Query().Get("q")) if r.URL.Query().Get("page") != "1" { _, _ = w.Write([]byte(`{"ok":true,"data":[]}`)) return } _, _ = w.Write([]byte(`{"ok":true,"data":[{"id":2,"name":"dev"},{"id":3,"name":"Dev"},{"id":4,"name":"devops"}]}`)) })) service := Teams{Client: client} team, err := service.Find(context.Background(), "acme", "devops") require.NoError(t, err) require.Equal(t, Team{ID: 4, Name: "devops", Organization: "acme", Units: map[string]string{}}, team) team, err = service.Find(context.Background(), "acme", "3") require.NoError(t, err) require.Equal(t, "Dev", team.Name) _, err = service.Find(context.Background(), "acme", "DEV") require.EqualError(t, err, `team "DEV" is ambiguous in acme (IDs 2, 3); pass an ID`) _, err = service.Find(context.Background(), "nope", "dev") require.EqualError(t, err, `organization "nope" not found`) require.Equal(t, []string{"devops", "devops", "", "", "DEV", "DEV"}, queries) }