sync app filled in
This commit is contained in:
@@ -2,11 +2,15 @@ package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -112,7 +116,7 @@ func TestApplyDeltaDetectsServerChanges(t *testing.T) {
|
||||
}
|
||||
|
||||
// Client sends empty delta
|
||||
result, err := service.ApplyDelta(ctx, snap.ID, Delta{Changes: nil})
|
||||
result, err := service.ApplyDelta(ctx, snap.ID, "user:test", Delta{Changes: nil})
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyDelta() error = %v", err)
|
||||
}
|
||||
@@ -133,6 +137,31 @@ func TestApplyDeltaDetectsServerChanges(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyDeltaMarshalsEmptyCollectionsAsArrays(t *testing.T) {
|
||||
service, _ := setupTestService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
snap, err := service.InitSync(ctx, "device-1", "user:test")
|
||||
if err != nil {
|
||||
t.Fatalf("InitSync() error = %v", err)
|
||||
}
|
||||
|
||||
result, err := service.ApplyDelta(ctx, snap.ID, "user:test", Delta{})
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyDelta() error = %v", err)
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal delta result: %v", err)
|
||||
}
|
||||
for _, expected := range []string{`"serverDelta":[]`, `"conflicts":[]`} {
|
||||
if !strings.Contains(string(payload), expected) {
|
||||
t.Fatalf("delta JSON = %s, want %s", payload, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyDeltaDetectsConflicts(t *testing.T) {
|
||||
service, sourceDir := setupTestService(t)
|
||||
ctx := context.Background()
|
||||
@@ -157,7 +186,7 @@ func TestApplyDeltaDetectsConflicts(t *testing.T) {
|
||||
Modified: time.Now().UTC(),
|
||||
}
|
||||
|
||||
result, err := service.ApplyDelta(ctx, snap.ID, Delta{Changes: []Change{clientChange}})
|
||||
result, err := service.ApplyDelta(ctx, snap.ID, "user:test", Delta{Changes: []Change{clientChange}})
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyDelta() error = %v", err)
|
||||
}
|
||||
@@ -189,15 +218,17 @@ func TestApplyDeltaAllowsDifferentFileEditsWithoutConflict(t *testing.T) {
|
||||
t.Fatalf("update guide.md: %v", err)
|
||||
}
|
||||
|
||||
clientContent := "# Hello\n\nClient-side hello update"
|
||||
clientChange := Change{
|
||||
Type: ChangeUpdate,
|
||||
Path: "hello.md",
|
||||
Hash: "clienthash-different-file",
|
||||
Size: 128,
|
||||
Hash: sha256Hex(clientContent),
|
||||
Content: clientContent,
|
||||
Size: int64(len(clientContent)),
|
||||
Modified: time.Now().UTC(),
|
||||
}
|
||||
|
||||
result, err := service.ApplyDelta(ctx, snap.ID, Delta{Changes: []Change{clientChange}})
|
||||
result, err := service.ApplyDelta(ctx, snap.ID, "user:test", Delta{Changes: []Change{clientChange}})
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyDelta() error = %v", err)
|
||||
}
|
||||
@@ -213,6 +244,71 @@ func TestApplyDeltaAllowsDifferentFileEditsWithoutConflict(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyDeltaAppliesClientCreateWithContent(t *testing.T) {
|
||||
service, sourceDir := setupTestService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
snap, err := service.InitSync(ctx, "device-1", "user:test")
|
||||
if err != nil {
|
||||
t.Fatalf("InitSync() error = %v", err)
|
||||
}
|
||||
|
||||
content := "# Inbox\n\nCreated from macOS"
|
||||
result, err := service.ApplyDelta(ctx, snap.ID, "user:test", Delta{Changes: []Change{{
|
||||
Type: ChangeCreate,
|
||||
Path: "inbox/from-mac.md",
|
||||
Hash: sha256Hex(content),
|
||||
Content: content,
|
||||
Size: int64(len(content)),
|
||||
Modified: time.Now().UTC(),
|
||||
}}})
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyDelta() error = %v", err)
|
||||
}
|
||||
if len(result.Conflicts) != 0 {
|
||||
t.Fatalf("expected no conflicts, got %#v", result.Conflicts)
|
||||
}
|
||||
if result.NewSnapshotID == "" || result.NewSnapshotID == snap.ID {
|
||||
t.Fatalf("new snapshot id = %q, old = %q", result.NewSnapshotID, snap.ID)
|
||||
}
|
||||
|
||||
written, err := os.ReadFile(filepath.Join(sourceDir, "inbox", "from-mac.md"))
|
||||
if err != nil {
|
||||
t.Fatalf("read synced file: %v", err)
|
||||
}
|
||||
if string(written) != content {
|
||||
t.Fatalf("synced content = %q, want %q", string(written), content)
|
||||
}
|
||||
|
||||
stored, err := service.GetContent(sha256Hex(content))
|
||||
if err != nil {
|
||||
t.Fatalf("GetContent() error = %v", err)
|
||||
}
|
||||
if string(stored) != content {
|
||||
t.Fatalf("stored content = %q, want %q", string(stored), content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyDeltaRejectsHashMismatch(t *testing.T) {
|
||||
service, _ := setupTestService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
snap, err := service.InitSync(ctx, "device-1", "user:test")
|
||||
if err != nil {
|
||||
t.Fatalf("InitSync() error = %v", err)
|
||||
}
|
||||
|
||||
_, err = service.ApplyDelta(ctx, snap.ID, "user:test", Delta{Changes: []Change{{
|
||||
Type: ChangeCreate,
|
||||
Path: "bad.md",
|
||||
Hash: "not-the-content-hash",
|
||||
Content: "# Bad\n",
|
||||
}}})
|
||||
if err == nil {
|
||||
t.Fatal("expected hash mismatch error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveConflictsPreservesServerContent(t *testing.T) {
|
||||
service, sourceDir := setupTestService(t)
|
||||
ctx := context.Background()
|
||||
@@ -227,7 +323,7 @@ func TestResolveConflictsPreservesServerContent(t *testing.T) {
|
||||
t.Fatalf("update hello.md: %v", err)
|
||||
}
|
||||
|
||||
_, err = service.ResolveConflicts(ctx, snap.ID, []Resolution{{
|
||||
_, err = service.ResolveConflicts(ctx, snap.ID, "user:test", []Resolution{{
|
||||
Path: "hello.md",
|
||||
Strategy: ResolutionServerWins,
|
||||
}})
|
||||
@@ -260,7 +356,7 @@ func TestResolveConflictsCreatesNewSnapshot(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
newSnap, err := service.ResolveConflicts(ctx, snap.ID, resolutions)
|
||||
newSnap, err := service.ResolveConflicts(ctx, snap.ID, "user:test", resolutions)
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveConflicts() error = %v", err)
|
||||
}
|
||||
@@ -300,6 +396,14 @@ func TestGetContentReturnsFileBytes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContentRejectsInvalidHash(t *testing.T) {
|
||||
service, _ := setupTestService(t)
|
||||
|
||||
if _, err := service.GetContent("short"); err == nil {
|
||||
t.Fatal("expected invalid hash error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotDeltaHandles100FilesUnderTarget(t *testing.T) {
|
||||
service, sourceDir := setupTestService(t)
|
||||
ctx := context.Background()
|
||||
@@ -329,7 +433,7 @@ func TestSnapshotDeltaHandles100FilesUnderTarget(t *testing.T) {
|
||||
}
|
||||
|
||||
start = time.Now()
|
||||
result, err := service.ApplyDelta(ctx, snap.ID, Delta{})
|
||||
result, err := service.ApplyDelta(ctx, snap.ID, "user:test", Delta{})
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyDelta() error = %v", err)
|
||||
}
|
||||
@@ -343,3 +447,8 @@ func TestSnapshotDeltaHandles100FilesUnderTarget(t *testing.T) {
|
||||
t.Fatalf("server delta path = %q, want note-050.md", result.ServerDelta[0].Path)
|
||||
}
|
||||
}
|
||||
|
||||
func sha256Hex(content string) string {
|
||||
sum := sha256.Sum256([]byte(content))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user