Files
clip-sync/internal/daemon/daemon_test.go
T
bruno d8ee48445b
CI / Lint (push) Successful in 18m7s
CI / Test (push) Successful in 9m2s
CI / Build (darwin/amd64) (push) Failing after 4m4s
CI / Build (linux/amd64) (push) Failing after 4m7s
CI / Build (windows/amd64) (push) Failing after 4m5s
CI / Build (darwin/arm64) (push) Failing after 4m4s
CI / Build (linux/arm64) (push) Failing after 4m7s
CI / Build (windows/arm64) (push) Failing after 4m6s
CI / Release (push) Skipped
fix: correction de toutes les erreurs CI — vet, lint, race detector et config
- clipboard_windows.go: remplacement unsafe.Pointer par unsafe.Add pour go vet 1.26
- clipboard_windows.go: correction errcheck (defer syscall) et ineffassign
- daemon_test.go: mockClipboard thread-safe avec sync.RWMutex (data race)
- daemon_test.go: correction errcheck sur go da.Run()
- server_test.go: vérification des erreurs json.Marshal
- config_test.go: vérification des erreurs MkdirAll/WriteFile
- peers.example.toml: mise à jour au format TOML actuel
2026-08-08 16:28:45 -04:00

178 lines
4.2 KiB
Go

package daemon
import (
"context"
"sync"
"testing"
"time"
"git.dracodev.net/Projets/clip-sync/internal/config"
)
// mockClipboard is a thread-safe in-memory clipboard for integration tests.
type mockClipboard struct {
mu sync.RWMutex
text string
}
func (m *mockClipboard) Read() (string, error) {
m.mu.RLock()
defer m.mu.RUnlock()
return m.text, nil
}
func (m *mockClipboard) Write(s string) error {
m.mu.Lock()
defer m.mu.Unlock()
m.text = s
return nil
}
func (m *mockClipboard) get() string {
m.mu.RLock()
defer m.mu.RUnlock()
return m.text
}
func (m *mockClipboard) set(s string) {
m.mu.Lock()
defer m.mu.Unlock()
m.text = s
}
// TestIntegrationTwoInstances simulates two clip-sync instances syncing via localhost
// with distinct origins to avoid the same-hostname anti-loop filter.
func TestIntegrationTwoInstances(t *testing.T) {
// Instance A: port 19137
cfgA := &config.Config{
Daemon: config.DaemonConfig{Port: 19137, PollIntervalMs: 50},
Peers: []config.PeerConfig{
{Name: "instance-b", Addr: "127.0.0.1:19138"},
},
}
// Instance B: port 19138
cfgB := &config.Config{
Daemon: config.DaemonConfig{Port: 19138, PollIntervalMs: 50},
Peers: []config.PeerConfig{
{Name: "instance-a", Addr: "127.0.0.1:19137"},
},
}
cbA := &mockClipboard{}
cbB := &mockClipboard{}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// Build daemons with distinct origins (simulating different machines).
da, err := newWithClipboardOrigin(cfgA, cbA, "instance-a")
if err != nil {
t.Fatalf("daemon A: %v", err)
}
db, err := newWithClipboardOrigin(cfgB, cbB, "instance-b")
if err != nil {
t.Fatalf("daemon B: %v", err)
}
// Start both daemons.
go func() { _ = da.Run(ctx) }()
go func() { _ = db.Run(ctx) }()
// Wait for servers to start.
time.Sleep(200 * time.Millisecond)
// Write text to mock clipboard A (simulating a local copy).
cbA.set("sync test from A")
da.filter.MarkWritten(cbA.get())
da.broadcaster.Broadcast(cbA.get())
// Wait for propagation A → B via HTTP.
time.Sleep(500 * time.Millisecond)
if got := cbB.get(); got != "sync test from A" {
t.Errorf("B clipboard = %q, want \"sync test from A\"", got)
}
// Now write to B and check A.
cbB.set("sync test from B")
db.filter.MarkWritten(cbB.get())
db.broadcaster.Broadcast(cbB.get())
time.Sleep(500 * time.Millisecond)
if got := cbA.get(); got != "sync test from B" {
t.Errorf("A clipboard = %q, want \"sync test from B\"", got)
}
cancel()
}
// TestNoPingPong verifies that the anti-loop filter prevents echo-back.
func TestNoPingPong(t *testing.T) {
cfgA := &config.Config{
Daemon: config.DaemonConfig{Port: 19139, PollIntervalMs: 50},
Peers: []config.PeerConfig{
{Name: "instance-b", Addr: "127.0.0.1:19140"},
},
}
cfgB := &config.Config{
Daemon: config.DaemonConfig{Port: 19140, PollIntervalMs: 50},
Peers: []config.PeerConfig{
{Name: "instance-a", Addr: "127.0.0.1:19139"},
},
}
cbA := &mockClipboard{}
cbB := &mockClipboard{}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
da, err := newWithClipboardOrigin(cfgA, cbA, "instance-a")
if err != nil {
t.Fatalf("daemon A: %v", err)
}
db, err := newWithClipboardOrigin(cfgB, cbB, "instance-b")
if err != nil {
t.Fatalf("daemon B: %v", err)
}
go func() { _ = da.Run(ctx) }()
go func() { _ = db.Run(ctx) }()
time.Sleep(200 * time.Millisecond)
// Phase 1: Write to A, verify B receives it.
cbA.set("ping-pong test")
da.filter.MarkWritten(cbA.get())
da.broadcaster.Broadcast(cbA.get())
time.Sleep(400 * time.Millisecond)
if got := cbB.get(); got != "ping-pong test" {
t.Errorf("B clipboard = %q, want \"ping-pong test\"", got)
}
// Phase 2: B's poller detects the new text and broadcasts it back.
// A's server should reject it via the dedup filter (hash match).
// Wait for B's poll cycle to fire.
time.Sleep(200 * time.Millisecond)
// Phase 3: Write new text on B, verify A receives it correctly
// (no interference from the blocked echo).
cbB.set("final test")
db.filter.MarkWritten(cbB.get())
db.broadcaster.Broadcast(cbB.get())
time.Sleep(400 * time.Millisecond)
if got := cbA.get(); got != "final test" {
t.Errorf("A clipboard = %q, want \"final test\"", got)
}
cancel()
}