|
4 | 4 | "context" |
5 | 5 | "database/sql" |
6 | 6 | "encoding/json" |
| 7 | + "errors" |
7 | 8 | "net/http" |
8 | 9 | "net/http/httptest" |
9 | 10 | "path/filepath" |
@@ -303,6 +304,81 @@ func TestJobsV2ManualTriggerAndCancel(t *testing.T) { |
303 | 304 | } |
304 | 305 | } |
305 | 306 |
|
| 307 | +func TestJobsV2FinishRunDoesNotOverrideCancelRequested(t *testing.T) { |
| 308 | + mgr, err := newJobsV2Manager(":memory:", 0, nil) |
| 309 | + if err != nil { |
| 310 | + t.Fatalf("newJobsV2Manager failed: %v", err) |
| 311 | + } |
| 312 | + defer func() { _ = mgr.Close() }() |
| 313 | + |
| 314 | + job, err := mgr.CreateJob(jobsV2Job{ |
| 315 | + Name: "cancel-authoritative", |
| 316 | + Enabled: true, |
| 317 | + RunnerType: jobsV2RunnerProgram, |
| 318 | + RunnerConfig: json.RawMessage(`{"command":"echo","args":["x"]}`), |
| 319 | + TriggerType: jobsV2TriggerManual, |
| 320 | + TriggerConfig: json.RawMessage(`{}`), |
| 321 | + RetryPolicy: json.RawMessage(`{"max_attempts":2}`), |
| 322 | + }) |
| 323 | + if err != nil { |
| 324 | + t.Fatalf("CreateJob failed: %v", err) |
| 325 | + } |
| 326 | + if job.ID == "" { |
| 327 | + t.Fatal("expected created job to have an id") |
| 328 | + } |
| 329 | + |
| 330 | + run, err := mgr.TriggerJob(job.ID) |
| 331 | + if err != nil { |
| 332 | + t.Fatalf("TriggerJob failed: %v", err) |
| 333 | + } |
| 334 | + |
| 335 | + started := time.Now().UTC() |
| 336 | + if _, err := mgr.db.Exec(`UPDATE job_runs_v2 SET status = ?, started_at = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, jobsV2RunRunning, started, run.ID); err != nil { |
| 337 | + t.Fatalf("mark run running: %v", err) |
| 338 | + } |
| 339 | + |
| 340 | + cancelled, err := mgr.CancelRun(run.ID) |
| 341 | + if err != nil { |
| 342 | + t.Fatalf("CancelRun failed: %v", err) |
| 343 | + } |
| 344 | + if cancelled.Status != jobsV2RunCancelRequested { |
| 345 | + t.Fatalf("cancelled status = %s, want %s", cancelled.Status, jobsV2RunCancelRequested) |
| 346 | + } |
| 347 | + |
| 348 | + result := jobsV2RunResult{Response: "partial output", ExitCode: 17} |
| 349 | + if err := mgr.finishRun(run.ID, jobsV2RunFailed, result, errors.New("runner failed after cancel request"), run.Attempt); err != nil { |
| 350 | + t.Fatalf("finishRun failed: %v", err) |
| 351 | + } |
| 352 | + |
| 353 | + current, err := mgr.GetRun(run.ID) |
| 354 | + if err != nil { |
| 355 | + t.Fatalf("GetRun failed: %v", err) |
| 356 | + } |
| 357 | + if current.Status != jobsV2RunCancelled { |
| 358 | + t.Fatalf("run status = %s, want %s", current.Status, jobsV2RunCancelled) |
| 359 | + } |
| 360 | + if current.ExitReason != exitReasonCancelled { |
| 361 | + t.Fatalf("exit reason = %q, want %q", current.ExitReason, exitReasonCancelled) |
| 362 | + } |
| 363 | + if current.Error != context.Canceled.Error() { |
| 364 | + t.Fatalf("error = %q, want %q", current.Error, context.Canceled.Error()) |
| 365 | + } |
| 366 | + if current.Response != result.Response { |
| 367 | + t.Fatalf("response = %q, want %q", current.Response, result.Response) |
| 368 | + } |
| 369 | + if current.FinishedAt == nil { |
| 370 | + t.Fatal("expected cancelled run to have finished_at set") |
| 371 | + } |
| 372 | + |
| 373 | + var retryRuns int |
| 374 | + if err := mgr.db.QueryRow(`SELECT COUNT(*) FROM job_runs_v2 WHERE job_id = ? AND id != ?`, job.ID, run.ID).Scan(&retryRuns); err != nil { |
| 375 | + t.Fatalf("count retry runs: %v", err) |
| 376 | + } |
| 377 | + if retryRuns != 0 { |
| 378 | + t.Fatalf("retry runs = %d, want 0", retryRuns) |
| 379 | + } |
| 380 | +} |
| 381 | + |
306 | 382 | func TestJobsV2CreateDefaultsEnabledWhenOmitted(t *testing.T) { |
307 | 383 | mgr, err := newJobsV2Manager(":memory:", 0, nil) |
308 | 384 | if err != nil { |
|
0 commit comments