You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
113 lines
3.4 KiB
113 lines
3.4 KiB
package systemtime
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type recordedCommand struct {
|
|
name string
|
|
args []string
|
|
}
|
|
|
|
type fakeCommandRunner struct {
|
|
commands []recordedCommand
|
|
failAt int
|
|
}
|
|
|
|
func (r *fakeCommandRunner) Run(_ context.Context, name string, args ...string) error {
|
|
r.commands = append(r.commands, recordedCommand{name: name, args: append([]string(nil), args...)})
|
|
if r.failAt > 0 && len(r.commands) == r.failAt {
|
|
return errors.New("command failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestOSNTPApplierSynchronizesAndLeavesDaemonRunning(t *testing.T) {
|
|
root := t.TempDir()
|
|
configPath := filepath.Join(root, "etc", "ntp.conf")
|
|
if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(configPath, []byte("server old.example iburst\n"), 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
applier, err := NewOSNTPApplier(configPath, filepath.Join(root, "S49ntp"), filepath.Join(root, "ntpd"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
runner := &fakeCommandRunner{}
|
|
applier.runner = runner
|
|
appliedAt := time.Date(2026, 7, 30, 7, 1, 2, 0, time.UTC)
|
|
applier.clock = func() time.Time { return appliedAt }
|
|
|
|
got, err := applier.SyncNTP(context.Background(), []string{"ntp.kriss.re.kr", "time.bora.net"})
|
|
if err != nil {
|
|
t.Fatalf("SyncNTP() error = %v", err)
|
|
}
|
|
if got != appliedAt {
|
|
t.Fatalf("SyncNTP() time = %v, want %v", got, appliedAt)
|
|
}
|
|
wantCommands := []recordedCommand{
|
|
{name: filepath.Join(root, "S49ntp"), args: []string{"stop"}},
|
|
{name: filepath.Join(root, "ntpd"), args: []string{"-g", "-q", "-c", configPath}},
|
|
{name: filepath.Join(root, "S49ntp"), args: []string{"start"}},
|
|
}
|
|
if !reflect.DeepEqual(runner.commands, wantCommands) {
|
|
t.Fatalf("commands = %#v, want %#v", runner.commands, wantCommands)
|
|
}
|
|
content, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
text := string(content)
|
|
if !strings.Contains(text, "server ntp.kriss.re.kr iburst") || !strings.Contains(text, "server time.bora.net iburst") {
|
|
t.Fatalf("NTP config = %q", text)
|
|
}
|
|
}
|
|
|
|
func TestOSNTPApplierRestoresConfigurationWhenSyncFails(t *testing.T) {
|
|
root := t.TempDir()
|
|
configPath := filepath.Join(root, "etc", "ntp.conf")
|
|
if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
before := []byte("server old.example iburst\n")
|
|
if err := os.WriteFile(configPath, before, 0o640); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
applier, err := NewOSNTPApplier(configPath, filepath.Join(root, "S49ntp"), filepath.Join(root, "ntpd"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
runner := &fakeCommandRunner{failAt: 2}
|
|
applier.runner = runner
|
|
applier.clock = func() time.Time { return time.Date(2026, 7, 30, 7, 1, 2, 0, time.UTC) }
|
|
|
|
if _, err := applier.SyncNTP(context.Background(), []string{"ntp.kriss.re.kr"}); err == nil {
|
|
t.Fatal("SyncNTP() succeeded, want failure")
|
|
}
|
|
after, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(after, before) {
|
|
t.Fatalf("restored config = %q, want %q", after, before)
|
|
}
|
|
if len(runner.commands) != 3 || runner.commands[2].args[0] != "start" {
|
|
t.Fatalf("rollback commands = %#v", runner.commands)
|
|
}
|
|
}
|
|
|
|
func TestBuildNTPConfigCannotContainLineInjection(t *testing.T) {
|
|
_, err := NormalizeNTPRequest(NTPRequest{Servers: []string{"ntp.example\nserver evil"}, Timezone: "UTC"})
|
|
if err == nil {
|
|
t.Fatal("NormalizeNTPRequest() accepted a line break")
|
|
}
|
|
}
|
|
|