Files
go-cart-actor/cmd/order/amqp_test.go
T
2026-07-01 10:40:28 +02:00

109 lines
3.2 KiB
Go

package main
import (
"context"
"fmt"
"io"
"log/slog"
"path/filepath"
"testing"
"time"
"git.k6n.net/mats/go-cart-actor/pkg/actor"
"git.k6n.net/mats/go-cart-actor/pkg/flow"
"git.k6n.net/mats/go-cart-actor/pkg/idempotency"
"git.k6n.net/mats/go-cart-actor/pkg/order"
)
func testServer(t *testing.T) *server {
t.Helper()
reg := actor.NewMutationRegistry()
order.RegisterMutations(reg)
storage := actor.NewDiskStorage[order.OrderGrain](t.TempDir(), reg)
pool, err := actor.NewSimpleGrainPool(actor.GrainPoolConfig[order.OrderGrain]{
Hostname: "test",
Spawn: func(_ context.Context, id uint64) (actor.Grain[order.OrderGrain], error) {
return order.NewOrderGrain(id, time.Now()), nil
},
SpawnHost: func(string) (actor.Host[order.OrderGrain], error) { return nil, fmt.Errorf("none") },
Destroy: func(actor.Grain[order.OrderGrain]) error { return nil },
TTL: time.Hour,
PoolSize: 100,
MutationRegistry: reg,
Storage: nil,
})
if err != nil {
t.Fatal(err)
}
t.Cleanup(pool.Close)
applier := &orderedApplier{pool: pool, storage: storage}
provider := order.NewPassthroughProvider("legacy", "ref", 15000)
freg := buildFlowRegistry(applier, provider, nil, nil, nil, nil, nil, slog.New(slog.NewTextHandler(io.Discard, nil)))
engine := flow.NewEngine(freg, slog.New(slog.NewTextHandler(io.Discard, nil)))
def, err := order.EmbeddedFlow("place-and-pay")
if err != nil {
t.Fatal(err)
}
idem, err := idempotency.Open(filepath.Join(t.TempDir(), "idem.log"))
if err != nil {
t.Fatal(err)
}
return &server{
pool: pool, applier: applier, storage: storage, reg: reg,
engine: engine, freg: freg,
flows: &flowStore{defs: map[string]*flow.Definition{def.Name: def}},
provider: provider, taxProvider: order.NewStaticTaxProvider(),
defaultFlow: def.Name, idem: idem, logger: slog.New(slog.NewTextHandler(io.Discard, nil)),
}
}
func TestIngestOrderFromQueue(t *testing.T) {
s := testServer(t)
ctx := context.Background()
body := []byte(`{
"checkoutId":"K-1","idempotencyKey":"checkout-K-1","cartId":"cart-1",
"currency":"SEK","country":"SE","customerEmail":"customer@example.com",
"lines":[{"reference":"l1","sku":"l1","name":"Widget","quantity":1,"unitPrice":15000,"taxRate":25}],
"payment":{"provider":"klarna","reference":"ref-123","amount":15000}
}`)
if err := ingestOrderFromQueue(ctx, s, body); err != nil {
t.Fatalf("ingest: %v", err)
}
// Retrieve the created order grain. The new order ID is recorded in idempotency.Store
id, ok := s.idem.Get("checkout-K-1")
if !ok {
t.Fatalf("expected order ID to be stored in idempotency cache")
}
g, err := s.applier.Get(ctx, id)
if err != nil {
t.Fatal(err)
}
if g.Status != order.StatusCaptured {
t.Fatalf("status = %q, want captured", g.Status)
}
if g.CapturedAmount != 15000 {
t.Fatalf("captured = %d, want 15000", g.CapturedAmount)
}
// Redelivery must be idempotent — no double capture, no new payment.
if err := ingestOrderFromQueue(ctx, s, body); err != nil {
t.Fatalf("re-ingest: %v", err)
}
g2, _ := s.applier.Get(ctx, id)
if g2.CapturedAmount != 15000 {
t.Fatalf("redelivery double-captured: %d", g2.CapturedAmount)
}
if len(g2.Payments) != 1 {
t.Fatalf("redelivery created %d payments, want 1", len(g2.Payments))
}
}