Files
lodge/backend/internal/client/remote_test.go
T

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
}