- ControlPlane 라우터에 워커/에이전트 구독 채널을 추가한다 - ProtoSocket 기반 실시간 양방향 채널( dispatcher, envelope, server)을 구현한다 - Forgejo push 이벤트를 provider 어댑터로 처리하도록 연동한다 - PostgreSQL 스토리지 백엔드를 분리하여 postgres.go로 신설한다 - Git engine command에 diff/checkout/merge 연쇄 연산을 추가한다 - 런타임 모델에 agentSession, runtimeChannel 스키마를 추가한다 - 데이터베이스 마이그레이션에 agent_session, runtime_channel, lease 테이블을 추가한다 - agent-contract에 Forgejo branch events 계약 문서를 작성한다 - 로드맵 마일스톤과 아카이브 작업을 갱신한다
140 lines
3.9 KiB
Go
140 lines
3.9 KiB
Go
package gitengine
|
|
|
|
import (
|
|
"errors"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
type fakeRunner struct {
|
|
outputs []string
|
|
errs []error
|
|
calls []fakeCall
|
|
}
|
|
|
|
type fakeCall struct {
|
|
workdir string
|
|
args []string
|
|
}
|
|
|
|
func (f *fakeRunner) Run(workdir string, args ...string) (string, error) {
|
|
f.calls = append(f.calls, fakeCall{
|
|
workdir: workdir,
|
|
args: append([]string(nil), args...),
|
|
})
|
|
callIndex := len(f.calls) - 1
|
|
var output string
|
|
if callIndex < len(f.outputs) {
|
|
output = f.outputs[callIndex]
|
|
}
|
|
var err error
|
|
if callIndex < len(f.errs) {
|
|
err = f.errs[callIndex]
|
|
}
|
|
return output, err
|
|
}
|
|
|
|
func TestStatusClean(t *testing.T) {
|
|
runner := &fakeRunner{outputs: []string{"\n"}}
|
|
result, err := Status(runner, "/repo")
|
|
if err != nil {
|
|
t.Fatalf("Status: %v", err)
|
|
}
|
|
if !result.Clean {
|
|
t.Fatal("expected clean status")
|
|
}
|
|
if !reflect.DeepEqual(runner.calls[0].args, []string{"status", "--porcelain"}) {
|
|
t.Fatalf("args: %#v", runner.calls[0].args)
|
|
}
|
|
}
|
|
|
|
func TestCloneFetchCheckout(t *testing.T) {
|
|
runner := &fakeRunner{}
|
|
if err := Clone(runner, CloneOptions{
|
|
RemoteURL: "ssh://git.example/nomadcode.git",
|
|
WorktreePath: "/workspace/nomadcode/000",
|
|
Branch: "develop",
|
|
}); err != nil {
|
|
t.Fatalf("Clone: %v", err)
|
|
}
|
|
if err := Fetch(runner, "/workspace/nomadcode/000", ""); err != nil {
|
|
t.Fatalf("Fetch: %v", err)
|
|
}
|
|
if err := Checkout(runner, "/workspace/nomadcode/000", "feature/a"); err != nil {
|
|
t.Fatalf("Checkout: %v", err)
|
|
}
|
|
|
|
want := []fakeCall{
|
|
{args: []string{"clone", "--branch", "develop", "ssh://git.example/nomadcode.git", "/workspace/nomadcode/000"}},
|
|
{workdir: "/workspace/nomadcode/000", args: []string{"fetch", "--prune", "origin"}},
|
|
{workdir: "/workspace/nomadcode/000", args: []string{"checkout", "feature/a"}},
|
|
}
|
|
if !reflect.DeepEqual(runner.calls, want) {
|
|
t.Fatalf("calls:\n got %#v\nwant %#v", runner.calls, want)
|
|
}
|
|
}
|
|
|
|
func TestCommitAndPush(t *testing.T) {
|
|
runner := &fakeRunner{}
|
|
if err := Commit(runner, "/repo", CommitOptions{Message: "sync roadmap", All: true}); err != nil {
|
|
t.Fatalf("Commit: %v", err)
|
|
}
|
|
if err := Push(runner, "/repo", "", "develop"); err != nil {
|
|
t.Fatalf("Push: %v", err)
|
|
}
|
|
want := []fakeCall{
|
|
{workdir: "/repo", args: []string{"add", "-A"}},
|
|
{workdir: "/repo", args: []string{"commit", "-m", "sync roadmap"}},
|
|
{workdir: "/repo", args: []string{"push", "origin", "develop"}},
|
|
}
|
|
if !reflect.DeepEqual(runner.calls, want) {
|
|
t.Fatalf("calls:\n got %#v\nwant %#v", runner.calls, want)
|
|
}
|
|
}
|
|
|
|
func TestHeadRevision(t *testing.T) {
|
|
runner := &fakeRunner{outputs: []string{"abc123\n"}}
|
|
got, err := HeadRevision(runner, "/repo")
|
|
if err != nil {
|
|
t.Fatalf("HeadRevision: %v", err)
|
|
}
|
|
if got != "abc123" {
|
|
t.Fatalf("revision: got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestChangedFiles(t *testing.T) {
|
|
runner := &fakeRunner{outputs: []string{"README.md\nservices/core/main.go\n\n"}}
|
|
files, err := ChangedFiles(runner, "/repo", "abc", "def")
|
|
if err != nil {
|
|
t.Fatalf("ChangedFiles: %v", err)
|
|
}
|
|
want := []string{"README.md", "services/core/main.go"}
|
|
if !reflect.DeepEqual(files, want) {
|
|
t.Fatalf("files: got %#v want %#v", files, want)
|
|
}
|
|
}
|
|
|
|
func TestChangedFilesRequiresRevisions(t *testing.T) {
|
|
_, err := ChangedFiles(&fakeRunner{}, "/repo", "", "def")
|
|
if !errors.Is(err, ErrInvalidGitInput) {
|
|
t.Fatalf("expected ErrInvalidGitInput, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestChangedFilesWithStatus(t *testing.T) {
|
|
runner := &fakeRunner{outputs: []string{"A\tREADME.md\nM\tservices/core/main.go\nD\told.md\nR100\tfrom.md\tto.md\n"}}
|
|
files, err := ChangedFilesWithStatus(runner, "/repo", "abc", "def")
|
|
if err != nil {
|
|
t.Fatalf("ChangedFilesWithStatus: %v", err)
|
|
}
|
|
want := []ChangedFile{
|
|
{Path: "README.md", ChangeType: "added"},
|
|
{Path: "services/core/main.go", ChangeType: "modified"},
|
|
{Path: "old.md", ChangeType: "deleted"},
|
|
{Path: "to.md", ChangeType: "renamed"},
|
|
}
|
|
if !reflect.DeepEqual(files, want) {
|
|
t.Fatalf("files:\n got %#v\nwant %#v", files, want)
|
|
}
|
|
}
|