package provider import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "orchestra/internal/domain" "orchestra/internal/store" ) func TestGiteaCommentsFetchAfterCursor(t *testing.T) { var path string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { path = r.URL.Path // Deliberately out of order, to prove the source sorts by id. w.Write([]byte(`[ {"id":920,"body":"and keep the flag","user":{"login":"kami"},"created_at":"2026-08-26T12:02:00Z"}, {"id":917,"body":"older","user":{"login":"kami"},"created_at":"2026-08-26T11:00:00Z"}, {"id":918,"body":"no, use b","user":{"login":"kami"},"created_at":"2026-08-26T12:00:00Z"} ]`)) })) defer srv.Close() g := GiteaComments{Gitea{BaseURL: srv.URL, Owner: "kami", Repo: "orchestra", Project: "p"}} task := domain.Task{ID: "t1", Source: g.SourceName(), ExternalID: "381"} got, next, err := g.FetchAfter(context.Background(), task, store.SourceCursor{Cursor: "917"}) if err != nil { t.Fatal(err) } if path != "/api/v1/repos/kami/orchestra/issues/381/comments" { t.Fatalf("path = %s", path) } if len(got) != 2 || got[0].ExternalID != "918" || got[1].ExternalID != "920" { t.Fatalf("inputs = %+v", got) } if got[0].Body != "no, use b" || got[0].Author != "kami" || got[0].Provider != "gitea:p" { t.Fatalf("first input = %+v", got[0]) } if next.Cursor != "920" || next.TaskID != "t1" { t.Fatalf("next = %+v", next) } // Nothing new: the cursor must stay put rather than regress. got, next, err = g.FetchAfter(context.Background(), task, store.SourceCursor{Cursor: "920"}) if err != nil || len(got) != 0 { t.Fatalf("inputs=%+v err=%v", got, err) } if next.Cursor != "920" { t.Fatalf("next = %+v", next) } } func TestGiteaCommentsIgnoresForeignAndUnkeyedTasks(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Errorf("unexpected request to %s", r.URL.Path) })) defer srv.Close() g := GiteaComments{Gitea{BaseURL: srv.URL, Owner: "kami", Repo: "orchestra", Project: "p"}} for name, task := range map[string]domain.Task{ "other source": {ID: "t1", Source: "vikunja", ExternalID: "381"}, "no issue": {ID: "t1", Source: g.SourceName()}, } { got, _, err := g.FetchAfter(context.Background(), task, store.SourceCursor{}) if err != nil || len(got) != 0 { t.Fatalf("%s: inputs=%+v err=%v", name, got, err) } } } func TestGiteaCommentsRejectsUnparsableCursorAndHTTPError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "boom", 500) })) defer srv.Close() g := GiteaComments{Gitea{BaseURL: srv.URL, Owner: "kami", Repo: "orchestra", Project: "p"}} task := domain.Task{ID: "t1", Source: g.SourceName(), ExternalID: "381"} if _, _, err := g.FetchAfter(context.Background(), task, store.SourceCursor{Cursor: "abc"}); err == nil { t.Fatal("bad cursor must not be treated as zero") } if _, _, err := g.FetchAfter(context.Background(), task, store.SourceCursor{}); err == nil { t.Fatal("http failure must be reported") } } // The issue body is the task's own statement of what it wants. It must reach // the store, because every rendered context ranks it above continuity and // below only a human decision. func TestGiteaIngestCarriesTheIssueBody(t *testing.T) { body := `[{"number":1,"title":"Add a --version flag","body":"Print one version line and exit 0. Keep --help working.","state":"open"}]` srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(body)) })) defer srv.Close() out := &sink{} g := Gitea{BaseURL: srv.URL, Owner: "kami", Repo: "test-e2e", Project: "test-e2e"} if _, err := g.Poll(context.Background(), out); err != nil { t.Fatal(err) } if len(out.events) != 1 { t.Fatalf("events = %d", len(out.events)) } var p struct { Title string `json:"title"` Description string `json:"description"` } if err := json.Unmarshal(out.events[0].Payload, &p); err != nil { t.Fatal(err) } if p.Title == "" { t.Fatal("title missing") } if !strings.Contains(p.Description, "Keep --help working") { t.Fatalf("description = %q", p.Description) } }