diff --git a/Makefile b/Makefile index f9c4076..1773bec 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,12 @@ test: vet: go vet ./... +lint: ## runs golangci-lint via go run + golangci-lint run ./... + +lint-fix: ## automatically fix lint issues where possible + golangci-lint run --fix ./... + check: fmt-check vet test build: diff --git a/internal/adapter/github/client.go b/internal/adapter/github/client.go index ce883ed..ec82c8e 100644 --- a/internal/adapter/github/client.go +++ b/internal/adapter/github/client.go @@ -34,7 +34,7 @@ func NewClient() (*Client, error) { }, nil } -func (c *Client) CreateIssue(repo, title, body string) (string, error) { +func (c *Client) CreateIssue(repo, title, body string, labels []string) (string, error) { if c == nil { return "", fmt.Errorf("GitHub client is not initialized") } @@ -51,6 +51,9 @@ func (c *Client) CreateIssue(repo, title, body string) (string, error) { "title": title, "body": body, } + if len(labels) > 0 { + payload["labels"] = labels + } jsonData, err := json.Marshal(payload) if err != nil { diff --git a/internal/adapter/github/client_test.go b/internal/adapter/github/client_test.go index ac39af2..76bec13 100644 --- a/internal/adapter/github/client_test.go +++ b/internal/adapter/github/client_test.go @@ -1,6 +1,7 @@ package github import ( + "encoding/json" "net/http" "net/http/httptest" "strings" @@ -11,11 +12,20 @@ func TestCreateIssueSuccess(t *testing.T) { var gotMethod string var gotPath string var gotAuth string + var gotLabels []string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotMethod = r.Method gotPath = r.URL.Path gotAuth = r.Header.Get("Authorization") + var payload map[string]interface{} + _ = json.NewDecoder(r.Body).Decode(&payload) + if labels, ok := payload["labels"].([]interface{}); ok { + gotLabels = make([]string, 0, len(labels)) + for _, label := range labels { + gotLabels = append(gotLabels, label.(string)) + } + } w.WriteHeader(http.StatusCreated) _, _ = w.Write([]byte(`{"html_url":"https://github.com/company/backend/issues/1"}`)) })) @@ -27,7 +37,7 @@ func TestCreateIssueSuccess(t *testing.T) { BaseURL: server.URL, } - url, err := client.CreateIssue("company/backend", "Title", "Body") + url, err := client.CreateIssue("company/backend", "Title", "Body", []string{"ai-generated"}) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -43,6 +53,9 @@ func TestCreateIssueSuccess(t *testing.T) { if gotAuth != "token token-123" { t.Fatalf("auth = %q, want %q", gotAuth, "token token-123") } + if len(gotLabels) != 1 || gotLabels[0] != "ai-generated" { + t.Fatalf("labels = %v, want [ai-generated]", gotLabels) + } } func TestCreateIssueFailure(t *testing.T) { @@ -57,7 +70,7 @@ func TestCreateIssueFailure(t *testing.T) { BaseURL: server.URL, } - _, err := client.CreateIssue("company/backend", "Title", "Body") + _, err := client.CreateIssue("company/backend", "Title", "Body", nil) if err == nil { t.Fatal("expected error, got nil") } diff --git a/internal/construction/construction.go b/internal/construction/construction.go index edf7a9a..4fd9fd0 100644 --- a/internal/construction/construction.go +++ b/internal/construction/construction.go @@ -20,10 +20,6 @@ func BuildPublishableIssue(draft *domain.IssueDraft, repositoryName, publisherNa issue.Body = strings.TrimSpace(draft.Body) } - if publisherName != "" { - issue.Body = strings.TrimSpace(issue.Body + "\n\n**Publisher:** @" + publisherName) - } - if err := ValidatePublishableIssue(issue); err != nil { return nil, err } diff --git a/internal/construction/construction_test.go b/internal/construction/construction_test.go index b93b90d..5ef7ed5 100644 --- a/internal/construction/construction_test.go +++ b/internal/construction/construction_test.go @@ -1,7 +1,6 @@ package construction import ( - "strings" "testing" "github.com/replworks/ai-issue/internal/domain" @@ -29,8 +28,8 @@ func TestBuildPublishableIssue(t *testing.T) { if issue.Publisher != "ai-backlog-bot" { t.Fatalf("publisher = %q, want %q", issue.Publisher, "ai-backlog-bot") } - if issue.Body == draft.Body { - t.Fatal("expected publisher traceability to be preserved in body") + if issue.Body != draft.Body { + t.Fatalf("body = %q, want %q", issue.Body, draft.Body) } } @@ -47,8 +46,8 @@ func TestBuildPublishableIssueTrimsPublisherPrefix(t *testing.T) { if issue.Publisher != "project-ai-bot" { t.Fatalf("publisher = %q, want %q", issue.Publisher, "project-ai-bot") } - if !contains(issue.Body, "**Publisher:** @project-ai-bot") { - t.Fatalf("body = %q, want publisher footer", issue.Body) + if issue.Body != draft.Body { + t.Fatalf("body = %q, want %q", issue.Body, draft.Body) } } @@ -110,7 +109,3 @@ func TestValidatePublishableIssue(t *testing.T) { }) } } - -func contains(s, substr string) bool { - return strings.Contains(s, substr) -} diff --git a/internal/publisher/publisher.go b/internal/publisher/publisher.go index 1c55440..bc511ea 100644 --- a/internal/publisher/publisher.go +++ b/internal/publisher/publisher.go @@ -25,10 +25,7 @@ func (s *Service) Publish(issue *domain.PublishableIssue) (string, error) { return "", fmt.Errorf("publishable issue is required") } - // Add AI marker - fullBody := fmt.Sprintf("**AI Generated** • Published by @%s\n\n%s", s.botName, issue.Body) - - url, err := s.ghClient.CreateIssue(issue.Repository, issue.Title, fullBody) + url, err := s.ghClient.CreateIssue(issue.Repository, issue.Title, issue.Body, []string{"ai-generated"}) if err != nil { return "", err } diff --git a/internal/publisher/publisher_test.go b/internal/publisher/publisher_test.go index b901353..7f68d91 100644 --- a/internal/publisher/publisher_test.go +++ b/internal/publisher/publisher_test.go @@ -1,6 +1,7 @@ package publisher import ( + "encoding/json" "net/http" "net/http/httptest" "strings" @@ -12,10 +13,14 @@ import ( ) func TestEndToEndPublicationFlow(t *testing.T) { + var gotBody map[string]interface{} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { t.Fatalf("method = %s, want POST", r.Method) } + if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { + t.Fatalf("failed to decode body: %v", err) + } w.WriteHeader(http.StatusCreated) })) defer server.Close() @@ -44,4 +49,11 @@ func TestEndToEndPublicationFlow(t *testing.T) { if !strings.Contains(url, "company/backend") { t.Fatalf("url = %q, want repository path", url) } + if body, _ := gotBody["body"].(string); strings.Contains(body, "AI Generated") || strings.Contains(body, "Publisher:") { + t.Fatalf("body = %q, want clean issue body", body) + } + labels, _ := gotBody["labels"].([]interface{}) + if len(labels) != 1 || labels[0] != "ai-generated" { + t.Fatalf("labels = %v, want [ai-generated]", labels) + } }