Files
2026-06-22 08:55:57 +08:00

111 lines
3.8 KiB
Go

package docs
import (
"context"
"encoding/json"
"errors"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/yan1h/agent-coding-workflow/internal/infra/git"
"github.com/yan1h/agent-coding-workflow/internal/jobs"
)
type fakeDocsRepo struct {
summaries map[string]*Summary // keyed by commitSHA|kind
upserts int
}
func key(commit, kind string) string { return commit + "|" + kind }
func newFakeDocsRepo() *fakeDocsRepo {
return &fakeDocsRepo{summaries: map[string]*Summary{}}
}
func (r *fakeDocsRepo) Get(_ context.Context, _ uuid.UUID, commit, kind string) (*Summary, error) {
return r.summaries[key(commit, kind)], nil
}
func (r *fakeDocsRepo) Upsert(_ context.Context, in UpsertInput) (*Summary, error) {
r.upserts++
s := &Summary{
ID: uuid.New(), WorkspaceID: in.WorkspaceID, WorktreeID: in.WorktreeID,
Branch: in.Branch, CommitSHA: in.CommitSHA, Kind: in.Kind, Title: in.Title,
BodyMD: in.BodyMD, Model: in.Model, Status: in.Status, Error: in.Error,
}
r.summaries[key(in.CommitSHA, in.Kind)] = s
return s, nil
}
type fakeLocator struct{}
func (fakeLocator) LocateWorkspace(context.Context, uuid.UUID) (string, error) { return "/tmp/x", nil }
func (fakeLocator) LocateWorktree(context.Context, uuid.UUID) (string, error) { return "/tmp/x", nil }
// gitShim embeds git.Runner (nil) and overrides only DiffStat, the single method
// the SummaryRunner invokes. Any other call would panic, which never happens here.
type gitShim struct {
git.Runner
diff string
}
func (g gitShim) DiffStat(context.Context, string, string) (string, error) { return g.diff, nil }
type fakeSummarizer struct {
title, body, model string
err error
}
func (s fakeSummarizer) Summarize(context.Context, string, string, string) (string, string, string, error) {
return s.title, s.body, s.model, s.err
}
func newRunner(repo Repository, summ Summarizer, diff string) *SummaryRunner {
return NewSummaryRunner(repo, fakeLocator{}, gitShim{diff: diff}, summ, nil)
}
func TestSummaryRunner_Success(t *testing.T) {
repo := newFakeDocsRepo()
wsID := uuid.New()
r := newRunner(repo, fakeSummarizer{title: "Add feature X", body: "# Add feature X\n\n- did stuff", model: "m1"}, "f.go | 2 +-")
payload, _ := json.Marshal(summaryPayload{WorkspaceID: wsID, Branch: "main", CommitSHA: "abc"})
require.NoError(t, r.Handle(context.Background(), jobs.Job{Payload: payload}))
s := repo.summaries[key("abc", KindCommit)]
require.NotNil(t, s)
require.Equal(t, StatusCompleted, s.Status)
require.Equal(t, "# Add feature X\n\n- did stuff", s.BodyMD)
require.NotNil(t, s.Title)
require.Equal(t, "Add feature X", *s.Title)
require.NotNil(t, s.Model)
require.Equal(t, "m1", *s.Model)
}
func TestSummaryRunner_IdempotentWhenCompleted(t *testing.T) {
repo := newFakeDocsRepo()
wsID := uuid.New()
repo.summaries[key("abc", KindCommit)] = &Summary{CommitSHA: "abc", Kind: KindCommit, Status: StatusCompleted}
r := newRunner(repo, fakeSummarizer{title: "x", body: "y", model: "m"}, "stat")
payload, _ := json.Marshal(summaryPayload{WorkspaceID: wsID, Branch: "main", CommitSHA: "abc"})
require.NoError(t, r.Handle(context.Background(), jobs.Job{Payload: payload}))
require.Equal(t, 0, repo.upserts, "completed summary must not be regenerated")
}
func TestSummaryRunner_FailurePersistsFailedRow(t *testing.T) {
repo := newFakeDocsRepo()
wsID := uuid.New()
r := newRunner(repo, fakeSummarizer{err: errors.New("llm down")}, "stat")
payload, _ := json.Marshal(summaryPayload{WorkspaceID: wsID, Branch: "main", CommitSHA: "abc"})
err := r.Handle(context.Background(), jobs.Job{Payload: payload})
require.Error(t, err)
s := repo.summaries[key("abc", KindCommit)]
require.NotNil(t, s)
require.Equal(t, StatusFailed, s.Status)
require.NotNil(t, s.Error)
}
var _ Repository = (*fakeDocsRepo)(nil)