220 lines
6.3 KiB
Go
220 lines
6.3 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"lodge/internal/gitrepo"
|
|
"lodge/internal/protocol"
|
|
)
|
|
|
|
func issueCode(t *testing.T, url string) string {
|
|
t.Helper()
|
|
var code protocol.CodeResponse
|
|
admin := &Client{URL: url, Token: adminToken}
|
|
if err := admin.call(context.Background(), http.MethodPost, "/api/codes", nil, &code); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return code.Code
|
|
}
|
|
|
|
func (m *machine) remote(url, code string) {
|
|
m.t.Helper()
|
|
ctx := context.Background()
|
|
if code != "" {
|
|
if err := m.config.Connect(ctx, url, code); err != nil {
|
|
m.t.Fatal(err)
|
|
}
|
|
}
|
|
if err := m.config.Bind(repoKey(m.t, m), url); err != nil {
|
|
m.t.Fatal(err)
|
|
}
|
|
m.sync()
|
|
}
|
|
|
|
func revisionsOn(t *testing.T, url, key string) []protocol.Revision {
|
|
t.Helper()
|
|
revisions, err := (&Client{URL: url, Token: adminToken}).Revisions(context.Background(), key, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return revisions
|
|
}
|
|
|
|
func TestBoundRepositoryNeverReachesTheDefaultServer(t *testing.T) {
|
|
personal, event, origin := newServer(t), newServer(t), newOrigin(t)
|
|
mac := newMachine(t, origin, personal, adminToken)
|
|
key := repoKey(t, mac)
|
|
ctx := context.Background()
|
|
|
|
if err := mac.config.Hook(ctx, mac.root); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stale, err := mac.config.reload()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mac.remote(event+"/", issueCode(t, event))
|
|
write(t, mac.root, "docs/agents/plan.md", "event plan")
|
|
if _, err := stale.Sync(ctx, mac.root, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
worktree := filepath.Join(t.TempDir(), "feature")
|
|
git(t, mac.root, "worktree", "add", "-b", "feature", worktree)
|
|
worktree, _ = filepath.EvalSymlinks(worktree)
|
|
if err := mac.config.Hook(ctx, worktree); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if data, _ := os.ReadFile(filepath.Join(worktree, "docs/agents/plan.md")); string(data) != "event plan" {
|
|
t.Fatalf("new worktree of a bound repository got %q", data)
|
|
}
|
|
|
|
other := newMachine(t, newOrigin(t), personal, adminToken)
|
|
other.config = mac.config
|
|
write(t, other.root, "docs/agents/private.md", "personal")
|
|
other.sync()
|
|
|
|
if revisions := revisionsOn(t, personal, key); len(revisions) != 0 {
|
|
t.Fatalf("the default server received %d revisions of a bound repository", len(revisions))
|
|
}
|
|
if revisions := revisionsOn(t, event, key); len(revisions) != 1 {
|
|
t.Fatalf("the event server has %d revisions, want 1", len(revisions))
|
|
}
|
|
if revisions := revisionsOn(t, event, repoKey(t, other)); len(revisions) != 0 {
|
|
t.Fatal("an unbound repository reached the event server")
|
|
}
|
|
if revisions := revisionsOn(t, personal, repoKey(t, other)); len(revisions) != 1 {
|
|
t.Fatal("an unbound repository did not reach the default server")
|
|
}
|
|
|
|
if c, err := mac.config.Server(key); err != nil || c.URL != event {
|
|
t.Fatalf("server of the bound repository = %v (%v), want %s", c, err, event)
|
|
}
|
|
}
|
|
|
|
func TestMachineWithoutDefaultServer(t *testing.T) {
|
|
event, origin := newServer(t), newOrigin(t)
|
|
mac := newMachine(t, origin, event, adminToken)
|
|
write(t, mac.root, "docs/agents/plan.md", "plan")
|
|
mac.sync()
|
|
|
|
fresh := newMachine(t, origin, "", "")
|
|
if _, err := fresh.config.Sync(context.Background(), fresh.root, true); !errors.Is(err, ErrNotLoggedIn) {
|
|
t.Fatalf("sync without any server: %v", err)
|
|
}
|
|
if roots := fresh.config.Worktrees(); len(roots) != 0 {
|
|
t.Fatalf("a repository without a server was registered: %v", roots)
|
|
}
|
|
if err := fresh.config.Connect(context.Background(), event, issueCode(t, event)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fresh.remote(event, "")
|
|
fresh.expect("docs/agents/plan.md", "plan")
|
|
if fresh.config.URL != "" {
|
|
t.Fatalf("lodge remote set the default server to %q", fresh.config.URL)
|
|
}
|
|
}
|
|
|
|
func TestMovingBetweenServersKeepsLocalFiles(t *testing.T) {
|
|
personal, event, origin := newServer(t), newServer(t), newOrigin(t)
|
|
mac := newMachine(t, origin, personal, adminToken)
|
|
dell := newMachine(t, origin, "", "")
|
|
key := repoKey(t, mac)
|
|
|
|
write(t, mac.root, "docs/agents/mine.md", "mine")
|
|
mac.sync()
|
|
dell.remote(event, issueCode(t, event))
|
|
write(t, dell.root, "docs/agents/theirs.md", "theirs")
|
|
dell.sync()
|
|
|
|
mac.remote(event, issueCode(t, event))
|
|
mac.expect("docs/agents/mine.md", "mine")
|
|
mac.expect("docs/agents/theirs.md", "theirs")
|
|
dell.sync()
|
|
dell.expect("docs/agents/mine.md", "mine")
|
|
|
|
if err := mac.config.Bind(key, ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mac.sync()
|
|
mac.expect("docs/agents/mine.md", "mine")
|
|
mac.expect("docs/agents/theirs.md", "theirs")
|
|
state, err := mac.client.State(context.Background(), key, "")
|
|
if err != nil || len(state.Spaces[protocol.SharedSpace]) != 2 {
|
|
t.Fatalf("back on the default server: %v (%v)", state.Spaces, err)
|
|
}
|
|
}
|
|
|
|
func TestStateFromBeforeServersIsAdopted(t *testing.T) {
|
|
mac, dell := setup(t)
|
|
write(t, mac.root, "docs/agents/plan.md", "plan")
|
|
mac.sync()
|
|
dell.sync()
|
|
repo := openRepo(t, mac)
|
|
if err := os.Rename(mac.client.statePath(repo), worktreePath(mac.config.DataDir, repo)+".json"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Remove(filepath.Join(dell.root, "docs/agents/plan.md")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dell.sync()
|
|
mac.sync()
|
|
mac.expect("docs/agents/plan.md", "")
|
|
}
|
|
|
|
func TestDaemonFollowsServersAddedLater(t *testing.T) {
|
|
event, origin := newServer(t), newOrigin(t)
|
|
mac := newMachine(t, origin, event, adminToken)
|
|
fresh := newMachine(t, origin, "", "")
|
|
daemonConfig, err := fresh.config.reload()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
done := make(chan error)
|
|
go func() { done <- daemonConfig.Daemon(ctx) }()
|
|
t.Cleanup(func() {
|
|
cancel()
|
|
if err := <-done; err != nil {
|
|
t.Error(err)
|
|
}
|
|
})
|
|
|
|
fresh.remote(event, issueCode(t, event))
|
|
write(t, mac.root, "docs/agents/first.md", "first")
|
|
mac.sync()
|
|
fresh.waitFor("docs/agents/first.md", "first")
|
|
write(t, mac.root, "docs/agents/second.md", "second")
|
|
mac.sync()
|
|
fresh.waitFor("docs/agents/second.md", "second")
|
|
}
|
|
|
|
func (m *machine) waitFor(path, content string) {
|
|
m.t.Helper()
|
|
deadline := time.Now().Add(15 * time.Second)
|
|
for {
|
|
if data, _ := os.ReadFile(filepath.Join(m.root, path)); string(data) == content {
|
|
return
|
|
}
|
|
if time.Now().After(deadline) {
|
|
m.t.Fatalf("the daemon did not bring %s", path)
|
|
}
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
}
|
|
|
|
func openRepo(t *testing.T, m *machine) *gitrepo.Repo {
|
|
t.Helper()
|
|
repo, err := gitrepo.Open(m.root)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return repo
|
|
}
|