package service import ( "strings" "testing" "time" ) func TestBuildStageHeartbeatProgresses(t *testing.T) { p1, m1 := buildStageHeartbeat("titan-13", "titan-04", 10*time.Second) p2, m2 := buildStageHeartbeat("titan-13", "titan-04", 3*time.Minute) p3, m3 := buildStageHeartbeat("titan-13", "titan-04", 11*time.Minute) if !(p1 > 8 && p1 < p2 && p2 < p3 && p3 <= 76) { t.Fatalf("unexpected build progress values: %v %v %v", p1, p2, p3) } if !strings.Contains(m1, "Scheduling") { t.Fatalf("expected scheduling message, got %q", m1) } if !strings.Contains(m2, "replacement image filesystem") { t.Fatalf("expected filesystem message, got %q", m2) } if !strings.Contains(m3, "Harbor") { t.Fatalf("expected Harbor message, got %q", m3) } } func TestFlashStageHeartbeatProgresses(t *testing.T) { p1, m1 := flashStageHeartbeat("titan-22", "registry.bstein.dev/metis/titan-13:latest", 5*time.Second) p2, m2 := flashStageHeartbeat("titan-22", "registry.bstein.dev/metis/titan-13:latest", 20*time.Second) p3, m3 := flashStageHeartbeat("titan-22", "registry.bstein.dev/metis/titan-13:latest", 90*time.Second) if !(p1 > 84 && p1 < p2 && p2 < p3 && p3 <= 98) { t.Fatalf("unexpected flash progress values: %v %v %v", p1, p2, p3) } if !strings.Contains(m1, "Pulling") { t.Fatalf("expected pulling message, got %q", m1) } if !strings.Contains(m2, "Writing") { t.Fatalf("expected writing message, got %q", m2) } if !strings.Contains(m3, "Flushing") { t.Fatalf("expected flushing message, got %q", m3) } } func TestParseRemoteProgressLogsFindsLatestMarker(t *testing.T) { logs := strings.Join([]string{ "plain log line", ProgressLogLine(RemoteProgressUpdate{Stage: "build", ProgressPct: 40, Message: "phase one"}), ProgressLogLine(RemoteProgressUpdate{Stage: "build", ProgressPct: 72, Message: "phase two"}), `{"local_path":"/tmp/out.img.xz"}`, }, "\n") update, ok := parseRemoteProgressLogs(logs) if !ok { t.Fatal("expected progress marker") } if update.ProgressPct != 72 || update.Message != "phase two" { t.Fatalf("unexpected update: %#v", update) } } func TestParseRemoteProgressLogsSkipsMalformedMarkers(t *testing.T) { logs := strings.Join([]string{ "plain log line", progressLogPrefix + "{bad-json", ProgressLogLine(RemoteProgressUpdate{Stage: "flash", ProgressPct: 90, Message: "writing"}), }, "\n") update, ok := parseRemoteProgressLogs(logs) if !ok { t.Fatal("expected valid marker after malformed marker") } if update.Stage != "flash" || update.ProgressPct != 90 || update.Message != "writing" { t.Fatalf("unexpected update: %#v", update) } } func TestApplyRemoteProgressUpdatesOnlyRunningJobs(t *testing.T) { app := newTestApp(t) running := app.newJob("replace", "titan-13", "titan-22", "/dev/sda") app.setJob(running.ID, func(j *Job) { j.Status = JobRunning j.Stage = "queued" j.ProgressPct = 10 }) queued := app.newJob("build", "titan-14", "titan-22", "") app.applyRemoteProgress("", RemoteProgressUpdate{Stage: "ignored"}) app.applyRemoteProgress("missing", RemoteProgressUpdate{Stage: "ignored"}) app.applyRemoteProgress(queued.ID, RemoteProgressUpdate{Stage: "ignored", ProgressPct: 99}) app.applyRemoteProgress( running.ID, RemoteProgressUpdate{ Stage: "flash", ProgressPct: 80, Message: "writing image", WrittenBytes: 1024, TotalBytes: 2048, }, ) app.applyRemoteProgress(running.ID, RemoteProgressUpdate{ProgressPct: 70}) got := app.job(running.ID) if got.Stage != "flash" || got.ProgressPct != 80 || got.Message != "writing image" { t.Fatalf("running job was not updated correctly: %+v", got) } if got.Written != 1024 || got.Total != 2048 || got.StageStartedAt.IsZero() { t.Fatalf("remote byte/stage fields not applied: %+v", got) } if app.job(queued.ID).Stage == "ignored" { t.Fatalf("queued job should not receive remote progress") } }