more platform
This commit is contained in:
1 parent
8a71e32149
commit
2a66754f9e
27 files changed
+2435
-540
No files matched your search
@@ -0,0 +1,306 @@
|
||||
package shipping
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// InMemoryProvider is a thread-safe in-memory implementation of Provider for
|
||||
// tests — no external API calls needed. Backed by a map from
|
||||
// "country|postcode" to available delivery option groups.
|
||||
type InMemoryProvider struct {
|
||||
mu sync.Mutex
|
||||
opts map[string][]DeliveryOptionGroup // key: "country|postcode"
|
||||
err error // injected error for testing failure paths
|
||||
}
|
||||
|
||||
// NewInMemoryProvider creates a provider seeded with delivery option groups.
|
||||
// seed is a sequence of (country, postcode, []DeliveryOptionGroup) triples.
|
||||
func NewInMemoryProvider(seed ...any) *InMemoryProvider {
|
||||
p := &InMemoryProvider{
|
||||
opts: make(map[string][]DeliveryOptionGroup),
|
||||
}
|
||||
for i := 0; i+2 < len(seed); i += 3 {
|
||||
country, ok := seed[i].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
postcode, ok := seed[i+1].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
groups, ok := seed[i+2].([]DeliveryOptionGroup)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
p.opts[providerKey(country, postcode)] = groups
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func providerKey(country, postcode string) string {
|
||||
return country + "|" + postcode
|
||||
}
|
||||
|
||||
// SetError injects an error that GetShippingOptions returns on every call
|
||||
// (for testing failure paths). Nil clears the error.
|
||||
func (p *InMemoryProvider) SetError(err error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.err = err
|
||||
}
|
||||
|
||||
func (p *InMemoryProvider) GetShippingOptions(_ context.Context, req GetOptionsRequest) ([]DeliveryOptionGroup, error) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if p.err != nil {
|
||||
return nil, p.err
|
||||
}
|
||||
|
||||
groups, ok := p.opts[providerKey(req.Country, req.PostCode)]
|
||||
if !ok {
|
||||
return []DeliveryOptionGroup{}, nil
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestProviderReturnsOptions(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := NewInMemoryProvider(
|
||||
"SE", "17173", []DeliveryOptionGroup{
|
||||
{
|
||||
Type: "home",
|
||||
Name: "Home delivery",
|
||||
DefaultOption: DeliveryOption{
|
||||
ID: "home-1",
|
||||
Name: "Home delivery",
|
||||
Description: "Delivered to your door",
|
||||
DeliveryDays: "2-3 working days",
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "pickup",
|
||||
Name: "Pickup point",
|
||||
DefaultOption: DeliveryOption{
|
||||
ID: "pickup-1",
|
||||
Name: "PostNord Storgatan",
|
||||
Description: "Collect at PostNord Storgatan",
|
||||
DeliveryDays: "2-3 working days",
|
||||
PickupLocation: &PickupLocation{
|
||||
ID: "loc-1",
|
||||
Name: "PostNord Storgatan",
|
||||
Address: Address{
|
||||
Street: "Storgatan 9",
|
||||
City: "Solna",
|
||||
PostCode: "17173",
|
||||
Country: "SE",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
groups, err := p.GetShippingOptions(ctx, GetOptionsRequest{
|
||||
Country: "SE",
|
||||
PostCode: "17173",
|
||||
Locale: "sv",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("GetShippingOptions: %v", err)
|
||||
}
|
||||
if len(groups) != 2 {
|
||||
t.Fatalf("expected 2 option groups, got %d", len(groups))
|
||||
}
|
||||
|
||||
// Verify home delivery option
|
||||
if groups[0].Type != "home" {
|
||||
t.Fatalf("first group: expected type=home, got %q", groups[0].Type)
|
||||
}
|
||||
if groups[0].DefaultOption.ID != "home-1" {
|
||||
t.Fatalf("home default option: expected ID=home-1, got %q", groups[0].DefaultOption.ID)
|
||||
}
|
||||
|
||||
// Verify pickup option with location
|
||||
if groups[1].Type != "pickup" {
|
||||
t.Fatalf("second group: expected type=pickup, got %q", groups[1].Type)
|
||||
}
|
||||
loc := groups[1].DefaultOption.PickupLocation
|
||||
if loc == nil {
|
||||
t.Fatal("pickup option: expected a PickupLocation, got nil")
|
||||
}
|
||||
if loc.Address.City != "Solna" {
|
||||
t.Fatalf("pickup address city: expected Solna, got %q", loc.Address.City)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderUnsupportedAddress(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := NewInMemoryProvider() // no seed data
|
||||
|
||||
groups, err := p.GetShippingOptions(ctx, GetOptionsRequest{
|
||||
Country: "NO",
|
||||
PostCode: "0150",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil error for unsupported address, got %v", err)
|
||||
}
|
||||
if groups == nil {
|
||||
t.Fatal("expected non-nil empty slice for unsupported address, got nil")
|
||||
}
|
||||
if len(groups) != 0 {
|
||||
t.Fatalf("expected 0 option groups for unsupported address, got %d", len(groups))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderError(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := NewInMemoryProvider(
|
||||
"SE", "17173", []DeliveryOptionGroup{
|
||||
{
|
||||
Type: "home",
|
||||
Name: "Home delivery",
|
||||
DefaultOption: DeliveryOption{
|
||||
ID: "home-1",
|
||||
Name: "Home delivery",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
injected := errors.New("api timeout")
|
||||
p.SetError(injected)
|
||||
|
||||
_, err := p.GetShippingOptions(ctx, GetOptionsRequest{
|
||||
Country: "SE",
|
||||
PostCode: "17173",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected injected error, got nil")
|
||||
}
|
||||
if !errors.Is(err, injected) {
|
||||
t.Fatalf("expected injected error %v, got %v", injected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderAdditionalOptions(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
additionalPoints := make([]DeliveryOption, 2)
|
||||
additionalPoints[0] = DeliveryOption{
|
||||
ID: "pickup-2",
|
||||
Name: "ICA Maxi Solna",
|
||||
PickupLocation: &PickupLocation{
|
||||
ID: "loc-2",
|
||||
Name: "ICA Maxi Solna",
|
||||
Address: Address{
|
||||
Street: "Solna Centrum 5",
|
||||
City: "Solna",
|
||||
PostCode: "17173",
|
||||
Country: "SE",
|
||||
},
|
||||
},
|
||||
}
|
||||
additionalPoints[1] = DeliveryOption{
|
||||
ID: "pickup-3",
|
||||
Name: "Coop Solna",
|
||||
PickupLocation: &PickupLocation{
|
||||
ID: "loc-3",
|
||||
Name: "Coop Solna",
|
||||
Address: Address{
|
||||
Street: "Solna Torg 2",
|
||||
City: "Solna",
|
||||
PostCode: "17173",
|
||||
Country: "SE",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
p := NewInMemoryProvider(
|
||||
"SE", "17173", []DeliveryOptionGroup{
|
||||
{
|
||||
Type: "pickup",
|
||||
Name: "Pickup point",
|
||||
DefaultOption: DeliveryOption{
|
||||
ID: "pickup-1",
|
||||
Name: "PostNord Storgatan",
|
||||
},
|
||||
AdditionalOptions: additionalPoints,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
groups, err := p.GetShippingOptions(ctx, GetOptionsRequest{
|
||||
Country: "SE",
|
||||
PostCode: "17173",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("GetShippingOptions: %v", err)
|
||||
}
|
||||
if len(groups) != 1 {
|
||||
t.Fatalf("expected 1 option group, got %d", len(groups))
|
||||
}
|
||||
|
||||
addl := groups[0].AdditionalOptions
|
||||
if len(addl) != 2 {
|
||||
t.Fatalf("expected 2 additional options, got %d", len(addl))
|
||||
}
|
||||
if addl[0].ID != "pickup-2" {
|
||||
t.Fatalf("additional[0]: expected ID=pickup-2, got %q", addl[0].ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTripEncodeDecode(t *testing.T) {
|
||||
original := []DeliveryOptionGroup{
|
||||
{
|
||||
Type: "home",
|
||||
Name: "Home delivery",
|
||||
DefaultOption: DeliveryOption{
|
||||
ID: "home-1",
|
||||
Name: "Home delivery",
|
||||
DeliveryDays: "2-3 working days",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
encoded, err := Encode(original)
|
||||
if err != nil {
|
||||
t.Fatalf("Encode: %v", err)
|
||||
}
|
||||
|
||||
decoded, err := Decode(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode: %v", err)
|
||||
}
|
||||
|
||||
if len(decoded) != 1 {
|
||||
t.Fatalf("round-trip: expected 1 group, got %d", len(decoded))
|
||||
}
|
||||
if decoded[0].DefaultOption.ID != "home-1" {
|
||||
t.Fatalf("round-trip: expected ID=home-1, got %q", decoded[0].DefaultOption.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoOptionsSentinelNotReturnedBySingleProvider(t *testing.T) {
|
||||
// ErrNoOptions is for multi-provider orchestrators. A single provider
|
||||
// returns an empty slice for unsupported addresses. Verify that the
|
||||
// in-memory provider follows this contract.
|
||||
ctx := context.Background()
|
||||
p := NewInMemoryProvider()
|
||||
|
||||
groups, err := p.GetShippingOptions(ctx, GetOptionsRequest{
|
||||
Country: "FI",
|
||||
PostCode: "00100",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil for unsupported address, got %v", err)
|
||||
}
|
||||
if groups == nil {
|
||||
t.Fatal("expected non-nil empty slice, got nil")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user