package main import ( "context" "fmt" "testing" "time" messages "git.tornberg.me/go-cart-actor/proto" "google.golang.org/grpc" ) // TestCartActorMutationAndState validates end-to-end gRPC mutation + state retrieval // against a locally started gRPC server (single-node scenario). // This test uses the oneof MutationEnvelope directly to avoid hitting external product // fetching logic (FetchItem) which would require network I/O. func TestCartActorMutationAndState(t *testing.T) { // Setup local grain pool + synced pool (no discovery, single host) pool := NewGrainLocalPool(1024, time.Minute, spawn) synced, err := NewSyncedPool(pool, "127.0.0.1", nil) if err != nil { t.Fatalf("NewSyncedPool error: %v", err) } // Start gRPC server (CartActor + ControlPlane) on :1337 grpcSrv, err := StartGRPCServer(":1337", pool, synced) if err != nil { t.Fatalf("StartGRPCServer error: %v", err) } defer grpcSrv.GracefulStop() // Dial the local server ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() conn, err := grpc.DialContext(ctx, "127.0.0.1:1337", grpc.WithInsecure(), grpc.WithBlock(), ) if err != nil { t.Fatalf("grpc.Dial error: %v", err) } defer conn.Close() cartClient := messages.NewCartActorClient(conn) // Create a short cart id (<=16 chars so it fits into the fixed CartId 16-byte array cleanly) cartID := fmt.Sprintf("cart-%d", time.Now().UnixNano()) // Build an AddItem payload (bypasses FetchItem to keep test deterministic) addItem := &messages.AddItem{ ItemId: 1, Quantity: 1, Price: 1000, OrgPrice: 1000, Sku: "test-sku", Name: "Test SKU", Image: "/img.png", Stock: 2, // InStock Tax: 2500, Country: "se", } // Build oneof envelope directly (no legacy handler/enum) envelope := &messages.MutationEnvelope{ CartId: cartID, ClientTimestamp: time.Now().Unix(), Mutation: &messages.MutationEnvelope_AddItem{ AddItem: addItem, }, } // Issue Mutate RPC mutResp, err := cartClient.Mutate(context.Background(), envelope) if err != nil { t.Fatalf("Mutate RPC error: %v", err) } if mutResp.StatusCode != 200 { t.Fatalf("Mutate returned non-200 status: %d, error: %s", mutResp.StatusCode, mutResp.GetError()) } // Validate the response state state := mutResp.GetState() if state == nil { t.Fatalf("Mutate response state is nil") } if len(state.Items) != 1 { t.Fatalf("Expected 1 item after mutation, got %d", len(state.Items)) } if state.Items[0].Sku != "test-sku" { t.Fatalf("Unexpected item SKU: %s", state.Items[0].Sku) } // Issue GetState RPC getResp, err := cartClient.GetState(context.Background(), &messages.StateRequest{ CartId: cartID, }) if err != nil { t.Fatalf("GetState RPC error: %v", err) } if getResp.StatusCode != 200 { t.Fatalf("GetState returned non-200 status: %d, error: %s", getResp.StatusCode, getResp.GetError()) } state2 := getResp.GetState() if state2 == nil { t.Fatalf("GetState response state is nil") } if len(state2.Items) != 1 { t.Fatalf("Expected 1 item in GetState, got %d", len(state2.Items)) } if state2.Items[0].Sku != "test-sku" { t.Fatalf("Unexpected SKU in GetState: %s", state2.Items[0].Sku) } } // Legacy serialization helper removed (oneof envelope used directly)