Phase 1 test coverage for the three 0% packages plus agentci/ improvement: - git/ (0% -> 79.5%): RepoStatus methods, status parsing with real temp repos, multi-repo parallel status, Push/Pull error paths, ahead/behind with bare remote, context cancellation, GitError, IsNonFastForward, service DirtyRepos/AheadRepos filtering - forge/ (0% -> 91.2%): All SDK wrapper functions tested via httptest mock server — client creation, repos, issues, PRs, labels, webhooks, orgs, meta, config resolution, SetPRDraft raw HTTP endpoint - gitea/ (0% -> 89.2%): All SDK wrapper functions tested via httptest mock server — client creation, repos, issues, PRs, meta, config resolution - agentci/ (56% -> 94.5%): Clotho DeterminePlan all code paths, security helpers (SanitizePath, EscapeShellArg, SecureSSHCommand, MaskToken) Key findings documented in FINDINGS.md: - Forgejo SDK validates token via HTTP on NewClient() - SDK route patterns differ from public API docs (/org/ vs /orgs/) - Gitea SDK requires auth token for GitHub mirror creation - Config resolution priority verified: config file < env vars < flags Co-Authored-By: Charon <developers@lethean.io>
155 lines
3.5 KiB
Go
155 lines
3.5 KiB
Go
package git
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// --- Service helper method tests ---
|
|
// These test DirtyRepos/AheadRepos filtering without needing the framework.
|
|
|
|
func TestService_DirtyRepos_Good(t *testing.T) {
|
|
s := &Service{
|
|
lastStatus: []RepoStatus{
|
|
{Name: "clean", Modified: 0, Untracked: 0, Staged: 0},
|
|
{Name: "dirty-modified", Modified: 2},
|
|
{Name: "dirty-untracked", Untracked: 1},
|
|
{Name: "dirty-staged", Staged: 3},
|
|
{Name: "errored", Modified: 5, Error: assert.AnError},
|
|
},
|
|
}
|
|
|
|
dirty := s.DirtyRepos()
|
|
assert.Len(t, dirty, 3)
|
|
|
|
names := make([]string, len(dirty))
|
|
for i, d := range dirty {
|
|
names[i] = d.Name
|
|
}
|
|
assert.Contains(t, names, "dirty-modified")
|
|
assert.Contains(t, names, "dirty-untracked")
|
|
assert.Contains(t, names, "dirty-staged")
|
|
}
|
|
|
|
func TestService_DirtyRepos_Good_NoneFound(t *testing.T) {
|
|
s := &Service{
|
|
lastStatus: []RepoStatus{
|
|
{Name: "clean1"},
|
|
{Name: "clean2"},
|
|
},
|
|
}
|
|
|
|
dirty := s.DirtyRepos()
|
|
assert.Empty(t, dirty)
|
|
}
|
|
|
|
func TestService_DirtyRepos_Good_EmptyStatus(t *testing.T) {
|
|
s := &Service{}
|
|
dirty := s.DirtyRepos()
|
|
assert.Empty(t, dirty)
|
|
}
|
|
|
|
func TestService_AheadRepos_Good(t *testing.T) {
|
|
s := &Service{
|
|
lastStatus: []RepoStatus{
|
|
{Name: "up-to-date", Ahead: 0},
|
|
{Name: "ahead-by-one", Ahead: 1},
|
|
{Name: "ahead-by-five", Ahead: 5},
|
|
{Name: "behind-only", Behind: 3},
|
|
{Name: "errored-ahead", Ahead: 2, Error: assert.AnError},
|
|
},
|
|
}
|
|
|
|
ahead := s.AheadRepos()
|
|
assert.Len(t, ahead, 2)
|
|
|
|
names := make([]string, len(ahead))
|
|
for i, a := range ahead {
|
|
names[i] = a.Name
|
|
}
|
|
assert.Contains(t, names, "ahead-by-one")
|
|
assert.Contains(t, names, "ahead-by-five")
|
|
}
|
|
|
|
func TestService_AheadRepos_Good_NoneFound(t *testing.T) {
|
|
s := &Service{
|
|
lastStatus: []RepoStatus{
|
|
{Name: "synced1"},
|
|
{Name: "synced2"},
|
|
},
|
|
}
|
|
|
|
ahead := s.AheadRepos()
|
|
assert.Empty(t, ahead)
|
|
}
|
|
|
|
func TestService_AheadRepos_Good_EmptyStatus(t *testing.T) {
|
|
s := &Service{}
|
|
ahead := s.AheadRepos()
|
|
assert.Empty(t, ahead)
|
|
}
|
|
|
|
func TestService_Status_Good(t *testing.T) {
|
|
expected := []RepoStatus{
|
|
{Name: "repo1", Branch: "main"},
|
|
{Name: "repo2", Branch: "develop"},
|
|
}
|
|
s := &Service{lastStatus: expected}
|
|
|
|
assert.Equal(t, expected, s.Status())
|
|
}
|
|
|
|
func TestService_Status_Good_NilSlice(t *testing.T) {
|
|
s := &Service{}
|
|
assert.Nil(t, s.Status())
|
|
}
|
|
|
|
// --- Query/Task type tests ---
|
|
|
|
func TestQueryStatus_MapsToStatusOptions(t *testing.T) {
|
|
q := QueryStatus{
|
|
Paths: []string{"/path/a", "/path/b"},
|
|
Names: map[string]string{"/path/a": "repo-a"},
|
|
}
|
|
|
|
// QueryStatus can be cast directly to StatusOptions.
|
|
opts := StatusOptions(q)
|
|
assert.Equal(t, q.Paths, opts.Paths)
|
|
assert.Equal(t, q.Names, opts.Names)
|
|
}
|
|
|
|
func TestServiceOptions_WorkDir(t *testing.T) {
|
|
opts := ServiceOptions{WorkDir: "/home/claude/repos"}
|
|
assert.Equal(t, "/home/claude/repos", opts.WorkDir)
|
|
}
|
|
|
|
// --- DirtyRepos excludes errored repos ---
|
|
|
|
func TestService_DirtyRepos_Good_ExcludesErrors(t *testing.T) {
|
|
s := &Service{
|
|
lastStatus: []RepoStatus{
|
|
{Name: "dirty-ok", Modified: 1},
|
|
{Name: "dirty-error", Modified: 1, Error: assert.AnError},
|
|
},
|
|
}
|
|
|
|
dirty := s.DirtyRepos()
|
|
assert.Len(t, dirty, 1)
|
|
assert.Equal(t, "dirty-ok", dirty[0].Name)
|
|
}
|
|
|
|
// --- AheadRepos excludes errored repos ---
|
|
|
|
func TestService_AheadRepos_Good_ExcludesErrors(t *testing.T) {
|
|
s := &Service{
|
|
lastStatus: []RepoStatus{
|
|
{Name: "ahead-ok", Ahead: 2},
|
|
{Name: "ahead-error", Ahead: 3, Error: assert.AnError},
|
|
},
|
|
}
|
|
|
|
ahead := s.AheadRepos()
|
|
assert.Len(t, ahead, 1)
|
|
assert.Equal(t, "ahead-ok", ahead[0].Name)
|
|
}
|