test(gitlab): expand client and service coverage (#2545)
Co-authored-by: Kandev Agent <agent@kandev.dev>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
package gitlab
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWatchHTTPRoutesRequireWorkspaceAndValidatePayloads(t *testing.T) {
|
||||
_, _, router := newWatchScopeFixture(t)
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
path string
|
||||
body string
|
||||
}{
|
||||
{name: "missing workspace", method: http.MethodGet, path: "/api/v1/gitlab/projects"},
|
||||
{name: "missing project", method: http.MethodGet, path: "/api/v1/gitlab/projects/branches?workspace_id=workspace-a"},
|
||||
{name: "invalid merge payload", method: http.MethodPut, path: "/api/v1/gitlab/mrs/merge?workspace_id=workspace-a", body: `{}`},
|
||||
{name: "invalid labels payload", method: http.MethodPut, path: "/api/v1/gitlab/mrs/labels?workspace_id=workspace-a", body: `{`},
|
||||
{name: "invalid assignees payload", method: http.MethodPut, path: "/api/v1/gitlab/mrs/assignees?workspace_id=workspace-a", body: `{}`},
|
||||
{name: "missing files iid", method: http.MethodGet, path: "/api/v1/gitlab/mrs/files?workspace_id=workspace-a&project=acme/widget"},
|
||||
{name: "missing commits project", method: http.MethodGet, path: "/api/v1/gitlab/mrs/commits?workspace_id=workspace-a&iid=2"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := httptestRequest(tc.method, tc.path, tc.body)
|
||||
response := executeRequest(router, req)
|
||||
if response.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, want 400; body=%s", response.Code, response.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchHTTPProjectAndMRActionRoutes(t *testing.T) {
|
||||
_, service, router := newWatchScopeFixture(t)
|
||||
client := service.workspaceClients["workspace-a"].(*MockClient)
|
||||
client.SeedProjectMembers("acme/widget", []ProjectMember{{ID: 7, Username: "alice", Name: "Alice"}})
|
||||
client.SeedBranches("acme/widget", []RepoBranch{{Name: "main"}, {Name: "release"}})
|
||||
client.SeedMR("acme/widget", &MR{IID: 2, Title: "Feature", State: mrStateOpen})
|
||||
client.SeedFiles("acme/widget", 2, []MRFile{{Filename: "main.go", Additions: 3}})
|
||||
client.SeedCommits("acme/widget", 2, []MRCommitInfo{{SHA: "abc123", Message: "change"}})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
path string
|
||||
body any
|
||||
wantString string
|
||||
}{
|
||||
{name: "list projects", method: http.MethodGet, path: "/projects", wantString: "kandev/sample"},
|
||||
{name: "search projects", method: http.MethodGet, path: "/projects/search?query=sample", wantString: "kandev/sample"},
|
||||
{name: "list branches", method: http.MethodGet, path: "/projects/branches?project=acme%2Fwidget", wantString: "release"},
|
||||
{name: "merge methods", method: http.MethodGet, path: "/projects/merge-methods?project=acme%2Fwidget", wantString: `"merge":true`},
|
||||
{name: "approve", method: http.MethodPost, path: "/mrs/approve", body: map[string]any{"project": "acme/widget", "iid": 2}, wantString: `"approved":true`},
|
||||
{name: "unapprove", method: http.MethodPost, path: "/mrs/unapprove", body: map[string]any{"project": "acme/widget", "iid": 2}, wantString: `"unapproved":true`},
|
||||
{name: "set labels", method: http.MethodPut, path: "/mrs/labels", body: map[string]any{"project": "acme/widget", "iid": 2, "labels": []string{"backend"}}, wantString: `"updated":true`},
|
||||
{name: "set assignees", method: http.MethodPut, path: "/mrs/assignees", body: map[string]any{"project": "acme/widget", "iid": 2, "assignee_ids": []int{7}}, wantString: `"updated":true`},
|
||||
{name: "files", method: http.MethodGet, path: "/mrs/files?project=acme%2Fwidget&iid=2", wantString: "main.go"},
|
||||
{name: "commits", method: http.MethodGet, path: "/mrs/commits?project=acme%2Fwidget&iid=2", wantString: "abc123"},
|
||||
{name: "merge", method: http.MethodPut, path: "/mrs/merge", body: map[string]any{"project": "acme/widget", "iid": 2, "method": "squash", "squash_commit_message": "combined"}, wantString: `"state":"merged"`},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
body := ""
|
||||
if tc.body != nil {
|
||||
encoded, err := json.Marshal(tc.body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body = string(encoded)
|
||||
}
|
||||
separator := "?"
|
||||
if strings.Contains(tc.path, "?") {
|
||||
separator = "&"
|
||||
}
|
||||
request := httptestRequest(tc.method, "/api/v1/gitlab"+tc.path+separator+"workspace_id=workspace-a", body)
|
||||
response := executeRequest(router, request)
|
||||
if response.Code != http.StatusOK || !strings.Contains(response.Body.String(), tc.wantString) {
|
||||
t.Fatalf("status=%d body=%s, want 200 containing %q", response.Code, response.Body.String(), tc.wantString)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
mr, err := client.GetMR(t.Context(), "acme/widget", 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(mr.Labels) != 1 || mr.Labels[0] != "backend" || len(mr.Assignees) != 1 || mr.Assignees[0].Username != "alice" || mr.State != gitlabStateMerged {
|
||||
t.Fatalf("mutated MR = %#v", mr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchHTTPActionPresetRoutes(t *testing.T) {
|
||||
_, _, router := newWatchScopeFixture(t)
|
||||
|
||||
get := watchScopeRequest(t, router, http.MethodGet, "/api/v1/gitlab/action-presets?workspace_id=workspace-a", nil)
|
||||
if get.Code != http.StatusOK || !bytes.Contains(get.Body.Bytes(), []byte(`"mr"`)) {
|
||||
t.Fatalf("get presets status=%d body=%s", get.Code, get.Body.String())
|
||||
}
|
||||
|
||||
custom := []ActionPreset{{ID: "review", Label: "Review", PromptTemplate: "Review {{url}}"}}
|
||||
update := watchScopeRequest(t, router, http.MethodPut, "/api/v1/gitlab/action-presets?workspace_id=workspace-a", UpdateActionPresetsRequest{MR: &custom})
|
||||
if update.Code != http.StatusOK || !bytes.Contains(update.Body.Bytes(), []byte(`"id":"review"`)) {
|
||||
t.Fatalf("update presets status=%d body=%s", update.Code, update.Body.String())
|
||||
}
|
||||
|
||||
reset := watchScopeRequest(t, router, http.MethodPost, "/api/v1/gitlab/action-presets/reset?workspace_id=workspace-a", nil)
|
||||
if reset.Code != http.StatusOK || bytes.Contains(reset.Body.Bytes(), []byte(`"prompt_template":"Review {{url}}"`)) {
|
||||
t.Fatalf("reset presets status=%d body=%s", reset.Code, reset.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func httptestRequest(method, path, body string) *http.Request {
|
||||
request, _ := http.NewRequest(method, path, strings.NewReader(body))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
return request
|
||||
}
|
||||
|
||||
func executeRequest(router http.Handler, request *http.Request) *httptest.ResponseRecorder {
|
||||
recorder := httptest.NewRecorder()
|
||||
router.ServeHTTP(recorder, request)
|
||||
return recorder
|
||||
}
|
||||
@@ -192,3 +192,60 @@ func TestMockControllerRejectsIncompleteRepoFilesRequest(t *testing.T) {
|
||||
t.Fatalf("status=%d body=%s", res.Code, res.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockControllerSeedsRemainingResourceTypes(t *testing.T) {
|
||||
router, workspaceA, _ := newMockControllerFixture(t)
|
||||
workspaceA.SeedMR("group/project", &MR{IID: 7, Title: "Review"})
|
||||
requests := []struct {
|
||||
method string
|
||||
path string
|
||||
body any
|
||||
}{
|
||||
{http.MethodPut, "/user", map[string]string{"username": "reviewer"}},
|
||||
{http.MethodPost, "/issues", mockIssueRequest{Project: "group/project", Issues: []Issue{{IID: 3, Title: "Bug", State: "opened"}}}},
|
||||
{http.MethodPost, "/pipelines", mockPipelineRequest{Project: "group/project", Pipelines: []Pipeline{{ID: 10, Status: "failed"}}}},
|
||||
{http.MethodPost, "/pipeline-jobs", mockPipelineJobsRequest{PipelineID: 10, Jobs: []PipelineJob{{ID: 11, Name: "test", Status: "failed"}}}},
|
||||
{http.MethodPost, "/discussions", mockDiscussionRequest{Project: "group/project", IID: 7, Discussions: []MRDiscussion{{ID: "thread-1", Resolvable: true}}}},
|
||||
{http.MethodPost, "/approvals", mockApprovalRequest{Project: "group/project", IID: 7, Approvals: []MRApproval{{Username: "alice"}}, Required: 2}},
|
||||
{http.MethodPost, "/branches", mockBranchesRequest{Project: "group/project", Branches: []RepoBranch{{Name: "main"}}}},
|
||||
}
|
||||
for _, item := range requests {
|
||||
response := mockControlRequest(t, router, item.method,
|
||||
"/api/v1/gitlab/mock"+item.path+"?workspace_id=workspace-a", item.body)
|
||||
if response.Code != http.StatusOK {
|
||||
t.Fatalf("seed %s status=%d body=%s", item.path, response.Code, response.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
if username, err := workspaceA.GetAuthenticatedUser(t.Context()); err != nil || username != "reviewer" {
|
||||
t.Fatalf("user = (%q, %v), want reviewer", username, err)
|
||||
}
|
||||
if state, err := workspaceA.GetIssueState(t.Context(), "group/project", 3); err != nil || state != "opened" {
|
||||
t.Fatalf("issue state = (%q, %v), want opened", state, err)
|
||||
}
|
||||
if jobs, err := workspaceA.ListPipelineJobs(t.Context(), "group/project", 10); err != nil || len(jobs) != 1 || jobs[0].ID != 11 {
|
||||
t.Fatalf("jobs = (%#v, %v), want job 11", jobs, err)
|
||||
}
|
||||
if discussions, err := workspaceA.ListMRDiscussions(t.Context(), "group/project", 7, nil); err != nil || len(discussions) != 1 || discussions[0].ID != "thread-1" {
|
||||
t.Fatalf("discussions = (%#v, %v), want thread-1", discussions, err)
|
||||
}
|
||||
if approvals, err := workspaceA.ListMRApprovals(t.Context(), "group/project", 7); err != nil || len(approvals) != 1 || approvals[0].Username != "alice" {
|
||||
t.Fatalf("approvals = (%#v, %v), want alice", approvals, err)
|
||||
}
|
||||
if branches, err := workspaceA.ListProjectBranches(t.Context(), "group/project"); err != nil || len(branches) != 1 || branches[0].Name != "main" {
|
||||
t.Fatalf("branches = (%#v, %v), want main", branches, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockControllerSeedRoutesRejectInvalidPayloads(t *testing.T) {
|
||||
router, _, _ := newMockControllerFixture(t)
|
||||
for _, path := range []string{
|
||||
"/mrs", "/issues", "/pipelines", "/pipeline-jobs", "/discussions",
|
||||
"/approvals", "/branches", "/members", "/files", "/commits",
|
||||
} {
|
||||
response := mockControlRequest(t, router, http.MethodPost, "/api/v1/gitlab/mock"+path, map[string]any{})
|
||||
if response.Code != http.StatusBadRequest {
|
||||
t.Errorf("%s status=%d body=%s, want 400", path, response.Code, response.Body.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
package gitlab
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPATClientSearchMRsPagedClampsPaginationAndReadsTotal(t *testing.T) {
|
||||
host, stop := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.URL.Path, "/merge_requests"; got != want {
|
||||
t.Errorf("path = %q, want %q", got, want)
|
||||
}
|
||||
query := r.URL.Query()
|
||||
if got, want := query.Get("page"), "1"; got != want {
|
||||
t.Errorf("page = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := query.Get("per_page"), "100"; got != want {
|
||||
t.Errorf("per_page = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := query.Get("state"), "opened"; got != want {
|
||||
t.Errorf("state = %q, want %q", got, want)
|
||||
}
|
||||
w.Header().Set("X-Total", "137")
|
||||
_ = json.NewEncoder(w).Encode([]rawMR{{IID: 8, Title: "Review me", State: "opened"}})
|
||||
}))
|
||||
t.Cleanup(stop)
|
||||
|
||||
page, err := NewPATClient(host, "tok").SearchMRsPaged(t.Context(), "labels=backend", "", 0, 500)
|
||||
if err != nil {
|
||||
t.Fatalf("SearchMRsPaged() error = %v", err)
|
||||
}
|
||||
if page.TotalCount != 137 || page.Page != 1 || page.PerPage != 100 {
|
||||
t.Fatalf("pagination = (%d, %d, %d), want (137, 1, 100)", page.TotalCount, page.Page, page.PerPage)
|
||||
}
|
||||
if len(page.MRs) != 1 || page.MRs[0].IID != 8 || page.MRs[0].State != "open" {
|
||||
t.Fatalf("MRs = %#v, want converted MR !8", page.MRs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPATClientListIssuesUsesDefaultPagination(t *testing.T) {
|
||||
host, stop := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
if got, want := query.Get("page"), "1"; got != want {
|
||||
t.Errorf("page = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := query.Get("per_page"), "50"; got != want {
|
||||
t.Errorf("per_page = %q, want %q", got, want)
|
||||
}
|
||||
w.Header().Set("X-Total", "1")
|
||||
_ = json.NewEncoder(w).Encode([]rawIssue{{IID: 3, Title: "Fix it", State: "opened"}})
|
||||
}))
|
||||
t.Cleanup(stop)
|
||||
|
||||
issues, err := NewPATClient(host, "tok").ListIssues(t.Context(), "open", "assignee_username=alice")
|
||||
if err != nil {
|
||||
t.Fatalf("ListIssues() error = %v", err)
|
||||
}
|
||||
if len(issues) != 1 || issues[0].IID != 3 || issues[0].State != "opened" {
|
||||
t.Fatalf("issues = %#v, want converted issue #3", issues)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPATClientProjectDiscovery(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
call func(*PATClient) ([]Project, error)
|
||||
wantPath string
|
||||
want map[string]string
|
||||
}{
|
||||
{
|
||||
name: "group projects with subgroup and search",
|
||||
call: func(c *PATClient) ([]Project, error) {
|
||||
return c.SearchGroupProjects(t.Context(), "acme/team", "api client", 0)
|
||||
},
|
||||
wantPath: "/groups/acme%2Fteam/projects",
|
||||
want: map[string]string{"per_page": "20", "simple": "true", "include_subgroups": "true", "search": "api client"},
|
||||
},
|
||||
{
|
||||
name: "member projects",
|
||||
call: func(c *PATClient) ([]Project, error) {
|
||||
return c.ListUserProjects(t.Context())
|
||||
},
|
||||
wantPath: "/projects",
|
||||
want: map[string]string{"membership": "true", "simple": "true", "per_page": "100", "order_by": "last_activity_at"},
|
||||
},
|
||||
{
|
||||
name: "global search trims blank query",
|
||||
call: func(c *PATClient) ([]Project, error) {
|
||||
return c.SearchProjects(t.Context(), " ", -1)
|
||||
},
|
||||
wantPath: "/projects",
|
||||
want: map[string]string{"membership": "true", "simple": "true", "per_page": "20"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
host, stop := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got := r.URL.EscapedPath(); got != tc.wantPath {
|
||||
t.Errorf("path = %q, want %q", got, tc.wantPath)
|
||||
}
|
||||
for key, want := range tc.want {
|
||||
if got := r.URL.Query().Get(key); got != want {
|
||||
t.Errorf("query %s = %q, want %q", key, got, want)
|
||||
}
|
||||
}
|
||||
if _, ok := tc.want["search"]; !ok && r.URL.Query().Has("search") {
|
||||
t.Errorf("unexpected search query %q", r.URL.Query().Get("search"))
|
||||
}
|
||||
_, _ = w.Write([]byte(`[{"id":12,"path_with_namespace":"acme/widget","name":"Widget","web_url":"https://gitlab.example/acme/widget","default_branch":"main"}]`))
|
||||
}))
|
||||
t.Cleanup(stop)
|
||||
|
||||
projects, err := tc.call(NewPATClient(host, "tok"))
|
||||
if err != nil {
|
||||
t.Fatalf("project discovery error = %v", err)
|
||||
}
|
||||
want := []Project{{ID: 12, PathWithNamespace: "acme/widget", Namespace: "acme", Name: "Widget", WebURL: "https://gitlab.example/acme/widget", DefaultBranch: "main"}}
|
||||
if !reflect.DeepEqual(projects, want) {
|
||||
t.Fatalf("projects = %#v, want %#v", projects, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPATClientMergeMRRequestAndResponse(t *testing.T) {
|
||||
host, stop := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Method, http.MethodPut; got != want {
|
||||
t.Errorf("method = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := r.URL.EscapedPath(), "/projects/acme%2Fwidget/merge_requests/9/merge"; got != want {
|
||||
t.Errorf("path = %q, want %q", got, want)
|
||||
}
|
||||
var request MergeMRRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
||||
t.Fatalf("decode request: %v", err)
|
||||
}
|
||||
if !request.Squash || request.SquashCommitMessage != "squashed changes" || request.ShouldRemoveSourceBranch {
|
||||
t.Errorf("request = %#v, want squash without source removal", request)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(rawMR{IID: 9, State: "merged", Title: "Feature"})
|
||||
}))
|
||||
t.Cleanup(stop)
|
||||
|
||||
mr, err := NewPATClient(host, "tok").MergeMR(t.Context(), "acme/widget", 9, true, "squashed changes")
|
||||
if err != nil {
|
||||
t.Fatalf("MergeMR() error = %v", err)
|
||||
}
|
||||
if mr.IID != 9 || mr.State != "merged" {
|
||||
t.Fatalf("MR = %#v, want merged MR !9", mr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPATClientProtectedBranchResponses(t *testing.T) {
|
||||
t.Run("not protected", func(t *testing.T) {
|
||||
host, stop := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
http.Error(w, `{"message":"404 Not Found"}`, http.StatusNotFound)
|
||||
}))
|
||||
t.Cleanup(stop)
|
||||
|
||||
branch, err := NewPATClient(host, "tok").GetProtectedBranch(t.Context(), "acme/widget", "feature/x")
|
||||
if err != nil || branch != nil {
|
||||
t.Fatalf("GetProtectedBranch() = (%#v, %v), want (nil, nil)", branch, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("protected", func(t *testing.T) {
|
||||
host, stop := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.URL.EscapedPath(), "/projects/acme%2Fwidget/protected_branches/feature%2Fx"; got != want {
|
||||
t.Errorf("path = %q, want %q", got, want)
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"name":"feature/x","push_access_levels":[{"access_level":30}],"merge_access_levels":[{"access_level":40}],"code_owner_approval_required":true}`))
|
||||
}))
|
||||
t.Cleanup(stop)
|
||||
|
||||
branch, err := NewPATClient(host, "tok").GetProtectedBranch(t.Context(), "acme/widget", "feature/x")
|
||||
if err != nil {
|
||||
t.Fatalf("GetProtectedBranch() error = %v", err)
|
||||
}
|
||||
want := &ProtectedBranch{Name: "feature/x", PushAccessLevel: 30, MergeAccessLevel: 40, CodeOwnerApprovalRequired: true}
|
||||
if !reflect.DeepEqual(branch, want) {
|
||||
t.Fatalf("branch = %#v, want %#v", branch, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPATClientGetProjectMergeMethods(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
want ProjectMergeMethods
|
||||
}{
|
||||
{name: "merge", body: `{"merge_method":"merge","squash_option":"never"}`, want: ProjectMergeMethods{Merge: true}},
|
||||
{name: "rebase and squash", body: `{"merge_method":"rebase_merge","squash_option":"default_on"}`, want: ProjectMergeMethods{RebaseMerge: true, AllowSquash: true}},
|
||||
{name: "fast forward", body: `{"merge_method":"ff","squash_option":"always"}`, want: ProjectMergeMethods{FastForward: true, AllowSquash: true}},
|
||||
{name: "unknown defaults to merge", body: `{"merge_method":"future"}`, want: ProjectMergeMethods{Merge: true}},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
host, stop := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = w.Write([]byte(tc.body))
|
||||
}))
|
||||
t.Cleanup(stop)
|
||||
|
||||
got, err := NewPATClient(host, "tok").GetProjectMergeMethods(t.Context(), "acme/widget")
|
||||
if err != nil {
|
||||
t.Fatalf("GetProjectMergeMethods() error = %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(*got, tc.want) {
|
||||
t.Fatalf("methods = %#v, want %#v", *got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPATClientActionErrorsPreserveAPIError(t *testing.T) {
|
||||
host, stop := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
http.Error(w, "upstream unavailable", http.StatusServiceUnavailable)
|
||||
}))
|
||||
t.Cleanup(stop)
|
||||
|
||||
_, err := NewPATClient(host, "tok").MergeMR(t.Context(), "acme/widget", 9, false, "")
|
||||
if err == nil {
|
||||
t.Fatal("MergeMR() error = nil, want upstream error")
|
||||
}
|
||||
var apiErr *APIError
|
||||
if !errors.As(err, &apiErr) || apiErr.StatusCode != http.StatusServiceUnavailable {
|
||||
t.Fatalf("error = %v, want wrapped APIError status 503", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPATClientMRListQueries(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
call func(*PATClient) ([]*MR, error)
|
||||
check func(*testing.T, *http.Request)
|
||||
}{
|
||||
{
|
||||
name: "authored",
|
||||
call: func(c *PATClient) ([]*MR, error) { return c.ListAuthoredMRs(t.Context(), "acme/widget") },
|
||||
check: func(t *testing.T, r *http.Request) {
|
||||
if got, want := r.URL.Query().Get("author_username"), "alice"; got != want {
|
||||
t.Errorf("author_username = %q, want %q", got, want)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "review requested",
|
||||
call: func(c *PATClient) ([]*MR, error) {
|
||||
return c.ListReviewRequestedMRs(t.Context(), "", "reviewer_username=alice&state=opened")
|
||||
},
|
||||
check: func(t *testing.T, r *http.Request) {
|
||||
if got, want := r.URL.Query().Get("reviewer_username"), "alice"; got != want {
|
||||
t.Errorf("reviewer_username = %q, want %q", got, want)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "search wrapper",
|
||||
call: func(c *PATClient) ([]*MR, error) { return c.SearchMRs(t.Context(), "state=closed", "") },
|
||||
check: func(t *testing.T, r *http.Request) {
|
||||
if got, want := r.URL.Query().Get("state"), "closed"; got != want {
|
||||
t.Errorf("state = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := r.URL.Query().Get("page"), "1"; got != want {
|
||||
t.Errorf("page = %q, want %q", got, want)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
host, stop := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/user" {
|
||||
_, _ = w.Write([]byte(`{"username":"alice"}`))
|
||||
return
|
||||
}
|
||||
tc.check(t, r)
|
||||
_, _ = w.Write([]byte(`[{"iid":11,"title":"MR","state":"opened"}]`))
|
||||
}))
|
||||
t.Cleanup(stop)
|
||||
|
||||
mrs, err := tc.call(NewPATClient(host, "tok"))
|
||||
if err != nil {
|
||||
t.Fatalf("list MRs error = %v", err)
|
||||
}
|
||||
if len(mrs) != 1 || mrs[0].IID != 11 || mrs[0].State != "open" {
|
||||
t.Fatalf("MRs = %#v, want converted MR !11", mrs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPATClientIssueStateAndGroups(t *testing.T) {
|
||||
host, stop := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/projects/acme/widget/issues/6":
|
||||
_, _ = w.Write([]byte(`{"state":"closed"}`))
|
||||
case "/groups":
|
||||
if got, want := r.URL.Query().Get("min_access_level"), "10"; got != want {
|
||||
t.Errorf("min_access_level = %q, want %q", got, want)
|
||||
}
|
||||
_, _ = w.Write([]byte(`[{"id":4,"path":"team","name":"Team","avatar_url":"https://img/team"}]`))
|
||||
default:
|
||||
t.Fatalf("unexpected path %q", r.URL.Path)
|
||||
}
|
||||
}))
|
||||
t.Cleanup(stop)
|
||||
|
||||
client := NewPATClient(host, "tok")
|
||||
state, err := client.GetIssueState(t.Context(), "acme/widget", 6)
|
||||
if err != nil || state != "closed" {
|
||||
t.Fatalf("GetIssueState() = (%q, %v), want closed", state, err)
|
||||
}
|
||||
groups, err := client.ListUserGroups(t.Context())
|
||||
if err != nil {
|
||||
t.Fatalf("ListUserGroups() error = %v", err)
|
||||
}
|
||||
want := []Group{{ID: 4, Path: "team", Name: "Team", AvatarURL: "https://img/team"}}
|
||||
if !reflect.DeepEqual(groups, want) {
|
||||
t.Fatalf("groups = %#v, want %#v", groups, want)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package gitlab
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestServiceMergeMRValidatesMethodAndUpdatesMR(t *testing.T) {
|
||||
client := NewMockClient("https://gitlab.example")
|
||||
client.SeedMR("acme/widget", &MR{IID: 4, Title: "Feature", State: mrStateOpen})
|
||||
service := NewService(client.Host(), client, "mock", nil, newTestLogger(t))
|
||||
|
||||
mr, err := service.MergeMR(t.Context(), "acme/widget", 4, "squash", "combined changes")
|
||||
if err != nil {
|
||||
t.Fatalf("MergeMR() error = %v", err)
|
||||
}
|
||||
if mr.State != gitlabStateMerged || mr.MergedAt == nil {
|
||||
t.Fatalf("MR = %#v, want merged state and timestamp", mr)
|
||||
}
|
||||
|
||||
stored, err := client.GetMR(t.Context(), "acme/widget", 4)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMR() error = %v", err)
|
||||
}
|
||||
if stored.State != gitlabStateMerged {
|
||||
t.Fatalf("stored state = %q, want %q", stored.State, gitlabStateMerged)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceMergeMRRejectsUnsupportedMethods(t *testing.T) {
|
||||
service := NewService(DefaultHost, NewMockClient(DefaultHost), "mock", nil, newTestLogger(t))
|
||||
tests := []struct {
|
||||
method string
|
||||
want string
|
||||
}{
|
||||
{method: "rebase_merge", want: "does not allow rebase merge"},
|
||||
{method: "ff", want: "does not allow fast-forward merge"},
|
||||
{method: "octopus", want: "unknown merge method"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.method, func(t *testing.T) {
|
||||
_, err := service.MergeMR(t.Context(), "acme/widget", 4, tc.method, "")
|
||||
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
||||
t.Fatalf("MergeMR() error = %v, want containing %q", err, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceClientActionsReturnErrNoClient(t *testing.T) {
|
||||
service := NewService(DefaultHost, nil, AuthMethodNone, nil, newTestLogger(t))
|
||||
tests := []struct {
|
||||
name string
|
||||
call func() error
|
||||
}{
|
||||
{name: "merge", call: func() error { _, err := service.MergeMR(t.Context(), "acme/widget", 1, "merge", ""); return err }},
|
||||
{name: "merge methods", call: func() error { _, err := service.GetProjectMergeMethods(t.Context(), "acme/widget"); return err }},
|
||||
{name: "approve", call: func() error { return service.SubmitMRApproval(t.Context(), "acme/widget", 1) }},
|
||||
{name: "unapprove", call: func() error { return service.SubmitMRUnapproval(t.Context(), "acme/widget", 1) }},
|
||||
{name: "labels", call: func() error { return service.SetMRLabels(t.Context(), "acme/widget", 1, nil) }},
|
||||
{name: "assignees", call: func() error { return service.SetMRAssignees(t.Context(), "acme/widget", 1, nil) }},
|
||||
{name: "files", call: func() error { _, err := service.GetMRFiles(t.Context(), "acme/widget", 1); return err }},
|
||||
{name: "commits", call: func() error { _, err := service.GetMRCommits(t.Context(), "acme/widget", 1); return err }},
|
||||
{name: "projects", call: func() error { _, err := service.ListUserProjects(t.Context()); return err }},
|
||||
{name: "search projects", call: func() error { _, err := service.SearchProjects(t.Context(), "widget", 10); return err }},
|
||||
{name: "branches", call: func() error { _, err := service.ListProjectBranches(t.Context(), "acme/widget"); return err }},
|
||||
{name: "search MRs", call: func() error { _, err := service.SearchUserMRs(t.Context(), "", ""); return err }},
|
||||
{name: "search MRs paged", call: func() error { _, err := service.SearchUserMRsPaged(t.Context(), "", "", 1, 10); return err }},
|
||||
{name: "search issues", call: func() error { _, err := service.SearchUserIssues(t.Context(), "", ""); return err }},
|
||||
{name: "search issues paged", call: func() error { _, err := service.SearchUserIssuesPaged(t.Context(), "", "", 1, 10); return err }},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if err := tc.call(); !errors.Is(err, ErrNoClient) {
|
||||
t.Fatalf("error = %v, want ErrNoClient", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceMockClientSearchAndActions(t *testing.T) {
|
||||
client := NewMockClient(DefaultHost)
|
||||
client.SeedProjectMembers("acme/widget", []ProjectMember{{ID: 7, Username: "alice", Name: "Alice"}})
|
||||
client.SeedBranches("acme/widget", []RepoBranch{{Name: "main"}})
|
||||
client.SeedMR("acme/widget", &MR{IID: 2, Title: "Feature", State: mrStateOpen})
|
||||
service := NewService(DefaultHost, client, "mock", nil, newTestLogger(t))
|
||||
|
||||
if err := service.SetMRLabels(t.Context(), "acme/widget", 2, []string{"backend"}); err != nil {
|
||||
t.Fatalf("SetMRLabels() error = %v", err)
|
||||
}
|
||||
if err := service.SetMRAssignees(t.Context(), "acme/widget", 2, []int{7}); err != nil {
|
||||
t.Fatalf("SetMRAssignees() error = %v", err)
|
||||
}
|
||||
mr, err := client.GetMR(t.Context(), "acme/widget", 2)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMR() error = %v", err)
|
||||
}
|
||||
if len(mr.Labels) != 1 || mr.Labels[0] != "backend" || len(mr.Assignees) != 1 || mr.Assignees[0].Username != "alice" {
|
||||
t.Fatalf("MR labels/assignees = %#v/%#v, want backend/alice", mr.Labels, mr.Assignees)
|
||||
}
|
||||
|
||||
branches, err := service.ListProjectBranches(t.Context(), "acme/widget")
|
||||
if err != nil || len(branches) != 1 || branches[0].Name != "main" {
|
||||
t.Fatalf("ListProjectBranches() = (%#v, %v), want main", branches, err)
|
||||
}
|
||||
projects, err := service.SearchProjects(t.Context(), "sample", 5)
|
||||
if err != nil || len(projects) != 1 || projects[0].PathWithNamespace != "kandev/sample" {
|
||||
t.Fatalf("SearchProjects() = (%#v, %v), want kandev/sample", projects, err)
|
||||
}
|
||||
missing, err := service.SearchProjects(t.Context(), "absent", 5)
|
||||
if err != nil || len(missing) != 0 {
|
||||
t.Fatalf("SearchProjects(absent) = (%#v, %v), want empty", missing, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package gitlab
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestServiceMRAutomationOperationsRequireStore(t *testing.T) {
|
||||
service := NewService(DefaultHost, NewMockClient(DefaultHost), "mock", nil, newTestLogger(t))
|
||||
tests := []struct {
|
||||
name string
|
||||
call func() error
|
||||
}{
|
||||
{name: "enabled prompts", call: func() error { _, err := service.HasEnabledTaskMRAgentPrompts(t.Context(), "task"); return err }},
|
||||
{name: "rebind reviewer", call: func() error { _, _, err := service.RebindTaskMRReviewer(t.Context(), "task"); return err }},
|
||||
{name: "get lifecycle state", call: func() error {
|
||||
_, err := service.GetTaskMRLifecycleState(t.Context(), "task", "repo", "group/project", 1)
|
||||
return err
|
||||
}},
|
||||
{name: "review request", call: func() error {
|
||||
return service.SetTaskMRReviewRequestState(t.Context(), "task", "repo", "group/project", 1, true)
|
||||
}},
|
||||
{name: "observed state", call: func() error {
|
||||
return service.SetTaskMRObservedState(t.Context(), "task", "repo", "group/project", 1, "merged")
|
||||
}},
|
||||
{name: "lifecycle prompt", call: func() error {
|
||||
return service.RecordTaskMRLifecyclePrompt(t.Context(), TaskMRLifecyclePrompt{TaskID: "task"})
|
||||
}},
|
||||
{name: "automation error", call: func() error {
|
||||
return service.RecordTaskMRAutomationError(t.Context(), "task", "repo", "group/project", 1, "failed")
|
||||
}},
|
||||
{name: "clear automation error", call: func() error {
|
||||
return service.ClearTaskMRAutomationError(t.Context(), "task", "repo", "group/project", 1)
|
||||
}},
|
||||
{name: "sync error", call: func() error {
|
||||
return service.RecordTaskMRSyncError(t.Context(), "task", "repo", "group/project", 1, "failed")
|
||||
}},
|
||||
{name: "clear sync error", call: func() error { return service.ClearTaskMRSyncError(t.Context(), "task", "repo", "group/project", 1) }},
|
||||
{name: "fix attempt", call: func() error { return service.RecordTaskMRFixAttempt(t.Context(), TaskMRFixAttempt{TaskID: "task"}) }},
|
||||
{name: "refresh fix checkpoint", call: func() error {
|
||||
return service.RefreshTaskMRFixCheckpoint(t.Context(), "task", "repo", "group/project", 1, "sig", `{}`)
|
||||
}},
|
||||
{name: "fix exhausted", call: func() error {
|
||||
return service.MarkTaskMRAutoFixExhausted(t.Context(), "task", "repo", "group/project", 1, "limit")
|
||||
}},
|
||||
{name: "merge attempt", call: func() error { return service.RecordTaskMRMergeAttempt(t.Context(), TaskMRMergeAttempt{TaskID: "task"}) }},
|
||||
{name: "subscribed MRs", call: func() error { _, err := service.ListAutomationSubscribedTaskMRs(t.Context()); return err }},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if err := tc.call(); !errors.Is(err, errStoreUnavailable) {
|
||||
t.Fatalf("error = %v, want errStoreUnavailable", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceMRAutomationStateRoundTrip(t *testing.T) {
|
||||
store := newTestStore(t)
|
||||
seedWorkspace(t, store, "workspace")
|
||||
seedTask(t, store, "task", "workspace")
|
||||
service := NewService(DefaultHost, NewMockClient(DefaultHost), "mock", nil, newTestLogger(t))
|
||||
service.SetStore(store)
|
||||
|
||||
if err := service.SetTaskMRReviewRequestState(t.Context(), "task", "repo", "group/project", 1, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := service.SetTaskMRObservedState(t.Context(), "task", "repo", "group/project", 1, "opened"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := service.RecordTaskMRAutomationError(t.Context(), "task", "repo", "group/project", 1, " delivery failed "); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := service.RecordTaskMRSyncError(t.Context(), "task", "repo", "group/project", 1, " sync failed "); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
state, err := service.GetTaskMRLifecycleState(t.Context(), "task", "repo", "group/project", 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !state.LastReviewRequested || state.LastObservedState != "opened" || stringValue(state.LastError) != " delivery failed " || stringValue(state.LastSyncError) != " sync failed " {
|
||||
t.Fatalf("state = %#v", state)
|
||||
}
|
||||
|
||||
if err := service.ClearTaskMRAutomationError(t.Context(), "task", "repo", "group/project", 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := service.ClearTaskMRSyncError(t.Context(), "task", "repo", "group/project", 1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
state, err = service.GetTaskMRLifecycleState(t.Context(), "task", "repo", "group/project", 1)
|
||||
if err != nil || state.LastError != nil || state.LastSyncError != nil {
|
||||
t.Fatalf("cleared state = (%#v, %v)", state, err)
|
||||
}
|
||||
}
|
||||
|
||||
func stringValue(value *string) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
return *value
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package gitlab
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestServiceIssueWatchLifecycle(t *testing.T) {
|
||||
store := newTestStore(t)
|
||||
client := NewMockClient(DefaultHost)
|
||||
client.SeedIssue("group/project", &Issue{IID: 3, Title: "Bug", State: "opened", ProjectPath: "group/project"})
|
||||
service := NewService(DefaultHost, client, "mock", nil, newTestLogger(t))
|
||||
service.SetStore(store)
|
||||
service.workspaceClients["workspace"] = client
|
||||
|
||||
watch, err := service.CreateIssueWatch(t.Context(), &CreateIssueWatchRequest{
|
||||
WorkspaceID: "workspace", WorkflowID: "workflow", WorkflowStepID: "step",
|
||||
AgentProfileID: "agent", ExecutorProfileID: "executor", Projects: []ProjectFilter{{Path: " group/project "}},
|
||||
Labels: []string{"bug"}, PollIntervalSeconds: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateIssueWatch() error = %v", err)
|
||||
}
|
||||
if watch.PollIntervalSeconds != minWatchPollIntervalSec || len(watch.Projects) != 1 || watch.Projects[0].Path != "group/project" {
|
||||
t.Fatalf("normalized watch = %#v", watch)
|
||||
}
|
||||
listed, err := service.ListIssueWatches(t.Context(), "workspace")
|
||||
if err != nil || len(listed) != 1 || listed[0].ID != watch.ID {
|
||||
t.Fatalf("ListIssueWatches() = (%#v, %v)", listed, err)
|
||||
}
|
||||
all, err := service.ListAllIssueWatches(t.Context())
|
||||
if err != nil || len(all) != 1 {
|
||||
t.Fatalf("ListAllIssueWatches() = (%#v, %v)", all, err)
|
||||
}
|
||||
|
||||
prompt, enabled, max := "Investigate", true, 2
|
||||
if err := service.UpdateIssueWatch(t.Context(), watch.ID, &UpdateIssueWatchRequest{Prompt: &prompt, Enabled: &enabled, MaxInflightTasks: &max}); err != nil {
|
||||
t.Fatalf("UpdateIssueWatch() error = %v", err)
|
||||
}
|
||||
updated, err := service.GetIssueWatch(t.Context(), watch.ID)
|
||||
if err != nil || updated.Prompt != prompt || updated.MaxInflightTasks == nil || *updated.MaxInflightTasks != 2 {
|
||||
t.Fatalf("updated watch = (%#v, %v)", updated, err)
|
||||
}
|
||||
found, err := service.TriggerIssueWatch(t.Context(), watch.ID)
|
||||
if err != nil || len(found) != 1 || found[0].IID != 3 {
|
||||
t.Fatalf("TriggerIssueWatch() = (%#v, %v)", found, err)
|
||||
}
|
||||
if ok, err := store.ReserveIssueWatchTask(t.Context(), watch.ID, "group/project", 3, "url"); err != nil || !ok {
|
||||
t.Fatalf("reserve issue = (%v, %v)", ok, err)
|
||||
}
|
||||
found, err = service.CheckIssueWatch(t.Context(), watch)
|
||||
if err != nil || len(found) != 0 {
|
||||
t.Fatalf("deduplicated CheckIssueWatch() = (%#v, %v)", found, err)
|
||||
}
|
||||
if err := service.DeleteIssueWatch(t.Context(), watch.ID); err != nil {
|
||||
t.Fatalf("DeleteIssueWatch() error = %v", err)
|
||||
}
|
||||
if deleted, err := service.GetIssueWatch(t.Context(), watch.ID); err != nil || deleted != nil {
|
||||
t.Fatalf("deleted watch = (%#v, %v)", deleted, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceReviewWatchLifecycle(t *testing.T) {
|
||||
store := newTestStore(t)
|
||||
client := NewMockClient(DefaultHost)
|
||||
client.SeedMR("group/project", &MR{IID: 5, Title: "Feature", State: mrStateOpen, ProjectPath: "group/project"})
|
||||
service := NewService(DefaultHost, client, "mock", nil, newTestLogger(t))
|
||||
service.SetStore(store)
|
||||
service.workspaceClients["workspace"] = client
|
||||
|
||||
watch, err := service.CreateReviewWatch(t.Context(), &CreateReviewWatchRequest{
|
||||
WorkspaceID: "workspace", WorkflowID: "workflow", WorkflowStepID: "step",
|
||||
AgentProfileID: "agent", ExecutorProfileID: "executor", Projects: []ProjectFilter{{Path: "group/project"}},
|
||||
PollIntervalSeconds: 1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CreateReviewWatch() error = %v", err)
|
||||
}
|
||||
if watch.ReviewScope != ReviewScopeUserAndTeams || watch.PollIntervalSeconds != minWatchPollIntervalSec {
|
||||
t.Fatalf("normalized watch = %#v", watch)
|
||||
}
|
||||
listed, err := service.ListReviewWatches(t.Context(), "workspace")
|
||||
if err != nil || len(listed) != 1 || listed[0].ID != watch.ID {
|
||||
t.Fatalf("ListReviewWatches() = (%#v, %v)", listed, err)
|
||||
}
|
||||
all, err := service.ListAllReviewWatches(t.Context())
|
||||
if err != nil || len(all) != 1 {
|
||||
t.Fatalf("ListAllReviewWatches() = (%#v, %v)", all, err)
|
||||
}
|
||||
|
||||
prompt, scope := "Review carefully", ReviewScopeUser
|
||||
if err := service.UpdateReviewWatch(t.Context(), watch.ID, &UpdateReviewWatchRequest{Prompt: &prompt, ReviewScope: &scope}); err != nil {
|
||||
t.Fatalf("UpdateReviewWatch() error = %v", err)
|
||||
}
|
||||
updated, err := service.GetReviewWatch(t.Context(), watch.ID)
|
||||
if err != nil || updated.Prompt != prompt || updated.ReviewScope != scope {
|
||||
t.Fatalf("updated watch = (%#v, %v)", updated, err)
|
||||
}
|
||||
found, err := service.TriggerReviewWatch(t.Context(), watch.ID)
|
||||
if err != nil || len(found) != 1 || found[0].IID != 5 {
|
||||
t.Fatalf("TriggerReviewWatch() = (%#v, %v)", found, err)
|
||||
}
|
||||
if ok, err := store.ReserveReviewMRTask(t.Context(), watch.ID, "group/project", 5, "url"); err != nil || !ok {
|
||||
t.Fatalf("reserve MR = (%v, %v)", ok, err)
|
||||
}
|
||||
found, err = service.CheckReviewWatch(t.Context(), watch)
|
||||
if err != nil || len(found) != 0 {
|
||||
t.Fatalf("deduplicated CheckReviewWatch() = (%#v, %v)", found, err)
|
||||
}
|
||||
if err := service.DeleteReviewWatch(t.Context(), watch.ID); err != nil {
|
||||
t.Fatalf("DeleteReviewWatch() error = %v", err)
|
||||
}
|
||||
if deleted, err := service.GetReviewWatch(t.Context(), watch.ID); err != nil || deleted != nil {
|
||||
t.Fatalf("deleted watch = (%#v, %v)", deleted, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceWatchValidationErrors(t *testing.T) {
|
||||
service := NewService(DefaultHost, NewMockClient(DefaultHost), "mock", nil, newTestLogger(t))
|
||||
if _, err := service.CreateIssueWatch(t.Context(), nil); err == nil {
|
||||
t.Fatal("CreateIssueWatch(nil) error = nil")
|
||||
}
|
||||
if _, err := service.CreateReviewWatch(t.Context(), nil); err == nil {
|
||||
t.Fatal("CreateReviewWatch(nil) error = nil")
|
||||
}
|
||||
if _, err := service.CheckIssueWatch(t.Context(), nil); err == nil {
|
||||
t.Fatal("CheckIssueWatch(nil) error = nil")
|
||||
}
|
||||
if _, err := service.CheckReviewWatch(t.Context(), nil); err == nil {
|
||||
t.Fatal("CheckReviewWatch(nil) error = nil")
|
||||
}
|
||||
disabledIssue := &IssueWatch{Enabled: false}
|
||||
if found, err := service.CheckIssueWatch(t.Context(), disabledIssue); err != nil || found != nil {
|
||||
t.Fatalf("disabled issue watch = (%#v, %v)", found, err)
|
||||
}
|
||||
disabledReview := &ReviewWatch{Enabled: false}
|
||||
if found, err := service.CheckReviewWatch(t.Context(), disabledReview); err != nil || found != nil {
|
||||
t.Fatalf("disabled review watch = (%#v, %v)", found, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user