package main import ( "net/http" "net/http/httptest" "strings" "testing" ) func TestCheckBotNeedsPushAccess(t *testing.T) { push := false srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Authorization") != "token secret" { w.WriteHeader(http.StatusUnauthorized) return } switch r.URL.Path { case "/api/v1/user": w.Write([]byte(`{"login":"git_ai_bot","email":"bot@example.com"}`)) case "/api/v1/repos/org/games": if push { w.Write([]byte(`{"permissions":{"push":true}}`)) } else { w.Write([]byte(`{"permissions":{"push":false}}`)) } default: w.WriteHeader(http.StatusNotFound) } })) defer srv.Close() c := &workerConfig{gitea: srv.URL, repo: "org/games", botToken: "secret"} if err := c.checkBot(); err == nil || !strings.Contains(err.Error(), "git_ai_bot can't push to org/games") { t.Errorf("read-only bot: got %v", err) } push = true if err := c.checkBot(); err != nil { t.Fatal(err) } if c.botLogin != "git_ai_bot" || c.botEmail != "bot@example.com" { t.Errorf("identity = %s <%s>", c.botLogin, c.botEmail) } c.botToken = "wrong" if err := c.checkBot(); err == nil || !strings.Contains(err.Error(), "GITEA_TOKEN") { t.Errorf("bad token: got %v", err) } } func TestCheckBotWithRepositoryOnlyToken(t *testing.T) { // Gitea answers /user with 403 when the token only has repository scope srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/v1/user": w.WriteHeader(http.StatusForbidden) w.Write([]byte(`{"message":"token does not have at least one of required scope(s), required=[read:user]"}`)) case "/api/v1/repos/org/games": w.Write([]byte(`{"permissions":{"push":true}}`)) } })) defer srv.Close() t.Setenv("BOT_NAME", "gbr-bot") c := &workerConfig{gitea: srv.URL, repo: "org/games", botToken: "secret"} if err := c.checkBot(); err != nil { t.Fatal(err) } if c.botLogin != "gbr-bot" || c.botEmail != "gbr-bot@users.noreply" { t.Errorf("identity = %s <%s>", c.botLogin, c.botEmail) } } func TestReplaceOldDraftsClosesOnlyThisGamesDrafts(t *testing.T) { var calls []string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls = append(calls, r.Method+" "+r.URL.Path) switch { case r.Method == http.MethodGet && r.URL.Path == "/api/v1/repos/org/games/pulls": w.Write([]byte(`[ {"number": 13, "head": {"ref": "draft/example-4b174b2d"}}, {"number": 20, "head": {"ref": "draft/example"}}, {"number": 21, "head": {"ref": "draft/example-other"}}, {"number": 22, "head": {"ref": "draft/other-4b174b2d"}}, {"number": 23, "head": {"ref": "fix/example-ports"}} ]`)) case r.Method == http.MethodDelete && r.URL.Path == "/api/v1/repos/org/games/branches/draft/example-4b174b2d": w.WriteHeader(http.StatusNoContent) case r.Method == http.MethodDelete: w.WriteHeader(http.StatusNotFound) // already gone is fine default: w.Write([]byte(`{}`)) } })) defer srv.Close() c := &workerConfig{gitea: srv.URL, repo: "org/games", botToken: "secret"} closed, err := c.replaceOldDrafts("example") if err != nil { t.Fatal(err) } if len(closed) != 2 || closed[0] != 13 || closed[1] != 20 { t.Errorf("closed %v, want [13 20]: not another game's draft, not draft/example-other, not someone's fix branch", closed) } got := strings.Join(calls, "\n") for _, want := range []string{"PATCH /api/v1/repos/org/games/pulls/13", "PATCH /api/v1/repos/org/games/pulls/20", "DELETE /api/v1/repos/org/games/branches/draft/example-4b174b2d", "DELETE /api/v1/repos/org/games/branches/draft/example"} { if !strings.Contains(got, want) { t.Errorf("missing %q in:\n%s", want, got) } } for _, unwanted := range []string{"pulls/21", "pulls/22", "pulls/23"} { if strings.Contains(got, unwanted) { t.Errorf("touched %s:\n%s", unwanted, got) } } }