diff --git a/README.md b/README.md index 13e4ea8..36f1ce8 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,6 @@ The library keeps several legacy knobs for source compatibility, but the Creator | `PartnerType` (`Associates`) | Explicit body parameter | Ignored. Partner type is implicit | | `Merchant` | Offer filtering selector | Ignored | | `OfferCount` | Offer summary limiter | Ignored | -| `EnableVariationSummary()` | Requested variation summary resource | No-op (resource is not exposed) | ### Marketplace routing precedence diff --git a/client_test.go b/client_test.go index 2ee5cd6..68552e0 100644 --- a/client_test.go +++ b/client_test.go @@ -117,7 +117,7 @@ func TestClientRequestSendsExpectedHeadersAndBody(t *testing.T) { if got, want := string(body), `{"hello":"world"}`; got != want { t.Errorf("api body = %q, want %q", got, want) } - _, _ = w.Write([]byte(`{"itemsResult":{}}`)) + _, _ = w.Write([]byte(`{"itemResults":{}}`)) } _, _, sv := newServers(t, tokenHandler, apiHandler) @@ -128,7 +128,7 @@ func TestClientRequestSendsExpectedHeadersAndBody(t *testing.T) { if err != nil { t.Fatalf("RequestContext: %v", err) } - if got, want := string(body), `{"itemsResult":{}}`; got != want { + if got, want := string(body), `{"itemResults":{}}`; got != want { t.Errorf("response body = %q, want %q", got, want) } diff --git a/entity/entity.go b/entity/entity.go index 49d5887..4e8d7bc 100644 --- a/entity/entity.go +++ b/entity/entity.go @@ -93,7 +93,6 @@ type Item struct { ASIN string ParentASIN string DetailPageURL string - Score *float64 `json:",omitempty"` CustomerReviews *struct { Count *int `json:",omitempty"` StarRating *struct { @@ -329,7 +328,7 @@ type Response struct { } `json:",omitempty"` ItemsResult *struct { Items []Item `json:",omitempty"` - } `json:",omitempty"` + } `json:"itemResults,omitempty"` SearchResult *struct { Items []Item `json:",omitempty"` SearchRefinements *struct { diff --git a/entity/entity_test.go b/entity/entity_test.go new file mode 100644 index 0000000..81a7064 --- /dev/null +++ b/entity/entity_test.go @@ -0,0 +1,538 @@ +package entity + +import ( + "testing" +) + +// TestDecodeGetVariationsResponse exercises the lowerCamelCase Creators API +// response shape for GetVariations, including the variationSummary block +// (pageCount, variationCount, price, variationDimensions). +func TestDecodeGetVariationsResponse(t *testing.T) { + body := []byte(`{ + "variationsResult": { + "items": [ + { + "asin": "B07YCM5K55", + "parentASIN": "B07YCM5JXX", + "detailPageURL": "https://www.amazon.com/dp/B07YCM5K55", + "images": { + "primary": { + "small": {"url": "https://example.test/s.jpg", "height": 75, "width": 75}, + "medium": {"url": "https://example.test/m.jpg", "height": 160, "width": 160}, + "large": {"url": "https://example.test/l.jpg", "height": 500, "width": 500} + } + }, + "offersV2": { + "listings": [ + { + "isBuyBoxWinner": true, + "type": "New" + } + ] + } + } + ], + "variationSummary": { + "pageCount": 3, + "variationCount": 27, + "price": { + "highestPrice": {"amount": 49.99, "currency": "USD", "displayAmount": "$49.99"}, + "lowestPrice": {"amount": 19.99, "currency": "USD", "displayAmount": "$19.99"} + }, + "variationDimensions": [ + {"displayName": "Size", "name": "size_name", "values": ["S", "M", "L"]}, + {"displayName": "Color", "name": "color_name", "values": ["Red", "Blue"]} + ] + } + } +}`) + resp, err := DecodeResponse(body) + if err != nil { + t.Fatalf("DecodeResponse: %+v", err) + } + if resp.VariationsResult == nil { + t.Fatal("VariationsResult is nil") + } + vs := resp.VariationsResult.VariationSummary + if vs == nil { + t.Fatal("VariationSummary is nil") + } + if vs.PageCount != 3 { + t.Errorf("PageCount = %d, want 3", vs.PageCount) + } + if vs.VariationCount != 27 { + t.Errorf("VariationCount = %d, want 27", vs.VariationCount) + } + if vs.Price == nil || vs.Price.HighestPrice == nil || vs.Price.LowestPrice == nil { + t.Fatal("price/highestPrice/lowestPrice is nil") + } + if got, want := vs.Price.HighestPrice.Amount, 49.99; got != want { + t.Errorf("HighestPrice.Amount = %v, want %v", got, want) + } + if got, want := vs.Price.LowestPrice.DisplayAmount, "$19.99"; got != want { + t.Errorf("LowestPrice.DisplayAmount = %q, want %q", got, want) + } + if got, want := len(vs.VariationDimensions), 2; got != want { + t.Fatalf("len(VariationDimensions) = %d, want %d", got, want) + } + if got, want := vs.VariationDimensions[0].Name, "size_name"; got != want { + t.Errorf("VariationDimensions[0].Name = %q, want %q", got, want) + } + // Items: primary image and isBuyBoxWinner on V2 listing. + if got, want := len(resp.VariationsResult.Items), 1; got != want { + t.Fatalf("len(Items) = %d, want %d", got, want) + } + item := resp.VariationsResult.Items[0] + if item.Images == nil || item.Images.Primary == nil || item.Images.Primary.Large == nil { + t.Fatal("Images.Primary.Large is nil") + } + if got, want := item.Images.Primary.Large.URL, "https://example.test/l.jpg"; got != want { + t.Errorf("Images.Primary.Large.URL = %q, want %q", got, want) + } + if item.OffersV2 == nil || item.OffersV2.Listings == nil || len(*item.OffersV2.Listings) != 1 { + t.Fatal("OffersV2.Listings missing/empty") + } + listing := (*item.OffersV2.Listings)[0] + if !listing.IsBuyboxWinner { + t.Errorf("isBuyBoxWinner did not decode into IsBuyboxWinner") + } +} + +// TestDecodeRealGetBrowseNodesSampleResponse pins the decode contract for +// GetBrowseNodes against a sample captured from the live Creators API. +// Exercises both the recursive `ancestor` chain (Mexico → Explore the World +// → Geography & Cultures → Children's Books → Subjects → Books) and the +// `children[]` list returned for root nodes. The top-level container is +// `browseNodesResult` (browseNodes plural, result singular) — matches the +// shape used by GetVariations rather than the unique `itemResults` plural +// used only by GetItems. +func TestDecodeRealGetBrowseNodesSampleResponse(t *testing.T) { + body := []byte(`{ + "browseNodesResult": { + "browseNodes": [ + { + "ancestor": { + "ancestor": { + "ancestor": { + "ancestor": { + "ancestor": { + "contextFreeName": "Books", + "displayName": "Books", + "id": "283155" + }, + "contextFreeName": "Subjects", + "displayName": "Subjects", + "id": "1000" + }, + "contextFreeName": "Children's Books", + "displayName": "Children's Books", + "id": "4" + }, + "contextFreeName": "Children's Geography & Cultures Books", + "displayName": "Geography & Cultures", + "id": "3344091011" + }, + "contextFreeName": "Children's Explore the World Books", + "displayName": "Explore the World", + "id": "3023" + }, + "contextFreeName": "Children's Mexico Books", + "displayName": "Mexico", + "id": "3040", + "isRoot": false + }, + { + "children": [ + {"contextFreeName": "Subjects", "displayName": "Subjects", "id": "1000"}, + {"contextFreeName": "Books Featured Categories", "displayName": "Books Featured Categories", "id": "51546011"}, + {"contextFreeName": "Specialty Boutique", "displayName": "Specialty Boutique", "id": "2349030011"} + ], + "contextFreeName": "Books", + "displayName": "Books", + "id": "283155", + "isRoot": true + } + ] + } +}`) + resp, err := DecodeResponse(body) + if err != nil { + t.Fatalf("DecodeResponse: %+v", err) + } + if resp.BrowseNodesResult == nil { + t.Fatal("BrowseNodesResult is nil") + } + if got, want := len(resp.BrowseNodesResult.BrowseNodes), 2; got != want { + t.Fatalf("len(BrowseNodes) = %d, want %d", got, want) + } + + // First node: Mexico, with a 5-level ancestor chain ending at the + // Books root. + mexico := resp.BrowseNodesResult.BrowseNodes[0] + if got, want := mexico.Id, "3040"; got != want { + t.Errorf("Mexico.Id = %q, want %q", got, want) + } + if got, want := mexico.DisplayName, "Mexico"; got != want { + t.Errorf("Mexico.DisplayName = %q, want %q", got, want) + } + if mexico.IsRoot { + t.Errorf("Mexico.IsRoot = true, want false") + } + // Walk the ancestor chain. + expected := []struct{ id, name string }{ + {"3023", "Explore the World"}, + {"3344091011", "Geography & Cultures"}, + {"4", "Children's Books"}, + {"1000", "Subjects"}, + {"283155", "Books"}, + } + cur := mexico.Ancestor + for i, want := range expected { + if cur == nil { + t.Fatalf("ancestor chain truncated at depth %d (expected %+v)", i, want) + } + if cur.Id != want.id || cur.DisplayName != want.name { + t.Errorf("ancestor[%d] = (%q, %q), want (%q, %q)", i, cur.Id, cur.DisplayName, want.id, want.name) + } + cur = cur.Ancestor + } + if cur != nil { + t.Errorf("ancestor chain has extra node beyond root: %+v", cur) + } + + // Second node: Books root, with three children. + books := resp.BrowseNodesResult.BrowseNodes[1] + if got, want := books.Id, "283155"; got != want { + t.Errorf("Books.Id = %q, want %q", got, want) + } + if !books.IsRoot { + t.Errorf("Books.IsRoot = false, want true") + } + if got, want := len(books.Children), 3; got != want { + t.Fatalf("len(Books.Children) = %d, want %d", got, want) + } + if got, want := books.Children[2].DisplayName, "Specialty Boutique"; got != want { + t.Errorf("Books.Children[2].DisplayName = %q, want %q", got, want) + } + if got, want := books.Children[1].Id, "51546011"; got != want { + t.Errorf("Books.Children[1].Id = %q, want %q", got, want) + } +} + +// TestDecodeBrowseNodeInfoWebsiteSalesRank exercises the websiteSalesRank +// nested block carried inside an item's browseNodeInfo. +func TestDecodeBrowseNodeInfoWebsiteSalesRank(t *testing.T) { + body := []byte(`{ + "itemResults": { + "items": [ + { + "asin": "B0", + "browseNodeInfo": { + "browseNodes": [ + { + "id": "1", + "displayName": "Books", + "contextFreeName": "Books", + "websiteSalesRank": { + "displayName": "Books", + "contextFreeName": "Books", + "salesRank": 7 + } + } + ] + } + } + ] + } +}`) + resp, err := DecodeResponse(body) + if err != nil { + t.Fatalf("DecodeResponse: %+v", err) + } + if resp.ItemsResult == nil || len(resp.ItemsResult.Items) == 0 { + t.Fatal("no items decoded") + } + bni := resp.ItemsResult.Items[0].BrowseNodeInfo + if bni == nil || len(bni.BrowseNodes) == 0 { + t.Fatal("BrowseNodeInfo.BrowseNodes empty") + } + wsr := bni.BrowseNodes[0].WebsiteSalesRank + if wsr == nil { + t.Fatal("WebsiteSalesRank is nil") + } + if got, want := wsr.SalesRank, 7; got != want { + t.Errorf("WebsiteSalesRank.SalesRank = %d, want %d", got, want) + } +} + +// TestDecodeIgnoresUnknownFields confirms that unknown JSON keys do not +// cause DecodeResponse to error (relevant if the Creators API adds new +// fields between SDK releases). +func TestDecodeIgnoresUnknownFields(t *testing.T) { + body := []byte(`{"itemResults":{"items":[{"asin":"A","unknownField":42}]},"newTopLevel":"ok"}`) + if _, err := DecodeResponse(body); err != nil { + t.Fatalf("DecodeResponse rejected unknown field: %+v", err) + } + // Sanity: malformed JSON still errors. + if _, err := DecodeResponse([]byte("{not json")); err == nil { + t.Error("expected error for malformed JSON, got nil") + } +} + +// TestDecodeRealGetVariationsSampleResponse pins the decode contract for +// GetVariations against a sample captured from the live Creators API. +// Notably: +// - the top-level container is `variationsResult` (variations plural, +// result singular) — this differs from GetItems' `itemResults` +// (item singular, results plural). +// - the `Price` block in `variationSummary` is sometimes returned with a +// capitalised key; case-insensitive decode covers it. +// - `variationDimensions[]` does NOT include a `locale` field; ensures +// the entity stays minimal. +func TestDecodeRealGetVariationsSampleResponse(t *testing.T) { + body := []byte(`{ + "variationsResult": { + "items": [ + { + "asin": "B019MNBMS4", + "detailPageURL": "https://www.amazon.co.uk/dp/B019MNBMS4?tag=xyz-20&linkCode=ogv&th=1&psc=1", + "itemInfo": { + "title": { + "displayValue": "Tommy Hilfiger Men's Ranger Leather Passcase Wallet", + "label": "title", + "locale": "en_GB" + } + }, + "variationAttributes": [ + {"name": "size_name", "value": "One Size"}, + {"name": "color_name", "value": "Navy"} + ] + }, + { + "asin": "B073211XCB", + "detailPageURL": "https://www.amazon.co.uk/dp/B073211XCB", + "itemInfo": { + "title": { + "displayValue": "Tommy Hilfiger mens RFID Blocking Wallet", + "label": "title", + "locale": "en_GB" + } + }, + "variationAttributes": [ + {"name": "size_name", "value": "One Size"}, + {"name": "color_name", "value": "Logan - Tan"} + ] + } + ], + "variationSummary": { + "pageCount": 2, + "Price": { + "highestPrice": {"amount": 30.87, "currency": "GBP", "displayAmount": "£30.87"}, + "lowestPrice": {"amount": 17.03, "currency": "GBP", "displayAmount": "£17.03"} + }, + "variationCount": 13, + "variationDimensions": [ + {"displayName": "Size", "name": "size_name", "values": ["One Size"]}, + {"displayName": "Colour", "name": "color_name", "values": ["Brown", "Navy", "Black", "Burgundy", "Cognac", "Gray", "Green", "Logan - Tan", "Navy/Black", "Red", "Rfid-black", "Rfid-navy", "Tan"]} + ] + } + } +}`) + resp, err := DecodeResponse(body) + if err != nil { + t.Fatalf("DecodeResponse: %+v", err) + } + if resp.VariationsResult == nil { + t.Fatal("VariationsResult is nil") + } + // Items decode end-to-end with title + variationAttributes. + if got, want := len(resp.VariationsResult.Items), 2; got != want { + t.Fatalf("len(Items) = %d, want %d", got, want) + } + first := resp.VariationsResult.Items[0] + if got, want := first.ASIN, "B019MNBMS4"; got != want { + t.Errorf("Items[0].ASIN = %q, want %q", got, want) + } + if first.ItemInfo == nil || first.ItemInfo.Title == nil { + t.Fatal("Items[0].ItemInfo.Title is nil") + } + if got, want := first.ItemInfo.Title.Locale, "en_GB"; got != want { + t.Errorf("Items[0].ItemInfo.Title.Locale = %q, want %q", got, want) + } + if got, want := len(first.VariationAttributes), 2; got != want { + t.Fatalf("Items[0].VariationAttributes len = %d, want %d", got, want) + } + if got, want := first.VariationAttributes[1].Name, "color_name"; got != want { + t.Errorf("Items[0].VariationAttributes[1].Name = %q, want %q", got, want) + } + if got, want := first.VariationAttributes[1].Value, "Navy"; got != want { + t.Errorf("Items[0].VariationAttributes[1].Value = %q, want %q", got, want) + } + // VariationSummary: pageCount/variationCount/Price/variationDimensions. + vs := resp.VariationsResult.VariationSummary + if vs == nil { + t.Fatal("VariationSummary is nil") + } + if vs.PageCount != 2 { + t.Errorf("VariationSummary.PageCount = %d, want 2", vs.PageCount) + } + if vs.VariationCount != 13 { + t.Errorf("VariationSummary.VariationCount = %d, want 13", vs.VariationCount) + } + if vs.Price == nil || vs.Price.HighestPrice == nil || vs.Price.LowestPrice == nil { + t.Fatal("VariationSummary.Price.{HighestPrice,LowestPrice} missing (capitalized 'Price' key did not decode)") + } + if got, want := vs.Price.HighestPrice.Currency, "GBP"; got != want { + t.Errorf("Highest currency = %q, want %q", got, want) + } + if got, want := vs.Price.LowestPrice.DisplayAmount, "£17.03"; got != want { + t.Errorf("Lowest displayAmount = %q, want %q", got, want) + } + if got, want := len(vs.VariationDimensions), 2; got != want { + t.Fatalf("len(VariationDimensions) = %d, want %d", got, want) + } + if got, want := vs.VariationDimensions[1].DisplayName, "Colour"; got != want { + t.Errorf("VariationDimensions[1].DisplayName = %q, want %q", got, want) + } + if got, want := len(vs.VariationDimensions[1].Values), 13; got != want { + t.Errorf("VariationDimensions[1].Values count = %d, want %d", got, want) + } +} + +// TestDecodeRealGetItemsSampleResponse pins the decode contract against an +// actual GetItems response captured from the Creators API. Notably the +// top-level container is `itemResults` (item singular, results plural) — not +// the `itemsResult` shape used by PA-API v5 / the third-party Python SDK. +func TestDecodeRealGetItemsSampleResponse(t *testing.T) { + body := []byte(`{ + "errors": [ + { + "code": "ItemNotAccessible", + "message": "The ItemId B01180YUXS is not accessible through the Creators API." + } + ], + "itemResults": { + "items": [ + { + "asin": "B0199980K4", + "detailPageURL": "https://www.amazon.com/dp/B0199980K4?tag=xyz-20&linkCode=ogi&language=en_US&th=1&psc=1", + "images": { + "primary": { + "small": { + "height": 75, + "url": "https://m.media-amazon.com/images/I/61s4tTAizUL._SL75_.jpg", + "width": 56 + } + } + }, + "itemInfo": { + "title": { + "displayValue": "Genghis: The Legend of the Ten", + "label": "Title", + "locale": "en_US" + } + }, + "parentASIN": "B07QGKM68X" + }, + { + "asin": "B00BKQTA4A", + "detailPageURL": "https://www.amazon.com/dp/B00BKQTA4A?tag=xyz-20&linkCode=ogi&language=en_US&th=1&psc=1", + "images": { + "primary": { + "small": { + "height": 75, + "url": "https://m.media-amazon.com/images/I/41OiLOcQVJL._SL75_.jpg", + "width": 46 + } + } + }, + "itemInfo": { + "features": { + "displayValues": [ + "Round watch featuring logoed white dial with stick indices", + "36 mm stainless steel case with mineral dial window", + "Quartz movement with analog display", + "Leather calfskin band with buckle closure", + "Water resistant to 30 m (99 ft): In general, withstands splashes or brief immersion in water, but not suitable for swimming" + ], + "label": "Features", + "locale": "en_US" + }, + "title": { + "displayValue": "Daniel Wellington Women's 0608DW Sheffield Stainless Steel Watch", + "label": "Title", + "locale": "en_US" + } + }, + "parentASIN": "B07L5N7P32" + } + ] + } +}`) + resp, err := DecodeResponse(body) + if err != nil { + t.Fatalf("DecodeResponse: %+v", err) + } + // Errors block populates and preserves order/content. + if got, want := len(resp.Errors), 1; got != want { + t.Fatalf("len(Errors) = %d, want %d", got, want) + } + if got, want := resp.Errors[0].Code, "ItemNotAccessible"; got != want { + t.Errorf("Errors[0].Code = %q, want %q", got, want) + } + // itemResults must decode (regression: previously absent). + if resp.ItemsResult == nil { + t.Fatal("ItemsResult is nil; itemResults JSON tag failed to decode") + } + if got, want := len(resp.ItemsResult.Items), 2; got != want { + t.Fatalf("len(Items) = %d, want %d", got, want) + } + first := resp.ItemsResult.Items[0] + if got, want := first.ASIN, "B0199980K4"; got != want { + t.Errorf("Items[0].ASIN = %q, want %q", got, want) + } + if got, want := first.ParentASIN, "B07QGKM68X"; got != want { + t.Errorf("Items[0].ParentASIN = %q, want %q", got, want) + } + if got, want := first.DetailPageURL, "https://www.amazon.com/dp/B0199980K4?tag=xyz-20&linkCode=ogi&language=en_US&th=1&psc=1"; got != want { + t.Errorf("Items[0].DetailPageURL = %q, want %q", got, want) + } + if first.Images == nil || first.Images.Primary == nil || first.Images.Primary.Small == nil { + t.Fatal("Items[0].Images.Primary.Small is nil") + } + if got, want := first.Images.Primary.Small.URL, "https://m.media-amazon.com/images/I/61s4tTAizUL._SL75_.jpg"; got != want { + t.Errorf("Items[0] small image URL = %q, want %q", got, want) + } + if first.ItemInfo == nil || first.ItemInfo.Title == nil { + t.Fatal("Items[0].ItemInfo.Title is nil") + } + if got, want := first.ItemInfo.Title.DisplayValue, "Genghis: The Legend of the Ten"; got != want { + t.Errorf("Items[0].ItemInfo.Title.DisplayValue = %q, want %q", got, want) + } + // Second item exercises the features block (IdInfo). + second := resp.ItemsResult.Items[1] + if second.ItemInfo == nil || second.ItemInfo.Features == nil { + t.Fatal("Items[1].ItemInfo.Features is nil") + } + if got, want := len(second.ItemInfo.Features.DisplayValues), 5; got != want { + t.Errorf("Items[1].ItemInfo.Features.DisplayValues count = %d, want %d", got, want) + } + if got, want := second.ItemInfo.Features.Label, "Features"; got != want { + t.Errorf("Items[1].ItemInfo.Features.Label = %q, want %q", got, want) + } +} + +/* Copyright 2026 goark contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/query/getitems_test.go b/query/getitems_test.go index d9bf0a0..c57a437 100644 --- a/query/getitems_test.go +++ b/query/getitems_test.go @@ -86,7 +86,7 @@ func TestResourcesInGetItems(t *testing.T) { str string }{ {q: NewGetItems("", "", "").EnableBrowseNodeInfo(), str: `{"resources":["browseNodeInfo.browseNodes","browseNodeInfo.browseNodes.ancestor","browseNodeInfo.browseNodes.salesRank","browseNodeInfo.websiteSalesRank"]}`}, - {q: NewGetItems("", "", "").EnableImages(), str: `{"resources":["images.primary.small","images.primary.medium","images.primary.large","images.primary.highRes","images.variants.small","images.variants.medium","images.variants.large","images.variants.highRes"]}`}, + {q: NewGetItems("", "", "").EnableImages(), str: `{"resources":["images.primary.small","images.primary.medium","images.primary.large","images.variants.small","images.variants.medium","images.variants.large"]}`}, {q: NewGetItems("", "", "").EnableItemInfo(), str: `{"resources":["itemInfo.byLineInfo","itemInfo.contentInfo","itemInfo.contentRating","itemInfo.classifications","itemInfo.externalIds","itemInfo.features","itemInfo.manufactureInfo","itemInfo.productInfo","itemInfo.technicalInfo","itemInfo.title","itemInfo.tradeInInfo"]}`}, // EnableOffers (V1) is now an alias for EnableOffersV2. {q: NewGetItems("", "", "").EnableOffers(), str: `{"resources":["offersV2.listings.availability","offersV2.listings.condition","offersV2.listings.dealDetails","offersV2.listings.isBuyBoxWinner","offersV2.listings.loyaltyPoints","offersV2.listings.merchantInfo","offersV2.listings.price","offersV2.listings.type"]}`}, diff --git a/query/getvariations_test.go b/query/getvariations_test.go index 2aee61b..23183ac 100644 --- a/query/getvariations_test.go +++ b/query/getvariations_test.go @@ -88,11 +88,11 @@ func TestResourcesInGetVariations(t *testing.T) { str string }{ {q: NewGetVariations("", "", "").EnableBrowseNodeInfo(), str: `{"resources":["browseNodeInfo.browseNodes","browseNodeInfo.browseNodes.ancestor","browseNodeInfo.browseNodes.salesRank","browseNodeInfo.websiteSalesRank"]}`}, - {q: NewGetVariations("", "", "").EnableImages(), str: `{"resources":["images.primary.small","images.primary.medium","images.primary.large","images.primary.highRes","images.variants.small","images.variants.medium","images.variants.large","images.variants.highRes"]}`}, + {q: NewGetVariations("", "", "").EnableImages(), str: `{"resources":["images.primary.small","images.primary.medium","images.primary.large","images.variants.small","images.variants.medium","images.variants.large"]}`}, {q: NewGetVariations("", "", "").EnableItemInfo(), str: `{"resources":["itemInfo.byLineInfo","itemInfo.contentInfo","itemInfo.contentRating","itemInfo.classifications","itemInfo.externalIds","itemInfo.features","itemInfo.manufactureInfo","itemInfo.productInfo","itemInfo.technicalInfo","itemInfo.title","itemInfo.tradeInInfo"]}`}, {q: NewGetVariations("", "", "").EnableOffers(), str: `{"resources":["offersV2.listings.availability","offersV2.listings.condition","offersV2.listings.dealDetails","offersV2.listings.isBuyBoxWinner","offersV2.listings.loyaltyPoints","offersV2.listings.merchantInfo","offersV2.listings.price","offersV2.listings.type"]}`}, {q: NewGetVariations("", "", "").EnableOffersV2(), str: `{"resources":["offersV2.listings.availability","offersV2.listings.condition","offersV2.listings.dealDetails","offersV2.listings.isBuyBoxWinner","offersV2.listings.loyaltyPoints","offersV2.listings.merchantInfo","offersV2.listings.price","offersV2.listings.type"]}`}, - {q: NewGetVariations("", "", "").EnableVariationSummary(), str: `{}`}, + {q: NewGetVariations("", "", "").EnableVariationSummary(), str: `{"resources":["variationSummary.price.highestPrice","variationSummary.price.lowestPrice","variationSummary.variationDimension"]}`}, } for _, tc := range testCases { diff --git a/query/query.go b/query/query.go index 52c6c06..ab547c4 100644 --- a/query/query.go +++ b/query/query.go @@ -145,12 +145,12 @@ func (q *Query) BrowseNodes() *Query { return q } -// VariationSummary selects the VariationSummary resource. -// -// Deprecated: the Creators API does not expose a VariationSummary resource. -// Calls to this method are now no-ops and the response will not contain a -// VariationSummary block. Retained so existing call sites compile. +// VariationSummary sets the resource of VariationSummary. The Creators API +// returns the variation summary (page count, total variation count, price +// range and variation dimensions) under the VariationsResult.VariationSummary +// container of the response. func (q *Query) VariationSummary() *Query { + q.enableResources[resourceVariationSummary] = true return q } diff --git a/query/query_test.go b/query/query_test.go index bf32533..35557b3 100644 --- a/query/query_test.go +++ b/query/query_test.go @@ -131,7 +131,7 @@ func TestResources(t *testing.T) { str string }{ {q: empty.With().BrowseNodeInfo(), str: `{"resources":["browseNodeInfo.browseNodes","browseNodeInfo.browseNodes.ancestor","browseNodeInfo.browseNodes.salesRank","browseNodeInfo.websiteSalesRank"]}`}, - {q: empty.With().Images(), str: `{"resources":["images.primary.small","images.primary.medium","images.primary.large","images.primary.highRes","images.variants.small","images.variants.medium","images.variants.large","images.variants.highRes"]}`}, + {q: empty.With().Images(), str: `{"resources":["images.primary.small","images.primary.medium","images.primary.large","images.variants.small","images.variants.medium","images.variants.large"]}`}, {q: empty.With().ItemInfo(), str: `{"resources":["itemInfo.byLineInfo","itemInfo.contentInfo","itemInfo.contentRating","itemInfo.classifications","itemInfo.externalIds","itemInfo.features","itemInfo.manufactureInfo","itemInfo.productInfo","itemInfo.technicalInfo","itemInfo.title","itemInfo.tradeInInfo"]}`}, // Offers (V1) is now an alias for OffersV2 in the Creators API. {q: empty.With().Offers(), str: `{"resources":["offersV2.listings.availability","offersV2.listings.condition","offersV2.listings.dealDetails","offersV2.listings.isBuyBoxWinner","offersV2.listings.loyaltyPoints","offersV2.listings.merchantInfo","offersV2.listings.price","offersV2.listings.type"]}`}, @@ -140,8 +140,7 @@ func TestResources(t *testing.T) { {q: empty.With().ParentASIN(), str: `{"resources":["parentASIN"]}`}, {q: empty.With().CustomerReviews(), str: `{"resources":["customerReviews.count","customerReviews.starRating"]}`}, {q: empty.With().BrowseNodes(), str: `{"resources":["browseNodes.ancestor","browseNodes.children"]}`}, - // VariationSummary is no longer exposed by the Creators API. - {q: empty.With().VariationSummary(), str: `{}`}, + {q: empty.With().VariationSummary(), str: `{"resources":["variationSummary.price.highestPrice","variationSummary.price.lowestPrice","variationSummary.variationDimension"]}`}, } for _, tc := range testCases { diff --git a/query/resources.go b/query/resources.go index 14d6400..1af3817 100644 --- a/query/resources.go +++ b/query/resources.go @@ -11,6 +11,7 @@ const ( resourceParentASIN //ParentASIN resource resourceCustomerReviews //CustomerReviews resource resourceBrowseNodes //BrowseNodes resource + resourceVariationSummary //VariationSummary resource ) // Resource string values match the Amazon Creators API enum values @@ -28,11 +29,9 @@ var ( "images.primary.small", "images.primary.medium", "images.primary.large", - "images.primary.highRes", "images.variants.small", "images.variants.medium", "images.variants.large", - "images.variants.highRes", } //ItemInfo resource resourcesItemInfo = []string{ @@ -77,6 +76,12 @@ var ( "browseNodes.ancestor", "browseNodes.children", } + //VariationSummary resource + resourcesVariationSummary = []string{ + "variationSummary.price.highestPrice", + "variationSummary.price.lowestPrice", + "variationSummary.variationDimension", + } resourcesMap = map[resource][]string{ resourceBrowseNodeInfo: resourcesBrowseNodeInfo, @@ -87,6 +92,7 @@ var ( resourceParentASIN: resourcesParentASIN, resourceCustomerReviews: resourcesCustomerReviews, resourceBrowseNodes: resourcesBrowseNodes, + resourceVariationSummary: resourcesVariationSummary, } ) diff --git a/query/searchitems_test.go b/query/searchitems_test.go index 1e29149..3ef75f4 100644 --- a/query/searchitems_test.go +++ b/query/searchitems_test.go @@ -124,7 +124,7 @@ func TestResourcesInSearchItems(t *testing.T) { str string }{ {q: NewSearchItems("", "", "").EnableBrowseNodeInfo(), str: `{"resources":["browseNodeInfo.browseNodes","browseNodeInfo.browseNodes.ancestor","browseNodeInfo.browseNodes.salesRank","browseNodeInfo.websiteSalesRank"]}`}, - {q: NewSearchItems("", "", "").EnableImages(), str: `{"resources":["images.primary.small","images.primary.medium","images.primary.large","images.primary.highRes","images.variants.small","images.variants.medium","images.variants.large","images.variants.highRes"]}`}, + {q: NewSearchItems("", "", "").EnableImages(), str: `{"resources":["images.primary.small","images.primary.medium","images.primary.large","images.variants.small","images.variants.medium","images.variants.large"]}`}, {q: NewSearchItems("", "", "").EnableItemInfo(), str: `{"resources":["itemInfo.byLineInfo","itemInfo.contentInfo","itemInfo.contentRating","itemInfo.classifications","itemInfo.externalIds","itemInfo.features","itemInfo.manufactureInfo","itemInfo.productInfo","itemInfo.technicalInfo","itemInfo.title","itemInfo.tradeInInfo"]}`}, {q: NewSearchItems("", "", "").EnableOffers(), str: `{"resources":["offersV2.listings.availability","offersV2.listings.condition","offersV2.listings.dealDetails","offersV2.listings.isBuyBoxWinner","offersV2.listings.loyaltyPoints","offersV2.listings.merchantInfo","offersV2.listings.price","offersV2.listings.type"]}`}, {q: NewSearchItems("", "", "").EnableOffersV2(), str: `{"resources":["offersV2.listings.availability","offersV2.listings.condition","offersV2.listings.dealDetails","offersV2.listings.isBuyBoxWinner","offersV2.listings.loyaltyPoints","offersV2.listings.merchantInfo","offersV2.listings.price","offersV2.listings.type"]}`},