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>
347 lines
7.3 KiB
Go
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:
|
|
}
|
|
}
|