Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/bug-fixes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
20 changes: 20 additions & 0 deletions internal/application/sync/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
34 changes: 34 additions & 0 deletions internal/application/sync/orchestrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,40 @@ 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_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")
Expand Down