package timesocket import ( "context" "net" "path/filepath" "testing" "time" "tera-clone/services/tera-api/internal/systemtime" ) func TestClientAndServerSyncThroughNarrowProtocol(t *testing.T) { socketPath := filepath.Join(t.TempDir(), "time.sock") listener, err := net.Listen("unix", socketPath) if err != nil { t.Skipf("unix sockets unavailable: %v", err) } server := Server{Controller: systemtime.MockController{}} done := make(chan error, 1) go func() { done <- server.Serve(listener) }() client := NewClient(socketPath) requested := time.Date(2026, 7, 23, 16, 30, 0, 0, time.FixedZone("KST", 9*60*60)) result, err := client.Sync(context.Background(), systemtime.Request{BrowserTime: requested, Timezone: "Asia/Seoul"}) if err != nil { t.Fatalf("Sync() error = %v", err) } if result.AppliedTime != requested.UTC() || result.Timezone != "Asia/Seoul" { t.Fatalf("Sync() = %+v, want %s Asia/Seoul", result, requested.UTC()) } if err := listener.Close(); err != nil { t.Fatal(err) } select { case err := <-done: if err != nil { t.Fatal(err) } case <-time.After(time.Second): t.Fatal("server did not stop") } } func TestClientReportsUnavailableSocket(t *testing.T) { client := NewClient(filepath.Join(t.TempDir(), "missing.sock")) _, err := client.Sync(context.Background(), systemtime.Request{}) if !systemtime.IsUnavailable(err) { t.Fatalf("Sync() error = %v, want unavailable", err) } } func TestClientAndServerSyncNTPThroughNarrowProtocol(t *testing.T) { socketPath := filepath.Join(t.TempDir(), "time.sock") listener, err := net.Listen("unix", socketPath) if err != nil { t.Skipf("unix sockets unavailable: %v", err) } defer listener.Close() server := Server{Controller: systemtime.MockController{}} go func() { _ = server.Serve(listener) }() client := NewClient(socketPath) result, err := client.SyncNTP(context.Background(), systemtime.NTPRequest{ Servers: []string{"ntp.kriss.re.kr", "time.bora.net"}, Timezone: "Asia/Seoul", }) if err != nil { t.Fatalf("SyncNTP() error = %v", err) } if result.Timezone != "Asia/Seoul" || len(result.Servers) != 2 || result.Servers[0] != "ntp.kriss.re.kr" { t.Fatalf("SyncNTP() = %+v", result) } } func TestServerRejectsUnsafeTimezoneWithoutCallingController(t *testing.T) { socketPath := filepath.Join(t.TempDir(), "time.sock") listener, err := net.Listen("unix", socketPath) if err != nil { t.Skipf("unix sockets unavailable: %v", err) } defer listener.Close() server := Server{Controller: systemtime.MockController{}} go func() { _ = server.Serve(listener) }() client := NewClient(socketPath) _, err = client.Sync(context.Background(), systemtime.Request{ BrowserTime: time.Date(2026, 7, 23, 0, 0, 0, 0, time.UTC), Timezone: "../../etc/passwd", }) if err == nil { t.Fatal("Sync() accepted an unsafe timezone") } } func TestClientConfiguresWithoutClockSync(t *testing.T) { listener, err := net.Listen("unix", filepath.Join(t.TempDir(), "settings.sock")) if err != nil { t.Fatal(err) } defer listener.Close() go func() { _ = (Server{Controller: systemtime.MockController{}}).Serve(listener) }() client := NewClient(listener.Addr().String()) if err := client.Configure(context.Background(), systemtime.Settings{Timezone: "Asia/Seoul", NTPEnabled: true, Servers: []string{"internal.example"}}); err != nil { t.Fatal(err) } }