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 new per-mutation AddItem RPC (breaking v2 API) to avoid external product fetch logic // 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", } // Issue AddItem RPC directly (breaking v2 API) addResp, err := cartClient.AddItem(context.Background(), &messages.AddItemRequest{ CartId: cartID, ClientTimestamp: time.Now().Unix(), Payload: addItem, }) if err != nil { t.Fatalf("AddItem RPC error: %v", err) } if addResp.StatusCode != 200 { t.Fatalf("AddItem returned non-200 status: %d, error: %s", addResp.StatusCode, addResp.GetError()) } // Validate the response state (from AddItem) state := addResp.GetState() if state == nil { t.Fatalf("AddItem response state is nil") } // (Removed obsolete Mutate response handling) if len(state.Items) != 1 { t.Fatalf("Expected 1 item after AddItem, 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)