package systemtime import ( "context" "errors" "os" "path/filepath" "strings" "testing" ) type settingsNTPFake struct { fakeNTPApplier fail error } func (a *settingsNTPFake) ConfigureNTP(context.Context, bool, []string) error { return a.fail } func TestConfigureTimeDoesNotRewriteClockAndRollsBackOnFailure(t *testing.T) { applier := &fakeApplier{} ntp := &settingsNTPFake{} controller := NewTransactionalController(applier, ntp) settings := Settings{Timezone: "Asia/Seoul", NTPEnabled: true, Servers: []string{"time.example"}} if err := controller.Configure(context.Background(), settings); err != nil { t.Fatal(err) } if !applier.appliedTime.IsZero() || applier.timezone != "Asia/Seoul" { t.Fatal("unexpected clock mutation") } ntp.fail = errors.New("daemon failed") if err := controller.Configure(context.Background(), settings); err == nil || !applier.rollbackCalled { t.Fatal("failure not rolled back") } } type settingsRunner struct { running bool calls []string } func (r *settingsRunner) Run(_ context.Context, _ string, args ...string) error { r.calls = append(r.calls, strings.Join(args, " ")) r.running = args[0] == "start" return nil } func TestConfigureNTPWorksOfflineAndDisabledIsIdempotent(t *testing.T) { root := t.TempDir() a, err := NewOSNTPApplier(filepath.Join(root, "ntp.conf"), filepath.Join(root, "S49ntp"), filepath.Join(root, "ntpd")) if err != nil { t.Fatal(err) } runner := &settingsRunner{} a.runner = runner a.running = func() (bool, error) { return runner.running, nil } if err := a.ConfigureNTP(context.Background(), true, []string{"internal.example"}); err != nil { t.Fatal(err) } content, _ := os.ReadFile(a.configPath) if !strings.Contains(string(content), "server internal.example iburst") || !runner.running { t.Fatal("configuration not applied") } if len(runner.calls) != 1 || runner.calls[0] != "start" { t.Fatal("unexpected one-shot sync", runner.calls) } if err := a.ConfigureNTP(context.Background(), false, nil); err != nil { t.Fatal(err) } if err := a.ConfigureNTP(context.Background(), false, nil); err != nil { t.Fatal(err) } if runner.running || len(runner.calls) != 2 { t.Fatal("disable not idempotent") } }