203 lines
5.1 KiB
Go
203 lines
5.1 KiB
Go
package settings
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDiskSafeFilename(t *testing.T) {
|
|
cases := map[string]string{
|
|
"Daily Backup": "Daily_Backup",
|
|
" hello!! ": "hello",
|
|
"../etc/passwd": "etc_passwd",
|
|
"": "unnamed",
|
|
"a/b\\c:d*e?f": "a_b_c_d_e_f",
|
|
}
|
|
for input, want := range cases {
|
|
got := DiskSafeFilename(input)
|
|
if got != want {
|
|
t.Fatalf("DiskSafeFilename(%q) = %q, want %q", input, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNodeActionGroupsRoundTrip(t *testing.T) {
|
|
dir := t.TempDir()
|
|
now := time.Now().UTC()
|
|
store := NodeActionGroupStore{
|
|
Groups: []NodeActionGroup{
|
|
{
|
|
ID: "g1",
|
|
NodeID: "n1",
|
|
Name: "Nightly",
|
|
Schedule: ActionGroupSchedule{
|
|
Enabled: true,
|
|
Kind: ActionGroupScheduleInterval,
|
|
EverySeconds: 60,
|
|
Times: []ActionGroupTimeSpec{},
|
|
},
|
|
Items: []NodeActionItem{
|
|
{
|
|
ID: "i1",
|
|
Source: ActionItemSourceLibrary,
|
|
LibraryActionID: BuiltinActionUptimeID,
|
|
Env: []ActionEnvVar{},
|
|
},
|
|
},
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
},
|
|
}
|
|
if err := SaveNodeActionGroups(dir, store); err != nil {
|
|
t.Fatalf("SaveNodeActionGroups: %v", err)
|
|
}
|
|
loaded, err := LoadNodeActionGroups(dir)
|
|
if err != nil {
|
|
t.Fatalf("LoadNodeActionGroups: %v", err)
|
|
}
|
|
if len(loaded.Groups) != 1 || loaded.Groups[0].Name != "Nightly" {
|
|
t.Fatalf("loaded = %#v", loaded)
|
|
}
|
|
if len(loaded.Groups[0].Items) != 1 {
|
|
t.Fatalf("items = %#v", loaded.Groups[0].Items)
|
|
}
|
|
}
|
|
|
|
func TestLibraryActionInUse(t *testing.T) {
|
|
store := NodeActionGroupStore{
|
|
Groups: []NodeActionGroup{
|
|
{
|
|
ID: "g1",
|
|
NodeID: "n1",
|
|
Name: "Nightly",
|
|
Items: []NodeActionItem{
|
|
{
|
|
ID: "lib-item",
|
|
Source: ActionItemSourceLibrary,
|
|
LibraryActionID: "custom-1",
|
|
},
|
|
{
|
|
ID: "local-item",
|
|
Source: ActionItemSourceLocal,
|
|
Name: "Echo",
|
|
Kind: ActionKindShell,
|
|
Body: "echo hi",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
if !LibraryActionInUse(store, "custom-1") {
|
|
t.Fatal("expected library action custom-1 to be in use")
|
|
}
|
|
if LibraryActionInUse(store, "other") {
|
|
t.Fatal("expected unrelated action not in use")
|
|
}
|
|
store.Groups[0].Items[0] = NodeActionItem{
|
|
ID: "cloned",
|
|
Source: ActionItemSourceLocal,
|
|
Name: "Echo host",
|
|
Kind: ActionKindShell,
|
|
Body: "echo",
|
|
}
|
|
if LibraryActionInUse(store, "custom-1") {
|
|
t.Fatal("expected cloned local item not to count as in use")
|
|
}
|
|
}
|
|
|
|
func TestIsScheduleDueInterval(t *testing.T) {
|
|
now := time.Date(2026, 7, 19, 12, 0, 0, 0, time.Local)
|
|
group := NodeActionGroup{
|
|
Schedule: ActionGroupSchedule{
|
|
Enabled: true,
|
|
Kind: ActionGroupScheduleInterval,
|
|
EverySeconds: 300,
|
|
},
|
|
}
|
|
if !IsScheduleDue(group, now) {
|
|
t.Fatal("expected due when never run")
|
|
}
|
|
last := now.Add(-301 * time.Second)
|
|
group.LastRunAt = &last
|
|
if !IsScheduleDue(group, now) {
|
|
t.Fatal("expected due after interval elapsed")
|
|
}
|
|
recent := now.Add(-10 * time.Second)
|
|
group.LastRunAt = &recent
|
|
if IsScheduleDue(group, now) {
|
|
t.Fatal("expected not due within interval")
|
|
}
|
|
}
|
|
|
|
func TestIsScheduleDueTimes(t *testing.T) {
|
|
now := time.Date(2026, 7, 19, 14, 30, 10, 0, time.Local) // Sunday
|
|
group := NodeActionGroup{
|
|
Schedule: ActionGroupSchedule{
|
|
Enabled: true,
|
|
Kind: ActionGroupScheduleTimes,
|
|
Times: []ActionGroupTimeSpec{
|
|
{Time: "14:30", DaysOfWeek: []int{0}},
|
|
},
|
|
},
|
|
}
|
|
if !IsScheduleDue(group, now) {
|
|
t.Fatal("expected due at matching time")
|
|
}
|
|
sameMinute := now
|
|
group.LastRunAt = &sameMinute
|
|
if IsScheduleDue(group, now) {
|
|
t.Fatal("expected not due again in same minute")
|
|
}
|
|
group.LastRunAt = nil
|
|
group.Schedule.Times[0].DaysOfWeek = []int{1} // Monday only
|
|
if IsScheduleDue(group, now) {
|
|
t.Fatal("expected not due on wrong weekday")
|
|
}
|
|
}
|
|
|
|
func TestActionLogAppendAndList(t *testing.T) {
|
|
dir := t.TempDir()
|
|
record := ActionGroupRunRecord{
|
|
ID: "run-1",
|
|
NodeID: "node-abc",
|
|
GroupID: "group-1",
|
|
GroupName: "Nightly Jobs",
|
|
Trigger: ActionRunTriggerManual,
|
|
StartedAt: time.Now().UTC(),
|
|
FinishedAt: time.Now().UTC(),
|
|
Actions: []ActionItemRunResult{},
|
|
}
|
|
if err := AppendActionLogRun(dir, record); err != nil {
|
|
t.Fatalf("AppendActionLogRun: %v", err)
|
|
}
|
|
path := ActionLogFilePath(dir, "node-abc", "Nightly Jobs")
|
|
if _, err := os.Stat(path); err != nil {
|
|
t.Fatalf("expected log file at %s: %v", path, err)
|
|
}
|
|
files, err := ListActionLogFiles(dir, "node-abc")
|
|
if err != nil {
|
|
t.Fatalf("ListActionLogFiles: %v", err)
|
|
}
|
|
if len(files) != 1 {
|
|
t.Fatalf("files = %#v", files)
|
|
}
|
|
wantName := DiskSafeFilename("Nightly Jobs") + ".json"
|
|
if files[0].Filename != wantName {
|
|
t.Fatalf("filename = %q want %q", files[0].Filename, wantName)
|
|
}
|
|
loaded, err := ReadActionLogFileByName(dir, "node-abc", wantName)
|
|
if err != nil {
|
|
t.Fatalf("ReadActionLogFileByName: %v", err)
|
|
}
|
|
if len(loaded.Runs) != 1 || loaded.Runs[0].ID != "run-1" {
|
|
t.Fatalf("loaded = %#v", loaded)
|
|
}
|
|
if _, err := ReadActionLogFileByName(dir, "node-abc", "../etc/passwd"); err == nil {
|
|
t.Fatal("expected path traversal rejected")
|
|
}
|
|
_ = filepath.Base(wantName)
|
|
}
|