Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 4 additions & 1 deletion internal/adapter/github/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand All @@ -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 {
Expand Down
17 changes: 15 additions & 2 deletions internal/adapter/github/client_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package github

import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
Expand All @@ -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"}`))
}))
Expand All @@ -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)
}
Expand All @@ -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) {
Expand All @@ -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")
}
Expand Down
4 changes: 0 additions & 4 deletions internal/construction/construction.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
13 changes: 4 additions & 9 deletions internal/construction/construction_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package construction

import (
"strings"
"testing"

"github.com/replworks/ai-issue/internal/domain"
Expand Down Expand Up @@ -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)
}
}

Expand All @@ -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)
}
}

Expand Down Expand Up @@ -110,7 +109,3 @@ func TestValidatePublishableIssue(t *testing.T) {
})
}
}

func contains(s, substr string) bool {
return strings.Contains(s, substr)
}
5 changes: 1 addition & 4 deletions internal/publisher/publisher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
12 changes: 12 additions & 0 deletions internal/publisher/publisher_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package publisher

import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
Expand All @@ -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()
Expand Down Expand Up @@ -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)
}
}
Loading