package state import ( "fmt" "os" "testing" "time" "gitlab.com/whom/bingobot/internal/logging" "gitlab.com/whom/bingobot/pkg/docbuf" ) /* WARNING: * Cannot run these tests in parallel! * limitation of SetupTest and CleanupTest */ const TestTok = "TEST_NAME" var loggingInitialized = false func SetupTest(t *testing.T) { maxEventsInMemory = 271 var err error // have to set up logger if !loggingInitialized { logging.Init() loggingInitialized = true } b := docbuf.NewReadWriteSeekString() eventStream, err = docbuf.NewDocumentBuffer(300, &b) if err != nil { t.Fatalf("error allocating buffer: %e", err) } } func TestEventMarshalUnmarshal(t *testing.T) { tm := time.Now() cev := ChallengeEvent{UserEvent{ uid: TestTok, created: tm, }} vev := VoteEvent(map[string]string{ VoteActionKey: "a", VoteRequesterKey: "r", VoteCreatedKey: "c", VoteStatusKey: VoteStatusFinalized, VoteResultKey: VoteResultFail, }) cstr, err := EventToString(cev); if err != nil { t.Fatalf("error marshalling challenge: %e, %s", err, cstr) } vstr, err := EventToString(vev); if err != nil { t.Fatalf("error marshalling vote: %e, %s", err, vstr) } if ev, err := EventFromString(cstr); err != nil || ev.Data()[UserEventUserKey] != cev.Data()[UserEventUserKey] || ev.Data()[UserEventCreatedKey] != cev.Data()[UserEventCreatedKey] { t.Fatalf("error unmarshalling challenge: %e, %v!=%v", err, ev, cev) } if ev, err := EventFromString(vstr); err != nil || fmt.Sprint(ev) != fmt.Sprint(vev) { t.Fatalf("error unmarshalling vote: %e, %v!=%v", err, ev, vev) } } func TestPubSub(t *testing.T) { SetupTest(t) c, e := UserActive.Subscribe() if e != nil { t.Errorf("Error subscribing to UserActive events: %e", e) } old, _ := time.Parse( time.RFC3339, VeryOldVote, ) for i := range 270 { if err := PublishEvent(UserActiveEvent{UserEvent{ uid: fmt.Sprintf("%d", i), created: old, }}); err != nil { t.Errorf("Failed to add event: %e", err) } } PublishEvent(UserActiveEvent{UserEvent{ uid: fmt.Sprintf(TestTok), created: time.Now(), }}) PublishEvent(ChallengeEvent{UserEvent{ uid: fmt.Sprintf(TestTok), created: time.Now(), }}) Loop: for i := 0; true; i++ { select { case e, ok := <-c: if !ok { t.Fatalf("Subscription Channel Closed") } if e.Type() != UserActive { t.Fatalf("Non UserActive Event in UserActive subscription: %v", e.Type()) } default: if i == maxEventsInMemory { break Loop } else { t.Fatalf("Unexpected number of events in channel: %d", i) } } } PublishEvent(UserActiveEvent{UserEvent{ uid: "uniqueToken", created: time.Now(), }}) select { case e, ok := <-c: if !ok || e.Data()[UserEventUserKey] != "uniqueToken" { t.Fatalf("didnt read correct event from channel: %v", e) } default: t.Fatalf("New event not published to subscription!") } } func TestFilterCache(t *testing.T) { SetupTest(t) old, _ := time.Parse( time.RFC3339, VeryOldVote, ) for i := range 270 { if err := PublishEvent(UserActiveEvent{UserEvent{ uid: fmt.Sprintf("%d", i), created: old, }}); err != nil { t.Errorf("Failed to add event: %e", err) } } PublishEvent(UserActiveEvent{UserEvent{ uid: fmt.Sprintf(TestTok), created: time.Now(), }}) PublishEvent(ChallengeEvent{UserEvent{ uid: fmt.Sprintf(TestTok), created: time.Now(), }}) events, err := GetMatchingEvents( UserActive, map[string]string{ UserEventUserKey: TestTok, }, ) if err != nil { t.Errorf("Error filtering events: %e", err) } if len(events) != 1 { t.Errorf("Got too many events from filter: %d", len(events)) } if events[0].Type() != UserActive { t.Errorf("Got wrong event!: %+v", events[0]) } } func TestVoteEventValidations(t *testing.T) { var err error if err = VoteEvent( map[string]string{ VoteRequesterKey: "r", VoteCreatedKey: "c", VoteStatusKey: VoteStatusInProgress, }, ).Validate(); err.Error() != VoteMissingKeyError+VoteActionKey { t.Errorf("Unexpected error from validation: %e", err) } if err = VoteEvent( map[string]string{ VoteActionKey: "a", VoteRequesterKey: "r", VoteCreatedKey: "c", VoteStatusKey: VoteStatusInProgress, }, ).Validate(); err != nil { t.Errorf("Unexpected error: %e", err) } if err = VoteEvent( map[string]string{ VoteActionKey: "a", VoteRequesterKey: "r", VoteCreatedKey: "c", VoteStatusKey: "s", }, ).Validate(); err.Error() != VoteBadStatusError+"s" { t.Errorf("Unexpected or no error: %e", err) } if err = VoteEvent( map[string]string{ VoteActionKey: "a", VoteRequesterKey: "r", VoteCreatedKey: "c", VoteStatusKey: VoteStatusInProgress, VoteResultKey: VoteResultFail, }, ).Validate(); err.Error() != VoteNotFinishedError { t.Errorf("Unexpected or no error: %e", err) } if err = VoteEvent( map[string]string{ VoteActionKey: "a", VoteRequesterKey: "r", VoteCreatedKey: "c", VoteStatusKey: VoteStatusFinalized, }, ).Validate(); err.Error() != VoteMissingResultError { t.Errorf("Unexpected or no error: %e", err) } if err = VoteEvent( map[string]string{ VoteActionKey: "a", VoteRequesterKey: "r", VoteCreatedKey: "c", VoteStatusKey: VoteStatusFinalized, VoteResultKey: "r", }, ).Validate(); err.Error() != VoteBadResultError+"r" { t.Errorf("Unexpected or no error: %e", err) } if err = VoteEvent( map[string]string{ VoteActionKey: "a", VoteRequesterKey: "r", VoteCreatedKey: "c", VoteStatusKey: VoteStatusFinalized, VoteResultKey: VoteResultFail, }, ).Validate(); err != nil { t.Errorf("Unexpected or no error: %e", err) } } func TestEventReplay(t *testing.T) { tmpTestFile, err := os.CreateTemp("", "TestEventReplayBackingStore") if err != nil { t.Fatalf("failed setting up tempfile: %s", err.Error()) } if err := tmpTestFile.Close(); err != nil { t.Fatalf("failed to close initial handle to tempfile: %s", err.Error()) } if err := Init(2, tmpTestFile.Name()); err != nil { t.Fatalf("failed initializing state machine: %s", err.Error()) } if err := PublishEvent(TestEvent{ ID: 1, Dispose: true, }); err != nil { t.Fatalf("failed to publish event 1: %s", err.Error()) } if err := PublishEvent(TestEvent{ ID: 2, Dispose: false, }); err != nil { t.Fatalf("failed to publish event 2: %s", err.Error()) } if err := PublishEvent(TestEvent{ ID: 3, Dispose: true, }); err != nil { t.Fatalf("failed to publish event 3: %s", err.Error()) } if err := PublishEvent(TestEvent{ ID: 4, Dispose: false, }); err != nil { t.Fatalf("failed to publish event 4: %s", err.Error()) } sub, err := Test.Subscribe() if err := Start(); err != nil { t.Fatalf("failed to start state machine: %s", err.Error()) } select { case ev1 := <- sub: if ev1.Type() != Test { t.Fatalf("wrong kind of event somehow: %+v", ev1) } if ev1.(TestEvent).ID != 2 { t.Fatalf("misordered event: %+v", ev1) } default: t.Fatal("no events available from replay") } select { case ev2 := <- sub: if ev2.Type() != Test { t.Fatalf("wrong kind of event somehow: %+v", ev2) } if ev2.(TestEvent).ID != 4 { t.Fatalf("misordered event: %+v", ev2) } default: t.Fatal("only one event made it out") } select { case <- sub: t.Fatalf("superfluous events left in subscription") default: } }