Add Publix API client for savings and store location endpoints
HTTP client that wraps the Publix services API with two endpoints: - /api/v4/savings — fetches weekly ad deals for a given store number - /api/v1/storelocation — finds nearby stores by ZIP code Includes request types (SavingsResponse, SavingItem, StoreResponse, Store) mapping directly to the Publix JSON schema. The client sends a PublixStore header for store-scoped requests and uses a 15-second timeout. Tests use httptest servers to verify header propagation, JSON decoding, and error handling for non-200 responses.
This commit is contained in:
143
internal/api/client_test.go
Normal file
143
internal/api/client_test.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tayloree/publix-deals/internal/api"
|
||||
)
|
||||
|
||||
func ptr(s string) *string { return &s }
|
||||
|
||||
func newTestSavingsServer(t *testing.T, storeNumber string, items []api.SavingItem) *httptest.Server {
|
||||
t.Helper()
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify the PublixStore header is sent
|
||||
got := r.Header.Get("PublixStore")
|
||||
if storeNumber != "" {
|
||||
assert.Equal(t, storeNumber, got, "PublixStore header mismatch")
|
||||
}
|
||||
|
||||
resp := api.SavingsResponse{
|
||||
Savings: items,
|
||||
LanguageID: 1,
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}))
|
||||
}
|
||||
|
||||
func newTestStoreServer(t *testing.T, stores []api.Store) *httptest.Server {
|
||||
t.Helper()
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.NotEmpty(t, r.URL.Query().Get("zipCode"), "zipCode param required")
|
||||
|
||||
resp := api.StoreResponse{Stores: stores}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}))
|
||||
}
|
||||
|
||||
func TestFetchSavings(t *testing.T) {
|
||||
items := []api.SavingItem{
|
||||
{
|
||||
ID: "test-1",
|
||||
Title: ptr("Chicken Breasts"),
|
||||
Savings: ptr("$3.99 lb"),
|
||||
Department: ptr("Meat"),
|
||||
Categories: []string{"meat"},
|
||||
StartFormatted: "2/18",
|
||||
EndFormatted: "2/24",
|
||||
},
|
||||
{
|
||||
ID: "test-2",
|
||||
Title: ptr("Nutella"),
|
||||
Savings: ptr("Buy 1 Get 1 FREE"),
|
||||
Categories: []string{"bogo", "grocery"},
|
||||
},
|
||||
}
|
||||
|
||||
srv := newTestSavingsServer(t, "1425", items)
|
||||
defer srv.Close()
|
||||
|
||||
client := api.NewClientWithBaseURLs(srv.URL, "")
|
||||
resp, err := client.FetchSavings(context.Background(), "1425")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, resp.Savings, 2)
|
||||
assert.Equal(t, "Chicken Breasts", *resp.Savings[0].Title)
|
||||
assert.Equal(t, "Buy 1 Get 1 FREE", *resp.Savings[1].Savings)
|
||||
}
|
||||
|
||||
func TestFetchSavings_EmptyStore(t *testing.T) {
|
||||
srv := newTestSavingsServer(t, "", nil)
|
||||
defer srv.Close()
|
||||
|
||||
client := api.NewClientWithBaseURLs(srv.URL, "")
|
||||
resp, err := client.FetchSavings(context.Background(), "")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, resp.Savings)
|
||||
}
|
||||
|
||||
func TestFetchSavings_ServerError(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
client := api.NewClientWithBaseURLs(srv.URL, "")
|
||||
_, err := client.FetchSavings(context.Background(), "1425")
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "500")
|
||||
}
|
||||
|
||||
func TestFetchStores(t *testing.T) {
|
||||
stores := []api.Store{
|
||||
{Key: "01425", Name: "Peachers Mill", City: "Clarksville", State: "TN", Zip: "37042", Distance: "5"},
|
||||
{Key: "00100", Name: "Downtown", City: "Nashville", State: "TN", Zip: "37201", Distance: "15"},
|
||||
}
|
||||
|
||||
srv := newTestStoreServer(t, stores)
|
||||
defer srv.Close()
|
||||
|
||||
client := api.NewClientWithBaseURLs("", srv.URL)
|
||||
result, err := client.FetchStores(context.Background(), "37042", 5)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
assert.Equal(t, "Peachers Mill", result[0].Name)
|
||||
assert.Equal(t, "01425", result[0].Key)
|
||||
}
|
||||
|
||||
func TestFetchStores_NoResults(t *testing.T) {
|
||||
srv := newTestStoreServer(t, nil)
|
||||
defer srv.Close()
|
||||
|
||||
client := api.NewClientWithBaseURLs("", srv.URL)
|
||||
result, err := client.FetchStores(context.Background(), "00000", 5)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
}
|
||||
|
||||
func TestStoreNumber(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{"01425", "1425"},
|
||||
{"00100", "100"},
|
||||
{"1425", "1425"},
|
||||
{"0", ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
assert.Equal(t, tt.want, api.StoreNumber(tt.input), "StoreNumber(%q)", tt.input)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user