package store import ( "strings" "testing" "time" ) func TestStopSuppressesUnstartedCallsButPreservesUnknown(t *testing.T) { s := preparedCurrentCallStore(t) pending := currentCall("queued-before-stop") if _, _, err := s.RecordExecute(pending); err != nil { t.Fatal(err) } unknown := currentCall("unknown-before-stop") if _, _, err := s.RecordExecute(unknown); err != nil { t.Fatal(err) } if err := s.ReserveExecute(unknown.DispatcherID, unknown.EventID, currentReservation(), currentMondayUTC()); err != nil { t.Fatal(err) } if err := s.MarkExecuteUnknown(unknown.DispatcherID, unknown.EventID); err != nil { t.Fatal(err) } if err := s.ApplyControl(currentDispatcherID, 1001, "task-asr", "stop"); err != nil { t.Fatal(err) } calls, err := s.ListPendingExecute(currentDispatcherID) if err != nil || len(calls) != 0 { t.Fatalf("stopped task still has dispatchable commands: %+v %v", calls, err) } var pendingStatus, unknownStatus string if err := s.db.QueryRow(`SELECT status FROM dispatcher_inbox WHERE event_id=?`, pending.EventID).Scan(&pendingStatus); err != nil { t.Fatal(err) } if err := s.db.QueryRow(`SELECT status FROM dispatcher_inbox WHERE event_id=?`, unknown.EventID).Scan(&unknownStatus); err != nil { t.Fatal(err) } if pendingStatus != "suppressed" || unknownStatus != "unknown" { t.Fatalf("stop did not preserve unknown and silently suppress unstarted: pending=%s unknown=%s", pendingStatus, unknownStatus) } if err := s.ApplyControl(currentDispatcherID, 1001, "task-asr", "resume"); err == nil { t.Fatal("stopped task resumed") } if outbox, err := s.ListPendingOutbox(currentDispatcherID); err != nil || len(outbox) != 0 { t.Fatalf("stop emitted per-call results: %+v %v", outbox, err) } } func TestEditCommitsSnapshotRevisionAndAckTogether(t *testing.T) { s := preparedCurrentCallStore(t) updated, err := s.ReadSnapshot(currentDispatcherID, 1001, "task-asr") if err != nil { t.Fatal(err) } updated.Task.Raw = []byte(strings.Replace(string(updated.Task.Raw), `"task_revision":1`, `"task_revision":2`, 1)) updated.Task.TaskRevision = 2 if status, err := s.CompleteEdit(updated, "edit-success"); err != nil || status != "applied" { t.Fatalf("edit status=%q: %v", status, err) } persisted, err := s.ReadSnapshot(currentDispatcherID, 1001, "task-asr") if err != nil || persisted.Task.TaskRevision != 2 { t.Fatalf("edit not durably bound: revision=%d err=%v", persisted.Task.TaskRevision, err) } var revision int64 if err := s.db.QueryRow(`SELECT task_revision FROM dispatcher_tasks WHERE task_id='task-asr'`).Scan(&revision); err != nil || revision != 2 { t.Fatalf("task and snapshot revisions differ: revision=%d err=%v", revision, err) } stale := updated stale.Task.Raw = []byte(strings.Replace(string(updated.Task.Raw), `"task_revision":2`, `"task_revision":1`, 1)) stale.Task.TaskRevision = 1 if status, err := s.CompleteEdit(stale, "edit-lower-revision"); err != nil || status != "applied" { t.Fatalf("latest event with lower source revision was rejected: %s %v", status, err) } conflicting := updated conflicting.Task.Raw = []byte(strings.Replace(string(updated.Task.Raw), `"status":"running"`, `"status":"paused"`, 1)) if status, err := s.CompleteEdit(conflicting, "edit-same-revision"); err != nil || status != "applied" { t.Fatalf("latest event with same source revision was rejected: %s %v", status, err) } persisted, err = s.ReadSnapshot(currentDispatcherID, 1001, "task-asr") if err != nil || persisted.Task.TaskRevision != 2 || string(persisted.Task.Raw) != string(conflicting.Task.Raw) { t.Fatalf("latest edit was not bound: revision=%d err=%v", persisted.Task.TaskRevision, err) } if err := s.ApplyControl(currentDispatcherID, 1001, "task-asr", "stop"); err != nil { t.Fatal(err) } updated.Task.Raw = []byte(strings.Replace(string(updated.Task.Raw), `"task_revision":2`, `"task_revision":3`, 1)) updated.Task.TaskRevision = 3 if status, err := s.CompleteEdit(updated, "edit-stopped"); err != nil || status != "rejected" { t.Fatalf("stopped edit status=%q: %v", status, err) } outbox, err := s.ListPendingOutbox(currentDispatcherID) if err != nil { t.Fatal(err) } foundRejected := false for _, item := range outbox { if strings.Contains(string(item.Body), `"event_id":"edit-stopped"`) && strings.Contains(string(item.Body), `"status":"rejected"`) { foundRejected = true } } if !foundRejected { t.Fatal("edit on stopped task lacked durable rejection receipt") } persisted, err = s.ReadSnapshot(currentDispatcherID, 1001, "task-asr") if err != nil || persisted.Task.TaskRevision != 2 { t.Fatalf("stopped edit modified task: revision=%d err=%v", persisted.Task.TaskRevision, err) } } func currentMondayUTC() time.Time { return time.Date(2026, 9, 21, 1, 30, 0, 0, time.UTC) }