mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
137 lines
5.0 KiB
Go
137 lines
5.0 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package services
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestPullCreateChoicesFetchRepositoryOnce(t *testing.T) {
|
|
for _, push := range []bool{false, true} {
|
|
t.Run(fmt.Sprintf("push=%t", push), func(t *testing.T) {
|
|
var mu sync.Mutex
|
|
calls := map[string]int{}
|
|
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
mu.Lock()
|
|
calls[r.URL.Path]++
|
|
mu.Unlock()
|
|
switch {
|
|
case r.URL.Path == "/api/v1/orgs/alice":
|
|
w.WriteHeader(http.StatusNotFound)
|
|
case r.URL.Path == "/api/v1/repos/alice/project":
|
|
_, _ = fmt.Fprintf(w, `{"full_name":"alice/project","default_branch":"trunk","permissions":{"push":%t}}`, push)
|
|
case strings.HasPrefix(r.URL.Path, "/api/v1/repos/alice/project/raw/"):
|
|
assert.Equal(t, "trunk", r.URL.Query().Get("ref"))
|
|
if r.URL.Path != "/api/v1/repos/alice/project/raw/.gitea/PULL_REQUEST_TEMPLATE.md" {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte("## Checklist\n"))
|
|
case r.URL.Path == "/api/v1/repos/alice/project/assignees":
|
|
_, _ = w.Write([]byte(`[{"login":"alice"}]`))
|
|
case r.URL.Path == "/api/v1/repos/alice/project/milestones" && r.URL.Query().Get("page") == "1":
|
|
_, _ = w.Write([]byte(`[{"id":1,"title":"v1"}]`))
|
|
case r.URL.Path == "/api/v1/repos/alice/project/labels" && r.URL.Query().Get("page") == "1":
|
|
_, _ = w.Write([]byte(`[{"id":2,"name":"bug"}]`))
|
|
case r.URL.Path == "/api/v1/repos/alice/project/milestones", r.URL.Path == "/api/v1/repos/alice/project/labels":
|
|
_, _ = w.Write([]byte(`[]`))
|
|
default:
|
|
t.Errorf("unexpected path %s", r.URL.Path)
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
choices, err := (Pulls{Client: client, Repository: "alice/project"}).CreateChoices(context.Background())
|
|
require.NoError(t, err)
|
|
require.Equal(t, "alice/project", choices.Repository.FullName)
|
|
require.Equal(t, "trunk", choices.Repository.DefaultBranch)
|
|
require.Equal(t, "## Checklist\n", choices.Template)
|
|
want := CreateSelectables{Assignees: []string{}, Milestones: []Milestone{}, Labels: []Label{}}
|
|
if push {
|
|
want = CreateSelectables{
|
|
CanPush: true,
|
|
Assignees: []string{"alice"},
|
|
Milestones: []Milestone{{ID: 1, Title: "v1"}},
|
|
Labels: []Label{{ID: 2, Name: "bug"}},
|
|
}
|
|
}
|
|
require.Equal(t, want, choices.CreateSelectables)
|
|
require.Equal(t, 1, calls["/api/v1/repos/alice/project"])
|
|
if !push {
|
|
require.Zero(t, calls["/api/v1/repos/alice/project/assignees"])
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPullCreateChoicesLoadConcurrently(t *testing.T) {
|
|
// The template, assignees, milestones, and labels requests only complete once all four are in flight.
|
|
paths := []string{"raw/PULL_REQUEST_TEMPLATE.md", "assignees", "milestones", "labels"}
|
|
arrived := map[string]chan struct{}{}
|
|
for _, path := range paths {
|
|
arrived[path] = make(chan struct{})
|
|
}
|
|
var once sync.Map
|
|
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
path := strings.TrimPrefix(r.URL.Path, "/api/v1/repos/alice/project/")
|
|
if r.URL.Path == "/api/v1/orgs/alice" {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
if r.URL.Path == "/api/v1/repos/alice/project" {
|
|
_, _ = w.Write([]byte(`{"full_name":"alice/project","default_branch":"main","permissions":{"push":true}}`))
|
|
return
|
|
}
|
|
if ch, ok := arrived[path]; ok {
|
|
if _, loaded := once.LoadOrStore(path, true); !loaded {
|
|
close(ch)
|
|
}
|
|
for _, other := range paths {
|
|
select {
|
|
case <-arrived[other]:
|
|
case <-time.After(5 * time.Second):
|
|
t.Errorf("%s did not run concurrently with %s", other, path)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
if strings.HasPrefix(path, "raw/") {
|
|
_, _ = w.Write([]byte("template"))
|
|
return
|
|
}
|
|
_, _ = w.Write([]byte(`[]`))
|
|
}))
|
|
choices, err := (Pulls{Client: client, Repository: "alice/project"}).CreateChoices(context.Background())
|
|
require.NoError(t, err)
|
|
require.Equal(t, "template", choices.Template)
|
|
}
|
|
|
|
func TestPullCreateChoicesReportFailures(t *testing.T) {
|
|
for _, failing := range []string{"/api/v1/repos/alice/project", "/api/v1/repos/alice/project/labels", "/api/v1/repos/alice/project/raw/PULL_REQUEST_TEMPLATE.md"} {
|
|
t.Run(failing, func(t *testing.T) {
|
|
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.URL.Path == failing:
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
case r.URL.Path == "/api/v1/repos/alice/project":
|
|
_, _ = w.Write([]byte(`{"full_name":"alice/project","default_branch":"main","permissions":{"push":true}}`))
|
|
default:
|
|
_, _ = w.Write([]byte(`[]`))
|
|
}
|
|
}))
|
|
_, err := (Pulls{Client: client, Repository: "alice/project"}).CreateChoices(context.Background())
|
|
require.ErrorContains(t, err, "500")
|
|
})
|
|
}
|
|
}
|