Compare commits

...

5 Commits

Author SHA1 Message Date
agent-company faf5fc1797 chore: add .air.toml for live reload dev workflow
Configures air to build ./cmd/server, watch .go/.html/.css/.js files
under the project tree, and auto-restart on changes. Excludes test
files and vendor directories.

Closes leeworks-agents/gitea-mobile#109

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 21:05:30 +00:00
AI-Manager 8c390e7505 Merge pull request 'test: add 43 integration tests for all HTTP handlers' (#146) from feature/integration-tests-batch1 into master
Build and Push / test (push) Failing after 1m9s
Build and Push / build (push) Has been skipped
2026-03-28 19:03:43 +00:00
AI-Manager ffacea132c Merge pull request 'test: add unit tests for GetTriageQueue aggregation' (#147) from feature/unit-tests-triage-queue-117 into master
Build and Push / test (push) Has been cancelled
Build and Push / build (push) Has been cancelled
2026-03-28 19:03:30 +00:00
agent-company f0addf8fad test: add unit tests for GetTriageQueue aggregation
Add 4 new integration-style unit tests for GetTriageQueue using mock
HTTP servers: full integration test verifying issue filtering (assigned
vs unassigned), PR inclusion, and priority sorting; empty orgs test;
all-assigned test (expect empty queue); and label extraction test
verifying multi-label items are correctly populated.

Closes leeworks-agents/gitea-mobile#117

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 18:14:37 +00:00
agent-company 2ea20da5ef test: add 43 integration tests for all HTTP handlers
Add comprehensive integration test suite using httptest with a mock
Gitea API server. Tests cover GET and POST handlers for dashboard,
issues, pulls, issue/PR detail, create issue, state changes, comments,
labels, assignees, reviews, and settings. Both regular and HTMX
request paths are tested. Includes TestMain to set working directory
to project root for template loading.

Covers issues: #140 #139 #138 #137 #136 #135 #134 #133 #124 #118
#113 #111 #110

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 18:12:53 +00:00
3 changed files with 1321 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./cmd/server"
delay = 500
exclude_dir = ["assets", "tmp", "vendor", "testdata", ".git", "node_modules"]
exclude_file = []
exclude_regex = ["_test\\.go$"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "html", "css", "js"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = true
[screen]
clear_on_rebuild = false
keep_scroll = true
+211
View File
@@ -377,6 +377,217 @@ func sortTriageQueue(queue []TriageItem) {
}
}
// --- Issue #117: Tests for GetTriageQueue aggregation ---
func TestGetTriageQueue_Integration(t *testing.T) {
// Mock server that returns issues (some assigned, some not) and PRs.
requestCount := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestCount++
switch {
case r.URL.Path == "/api/v1/user/orgs":
json.NewEncoder(w).Encode([]Org{{Name: "org1"}})
case strings.HasPrefix(r.URL.Path, "/api/v1/orgs/org1/repos"):
json.NewEncoder(w).Encode([]Repo{
{ID: 1, Name: "repo1", FullName: "org1/repo1", Owner: struct {
Login string `json:"login"`
}{Login: "org1"}},
})
case strings.HasSuffix(r.URL.Path, "/issues") && r.Method == "GET":
// Return mix of assigned and unassigned issues.
issues := []map[string]interface{}{
{
"id": 1, "number": 1, "title": "Unassigned bug",
"state": "open", "assignee": nil, "assignees": []interface{}{},
"labels": []map[string]interface{}{{"id": 1, "name": "P1", "color": "ff0000"}},
"html_url": "http://example.com/org1/repo1/issues/1",
},
{
"id": 2, "number": 2, "title": "Assigned issue",
"state": "open",
"assignee": map[string]string{"login": "dev1", "avatar_url": ""},
"assignees": []map[string]string{{"login": "dev1", "avatar_url": ""}},
"labels": []interface{}{},
"html_url": "http://example.com/org1/repo1/issues/2",
},
{
"id": 3, "number": 3, "title": "Unassigned low priority",
"state": "open", "assignee": nil, "assignees": []interface{}{},
"labels": []map[string]interface{}{{"id": 2, "name": "P3", "color": "00ff00"}},
"html_url": "http://example.com/org1/repo1/issues/3",
},
}
json.NewEncoder(w).Encode(issues)
case strings.HasSuffix(r.URL.Path, "/pulls") && r.Method == "GET":
prs := []map[string]interface{}{
{
"id": 10, "number": 10, "title": "Open PR needs review",
"state": "open", "body": "please review",
"labels": []map[string]interface{}{{"id": 3, "name": "P2", "color": "ffff00"}},
"html_url": "http://example.com/org1/repo1/pulls/10",
"head": map[string]string{"label": "feature", "ref": "feature"},
"base": map[string]string{"label": "master", "ref": "master"},
},
}
json.NewEncoder(w).Encode(prs)
case strings.HasSuffix(r.URL.Path, "/reviews"):
json.NewEncoder(w).Encode([]interface{}{})
default:
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "unexpected request: %s %s", r.Method, r.URL.Path)
}
}))
defer server.Close()
c := NewClient(server.URL)
queue, err := c.GetTriageQueue(context.Background(), "test-token", []string{"org1"})
if err != nil {
t.Fatalf("GetTriageQueue: %v", err)
}
// Should include: 2 unassigned issues + 1 PR = 3 items.
// Assigned issue (#2) should be excluded.
if len(queue) != 3 {
t.Fatalf("expected 3 triage items, got %d", len(queue))
}
// Verify sorting: P1 > P2 > P3.
if queue[0].Title != "Unassigned bug" {
t.Errorf("queue[0] should be P1 'Unassigned bug', got %q", queue[0].Title)
}
if queue[1].Title != "Open PR needs review" {
t.Errorf("queue[1] should be P2 'Open PR needs review', got %q", queue[1].Title)
}
if queue[2].Title != "Unassigned low priority" {
t.Errorf("queue[2] should be P3 'Unassigned low priority', got %q", queue[2].Title)
}
// Verify types.
if queue[0].Type != "issue" {
t.Errorf("queue[0].Type = %q, want 'issue'", queue[0].Type)
}
if queue[1].Type != "pull" {
t.Errorf("queue[1].Type = %q, want 'pull'", queue[1].Type)
}
}
func TestGetTriageQueue_EmptyOrgs(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.URL.Path == "/api/v1/user/orgs":
json.NewEncoder(w).Encode([]Org{})
default:
json.NewEncoder(w).Encode([]interface{}{})
}
}))
defer server.Close()
c := NewClient(server.URL)
queue, err := c.GetTriageQueue(context.Background(), "test-token", []string{})
if err != nil {
t.Fatalf("GetTriageQueue with empty orgs: %v", err)
}
if len(queue) != 0 {
t.Errorf("expected empty queue for empty orgs, got %d items", len(queue))
}
}
func TestGetTriageQueue_AllAssigned(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.URL.Path == "/api/v1/user/orgs":
json.NewEncoder(w).Encode([]Org{{Name: "org1"}})
case strings.HasPrefix(r.URL.Path, "/api/v1/orgs/org1/repos"):
json.NewEncoder(w).Encode([]Repo{
{ID: 1, Name: "repo1", FullName: "org1/repo1", Owner: struct {
Login string `json:"login"`
}{Login: "org1"}},
})
case strings.HasSuffix(r.URL.Path, "/issues"):
// All issues are assigned.
json.NewEncoder(w).Encode([]map[string]interface{}{
{
"id": 1, "number": 1, "title": "Assigned issue",
"state": "open",
"assignee": map[string]string{"login": "dev1"},
"assignees": []map[string]string{{"login": "dev1"}},
"labels": []interface{}{},
"html_url": "http://example.com/org1/repo1/issues/1",
},
})
case strings.HasSuffix(r.URL.Path, "/pulls"):
json.NewEncoder(w).Encode([]interface{}{}) // No PRs.
case strings.HasSuffix(r.URL.Path, "/reviews"):
json.NewEncoder(w).Encode([]interface{}{})
default:
json.NewEncoder(w).Encode([]interface{}{})
}
}))
defer server.Close()
c := NewClient(server.URL)
queue, err := c.GetTriageQueue(context.Background(), "test-token", []string{"org1"})
if err != nil {
t.Fatalf("GetTriageQueue: %v", err)
}
// Only PRs should appear (none here), all issues are assigned.
if len(queue) != 0 {
t.Errorf("expected 0 items (all assigned), got %d", len(queue))
}
}
func TestGetTriageQueue_LabelExtraction(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.URL.Path == "/api/v1/user/orgs":
json.NewEncoder(w).Encode([]Org{{Name: "org1"}})
case strings.HasPrefix(r.URL.Path, "/api/v1/orgs/org1/repos"):
json.NewEncoder(w).Encode([]Repo{
{ID: 1, Name: "repo1", FullName: "org1/repo1", Owner: struct {
Login string `json:"login"`
}{Login: "org1"}},
})
case strings.HasSuffix(r.URL.Path, "/issues"):
json.NewEncoder(w).Encode([]map[string]interface{}{
{
"id": 1, "number": 1, "title": "Multi-label issue",
"state": "open", "assignee": nil, "assignees": []interface{}{},
"labels": []map[string]interface{}{
{"id": 1, "name": "bug", "color": "d73a4a"},
{"id": 2, "name": "P1", "color": "ff0000"},
{"id": 3, "name": "help wanted", "color": "0e8a16"},
},
"html_url": "http://example.com/org1/repo1/issues/1",
},
})
case strings.HasSuffix(r.URL.Path, "/pulls"):
json.NewEncoder(w).Encode([]interface{}{})
case strings.HasSuffix(r.URL.Path, "/reviews"):
json.NewEncoder(w).Encode([]interface{}{})
default:
json.NewEncoder(w).Encode([]interface{}{})
}
}))
defer server.Close()
c := NewClient(server.URL)
queue, err := c.GetTriageQueue(context.Background(), "test-token", []string{"org1"})
if err != nil {
t.Fatalf("GetTriageQueue: %v", err)
}
if len(queue) != 1 {
t.Fatalf("expected 1 item, got %d", len(queue))
}
if len(queue[0].Labels) != 3 {
t.Errorf("expected 3 labels, got %d: %v", len(queue[0].Labels), queue[0].Labels)
}
}
// --- Issue #122: Tests for ListOrgsAndRepos and CreateIssue ---
func TestListOrgsAndRepos(t *testing.T) {
File diff suppressed because it is too large Load Diff