95 lines
3.5 KiB
Go
95 lines
3.5 KiB
Go
package store
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestCurrentSIPNotificationRequiresFullDrainAndExactLoadedRevision(t *testing.T) {
|
|
s := preparedCurrentCallStore(t)
|
|
if err := s.MarkReadyForSIP(currentDispatcherID, 8); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cmd := currentCall("ongoing-before-sip-change")
|
|
if _, _, err := s.RecordExecute(cmd); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.ReserveExecute(cmd.DispatcherID, cmd.EventID, currentReservation(), currentMondayUTC()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.MarkExecuteUnknown(cmd.DispatcherID, cmd.EventID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if count, err := s.OccupiedCalls(currentDispatcherID); err != nil || count != 1 {
|
|
t.Fatalf("unknown call was not counted: %d %v", count, err)
|
|
}
|
|
if err := s.NoteSIPChange(currentDispatcherID, 9); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if admitted, err := s.CanAdmit(currentDispatcherID, 1001, "task-asr"); err != nil || admitted {
|
|
t.Fatalf("SIP change failed to close admission: %v %v", admitted, err)
|
|
}
|
|
if applied, pending, err := s.SIPState(currentDispatcherID); err != nil || applied != 8 || pending != 9 {
|
|
t.Fatalf("SIP notification not durable: applied=%d pending=%d %v", applied, pending, err)
|
|
}
|
|
if err := s.MarkReadyForSIP(currentDispatcherID, 9); err == nil {
|
|
t.Fatal("changed SIP opened without matching task snapshot or drain")
|
|
}
|
|
snapshot, err := s.ReadSnapshot(currentDispatcherID, 1001, "task-asr")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
snapshot.SIP.Revision = 9
|
|
if err := s.SaveSnapshot(snapshot); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.MarkReadyForSIP(currentDispatcherID, 9); err == nil {
|
|
t.Fatal("unknown ongoing call was released by SIP change")
|
|
}
|
|
// An external definitive call end must be recorded before old SIP loads can
|
|
// drain. Unknown outcomes never expire or auto-release on their own.
|
|
if _, err := s.db.Exec(`UPDATE dispatcher_inbox SET status='finished' WHERE dispatcher_id=? AND event_id=? AND status='unknown'`, cmd.DispatcherID, cmd.EventID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.MarkReadyForSIP(currentDispatcherID, 9); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if admitted, err := s.CanAdmit(currentDispatcherID, 1001, "task-asr"); err != nil || !admitted {
|
|
t.Fatalf("drained verified SIP revision did not reopen: %v %v", admitted, err)
|
|
}
|
|
if applied, pending, err := s.SIPState(currentDispatcherID); err != nil || applied != 9 || pending != 0 {
|
|
t.Fatalf("SIP state not committed after reload: %d %d %v", applied, pending, err)
|
|
}
|
|
if err := s.NoteSIPChange(currentDispatcherID, 8); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if applied, pending, err := s.SIPState(currentDispatcherID); err != nil || applied != 9 || pending != 0 {
|
|
t.Fatalf("stale notification downgraded applied SIP: %d %d %v", applied, pending, err)
|
|
}
|
|
}
|
|
|
|
func TestCurrentPendingSIPChangeSurvivesRestartWithoutOldDataDeletion(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "state.db")
|
|
s, err := OpenCurrent(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.NoteSIPChange(currentDispatcherID, 12); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reopened, err := OpenCurrent(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer reopened.Close()
|
|
if applied, pending, err := reopened.SIPState(currentDispatcherID); err != nil || applied != 0 || pending != 12 {
|
|
t.Fatalf("lost pending SIP notification on restart: %d %d %v", applied, pending, err)
|
|
}
|
|
if err := reopened.MarkReadyForSIP(currentDispatcherID, 11); err == nil {
|
|
t.Fatal("older loaded SIP overrode durable pending notification")
|
|
}
|
|
}
|