bingobot/internal/state/state_test.go
Ava Affine e7d229c217 Early functionality modules
0. tests for event replay
   Tests are now included for the event replay features. This includes many fixes on top of
   the last commit as well as some tweaks to config module values.
1. activity module
   An activity module is created to track the useractive events and provide a counter for them.
   It also encapsulates logic to discard old useractive events.
2. web module
   A web module is created. This module serves a static webpage showing runtime information.
   Currently it only shows a snapshot of the user activity data. It is my intention that it
   eventually also shows an audit log, known users and channels, uptime, and more. Future work
   will also be needed in order to use HTML templating so that it doesn't look so... basic.
   Live updates to the information may also be desired.

Signed-off-by: Ava Affine <ava@sunnypup.io>
2025-01-07 16:43:55 -08:00

347 lines
7.3 KiB
Go

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:
}
}