From 9875a196e9d6b139ff153c2bf71bf3b84f114cdf Mon Sep 17 00:00:00 2001 From: diPhantxm Date: Sun, 19 Mar 2023 22:16:32 +0300 Subject: [PATCH] add all remained methods for managing products --- ENDPOINTS.md | 20 +- ozon/products.go | 516 ++++++++++++++++++++++++++++++ ozon/products_test.go | 722 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1248 insertions(+), 10 deletions(-) diff --git a/ENDPOINTS.md b/ENDPOINTS.md index a1c7b5c..451d3f7 100644 --- a/ENDPOINTS.md +++ b/ENDPOINTS.md @@ -15,16 +15,16 @@ - [x] Product details - [x] Get products' content rating by SKU - [x] Get a list of products by identifiers -- [ ] Get a description of the product characteristics -- [ ] Get product description -- [ ] Product range limit, limits on product creation and update -- [ ] Change product identifiers from the seller's system -- [ ] Archive a product -- [ ] Unarchive a product -- [ ] Remove a product without an SKU from the archive -- [ ] Get a list of geo-restrictions for services -- [ ] Upload activation codes for services and digital products -- [ ] Status of uploading activation codes +- [x] Get a description of the product characteristics +- [x] Get product description +- [x] Product range limit, limits on product creation and update +- [x] Change product identifiers from the seller's system +- [x] Archive a product +- [x] Unarchive a product +- [x] Remove a product without an SKU from the archive +- [x] Get a list of geo-restrictions for services +- [x] Upload activation codes for services and digital products +- [x] Status of uploading activation codes ## Prices and Stocks - [x] Update stocks diff --git a/ozon/products.go b/ozon/products.go index 1e212e8..6865a4b 100644 --- a/ozon/products.go +++ b/ozon/products.go @@ -1198,3 +1198,519 @@ func (c Products) ListProductsByIDs(params *ListProductsByIDsParams) (*ListProdu return resp, nil } + +type GetDescriptionOfProductParams struct { + // Filter by product + Filter GetDescriptionOfProductFilter `json:"filter"` + + // Identifier of the last value on the page. Leave this field blank in the first request. + // + // To get the next values, specify `last_id` from the response of the previous request + LastId string `json:"last_id"` + + // Number of values per page. Minimum is 1, maximum is 1000 + Limit int64 `json:"limit"` + + // The parameter by which the products will be sorted + SortBy string `json:"sort_by"` + + // Sorting direction + SortDirection string `json:"sort_direction"` +} + +type GetDescriptionOfProductFilter struct { + // Filter by the `offer_id` parameter. It is possible to pass a list of values + OfferId []string `json:"offer_id"` + + // Filter by the product_id parameter. It is possible to pass a list of values + ProductId []int64 `json:"product_id"` + + // Filter by product visibility + Visibility string `json:"visibility"` +} + +type GetDescriptionOfProductResponse struct { + core.CommonResponse + + // Request results + Result []struct { + // Array of product characteristics + Attributes []struct { + // Characteristic identifier + AttributeId int64 `json:"attribute_id"` + + // Identifier of the characteristic that supports nested properties. + // For example, the "Processor" characteristic has nested characteristics "Manufacturer" and "L2 Cache". + // Each of the nested characteristics can have multiple value variants + ComplexId int64 `json:"complex_id"` + + // Array of characteristic values + Values []struct { + // Characteristic identifier in the dictionary + DictionaryValueId int64 `json:"dictionary_value_id"` + + // Product characteristic value + Value string `json:"value"` + } `json:"values"` + } `json:"attributes"` + + // Barcode + Barcode string `json:"barcode"` + + // Category identifier + CategoryId int64 `json:"category_id"` + + // Marketing color + ColorImage string `json:"color_image"` + + // Array of nested characteristics + ComplexAttributes []struct { + // Array of product characteristics + Attributes []struct { + // Characteristic identifier + AttributeId int64 `json:"attribute_id"` + + // Identifier of the characteristic that supports nested properties. + // For example, the "Processor" characteristic has nested characteristics "Manufacturer" and "L2 Cache". + // Each of the nested characteristics can have multiple value variants + ComplexId int64 `json:"complex_id"` + + // Array of characteristic values + Values []struct { + // Characteristic identifier in the dictionary + DictionaryValueId int64 `json:"dictionary_value_id"` + + // Product characteristic value + Value string `json:"value"` + } `json:"values"` + } `json:"attributes` + } `json:"complex_attributes"` + + // Depth + Depth int32 `json:"depth"` + + // Dimension measurement units: + // - mm — millimeters, + // - cm — centimeters, + // - in — inches + DimensionUnit string `json:"dimension_unit"` + + // Package height + Height int32 `json:"height"` + + // Product characteristic identifier + Id int64 `json:"id"` + + // Identifier for subsequent batch loading of images + ImageGroupId string `json:"image_group_id"` + + // Array of links to product images + Images []struct { + Default bool `json:"default"` + FileName string `json:"file_name"` + Index int64 `json:"index"` + } `json:"images"` + + // Array of 360 images + Images360 []struct { + FileName string `json:"file_name"` + Index int64 `json:"index"` + } `json:"images360"` + + // Product name. Up to 500 characters + Name string `json:"name"` + + // Product identifier in the seller's system + OfferId string `json:"offer_id"` + + // Array of PDF files + PDFList []struct { + // Path to PDF file + FileName string `json:"file_name"` + + // Storage order index + Index int64 `json:"index"` + + // File name + Name string `json:"name"` + } `json:"pdf_list"` + + // Weight of product in the package + Weight int32 `json:"weight"` + + // Weight measurement unit + WeightUnit string `json:"weight_unit"` + + // Package width + Width int32 `json:"width"` + } `json:"result"` + + // Identifier of the last value on the page. + // + // To get the next values, specify the recieved value in the next request in the last_id parameter + LastId string `json:"last_id"` + + // Number of products in the list + Total int32 `json:"total"` +} + +// Returns a product characteristics description by product identifier. You can search for the product by `offer_id` or `product_id` +func (c Products) GetDescriptionOfProduct(params *GetDescriptionOfProductParams) (*GetDescriptionOfProductResponse, error) { + url := "/v3/products/info/attributes" + + resp := &GetDescriptionOfProductResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type GetProductDescriptionParams struct { + // Product identifier in the seller's system + OfferId string `json:"offer_id"` + + // Product identifier + ProductId int64 `json:"product_id"` +} + +type GetProductDescriptionResponse struct { + core.CommonResponse + + // Method result + Result struct { + // Description + Description string `json:"description"` + + // Identifier + Id int64 `json:"id"` + + // Name + Name string `json:"name"` + + // Product identifier in the seller's system + OfferId string `json:"offer_id"` + } `json:"result"` +} + +// Get product description +func (c Products) GetProductDescription(params *GetProductDescriptionParams) (*GetProductDescriptionResponse, error) { + url := "/v1/product/info/description" + + resp := &GetProductDescriptionResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type GetProductRangeLimitResponse struct { + core.CommonResponse + + // Daily product creation limit + DailyCreate GetProductRangeLimitUploadQuota `json:"daily_create"` + + // Daily product update limit + DailyUpdate GetProductRangeLimitUploadQuota `json:"daily_update"` + + // Product range limit + Total struct { + // How many products you can create in your personal account + Limit int64 `json:"limit"` + + // How many products you've already created + Usage int64 `json:"usage"` + } `json:"total"` +} + +type GetProductRangeLimitUploadQuota struct { + // How many products you can create in one day + Limit int64 `json:"limit"` + + // Counter reset time for the current day in UTC format + ResetAt time.Time `json:"reset_at"` + + // How many products you've created in the current day + Usage int64 `json:"usage"` +} + +// Method for getting information about the following limits: +// - Product range limit: how many products you can create in your personal account. +// - Products creation limit: how many products you can create per day. +// - Products update limit: how many products you can update per day. +// If you have a product range limit and you exceed it, you won't be able to create new products +func (c Products) GetProductRangeLimit() (*GetProductRangeLimitResponse, error) { + url := "/v4/product/info/limit" + + resp := &GetProductRangeLimitResponse{} + + response, err := c.client.Request(http.MethodPost, url, &struct{}{}, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type ChangeProductIDsParams struct { + // List of pairs with new and old values of product identifiers + UpdateOfferId []ChangeProductIDsUpdateOffer `json:"update_offer_id"` +} + +type ChangeProductIDsUpdateOffer struct { + // New product identifier + // + // The maximum length of a string is 50 characters + NewOfferId string `json:"new_offer_id"` + + // Old product identifier + OfferId string `json:"offer_id"` +} + +type ChangeProductIDsResponse struct { + core.CommonResponse + + // Errors list + Errors []struct { + // Error message + Message string `json:"message"` + + // Product identifier that wasn't changed + OfferId string `json:"offer_id"` + } `json:"errors"` +} + +// Method for changing the offer_id linked to products. You can change multiple offer_id in this method. +// +// We recommend transmitting up to 250 values in an array +func (c Products) ChangeProductIDs(params *ChangeProductIDsParams) (*ChangeProductIDsResponse, error) { + url := "/v1/product/update/offer-id" + + resp := &ChangeProductIDsResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type ArchiveProductParams struct { + // Product identifier + ProductId []int64 `json:"product_id"` +} + +type ArchiveProductResponse struct { + core.CommonResponse + + // The result of processing the request. true if the request was executed without errors + Result bool `json:"result"` +} + +// Archive product +func (c Products) ArchiveProduct(params *ArchiveProductParams) (*ArchiveProductResponse, error) { + url := "/v1/product/archive" + + resp := &ArchiveProductResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +// Unarchive product +func (c Products) UnarchiveProduct(params *ArchiveProductParams) (*ArchiveProductResponse, error) { + url := "/v1/product/unarchive" + + resp := &ArchiveProductResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type RemoveProductWithoutSKUParams struct { + // Product identifier + Products []RemoveProductWithoutSKUProduct `json:"products"` +} + +type RemoveProductWithoutSKUProduct struct { + // Product identifier in the seller's system + OfferId string `json:"offer_id"` +} + +type RemoveProductWithoutSKUResponse struct { + core.CommonResponse + + // Product processing status + Status []struct { + // Reason of the error that occurred while processing the request + Error string `json:"error"` + + // If the request was executed without errors and the products were deleted, the value is true + IsDeleted bool `json:"is_deleted"` + + // Product identifier in the seller's system + OfferId string `json:"offer_id"` + } `json:"status"` +} + +// Remove a product without an SKU from the archive +// +// You can pass up to 500 identifiers in one request +func (c Products) RemoveProductWithoutSKU(params *RemoveProductWithoutSKUParams) (*RemoveProductWithoutSKUResponse, error) { + url := "/v2/products/delete" + + resp := &RemoveProductWithoutSKUResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type ListGeoRestrictionsParams struct { + // Filter. To get all geo-restrictions, leave names blank and specify true in the only_visible parameter + Filter ListGeoRestrictionsFilter `json:"filter"` + + // Order number of geo-restriction from which to output data in the response. + // + // If you specify 23 in this parameter, the first item in the restrictions list will output order_number = 24. + // If you want to get all geo-restrictions, pass 0 in this parameter + LastOrderNumber int64 `json:"last_order_number"` + + // Number of items in the response + Limit int64 `json:"limit"` +} + +type ListGeoRestrictionsFilter struct { + // List with city names + Names []string `json:"names"` + + // Value visibility. We recommend always passing true in this parameter + OnlyVisible bool `json:"only_visible"` +} + +type ListGeoRestrictionsResponse struct { + core.CommonResponse + + // Restrictions + Restrictions []struct { + // Geo-restriction identifier + Id string `json:"id"` + + // Item visibility + IsVisible bool `json:"is_visible"` + + // City name + Name string `json:"name"` + + // Geo-restriction order number. + // + // If you specify 23 in the last_order_number parameter in the request, + // the first item in the restrictions list will have order_number = 24 + OrderNumber int64 `json:"order_number"` + } `json:"restrictions"` +} + +// Get a list of geo-restrictions for services +func (c Products) ListGeoRestrictions(params *ListGeoRestrictionsParams) (*ListGeoRestrictionsResponse, error) { + url := "/v1/products/geo-restrictions-catalog-by-filter" + + resp := &ListGeoRestrictionsResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type UploadActivationCodesParams struct { + // Digital activation codes + DigitalCodes []string `json:"digital_codes"` + + // Product identifier + ProductId int64 `json:"product_id"` +} + +type UploadActivationCodesResponse struct { + core.CommonResponse + + // Method result + Result struct { + // Uploading digital code task identifier + TaskId int64 `json:"task_id"` + } `json:"result"` +} + +// Upload activation codes when you upload service or digital products. Activation code is associated with the digital product card +func (c Products) UploadActivationCodes(params *UploadActivationCodesParams) (*UploadActivationCodesResponse, error) { + url := "/v1/product/upload_digital_codes" + + resp := &UploadActivationCodesResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} + +type StatusOfUploadingActivationCodesParams struct { + // Uploading activation codes task identifier that was received from the `/v1/product/upload_digital_codes` method + TaskId int64 `json:"task_id"` +} + +type StatusOfUploadingActivationCodesResponse struct { + core.CommonResponse + + // Method result + Result struct { + // Upload status: + // - pending — products in queue for processing. + // - imported — the product has been successfully uploaded. + // - failed — the product was uploaded with errors + Status string `json:"status"` + } `json:"result"` +} + +// Get status of uploading activation codes task for services and digital products +func (c Products) StatusOfUploadingActivationCodes(params *StatusOfUploadingActivationCodesParams) (*StatusOfUploadingActivationCodesResponse, error) { + url := "/v1/product/upload_digital_codes/info" + + resp := &StatusOfUploadingActivationCodesResponse{} + + response, err := c.client.Request(http.MethodPost, url, params, resp) + if err != nil { + return nil, err + } + response.CopyCommonResponse(&resp.CommonResponse) + + return resp, nil +} diff --git a/ozon/products_test.go b/ozon/products_test.go index a692857..13cdf98 100644 --- a/ozon/products_test.go +++ b/ozon/products_test.go @@ -1475,3 +1475,725 @@ func TestListProductsByIDs(t *testing.T) { } } } + +func TestGetDescriptionOfProduct(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *GetDescriptionOfProductParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &GetDescriptionOfProductParams{ + Filter: GetDescriptionOfProductFilter{ + ProductId: []int64{213761435}, + Visibility: "ALL", + }, + LastId: "okVsfA==«", + SortBy: "ASC", + Limit: 100, + }, + `{ + "result": [ + { + "id": 213761435, + "barcode": "", + "category_id": 17038062, + "name": "Пленка защитная для Xiaomi Redmi Note 10 Pro 5G", + "offer_id": "21470", + "height": 10, + "depth": 210, + "width": 140, + "dimension_unit": "mm", + "weight": 50, + "weight_unit": "g", + "images": [ + { + "file_name": "https://cdn1.ozone.ru/s3/multimedia-f/6190456071.jpg", + "default": true, + "index": 0 + }, + { + "file_name": "https://cdn1.ozone.ru/s3/multimedia-7/6190456099.jpg", + "default": false, + "index": 1 + }, + { + "file_name": "https://cdn1.ozone.ru/s3/multimedia-9/6190456065.jpg", + "default": false, + "index": 2 + } + ], + "image_group_id": "", + "images360": [], + "pdf_list": [], + "attributes": [ + { + "attribute_id": 5219, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 970718176, + "value": "универсальный" + } + ] + }, + { + "attribute_id": 11051, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 970736931, + "value": "Прозрачный" + } + ] + }, + { + "attribute_id": 10100, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 0, + "value": "false" + } + ] + }, + { + "attribute_id": 11794, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 970860783, + "value": "safe" + } + ] + }, + { + "attribute_id": 9048, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 0, + "value": "Пленка защитная для Xiaomi Redmi Note 10 Pro 5G" + } + ] + }, + { + "attribute_id": 5076, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 39638, + "value": "Xiaomi" + } + ] + }, + { + "attribute_id": 9024, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 0, + "value": "21470" + } + ] + }, + { + "attribute_id": 10015, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 0, + "value": "false" + } + ] + }, + { + "attribute_id": 85, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 971034861, + "value": "Brand" + } + ] + }, + { + "attribute_id": 9461, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 349824787, + "value": "Защитная пленка для смартфона" + } + ] + }, + { + "attribute_id": 4180, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 0, + "value": "Пленка защитная для Xiaomi Redmi Note 10 Pro 5G" + } + ] + }, + { + "attribute_id": 4191, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 0, + "value": "Пленка предназначена для модели Xiaomi Redmi Note 10 Pro 5G. Защитная гидрогелевая пленка обеспечит защиту вашего смартфона от царапин, пыли, сколов и потертостей." + } + ] + }, + { + "attribute_id": 8229, + "complex_id": 0, + "values": [ + { + "dictionary_value_id": 91521, + "value": "Защитная пленка" + } + ] + } + ], + "complex_attributes": [], + "color_image": "", + "last_id": "" + } + ], + "total": 1, + "last_id": "onVsfA==" + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &GetDescriptionOfProductParams{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + }, + } + + for _, test := range tests { + c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) + + resp, err := c.Products().GetDescriptionOfProduct(test.params) + if err != nil { + t.Error(err) + } + + if resp.StatusCode != test.statusCode { + t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) + } + + if resp.StatusCode == http.StatusOK { + if len(resp.Result) != len(test.params.Filter.ProductId) && len(resp.Result) != len(test.params.Filter.OfferId) { + t.Errorf("Amount of products in request and response are not equal") + } + } + } +} + +func TestGetProductDescription(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *GetProductDescriptionParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &GetProductDescriptionParams{ + OfferId: "5", + ProductId: 73453843, + }, + `{ + "result": { + "id": 73453843, + "offer_id": "5", + "name": "Онлайн курс по дрессировке собак \"Воспитанная собака за 3 недели\"", + "description": "Экспресс-курс - это сокращённый вариант курса \"Собака: инструкция по применению\", дающий базовый минимум знаний, навыков, умений. Это оптимальный вариант для совершения первых шагов по воспитанию!

Что дает Экспресс-курс:К концу экспресс-курса дрессировки вы получаете воспитанного друга и соратника, который ориентируется на вас в любой ситуации.Благополучие собаки: больше не будет срывов с поводка, преследования кошек, попыток съесть что-либо на улице и т. д.Принципиально другой уровень общения, без раздражения, криков и недовольства поведением животного." + } + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &GetProductDescriptionParams{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + }, + } + + for _, test := range tests { + c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) + + resp, err := c.Products().GetProductDescription(test.params) + if err != nil { + t.Error(err) + } + + if resp.StatusCode != test.statusCode { + t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) + } + + if resp.StatusCode == http.StatusOK { + if resp.Result.Id != test.params.ProductId { + t.Errorf("Product ids in request and response are not equal") + } + if resp.Result.OfferId != test.params.OfferId { + t.Errorf("Offer ids in request and response are not equal") + } + } + } +} + +func TestGetProductRangeLimit(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + `{ + "daily_create": { + "limit": 0, + "reset_at": "2019-08-24T14:15:22Z", + "usage": 0 + }, + "daily_update": { + "limit": 0, + "reset_at": "2019-08-24T14:15:22Z", + "usage": 0 + }, + "total": { + "limit": 0, + "usage": 0 + } + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + }, + } + + for _, test := range tests { + c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) + + resp, err := c.Products().GetProductRangeLimit() + if err != nil { + t.Error(err) + } + + if resp.StatusCode != test.statusCode { + t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) + } + } +} + +func TestChangeProductIDs(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *ChangeProductIDsParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &ChangeProductIDsParams{ + UpdateOfferId: []ChangeProductIDsUpdateOffer{ + { + NewOfferId: "new id", + OfferId: "old id", + }, + }, + }, + `{ + "errors": [ + { + "message": "string", + "offer_id": "string" + } + ] + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &ChangeProductIDsParams{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + }, + } + + for _, test := range tests { + c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) + + resp, err := c.Products().ChangeProductIDs(test.params) + if err != nil { + t.Error(err) + } + + if resp.StatusCode != test.statusCode { + t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) + } + } +} + +func TestArchiveProduct(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *ArchiveProductParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &ArchiveProductParams{ + ProductId: []int64{125529926}, + }, + `{ + "result": true + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &ArchiveProductParams{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + }, + } + + for _, test := range tests { + c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) + + resp, err := c.Products().ArchiveProduct(test.params) + if err != nil { + t.Error(err) + } + + if resp.StatusCode != test.statusCode { + t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) + } + } +} + +func TestUnarchiveProduct(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *ArchiveProductParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &ArchiveProductParams{ + ProductId: []int64{125529926}, + }, + `{ + "result": true + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &ArchiveProductParams{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + }, + } + + for _, test := range tests { + c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) + + resp, err := c.Products().UnarchiveProduct(test.params) + if err != nil { + t.Error(err) + } + + if resp.StatusCode != test.statusCode { + t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) + } + } +} + +func TestRemoveProductWithoutSKU(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *RemoveProductWithoutSKUParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &RemoveProductWithoutSKUParams{ + Products: []RemoveProductWithoutSKUProduct{ + { + OfferId: "033", + }, + }, + }, + `{ + "status": [ + { + "offer_id": "033", + "is_deleted": true, + "error": "" + } + ] + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &RemoveProductWithoutSKUParams{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + }, + } + + for _, test := range tests { + c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) + + resp, err := c.Products().RemoveProductWithoutSKU(test.params) + if err != nil { + t.Error(err) + } + + if resp.StatusCode != test.statusCode { + t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) + } + + if resp.StatusCode == http.StatusOK { + if len(resp.Status) > 0 { + if resp.Status[0].OfferId != test.params.Products[0].OfferId { + t.Errorf("Offer ids in request and response are not equal") + } + } + } + } +} + +func TestListGeoRestrictions(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *ListGeoRestrictionsParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &ListGeoRestrictionsParams{ + Filter: ListGeoRestrictionsFilter{ + OnlyVisible: true, + }, + LastOrderNumber: 0, + Limit: 3, + }, + `{ + "restrictions": [ + { + "id": "world", + "name": "Весь Мир", + "is_visible": true, + "order_number": 1 + }, + { + "id": "42fb1c32-0cfe-5c96-9fb5-7f8e8449f28c", + "name": "Все города РФ", + "is_visible": true, + "order_number": 2 + }, + { + "id": "moscow", + "name": "Москва", + "is_visible": true, + "order_number": 3 + } + ] + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &ListGeoRestrictionsParams{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + }, + } + + for _, test := range tests { + c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) + + resp, err := c.Products().ListGeoRestrictions(test.params) + if err != nil { + t.Error(err) + } + + if resp.StatusCode != test.statusCode { + t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) + } + } +} + +func TestUploadActivationCodes(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *UploadActivationCodesParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &UploadActivationCodesParams{ + DigitalCodes: []string{"764282654334"}, + ProductId: 73160317, + }, + `{ + "result": { + "task_id": 172549811 + } + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &UploadActivationCodesParams{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + }, + } + + for _, test := range tests { + c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) + + resp, err := c.Products().UploadActivationCodes(test.params) + if err != nil { + t.Error(err) + } + + if resp.StatusCode != test.statusCode { + t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) + } + } +} + +func TestStatusOfUploadingActivationCodes(t *testing.T) { + t.Parallel() + + tests := []struct { + statusCode int + headers map[string]string + params *StatusOfUploadingActivationCodesParams + response string + }{ + // Test Ok + { + http.StatusOK, + map[string]string{"Client-Id": "my-client-id", "Api-Key": "my-api-key"}, + &StatusOfUploadingActivationCodesParams{ + TaskId: 178574231, + }, + `{ + "result": { + "status": "imported" + } + }`, + }, + // Test No Client-Id or Api-Key + { + http.StatusUnauthorized, + map[string]string{}, + &StatusOfUploadingActivationCodesParams{}, + `{ + "code": 16, + "message": "Client-Id and Api-Key headers are required" + }`, + }, + } + + for _, test := range tests { + c := NewMockClient(core.NewMockHttpHandler(test.statusCode, test.response, test.headers)) + + resp, err := c.Products().StatusOfUploadingActivationCodes(test.params) + if err != nil { + t.Error(err) + } + + if resp.StatusCode != test.statusCode { + t.Errorf("got wrong status code: got: %d, expected: %d", resp.StatusCode, test.statusCode) + } + } +}