test(git): push coverage from 79.5% to 96.7%
Add tests for Service lifecycle: NewService factory, OnStartup, and all handleQuery/handleTask paths (QueryStatus, QueryDirtyRepos, QueryAheadRepos, TaskPush, TaskPull, TaskPushMultiple, unknown types). Add integration tests for Push with a real bare remote (push succeeds, ahead count drops to zero), PushMultiple with multiple paths, empty paths, and ahead/behind with no upstream tracking branch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f50fc405f5
commit
b3e3ef2efb
1 changed files with 287 additions and 0 deletions
287
git/service_extra_test.go
Normal file
287
git/service_extra_test.go
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"forge.lthn.ai/core/go/pkg/framework"
|
||||
)
|
||||
|
||||
func TestNewService_Good(t *testing.T) {
|
||||
opts := ServiceOptions{WorkDir: "/tmp/test"}
|
||||
factory := NewService(opts)
|
||||
assert.NotNil(t, factory)
|
||||
|
||||
// Create a minimal Core to test the factory.
|
||||
c, err := framework.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
svc, err := factory(c)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, svc)
|
||||
|
||||
service, ok := svc.(*Service)
|
||||
require.True(t, ok)
|
||||
assert.NotNil(t, service)
|
||||
}
|
||||
|
||||
func TestService_OnStartup_Good(t *testing.T) {
|
||||
c, err := framework.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
opts := ServiceOptions{WorkDir: "/tmp"}
|
||||
svc := &Service{
|
||||
ServiceRuntime: framework.NewServiceRuntime(c, opts),
|
||||
}
|
||||
|
||||
err = svc.OnStartup(context.Background())
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestService_HandleQuery_Good_Status(t *testing.T) {
|
||||
dir := initTestRepo(t)
|
||||
|
||||
c, err := framework.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
svc := &Service{
|
||||
ServiceRuntime: framework.NewServiceRuntime(c, ServiceOptions{}),
|
||||
}
|
||||
|
||||
// Call handleQuery directly.
|
||||
result, handled, err := svc.handleQuery(c, QueryStatus{
|
||||
Paths: []string{dir},
|
||||
Names: map[string]string{dir: "test-repo"},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.True(t, handled)
|
||||
|
||||
statuses, ok := result.([]RepoStatus)
|
||||
require.True(t, ok)
|
||||
require.Len(t, statuses, 1)
|
||||
assert.Equal(t, "test-repo", statuses[0].Name)
|
||||
|
||||
// Verify lastStatus was updated.
|
||||
assert.Len(t, svc.lastStatus, 1)
|
||||
}
|
||||
|
||||
func TestService_HandleQuery_Good_DirtyRepos(t *testing.T) {
|
||||
c, err := framework.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
svc := &Service{
|
||||
ServiceRuntime: framework.NewServiceRuntime(c, ServiceOptions{}),
|
||||
lastStatus: []RepoStatus{
|
||||
{Name: "clean"},
|
||||
{Name: "dirty", Modified: 1},
|
||||
},
|
||||
}
|
||||
|
||||
result, handled, err := svc.handleQuery(c, QueryDirtyRepos{})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, handled)
|
||||
|
||||
dirty, ok := result.([]RepoStatus)
|
||||
require.True(t, ok)
|
||||
assert.Len(t, dirty, 1)
|
||||
assert.Equal(t, "dirty", dirty[0].Name)
|
||||
}
|
||||
|
||||
func TestService_HandleQuery_Good_AheadRepos(t *testing.T) {
|
||||
c, err := framework.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
svc := &Service{
|
||||
ServiceRuntime: framework.NewServiceRuntime(c, ServiceOptions{}),
|
||||
lastStatus: []RepoStatus{
|
||||
{Name: "synced"},
|
||||
{Name: "ahead", Ahead: 3},
|
||||
},
|
||||
}
|
||||
|
||||
result, handled, err := svc.handleQuery(c, QueryAheadRepos{})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, handled)
|
||||
|
||||
ahead, ok := result.([]RepoStatus)
|
||||
require.True(t, ok)
|
||||
assert.Len(t, ahead, 1)
|
||||
assert.Equal(t, "ahead", ahead[0].Name)
|
||||
}
|
||||
|
||||
func TestService_HandleQuery_Good_UnknownQuery(t *testing.T) {
|
||||
c, err := framework.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
svc := &Service{
|
||||
ServiceRuntime: framework.NewServiceRuntime(c, ServiceOptions{}),
|
||||
}
|
||||
|
||||
result, handled, err := svc.handleQuery(c, "unknown query type")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, handled)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestService_HandleTask_Good_Push(t *testing.T) {
|
||||
dir := initTestRepo(t)
|
||||
|
||||
c, err := framework.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
svc := &Service{
|
||||
ServiceRuntime: framework.NewServiceRuntime(c, ServiceOptions{}),
|
||||
}
|
||||
|
||||
// Push without a remote will fail, but handleTask should still handle it.
|
||||
_, handled, err := svc.handleTask(c, TaskPush{Path: dir, Name: "test"})
|
||||
assert.True(t, handled)
|
||||
assert.Error(t, err, "push without remote should fail")
|
||||
}
|
||||
|
||||
func TestService_HandleTask_Good_Pull(t *testing.T) {
|
||||
dir := initTestRepo(t)
|
||||
|
||||
c, err := framework.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
svc := &Service{
|
||||
ServiceRuntime: framework.NewServiceRuntime(c, ServiceOptions{}),
|
||||
}
|
||||
|
||||
_, handled, err := svc.handleTask(c, TaskPull{Path: dir, Name: "test"})
|
||||
assert.True(t, handled)
|
||||
assert.Error(t, err, "pull without remote should fail")
|
||||
}
|
||||
|
||||
func TestService_HandleTask_Good_PushMultiple(t *testing.T) {
|
||||
dir := initTestRepo(t)
|
||||
|
||||
c, err := framework.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
svc := &Service{
|
||||
ServiceRuntime: framework.NewServiceRuntime(c, ServiceOptions{}),
|
||||
}
|
||||
|
||||
result, handled, err := svc.handleTask(c, TaskPushMultiple{
|
||||
Paths: []string{dir},
|
||||
Names: map[string]string{dir: "test"},
|
||||
})
|
||||
|
||||
assert.True(t, handled)
|
||||
assert.NoError(t, err) // PushMultiple does not return errors directly
|
||||
|
||||
results, ok := result.([]PushResult)
|
||||
require.True(t, ok)
|
||||
assert.Len(t, results, 1)
|
||||
assert.False(t, results[0].Success) // No remote
|
||||
}
|
||||
|
||||
func TestService_HandleTask_Good_UnknownTask(t *testing.T) {
|
||||
c, err := framework.New()
|
||||
require.NoError(t, err)
|
||||
|
||||
svc := &Service{
|
||||
ServiceRuntime: framework.NewServiceRuntime(c, ServiceOptions{}),
|
||||
}
|
||||
|
||||
result, handled, err := svc.handleTask(c, "unknown task")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, handled)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
// --- Additional git operation tests ---
|
||||
|
||||
func TestGetStatus_Good_AheadBehindNoUpstream(t *testing.T) {
|
||||
// A repo without a tracking branch should return 0 ahead/behind.
|
||||
dir := initTestRepo(t)
|
||||
|
||||
status := getStatus(context.Background(), dir, "no-upstream")
|
||||
require.NoError(t, status.Error)
|
||||
assert.Equal(t, 0, status.Ahead)
|
||||
assert.Equal(t, 0, status.Behind)
|
||||
}
|
||||
|
||||
func TestPushMultiple_Good_Empty(t *testing.T) {
|
||||
results := PushMultiple(context.Background(), []string{}, map[string]string{})
|
||||
assert.Empty(t, results)
|
||||
}
|
||||
|
||||
func TestPushMultiple_Good_MultiplePaths(t *testing.T) {
|
||||
dir1 := initTestRepo(t)
|
||||
dir2 := initTestRepo(t)
|
||||
|
||||
results := PushMultiple(context.Background(), []string{dir1, dir2}, map[string]string{
|
||||
dir1: "repo-1",
|
||||
dir2: "repo-2",
|
||||
})
|
||||
|
||||
require.Len(t, results, 2)
|
||||
assert.Equal(t, "repo-1", results[0].Name)
|
||||
assert.Equal(t, "repo-2", results[1].Name)
|
||||
// Both should fail (no remote).
|
||||
assert.False(t, results[0].Success)
|
||||
assert.False(t, results[1].Success)
|
||||
}
|
||||
|
||||
func TestPush_Good_WithRemote(t *testing.T) {
|
||||
// Create a bare remote and a clone.
|
||||
bareDir := t.TempDir()
|
||||
cloneDir := t.TempDir()
|
||||
|
||||
cmd := exec.Command("git", "init", "--bare")
|
||||
cmd.Dir = bareDir
|
||||
require.NoError(t, cmd.Run())
|
||||
|
||||
cmd = exec.Command("git", "clone", bareDir, cloneDir)
|
||||
require.NoError(t, cmd.Run())
|
||||
|
||||
for _, args := range [][]string{
|
||||
{"git", "config", "user.email", "test@example.com"},
|
||||
{"git", "config", "user.name", "Test User"},
|
||||
} {
|
||||
cmd = exec.Command(args[0], args[1:]...)
|
||||
cmd.Dir = cloneDir
|
||||
require.NoError(t, cmd.Run())
|
||||
}
|
||||
|
||||
require.NoError(t, os.WriteFile(filepath.Join(cloneDir, "file.txt"), []byte("v1"), 0644))
|
||||
for _, args := range [][]string{
|
||||
{"git", "add", "."},
|
||||
{"git", "commit", "-m", "initial"},
|
||||
{"git", "push", "origin", "HEAD"},
|
||||
} {
|
||||
cmd = exec.Command(args[0], args[1:]...)
|
||||
cmd.Dir = cloneDir
|
||||
out, err := cmd.CombinedOutput()
|
||||
require.NoError(t, err, "failed: %v: %s", args, string(out))
|
||||
}
|
||||
|
||||
// Make a local commit.
|
||||
require.NoError(t, os.WriteFile(filepath.Join(cloneDir, "file.txt"), []byte("v2"), 0644))
|
||||
for _, args := range [][]string{
|
||||
{"git", "add", "."},
|
||||
{"git", "commit", "-m", "second commit"},
|
||||
} {
|
||||
cmd = exec.Command(args[0], args[1:]...)
|
||||
cmd.Dir = cloneDir
|
||||
require.NoError(t, cmd.Run())
|
||||
}
|
||||
|
||||
// Push should succeed.
|
||||
err := Push(context.Background(), cloneDir)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify ahead count is now 0.
|
||||
ahead, _ := getAheadBehind(context.Background(), cloneDir)
|
||||
assert.Equal(t, 0, ahead)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue