feat(t224): add incremental freight sync
This commit is contained in:
@@ -57,11 +57,33 @@ func New(baseURL, apiKey string, timeout time.Duration) (*Client, error) {
|
||||
func (client *Client) QueryOrder(
|
||||
ctx context.Context,
|
||||
orderNumber string,
|
||||
) (domain.FreightSourceBatch, error) {
|
||||
return client.query(ctx, map[string]string{
|
||||
"mode": domain.FreightSyncOrderNumber,
|
||||
"order_number": orderNumber,
|
||||
}, domain.FreightSyncOrderNumber, "", "")
|
||||
}
|
||||
|
||||
func (client *Client) QueryCreatedRange(
|
||||
ctx context.Context,
|
||||
createdFrom, createdTo string,
|
||||
) (domain.FreightSourceBatch, error) {
|
||||
return client.query(ctx, map[string]string{
|
||||
"mode": domain.FreightSyncCreatedRange,
|
||||
"created_from": createdFrom,
|
||||
"created_to": createdTo,
|
||||
}, domain.FreightSyncCreatedRange, createdFrom, createdTo)
|
||||
}
|
||||
|
||||
func (client *Client) query(
|
||||
ctx context.Context,
|
||||
payload map[string]string,
|
||||
expectedMode, expectedFrom, expectedTo string,
|
||||
) (domain.FreightSourceBatch, error) {
|
||||
if client.apiKey == "" {
|
||||
return domain.FreightSourceBatch{}, ErrNotConfigured
|
||||
}
|
||||
body, err := json.Marshal(map[string]string{"order_number": orderNumber})
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return domain.FreightSourceBatch{}, ErrProtocol
|
||||
}
|
||||
@@ -106,9 +128,19 @@ func (client *Client) QueryOrder(
|
||||
if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
|
||||
return domain.FreightSourceBatch{}, ErrProtocol
|
||||
}
|
||||
if result.SchemaVersion != 1 || result.Query.Mode != "ORDER_NUMBER" ||
|
||||
if result.SchemaVersion != 1 || result.Query.Mode != expectedMode ||
|
||||
result.Orders == nil {
|
||||
return domain.FreightSourceBatch{}, ErrProtocol
|
||||
}
|
||||
if expectedMode == domain.FreightSyncOrderNumber {
|
||||
if result.Query.CreatedFrom != nil || result.Query.CreatedTo != nil {
|
||||
return domain.FreightSourceBatch{}, ErrProtocol
|
||||
}
|
||||
} else if result.Query.CreatedFrom == nil ||
|
||||
result.Query.CreatedTo == nil ||
|
||||
*result.Query.CreatedFrom != expectedFrom ||
|
||||
*result.Query.CreatedTo != expectedTo {
|
||||
return domain.FreightSourceBatch{}, ErrProtocol
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package erpconnector
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -61,6 +62,85 @@ func TestQueryOrderAcceptsAllowlistResponse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryCreatedRangeUsesStrictContract(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
) {
|
||||
var body map[string]string
|
||||
if err := json.NewDecoder(request.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode request: %v", err)
|
||||
}
|
||||
if body["mode"] != "CREATED_RANGE" ||
|
||||
body["created_from"] != "2026-07-22" ||
|
||||
body["created_to"] != "2026-07-28" ||
|
||||
len(body) != 3 {
|
||||
t.Fatalf("request body = %#v", body)
|
||||
}
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
_, _ = writer.Write([]byte(`{
|
||||
"schema_version":1,
|
||||
"query":{
|
||||
"mode":"CREATED_RANGE",
|
||||
"created_from":"2026-07-22",
|
||||
"created_to":"2026-07-28"
|
||||
},
|
||||
"orders":[]
|
||||
}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
client, _ := New(
|
||||
server.URL,
|
||||
"12345678901234567890123456789012",
|
||||
time.Second,
|
||||
)
|
||||
|
||||
result, err := client.QueryCreatedRange(
|
||||
context.Background(),
|
||||
"2026-07-22",
|
||||
"2026-07-28",
|
||||
)
|
||||
|
||||
if err != nil || result.Query.CreatedFrom == nil ||
|
||||
*result.Query.CreatedFrom != "2026-07-22" {
|
||||
t.Fatalf("QueryCreatedRange() = %+v, %v", result, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryCreatedRangeRejectsMismatchedResponseWindow(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(
|
||||
writer http.ResponseWriter,
|
||||
_ *http.Request,
|
||||
) {
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
_, _ = writer.Write([]byte(`{
|
||||
"schema_version":1,
|
||||
"query":{
|
||||
"mode":"CREATED_RANGE",
|
||||
"created_from":"2026-07-21",
|
||||
"created_to":"2026-07-28"
|
||||
},
|
||||
"orders":[]
|
||||
}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
client, _ := New(
|
||||
server.URL,
|
||||
"12345678901234567890123456789012",
|
||||
time.Second,
|
||||
)
|
||||
|
||||
_, err := client.QueryCreatedRange(
|
||||
context.Background(),
|
||||
"2026-07-22",
|
||||
"2026-07-28",
|
||||
)
|
||||
|
||||
if !errors.Is(err, ErrProtocol) {
|
||||
t.Fatalf("QueryCreatedRange() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryOrderRejectsUnexpectedPIIField(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(
|
||||
writer http.ResponseWriter,
|
||||
|
||||
@@ -34,8 +34,11 @@ func TestClaimsMigrationPreservesHistoryAcrossUpDownUp(t *testing.T) {
|
||||
|
||||
if applied, err := runner.Up(ctx); err != nil {
|
||||
t.Fatalf("initial Up() error = %v", err)
|
||||
} else if applied != 13 {
|
||||
t.Fatalf("initial Up() applied = %d, want 13", applied)
|
||||
} else if applied != 14 {
|
||||
t.Fatalf("initial Up() applied = %d, want 14", applied)
|
||||
}
|
||||
if err := runner.Down(ctx); err != nil {
|
||||
t.Fatalf("initial Down(v14) error = %v", err)
|
||||
}
|
||||
if err := runner.Down(ctx); err != nil {
|
||||
t.Fatalf("initial Down(v13) error = %v", err)
|
||||
@@ -68,9 +71,14 @@ func TestClaimsMigrationPreservesHistoryAcrossUpDownUp(t *testing.T) {
|
||||
seedClaimsHistoricalFixture(t, db)
|
||||
|
||||
if applied, err := runner.Up(ctx); err != nil {
|
||||
t.Fatalf("Up(v5-v13) over historical data error = %v", err)
|
||||
} else if applied != 9 {
|
||||
t.Fatalf("Up(v5-v13) applied = %d, want 9", applied)
|
||||
t.Fatalf("Up(v5-v14) over historical data error = %v", err)
|
||||
} else if applied != 10 {
|
||||
t.Fatalf("Up(v5-v14) applied = %d, want 10", applied)
|
||||
}
|
||||
assertClaimsHistory(t, db, true)
|
||||
|
||||
if err := runner.Down(ctx); err != nil {
|
||||
t.Fatalf("Down(v14) with compatible history error = %v", err)
|
||||
}
|
||||
assertClaimsHistory(t, db, true)
|
||||
|
||||
@@ -125,9 +133,9 @@ func TestClaimsMigrationPreservesHistoryAcrossUpDownUp(t *testing.T) {
|
||||
assertClaimsHistory(t, db, false)
|
||||
|
||||
if applied, err := runner.Up(ctx); err != nil {
|
||||
t.Fatalf("final Up(v4-v13) error = %v", err)
|
||||
} else if applied != 10 {
|
||||
t.Fatalf("final Up(v4-v13) applied = %d, want 10", applied)
|
||||
t.Fatalf("final Up(v4-v14) error = %v", err)
|
||||
} else if applied != 11 {
|
||||
t.Fatalf("final Up(v4-v14) applied = %d, want 11", applied)
|
||||
}
|
||||
assertClaimsHistory(t, db, true)
|
||||
}
|
||||
@@ -363,6 +371,9 @@ func TestClaimsMigrationDownFailsClosedForNewAuditData(t *testing.T) {
|
||||
t.Fatalf("insert v4 audit event: %v", err)
|
||||
}
|
||||
|
||||
if err := runner.Down(ctx); err != nil {
|
||||
t.Fatalf("Down(v14) error = %v", err)
|
||||
}
|
||||
if err := runner.Down(ctx); err != nil {
|
||||
t.Fatalf("Down(v13) error = %v", err)
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ func TestRunnerSupportsUpStatusDownAndIdempotentUp(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Up() error = %v", err)
|
||||
}
|
||||
if applied != 13 {
|
||||
t.Fatalf("Up() applied = %d, want 13", applied)
|
||||
if applied != 14 {
|
||||
t.Fatalf("Up() applied = %d, want 14", applied)
|
||||
}
|
||||
assertStatuses(t, runner, map[int64]bool{
|
||||
1: true,
|
||||
@@ -44,6 +44,7 @@ func TestRunnerSupportsUpStatusDownAndIdempotentUp(t *testing.T) {
|
||||
11: true,
|
||||
12: true,
|
||||
13: true,
|
||||
14: true,
|
||||
})
|
||||
|
||||
applied, err = runner.Up(context.Background())
|
||||
@@ -70,7 +71,8 @@ func TestRunnerSupportsUpStatusDownAndIdempotentUp(t *testing.T) {
|
||||
10: true,
|
||||
11: true,
|
||||
12: true,
|
||||
13: false,
|
||||
13: true,
|
||||
14: false,
|
||||
})
|
||||
|
||||
applied, err = runner.Up(context.Background())
|
||||
@@ -94,6 +96,7 @@ func TestRunnerSupportsUpStatusDownAndIdempotentUp(t *testing.T) {
|
||||
11: true,
|
||||
12: true,
|
||||
13: true,
|
||||
14: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user