Co-authored-by: matst80 <mats.tornberg@gmail.com> Reviewed-on: #8 Co-authored-by: Mats Törnberg <mats@tornberg.me> Co-committed-by: Mats Törnberg <mats@tornberg.me>
135 lines
4.0 KiB
Go
135 lines
4.0 KiB
Go
package actor
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"slices"
|
|
"testing"
|
|
|
|
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 TestConcurrentSafeRegistrationLookup(t *testing.T) {
|
|
// // This test is light-weight; it ensures locks don't deadlock under simple concurrent access.
|
|
// reg := NewMutationRegistry().(*ProtoMutationRegistry)
|
|
// mut := NewMutation[cartState, *messages.Noop](
|
|
// func(state *cartState, msg *messages.Noop) error { state.calls++; return nil },
|
|
// func() *messages.Noop { return &messages.Noop{} },
|
|
// )
|
|
// reg.RegisterMutations(mut)
|
|
|
|
// done := make(chan struct{})
|
|
// const workers = 25
|
|
// for i := 0; i < workers; i++ {
|
|
// go func() {
|
|
// for j := 0; j < 100; j++ {
|
|
// _, _ = reg.Create("Noop")
|
|
// _, _ = reg.GetTypeName(&messages.Noop{})
|
|
// _ = reg.Apply(&cartState{}, &messages.Noop{})
|
|
// }
|
|
// done <- struct{}{}
|
|
// }()
|
|
// }
|
|
|
|
// for i := 0; i < workers; i++ {
|
|
// <-done
|
|
// }
|
|
// }
|
|
|
|
// Helpers
|