package writeback import ( "context" "encoding/json" "io" "net/http" "net/http/httptest" "strings" "sync" "testing" "ukrrs.com/mopac/harness/internal/config" ) func TestRedmineWriterNoteAndStatus(t *testing.T) { var mu sync.Mutex var puts []struct { path string body string } srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if got := r.Header.Get("X-Redmine-API-Key"); got != "rm-key" { http.Error(w, "bad key", http.StatusUnauthorized) return } switch { case r.Method == http.MethodPut && r.URL.Path == "/issues/421.json": raw, _ := io.ReadAll(r.Body) mu.Lock() puts = append(puts, struct{ path, body string }{r.URL.Path, string(raw)}) mu.Unlock() w.WriteHeader(http.StatusNoContent) case r.Method == http.MethodGet && r.URL.Path == "/issue_statuses.json": w.Write([]byte(`{"issue_statuses":[{"id":2,"name":"In Progress"},{"id":3,"name":"Done"}]}`)) case r.Method == http.MethodGet && r.URL.Path == "/issues/421.json": w.Write([]byte(`{"issue":{"id":421,"updated_on":"2026-08-28T22:31:11Z"}}`)) default: http.NotFound(w, r) } })) defer srv.Close() w := NewRedmineWriter(config.RedmineConfig{URL: srv.URL}, "rm-key") if err := w.AddNote(context.Background(), "421", "REPORT body here"); err != nil { t.Fatalf("AddNote: %v", err) } if err := w.SetStatus(context.Background(), "421", "Done"); err != nil { t.Fatalf("SetStatus: %v", err) } updated, err := w.UpdatedOn(context.Background(), "421") if err != nil || updated != "2026-08-28T22:31:11Z" { t.Fatalf("UpdatedOn = %q err=%v", updated, err) } mu.Lock() defer mu.Unlock() if len(puts) != 2 { t.Fatalf("PUTs = %d, want 2 (note + status)", len(puts)) } var noteBody struct { Issue struct { Notes string `json:"notes"` } `json:"issue"` } if err := json.Unmarshal([]byte(puts[0].body), ¬eBody); err != nil { t.Fatalf("note PUT body: %v", err) } if noteBody.Issue.Notes != "REPORT body here" { t.Errorf("note body = %+v", noteBody) } var statusBody struct { Issue struct { StatusID int `json:"status_id"` } `json:"issue"` } if err := json.Unmarshal([]byte(puts[1].body), &statusBody); err != nil { t.Fatalf("status PUT body: %v", err) } if statusBody.Issue.StatusID != 3 { t.Errorf("status_id = %d, want 3 (Done)", statusBody.Issue.StatusID) } } func TestRedmineWriterUnknownStatus(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/issue_statuses.json" { w.Write([]byte(`{"issue_statuses":[{"id":3,"name":"Done"}]}`)) return } http.NotFound(w, r) })) defer srv.Close() w := NewRedmineWriter(config.RedmineConfig{URL: srv.URL}, "k") err := w.SetStatus(context.Background(), "421", "Nonexistent") if err == nil || !strings.Contains(err.Error(), "not found") { t.Fatalf("err = %v, want unknown-status error", err) } } func TestGiteaWriterCreateAndUpdate(t *testing.T) { var mu sync.Mutex sha := "" var methods []string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if got := r.Header.Get("Authorization"); got != "token gt-key" { http.Error(w, "bad token", http.StatusUnauthorized) return } if !strings.HasPrefix(r.URL.Path, "/api/v1/repos/ukrrs/reports/contents/") { http.NotFound(w, r) return } mu.Lock() defer mu.Unlock() switch r.Method { case http.MethodGet: if sha == "" { http.Error(w, `{"message":"Not Found"}`, http.StatusNotFound) return } w.Write([]byte(`{"sha":"` + sha + `","type":"file"}`)) case http.MethodPost, http.MethodPut: methods = append(methods, r.Method) raw, _ := io.ReadAll(r.Body) var body map[string]any json.Unmarshal(raw, &body) if body["branch"] != "main" { t.Errorf("branch = %v, want main", body["branch"]) } if r.Method == http.MethodPut && body["sha"] == nil { t.Errorf("update PUT must carry the existing sha") } sha = "newsha" w.Write([]byte(`{"content":{"path":"reports/x.md"}}`)) } })) defer srv.Close() w := NewGiteaWriter(config.GiteaConfig{ URL: srv.URL, Owner: "ukrrs", Repo: "reports", Branch: "main", }, "gt-key") if err := w.CommitFile(context.Background(), "reports/x.md", "hello", "first"); err != nil { t.Fatalf("create: %v", err) } if err := w.CommitFile(context.Background(), "reports/x.md", "hello again", "second"); err != nil { t.Fatalf("update: %v", err) } mu.Lock() defer mu.Unlock() if len(methods) != 2 || methods[0] != http.MethodPost || methods[1] != http.MethodPut { t.Errorf("methods = %v, want [POST PUT] (create then update)", methods) } }