208 lines
5.7 KiB
Go
208 lines
5.7 KiB
Go
package actor
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
cart_messages "git.k6n.net/go-cart-actor/proto/cart"
|
|
)
|
|
|
|
type cartState struct {
|
|
calls int
|
|
lastAdded *cart_messages.AddItem
|
|
}
|
|
|
|
func TestRegisteredMutationBasics(t *testing.T) {
|
|
reg := NewMutationRegistry().(*ProtoMutationRegistry)
|
|
|
|
addItemMutation := NewMutation(
|
|
func(state *cartState, msg *cart_messages.AddItem) error {
|
|
state.calls++
|
|
// copy to avoid external mutation side-effects (not strictly necessary for the test)
|
|
cp := msg
|
|
state.lastAdded = cp
|
|
return nil
|
|
},
|
|
)
|
|
|
|
// Sanity check on mutation metadata
|
|
if addItemMutation.Name() != "AddItem" {
|
|
t.Fatalf("expected mutation Name() == AddItem, got %s", addItemMutation.Name())
|
|
}
|
|
if got, want := addItemMutation.Type(), reflect.TypeOf(cart_messages.AddItem{}); got != want {
|
|
t.Fatalf("expected Type() == %v, got %v", want, got)
|
|
}
|
|
|
|
reg.RegisterMutations(addItemMutation)
|
|
|
|
// RegisteredMutations: membership (order not guaranteed)
|
|
names := reg.RegisteredMutations()
|
|
if !slices.Contains(names, "AddItem") {
|
|
t.Fatalf("RegisteredMutations missing AddItem, got %v", names)
|
|
}
|
|
|
|
// RegisteredMutationTypes: membership (order not guaranteed)
|
|
types := reg.RegisteredMutationTypes()
|
|
if !slices.Contains(types, reflect.TypeOf(cart_messages.AddItem{})) {
|
|
t.Fatalf("RegisteredMutationTypes missing AddItem type, got %v", types)
|
|
}
|
|
|
|
// GetTypeName should resolve for a pointer instance
|
|
name, ok := reg.GetTypeName(&cart_messages.AddItem{})
|
|
if !ok || name != "AddItem" {
|
|
t.Fatalf("GetTypeName returned (%q,%v), expected (AddItem,true)", name, ok)
|
|
}
|
|
|
|
// GetTypeName should fail for unregistered type
|
|
if name, ok := reg.GetTypeName(&cart_messages.RemoveItem{}); ok || name != "" {
|
|
t.Fatalf("expected GetTypeName to fail for unregistered message, got (%q,%v)", name, ok)
|
|
}
|
|
|
|
// Create by name
|
|
msg, ok := reg.Create("AddItem")
|
|
if !ok {
|
|
t.Fatalf("Create failed for registered mutation")
|
|
}
|
|
if _, isAddItem := msg.(*cart_messages.AddItem); !isAddItem {
|
|
t.Fatalf("Create returned wrong concrete type: %T", msg)
|
|
}
|
|
|
|
// Create unknown
|
|
if m2, ok := reg.Create("Unknown"); ok || m2 != nil {
|
|
t.Fatalf("Create should fail for unknown mutation, got (%T,%v)", m2, ok)
|
|
}
|
|
|
|
// Apply happy path
|
|
state := &cartState{}
|
|
add := &cart_messages.AddItem{ItemId: 42, Quantity: 3, Sku: "ABC"}
|
|
if _, err := reg.Apply(context.Background(), state, add); err != nil {
|
|
t.Fatalf("Apply returned error: %v", err)
|
|
}
|
|
if state.calls != 1 {
|
|
t.Fatalf("handler not invoked expected calls=1 got=%d", state.calls)
|
|
}
|
|
if state.lastAdded == nil || state.lastAdded.ItemId != 42 || state.lastAdded.Quantity != 3 {
|
|
t.Fatalf("state not updated correctly: %+v", state.lastAdded)
|
|
}
|
|
|
|
// Apply nil grain
|
|
if _, err := reg.Apply(context.Background(), nil, add); err == nil {
|
|
t.Fatalf("expected error for nil grain")
|
|
}
|
|
|
|
// Apply nil message
|
|
if _, err := reg.Apply(context.Background(), state, nil); err == nil {
|
|
t.Fatalf("expected error for nil mutation message")
|
|
}
|
|
|
|
// Apply unregistered message
|
|
_, err := reg.Apply(context.Background(), state, &cart_messages.RemoveItem{})
|
|
if err != ErrMutationNotRegistered {
|
|
t.Fatalf("expected ErrMutationNotRegistered, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEventChannel(t *testing.T) {
|
|
reg := NewMutationRegistry().(*ProtoMutationRegistry)
|
|
|
|
addItemMutation := NewMutation(
|
|
func(state *cartState, msg *cart_messages.AddItem) error {
|
|
state.calls++
|
|
return nil
|
|
},
|
|
)
|
|
|
|
reg.RegisterMutations(addItemMutation)
|
|
|
|
eventCh := make(chan ApplyResult, 10)
|
|
reg.SetEventChannel(eventCh)
|
|
|
|
state := &cartState{}
|
|
add := &cart_messages.AddItem{ItemId: 42, Quantity: 3, Sku: "ABC"}
|
|
results, err := reg.Apply(context.Background(), state, add)
|
|
if err != nil {
|
|
t.Fatalf("Apply returned error: %v", err)
|
|
}
|
|
if len(results) != 1 {
|
|
t.Fatalf("expected 1 result, got %d", len(results))
|
|
}
|
|
|
|
// Receive from channel with timeout
|
|
select {
|
|
case res := <-eventCh:
|
|
if res.Type != "AddItem" {
|
|
t.Fatalf("expected type AddItem, got %s", res.Type)
|
|
}
|
|
if res.Error != nil {
|
|
t.Fatalf("expected no error, got %v", res.Error)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("expected to receive event on channel within timeout")
|
|
}
|
|
}
|
|
|
|
func TestEventChannelClosed(t *testing.T) {
|
|
reg := NewMutationRegistry().(*ProtoMutationRegistry)
|
|
|
|
addItemMutation := NewMutation(
|
|
func(state *cartState, msg *cart_messages.AddItem) error {
|
|
state.calls++
|
|
return nil
|
|
},
|
|
)
|
|
|
|
reg.RegisterMutations(addItemMutation)
|
|
|
|
eventCh := make(chan ApplyResult, 10)
|
|
reg.SetEventChannel(eventCh)
|
|
|
|
close(eventCh) // Close the channel to simulate external close
|
|
|
|
state := &cartState{}
|
|
add := &cart_messages.AddItem{ItemId: 42, Quantity: 3, Sku: "ABC"}
|
|
// This should not panic due to recover in goroutine
|
|
results, err := reg.Apply(context.Background(), state, add)
|
|
if err != nil {
|
|
t.Fatalf("Apply returned error: %v", err)
|
|
}
|
|
if len(results) != 1 {
|
|
t.Fatalf("expected 1 result, got %d", len(results))
|
|
}
|
|
// Test passes if no panic occurs
|
|
}
|
|
|
|
func TestEventChannelUnbufferedNoListener(t *testing.T) {
|
|
reg := NewMutationRegistry().(*ProtoMutationRegistry)
|
|
|
|
addItemMutation := NewMutation(
|
|
func(state *cartState, msg *cart_messages.AddItem) error {
|
|
state.calls++
|
|
return nil
|
|
},
|
|
)
|
|
|
|
reg.RegisterMutations(addItemMutation)
|
|
|
|
eventCh := make(chan ApplyResult) // unbuffered
|
|
reg.SetEventChannel(eventCh)
|
|
|
|
// No goroutine reading from eventCh
|
|
|
|
state := &cartState{}
|
|
add := &cart_messages.AddItem{ItemId: 42, Quantity: 3, Sku: "ABC"}
|
|
results, err := reg.Apply(context.Background(), state, add)
|
|
if err != nil {
|
|
t.Fatalf("Apply returned error: %v", err)
|
|
}
|
|
if len(results) != 1 {
|
|
t.Fatalf("expected 1 result, got %d", len(results))
|
|
}
|
|
// Since no listener, the send should go to default and not block
|
|
// Test passes if Apply completes without hanging
|
|
}
|
|
|
|
// Helpers
|