package webui import ( "context" "os" "os/exec" "path/filepath" "strings" "testing" "time" "bee/audit/internal/app" ) func TestTaskQueuePersistsAndRecoversPendingTasks(t *testing.T) { dir := t.TempDir() q := &taskQueue{ statePath: filepath.Join(dir, "tasks-state.json"), logsDir: filepath.Join(dir, "tasks"), trigger: make(chan struct{}, 1), } if err := os.MkdirAll(q.logsDir, 0755); err != nil { t.Fatal(err) } started := time.Now().Add(-time.Minute) task := &Task{ ID: "task-1", Name: "Memory Burn-in", Target: "memory-stress", Priority: 2, Status: TaskRunning, CreatedAt: time.Now().Add(-2 * time.Minute), StartedAt: &started, params: taskParams{ Duration: 300, BurnProfile: "smoke", }, } q.tasks = append(q.tasks, task) q.assignTaskLogPathLocked(task) q.persistLocked() recovered := &taskQueue{ statePath: q.statePath, logsDir: q.logsDir, trigger: make(chan struct{}, 1), } recovered.loadLocked() if len(recovered.tasks) != 1 { t.Fatalf("tasks=%d want 1", len(recovered.tasks)) } got := recovered.tasks[0] if got.Status != TaskPending { t.Fatalf("status=%q want %q", got.Status, TaskPending) } if got.params.Duration != 300 || got.params.BurnProfile != "smoke" { t.Fatalf("params=%+v", got.params) } if got.LogPath == "" { t.Fatal("expected log path") } } func TestNewTaskJobStateLoadsExistingLog(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "task.log") if err := os.WriteFile(path, []byte("line1\nline2\n"), 0644); err != nil { t.Fatal(err) } j := newTaskJobState(path) existing, ch := j.subscribe() if ch == nil { t.Fatal("expected live subscription channel") } if len(existing) != 2 || existing[0] != "line1" || existing[1] != "line2" { t.Fatalf("existing=%v", existing) } } func TestResolveBurnPreset(t *testing.T) { tests := []struct { profile string want burnPreset }{ {profile: "smoke", want: burnPreset{NvidiaDiag: 1, DurationSec: 5 * 60}}, {profile: "acceptance", want: burnPreset{NvidiaDiag: 3, DurationSec: 60 * 60}}, {profile: "overnight", want: burnPreset{NvidiaDiag: 4, DurationSec: 8 * 60 * 60}}, {profile: "", want: burnPreset{NvidiaDiag: 1, DurationSec: 5 * 60}}, } for _, tc := range tests { if got := resolveBurnPreset(tc.profile); got != tc.want { t.Fatalf("resolveBurnPreset(%q)=%+v want %+v", tc.profile, got, tc.want) } } } func TestTaskDisplayNameUsesNvidiaStressLoader(t *testing.T) { tests := []struct { loader string want string }{ {loader: "", want: "NVIDIA GPU Stress (bee-gpu-burn)"}, {loader: "builtin", want: "NVIDIA GPU Stress (bee-gpu-burn)"}, {loader: "john", want: "NVIDIA GPU Stress (John/OpenCL)"}, {loader: "nccl", want: "NVIDIA GPU Stress (NCCL)"}, } for _, tc := range tests { if got := taskDisplayName("nvidia-stress", "acceptance", tc.loader); got != tc.want { t.Fatalf("taskDisplayName(loader=%q)=%q want %q", tc.loader, got, tc.want) } } } func TestRunTaskHonorsCancel(t *testing.T) { blocked := make(chan struct{}) released := make(chan struct{}) aRun := func(_ any, ctx context.Context, _ string, _ int, _ func(string)) (string, error) { close(blocked) select { case <-ctx.Done(): close(released) return "", ctx.Err() case <-time.After(5 * time.Second): close(released) return "unexpected", nil } } q := &taskQueue{ opts: &HandlerOptions{App: &app.App{}}, } tk := &Task{ ID: "cpu-1", Name: "CPU SAT", Target: "cpu", Status: TaskRunning, CreatedAt: time.Now(), params: taskParams{Duration: 60}, } j := &jobState{} ctx, cancel := context.WithCancel(context.Background()) j.cancel = cancel tk.job = j orig := runCPUAcceptancePackCtx runCPUAcceptancePackCtx = func(_ *app.App, ctx context.Context, baseDir string, durationSec int, logFunc func(string)) (string, error) { return aRun(nil, ctx, baseDir, durationSec, logFunc) } defer func() { runCPUAcceptancePackCtx = orig }() done := make(chan struct{}) go func() { q.runTask(tk, j, ctx) close(done) }() <-blocked j.abort() select { case <-released: case <-time.After(2 * time.Second): t.Fatal("task did not observe cancel") } select { case <-done: case <-time.After(2 * time.Second): t.Fatal("runTask did not return after cancel") } } func TestRunTaskUsesBurnProfileDurationForCPU(t *testing.T) { var gotDuration int q := &taskQueue{ opts: &HandlerOptions{App: &app.App{}}, } tk := &Task{ ID: "cpu-burn-1", Name: "CPU Burn-in", Target: "cpu", Status: TaskRunning, CreatedAt: time.Now(), params: taskParams{BurnProfile: "smoke"}, } j := &jobState{} orig := runCPUAcceptancePackCtx runCPUAcceptancePackCtx = func(_ *app.App, _ context.Context, _ string, durationSec int, _ func(string)) (string, error) { gotDuration = durationSec return "/tmp/cpu-burn.tar.gz", nil } defer func() { runCPUAcceptancePackCtx = orig }() q.runTask(tk, j, context.Background()) if gotDuration != 5*60 { t.Fatalf("duration=%d want %d", gotDuration, 5*60) } } func TestRunTaskBuildsSupportBundleWithoutApp(t *testing.T) { dir := t.TempDir() q := &taskQueue{ opts: &HandlerOptions{ExportDir: dir}, } tk := &Task{ ID: "support-bundle-1", Name: "Support Bundle", Target: "support-bundle", Status: TaskRunning, CreatedAt: time.Now(), } j := &jobState{} var gotExportDir string orig := buildSupportBundle buildSupportBundle = func(exportDir string) (string, error) { gotExportDir = exportDir return filepath.Join(exportDir, "bundle.tar.gz"), nil } defer func() { buildSupportBundle = orig }() q.runTask(tk, j, context.Background()) if gotExportDir != dir { t.Fatalf("exportDir=%q want %q", gotExportDir, dir) } if j.err != "" { t.Fatalf("unexpected error: %q", j.err) } if !strings.Contains(strings.Join(j.lines, "\n"), "Archive: "+filepath.Join(dir, "bundle.tar.gz")) { t.Fatalf("lines=%v", j.lines) } } func TestRunTaskInstallUsesSharedCommandStreaming(t *testing.T) { q := &taskQueue{ opts: &HandlerOptions{}, } tk := &Task{ ID: "install-1", Name: "Install to Disk", Target: "install", Status: TaskRunning, CreatedAt: time.Now(), params: taskParams{Device: "/dev/sda"}, } j := &jobState{} var gotDevice string var gotLogPath string orig := installCommand installCommand = func(ctx context.Context, device string, logPath string) *exec.Cmd { gotDevice = device gotLogPath = logPath return exec.CommandContext(ctx, "sh", "-c", "printf 'line1\nline2\n'") } defer func() { installCommand = orig }() q.runTask(tk, j, context.Background()) if gotDevice != "/dev/sda" { t.Fatalf("device=%q want /dev/sda", gotDevice) } if gotLogPath == "" { t.Fatal("expected install log path") } logs := strings.Join(j.lines, "\n") if !strings.Contains(logs, "Install log: ") { t.Fatalf("missing install log line: %v", j.lines) } if !strings.Contains(logs, "line1") || !strings.Contains(logs, "line2") { t.Fatalf("missing streamed output: %v", j.lines) } if j.err != "" { t.Fatalf("unexpected error: %q", j.err) } }