From 616cba4623f386e6f37fe1043e907471acb8d858 Mon Sep 17 00:00:00 2001 From: Erick Shaffer Date: Fri, 17 Jul 2026 22:09:26 -0600 Subject: [PATCH 1/2] fix: fetch targeted Amazon orders directly --- docs/bug-fixes.md | 20 +++++++++++++++++++ internal/application/sync/fetch.go | 20 +++++++++++++++++++ .../application/sync/orchestrator_test.go | 17 ++++++++++++++++ 3 files changed, 57 insertions(+) diff --git a/docs/bug-fixes.md b/docs/bug-fixes.md index 2cf1d0c..6fc788e 100644 --- a/docs/bug-fixes.md +++ b/docs/bug-fixes.md @@ -484,6 +484,26 @@ The group path now records each authoritative Amazon return as successfully proc --- +### 2026-07-17: Amazon order-id runs fetched every order before filtering + +**Description:** +An `itemize amazon -order-id ...` run still paginated the entire Amazon order history and fetched transaction details for every order. Targeted recovery runs took minutes and could cause Amazon to expire the session before reaching the requested order. + +**Test Case:** +`TestOrchestrator_fetchOrdersUsesDirectLookupForOrderID` first reproduced the issue by requiring `GetOrderDetails` for the requested ID and proving `FetchOrders` was not called. + +**Root Cause:** +The order-id filter was applied in the processing loop, after the bulk provider fetch had already completed. + +**Fix Applied:** +The orchestrator now uses the provider's direct `GetOrderDetails` operation whenever `Options.OrderID` is set, while preserving provider-fetch audit logging. + +**Verification:** +- The regression test failed before the fix and passes after it. +- Existing bulk-fetch and provider-fetch audit tests remain green. + +--- + ### 2025-09-01: No bugs discovered yet The project was developed using strict TDD methodology from the start, preventing bugs through test-first development. diff --git a/internal/application/sync/fetch.go b/internal/application/sync/fetch.go index c2ade19..7d37e0f 100644 --- a/internal/application/sync/fetch.go +++ b/internal/application/sync/fetch.go @@ -18,6 +18,26 @@ import ( // fetchOrders fetches orders from the provider based on the given options func (o *Orchestrator) fetchOrders(ctx context.Context, opts Options) ([]providers.Order, error) { + if opts.OrderID != "" { + started := time.Now() + order, err := o.provider.GetOrderDetails(ctx, opts.OrderID) + orders := make([]providers.Order, 0, 1) + if order != nil { + orders = append(orders, order) + } + var response any + if o.storage != nil { + response = summarizeOrdersForFetchLog(orders) + } + o.logProviderFetch("order_details", map[string]any{ + "order_id": opts.OrderID, + }, response, err, time.Since(started), len(orders), 0) + if err != nil { + return nil, fmt.Errorf("failed to fetch order %s: %w", opts.OrderID, err) + } + return orders, nil + } + endDate := time.Now() startDate := endDate.AddDate(0, 0, -opts.LookbackDays) diff --git a/internal/application/sync/orchestrator_test.go b/internal/application/sync/orchestrator_test.go index 769f480..6898917 100644 --- a/internal/application/sync/orchestrator_test.go +++ b/internal/application/sync/orchestrator_test.go @@ -474,6 +474,23 @@ func TestOrchestrator_fetchOrders(t *testing.T) { } } +func TestOrchestrator_fetchOrdersUsesDirectLookupForOrderID(t *testing.T) { + mockProvider := new(MockProvider) + order := &MockOrder{} + mockProvider.On("GetOrderDetails", mock.Anything, "112-5922286-2695428").Return(order, nil) + orchestrator := NewOrchestrator(mockProvider, nil, nil, slog.Default()) + + orders, err := orchestrator.fetchOrders(context.Background(), Options{ + LookbackDays: 30, + OrderID: "112-5922286-2695428", + }) + + require.NoError(t, err) + assert.Equal(t, []providers.Order{order}, orders) + mockProvider.AssertNotCalled(t, "FetchOrders", mock.Anything, mock.Anything) + mockProvider.AssertExpectations(t) +} + func TestOrchestrator_fetchOrders_LogsProviderFetch(t *testing.T) { mockProvider := new(MockProvider) mockProvider.On("DisplayName").Return("Costco") From 860cbfc7cb6263f697f413fbf244ad0050181ff5 Mon Sep 17 00:00:00 2001 From: Erick Shaffer Date: Fri, 17 Jul 2026 22:15:09 -0600 Subject: [PATCH 2/2] test: cover targeted Amazon fetch failures --- internal/application/sync/orchestrator_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/internal/application/sync/orchestrator_test.go b/internal/application/sync/orchestrator_test.go index 6898917..cd51cb4 100644 --- a/internal/application/sync/orchestrator_test.go +++ b/internal/application/sync/orchestrator_test.go @@ -491,6 +491,23 @@ func TestOrchestrator_fetchOrdersUsesDirectLookupForOrderID(t *testing.T) { mockProvider.AssertExpectations(t) } +func TestOrchestrator_fetchOrdersReturnsDirectLookupError(t *testing.T) { + mockProvider := new(MockProvider) + mockProvider.On("GetOrderDetails", mock.Anything, "112-5922286-2695428"). + Return(nil, errors.New("session expired")) + orchestrator := NewOrchestrator(mockProvider, nil, nil, slog.Default()) + + orders, err := orchestrator.fetchOrders(context.Background(), Options{ + LookbackDays: 30, + OrderID: "112-5922286-2695428", + }) + + require.EqualError(t, err, "failed to fetch order 112-5922286-2695428: session expired") + assert.Nil(t, orders) + mockProvider.AssertNotCalled(t, "FetchOrders", mock.Anything, mock.Anything) + mockProvider.AssertExpectations(t) +} + func TestOrchestrator_fetchOrders_LogsProviderFetch(t *testing.T) { mockProvider := new(MockProvider) mockProvider.On("DisplayName").Return("Costco")