diff --git a/client.go b/client.go index d1ee667..141703a 100644 --- a/client.go +++ b/client.go @@ -70,6 +70,27 @@ func (c *Client) Post(ctx context.Context, path string, param url.Values, body i return } +// Put request for TikTok requests. +// Note: Timestamp, appkey and signature will auto-management by action. +func (c *Client) Put(ctx context.Context, path string, param url.Values, body interface{}, resp interface{}) (err error) { + param = c.prepareParam(path, param) + r := c.prepareBody(body) + err = c.request(ctx, http.MethodPut, APIBaseURL, path, param, r, resp) + return +} + +// Delete request for TikTok requests. I don't known why there is body in delete request. +// Note: Timestamp, appkey and signature will auto-management by action. +func (c *Client) Delete(ctx context.Context, path string, param url.Values, body interface{}, resp interface{}) (err error) { + param = c.prepareParam(path, param) + var r io.Reader + if body != nil { + r = c.prepareBody(body) + } + err = c.request(ctx, http.MethodDelete, APIBaseURL, path, param, r, resp) + return +} + func (c *Client) prepareParam(path string, param url.Values) url.Values { ak := safeGet(param, "access_token") if ak != "" { diff --git a/helper_test.go b/helper_test.go index da166e9..a3f5e05 100644 --- a/helper_test.go +++ b/helper_test.go @@ -48,12 +48,10 @@ func setupMock(t *testing.T, tt TestRecord, args, want interface{}) { if tt.Request.Query != "" { require.Equal(t, tt.Request.Query, r.URL.RawQuery) } - var b []byte - if r.Method == http.MethodPost || r.Method == http.MethodPut { - defer r.Body.Close() - b, _ = io.ReadAll(r.Body) - } + if len(tt.Request.Body) > 0 { + defer r.Body.Close() + b, _ := io.ReadAll(r.Body) require.JSONEq(t, string(tt.Request.Body), string(b)) } else { tt.Request.Body = []byte(`{}`) @@ -85,3 +83,30 @@ func mockTime() func() { } } } + +func mockTests(t *testing.T, fn string, args, want interface{}, exec func() (interface{}, error)) { + t.Helper() + + restore := mockTime() + defer restore() + + tests := loadTestData(t, fn) + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + setupMock(t, tt, args, want) + + got, err := exec() + if tt.WantErr { + require.Error(t, err) + return + } else { + require.NoError(t, err) + if want != nil { + require.Equal(t, want, got) + } + } + }) + } +} diff --git a/product.go b/product.go index d199e1e..1f226e0 100644 --- a/product.go +++ b/product.go @@ -83,42 +83,110 @@ func (c *Client) UploadFile(ctx context.Context, p Param, fn string, body []byte return } -func (c *Client) CreateProduct(ctx context.Context, p Param) (err error) { +func (c *Client) CreateProduct(ctx context.Context, p Param, req CreateProductRequest) (product Product, err error) { + var param url.Values + if param, err = c.params(p); err != nil { + return + } + if err = c.validate.Struct(&req); err != nil { + return + } + err = c.Post(ctx, "/api/products", param, req, &product) return } -func (c *Client) EditProduct(ctx context.Context, p Param) (err error) { +func (c *Client) EditProduct(ctx context.Context, p Param, req EditProductRequest) (product Product, err error) { + var param url.Values + if param, err = c.params(p); err != nil { + return + } + + if err = c.validate.Struct(&req); err != nil { + return + } + err = c.Put(ctx, "/api/products", param, req, &product) return } -func (c *Client) GetProductList(ctx context.Context, p Param) (err error) { +func (c *Client) GetProductList(ctx context.Context, p Param, req ProductSearchRequest) (list ProductSearchList, err error) { + var param url.Values + if param, err = c.params(p); err != nil { + return + } + err = c.Post(ctx, "/api/products/search", param, req, &list) return } -func (c *Client) GetProductDetail(ctx context.Context, p Param) (err error) { +func (c *Client) GetProductDetail(ctx context.Context, p Param, productID string) (product Product, err error) { + var param url.Values + if param, err = c.params(p); err != nil { + return + } + param.Set("product_id", productID) + err = c.Get(ctx, "/api/products/details", param, &product) return } -func (c *Client) UpdatePrice(ctx context.Context, p Param) (err error) { +func (c *Client) UpdatePrice(ctx context.Context, p Param, req UpdatePriceRequest) (list UpdatePriceFailedSKU, err error) { + var param url.Values + if param, err = c.params(p); err != nil { + return + } + err = c.Put(ctx, "/api/products/prices", param, req, &list) return } -func (c *Client) UpdateStock(ctx context.Context, p Param) (err error) { +func (c *Client) UpdateStock(ctx context.Context, p Param, req UpdateStockRequest) (list UpdateStockFailedSKU, err error) { + var param url.Values + if param, err = c.params(p); err != nil { + return + } + err = c.Put(ctx, "/api/products/stocks", param, req, &list) return } -func (c *Client) DeactivateProducts(ctx context.Context, p Param) (err error) { +func (c *Client) DeactivateProducts(ctx context.Context, p Param, ids []string) (list FailedProductIDs, err error) { + var param url.Values + if param, err = c.params(p); err != nil { + return + } + req := map[string][]string{ + "product_ids": ids, + } + err = c.Post(ctx, "/api/products/inactivated_products", param, req, &list) return } -func (c *Client) DeleteProducts(ctx context.Context, p Param) (err error) { +func (c *Client) DeleteProducts(ctx context.Context, p Param, ids []string) (list FailedProductIDs, err error) { + var param url.Values + if param, err = c.params(p); err != nil { + return + } + req := map[string][]string{ + "product_ids": ids, + } + err = c.Delete(ctx, "/api/products", param, req, &list) return } -func (c *Client) RecoverProduct(ctx context.Context, p Param) (err error) { +func (c *Client) RecoverProduct(ctx context.Context, p Param, ids []string) (list FailedProductIDs, err error) { + param := url.Values{} + param.Set("access_token", p.AccessToken) + req := map[string][]string{ + "product_ids": ids, + } + err = c.Post(ctx, "/api/products/recover", param, req, &list) return } -func (c *Client) ActivateProduct(ctx context.Context, p Param) (err error) { +func (c *Client) ActivateProduct(ctx context.Context, p Param, ids []string) (list FailedProductIDs, err error) { + var param url.Values + if param, err = c.params(p); err != nil { + return + } + req := map[string][]string{ + "product_ids": ids, + } + err = c.Post(ctx, "/api/products/activate", param, req, &list) return } diff --git a/product_test.go b/product_test.go index 8446df7..dedf545 100644 --- a/product_test.go +++ b/product_test.go @@ -2,6 +2,7 @@ package tiktok_test import ( "context" + "encoding/json" "strings" "testing" @@ -235,3 +236,199 @@ func TestClient_UploadFile(t *testing.T) { }) } } + +// TODO: Fix those tests. +// func TestClient_CreateProduct(t *testing.T) { +// var args struct { +// AppKey string `json:"app_key"` +// AppSecret string `json:"app_secret"` +// AccessToken string `json:"access_token"` +// ShopID string `json:"shop_id"` +// RequestJSON json.RawMessage `json:"request_json"` +// } +// var want tiktok.Product +// mockTests(t, "testdata/product/create_product.json", &args, &want, func() (got interface{}, err error) { +// c, err := tiktok.New(args.AppKey, args.AppSecret) +// require.NoError(t, err) +// var req tiktok.CreateProductRequest +// err = json.Unmarshal(args.RequestJSON, &req) +// require.NoError(t, err) +// product, err := c.CreateProduct(context.TODO(), tiktok.Param{args.AccessToken, args.ShopID}, req) +// got = &product +// return +// }) +// } + +// func TestClient_EditProduct(t *testing.T) { +// var args struct { +// AppKey string `json:"app_key"` +// AppSecret string `json:"app_secret"` +// AccessToken string `json:"access_token"` +// ShopID string `json:"shop_id"` +// RequestJSON json.RawMessage `json:"request_json"` +// } +// var want tiktok.Product +// mockTests(t, "testdata/product/edit_product.json", &args, &want, func() (got interface{}, err error) { +// c, err := tiktok.New(args.AppKey, args.AppSecret) +// require.NoError(t, err) +// var req tiktok.CreateProductRequest +// err = json.Unmarshal(args.RequestJSON, &req) +// require.NoError(t, err) +// product, err := c.CreateProduct(context.TODO(), tiktok.Param{args.AccessToken, args.ShopID}, req) +// got = &product +// return +// }) +// } + +func TestClient_GetProductList(t *testing.T) { + var args struct { + AppKey string `json:"app_key"` + AppSecret string `json:"app_secret"` + AccessToken string `json:"access_token"` + ShopID string `json:"shop_id"` + RequestJSON json.RawMessage `json:"request_json"` + } + var want tiktok.ProductSearchList + mockTests(t, "testdata/product/get_product_list.json", &args, &want, func() (got interface{}, err error) { + c, err := tiktok.New(args.AppKey, args.AppSecret) + require.NoError(t, err) + var req tiktok.ProductSearchRequest + err = json.Unmarshal(args.RequestJSON, &req) + require.NoError(t, err) + product, err := c.GetProductList(context.TODO(), tiktok.Param{args.AccessToken, args.ShopID}, req) + got = &product + return + }) +} + +func TestClient_GetProductDetail(t *testing.T) { + var args struct { + AppKey string `json:"app_key"` + AppSecret string `json:"app_secret"` + AccessToken string `json:"access_token"` + ShopID string `json:"shop_id"` + ProductID string `json:"product_id"` + } + var want tiktok.Product + mockTests(t, "testdata/product/get_product_detail.json", &args, &want, func() (got interface{}, err error) { + c, err := tiktok.New(args.AppKey, args.AppSecret) + require.NoError(t, err) + product, err := c.GetProductDetail(context.TODO(), tiktok.Param{args.AccessToken, args.ShopID}, args.ProductID) + got = &product + return + }) +} + +func TestClient_UpdatePrice(t *testing.T) { + var args struct { + AppKey string `json:"app_key"` + AppSecret string `json:"app_secret"` + AccessToken string `json:"access_token"` + ShopID string `json:"shop_id"` + RequestJSON json.RawMessage `json:"request_json"` + } + var want tiktok.UpdatePriceFailedSKU + mockTests(t, "testdata/product/update_price.json", &args, &want, func() (got interface{}, err error) { + c, err := tiktok.New(args.AppKey, args.AppSecret) + require.NoError(t, err) + var req tiktok.UpdatePriceRequest + err = json.Unmarshal(args.RequestJSON, &req) + require.NoError(t, err) + product, err := c.UpdatePrice(context.TODO(), tiktok.Param{args.AccessToken, args.ShopID}, req) + got = &product + return + }) +} + +func TestClient_UpdateStock(t *testing.T) { + var args struct { + AppKey string `json:"app_key"` + AppSecret string `json:"app_secret"` + AccessToken string `json:"access_token"` + ShopID string `json:"shop_id"` + RequestJSON json.RawMessage `json:"request_json"` + } + var want tiktok.UpdateStockFailedSKU + mockTests(t, "testdata/product/update_stock.json", &args, &want, func() (got interface{}, err error) { + c, err := tiktok.New(args.AppKey, args.AppSecret) + require.NoError(t, err) + var req tiktok.UpdateStockRequest + err = json.Unmarshal(args.RequestJSON, &req) + require.NoError(t, err) + product, err := c.UpdateStock(context.TODO(), tiktok.Param{args.AccessToken, args.ShopID}, req) + got = &product + return + }) +} + +func TestClient_DeactivateProducts(t *testing.T) { + var args struct { + AppKey string `json:"app_key"` + AppSecret string `json:"app_secret"` + AccessToken string `json:"access_token"` + ShopID string `json:"shop_id"` + ProductID []string `json:"product_id"` + } + var want tiktok.FailedProductIDs + mockTests(t, "testdata/product/deactivate_products.json", &args, &want, func() (got interface{}, err error) { + c, err := tiktok.New(args.AppKey, args.AppSecret) + require.NoError(t, err) + product, err := c.DeactivateProducts(context.TODO(), tiktok.Param{args.AccessToken, args.ShopID}, args.ProductID) + got = &product + return + }) +} + +func TestClient_DeleteProducts(t *testing.T) { + var args struct { + AppKey string `json:"app_key"` + AppSecret string `json:"app_secret"` + AccessToken string `json:"access_token"` + ShopID string `json:"shop_id"` + ProductID []string `json:"product_id"` + } + var want tiktok.FailedProductIDs + mockTests(t, "testdata/product/delete_products.json", &args, &want, func() (got interface{}, err error) { + c, err := tiktok.New(args.AppKey, args.AppSecret) + require.NoError(t, err) + product, err := c.DeleteProducts(context.TODO(), tiktok.Param{args.AccessToken, args.ShopID}, args.ProductID) + got = &product + return + }) +} + +func TestClient_RecoverProduct(t *testing.T) { + var args struct { + AppKey string `json:"app_key"` + AppSecret string `json:"app_secret"` + AccessToken string `json:"access_token"` + ShopID string `json:"shop_id"` + ProductID []string `json:"product_id"` + } + var want tiktok.FailedProductIDs + mockTests(t, "testdata/product/recover_product.json", &args, &want, func() (got interface{}, err error) { + c, err := tiktok.New(args.AppKey, args.AppSecret) + require.NoError(t, err) + product, err := c.RecoverProduct(context.TODO(), tiktok.Param{args.AccessToken, args.ShopID}, args.ProductID) + got = &product + return + }) +} + +func TestClient_ActivateProduct(t *testing.T) { + var args struct { + AppKey string `json:"app_key"` + AppSecret string `json:"app_secret"` + AccessToken string `json:"access_token"` + ShopID string `json:"shop_id"` + ProductID []string `json:"product_id"` + } + var want tiktok.FailedProductIDs + mockTests(t, "testdata/product/activate_product.json", &args, &want, func() (got interface{}, err error) { + c, err := tiktok.New(args.AppKey, args.AppSecret) + require.NoError(t, err) + product, err := c.ActivateProduct(context.TODO(), tiktok.Param{args.AccessToken, args.ShopID}, args.ProductID) + got = &product + return + }) +} diff --git a/request.go b/request.go index 3941a23..f3c2673 100644 --- a/request.go +++ b/request.go @@ -90,3 +90,84 @@ const ( ImgSceneCertificationImage ImgSceneSizeChartImage ) + +type SKU struct { + ID string `json:"id,omitempty"` + SalesAttributes []SalesAttribute `json:"sales_attributes,omitempty"` + StockInfos []StockInfo `json:"stock_infos,omitempty"` + SellerSku string `json:"seller_sku,omitempty"` + OriginalPrice string `json:"original_price,omitempty"` +} + +type File struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` +} + +type Image struct { + ID string `json:"id"` +} + +type ProductCertificationRequest struct { + ID string `json:"id"` + Files []File `json:"files"` + Images []Image `json:"images"` +} + +type CreateProductRequest struct { + ProductName string `json:"product_name"` + Description string `json:"description"` + CategoryID string `json:"category_id"` + BrandID string `json:"brand_id"` + Images []Image `json:"images"` + WarrantyPeriod int `json:"warranty_period"` + WarrantyPolicy string `json:"warranty_policy"` + PackageLength int `json:"package_length"` + PackageWidth int `json:"package_width"` + PackageHeight int `json:"package_height"` + PackageWeight string `json:"package_weight"` + ProductCertifications []ProductCertification `json:"product_certifications"` + IsCodOpen bool `json:"is_cod_open"` + Skus []SKU `json:"skus"` +} + +type StockInfo struct { + WarehouseID string `json:"warehouse_id"` + AvailableStock int `json:"available_stock"` +} + +type EditProductRequest struct { + ProductID string `json:"product_id"` + ProductName string `json:"product_name"` + Description string `json:"description"` + CategoryID string `json:"category_id"` + Images []Image `json:"images"` + PackageWeight string `json:"package_weight"` + IsCodOpen bool `json:"is_cod_open"` + Skus []SKU `json:"skus"` + BrandID string `json:"brand_id"` + WarrantyPeriod int `json:"warranty_period"` + WarrantyPolicy string `json:"warranty_policy"` + PackageLength int `json:"package_length"` + PackageWidth int `json:"package_width"` + PackageHeight int `json:"package_height"` + ProductCertifications []ProductCertificationRequest `json:"product_certifications"` +} + +type ProductSearchRequest struct { + PageSize int `json:"page_size"` + PageNumber int `json:"page_number"` + SearchStatus int `json:"search_status"` + SellerSkuList []string `json:"seller_sku_list"` +} + +type UpdatePriceRequest struct { + ProductID string `json:"product_id"` + Skus []SKU `json:"skus"` +} + +type UpdateStockRequest struct { + ProductID string `json:"product_id"` + Skus []SKU `json:"skus"` +} diff --git a/response.go b/response.go index 3256b04..3215206 100644 --- a/response.go +++ b/response.go @@ -269,3 +269,36 @@ type FileInfo struct { FileName string `json:"file_name"` FileType string `json:"file_type"` } + +type SalesAttribute struct { + AttributeID string `json:"attribute_id"` + ValueID string `json:"value_id"` +} + +type ProductSKU struct { + ID string `json:"id"` + SellerSku string `json:"seller_sku"` + SalesAttributes []SalesAttribute `json:"sales_attributes"` +} + +type Product struct { + ProductID string `json:"product_id"` + Skus []ProductSKU `json:"skus"` +} + +type ProductSearchList struct{} + +type UpdatePriceFailedSKU struct { + FailedSKUIDs []string `json:"failed_sku_ids"` +} + +type FailedSKUStock struct { + ID string `json:"id"` + FailedWarehouseIDs []string `json:"failed_warehouse_ids"` +} + +type UpdateStockFailedSKU struct { + FailedSKUs []FailedSKUStock `json:"failed_skus"` +} + +type FailedProductIDs struct{} diff --git a/testdata/product/activate_product.json b/testdata/product/activate_product.json new file mode 100644 index 0000000..c9daf55 --- /dev/null +++ b/testdata/product/activate_product.json @@ -0,0 +1,52 @@ +[ + { + "name": "Official example", + "args": { + "app_key": "12abcd", + "app_secret": "123", + "access_token": "abc1c123-3128-aa17-125e-2d2d51ab814fa", + "shop_id": "123456", + "product_id": [ + "12312312123", + "123123123", + "123123123", + "1231231231" + ] + }, + "request": { + "method": "POST", + "url": "https://open-api.tiktokglobalshop.com/api/products/activate", + "headers": {}, + "query": "access_token=abc1c123-3128-aa17-125e-2d2d51ab814fa&app_key=12abcd&shop_id=123456&sign=d84e77bfa3701c3c3e4d37ad21b5b429478825b7e8af596cae74f2801859e2ea×tamp=1600000000", + "body": { + "product_ids": [ + "12312312123", + "123123123", + "123123123", + "1231231231" + ] + } + }, + "response": { + "status": 200, + "body": { + "code": 0, + "message": "Success", + "request_id": "123123123", + "data": { + "failed_product_ids": [ + "123123123", + "123123123" + ] + } + } + }, + "want": { + "failed_product_ids": [ + "123123123", + "123123123" + ] + }, + "want_err": false + } +] \ No newline at end of file diff --git a/testdata/product/create_product.json b/testdata/product/create_product.json new file mode 100644 index 0000000..65866be --- /dev/null +++ b/testdata/product/create_product.json @@ -0,0 +1,249 @@ +[ + { + "name": "Official example", + "args": { + "app_key": "12abcd", + "app_secret": "123", + "access_token": "abc1c123-3128-aa17-125e-2d2d51ab814fa", + "shop_id": "123456", + "request_json": { + "product_name": "aaaaaa", + "description": "", + "category_id": "111111", + "brand_id": "2222222", + "images": [ + { + "id": "fadfasdf" + }, + { + "id": "asdfasdfasdf" + }, + { + "id": "asdfasdf" + }, + { + "id": "asdfasdf" + }, + { + "id": "asdfasdf" + } + ], + "warranty_period": 1, + "warranty_policy": "N/A", + "package_length": 10, + "package_width": 10, + "package_height": 10, + "package_weight": "20", + "product_certifications": [ + { + "id": "1111111", + "files": [ + { + "id": "dfgsdfgsdfgsdfg", + "name": "aaaa.pdf", + "type": "PDF" + }, + { + "id": "asdfasdfasdfasdf", + "name": "bbb.pdf", + "type": "PDF" + } + ], + "images": [ + { + "id": "sdafasdfasdfasdfsd" + } + ] + } + ], + "is_cod_open": false, + "skus": [ + { + "sales_attributes": [ + { + "attribute_id": "11111", + "custom_value": "yellow" + }, + { + "attribute_id": "222222", + "custom_value": "small", + "sku_img": { + "id": "asdfasdfasdfasdf" + } + } + ], + "stock_infos": [ + { + "warehouse_id": "3333333", + "available_stock": 200 + } + ], + "seller_sku": "test1", + "original_price": "100" + }, + { + "sales_attributes": [ + { + "attribute_id": "100000", + "custom_value": "red" + }, + { + "attribute_id": "100007", + "custom_value": "big", + "sku_img": { + "id": "asdfasdfasdfasdfsda" + } + } + ], + "stock_infos": [ + { + "warehouse_id": "4444444", + "available_stock": 0 + } + ], + "seller_sku": "test2", + "original_price": "100" + } + ] + } + }, + "request": { + "method": "POST", + "url": "https://open-api.tiktokglobalshop.com/api/products", + "headers": {}, + "query": "access_token=abc1c123-3128-aa17-125e-2d2d51ab814fa&app_key=12abcd&shop_id=123456&sign=0bd65a4d92ab3328d0237c1714eb8664616efc55dfb41a7223f48517eb2e1358×tamp=1600000000", + "body": { + "product_name": "aaaaaa", + "description": "", + "category_id": "111111", + "brand_id": "2222222", + "images": [ + { + "id": "fadfasdf" + }, + { + "id": "asdfasdfasdf" + }, + { + "id": "asdfasdf" + }, + { + "id": "asdfasdf" + }, + { + "id": "asdfasdf" + } + ], + "warranty_period": 1, + "warranty_policy": "N/A", + "package_length": 10, + "package_width": 10, + "package_height": 10, + "package_weight": "20", + "product_certifications": [ + { + "id": "1111111", + "files": [ + { + "id": "dfgsdfgsdfgsdfg", + "name": "aaaa.pdf", + "type": "PDF" + }, + { + "id": "asdfasdfasdfasdf", + "name": "bbb.pdf", + "type": "PDF" + } + ], + "images": [ + { + "id": "sdafasdfasdfasdfsd" + } + ] + } + ], + "is_cod_open": false, + "skus": [ + { + "sales_attributes": [ + { + "attribute_id": "11111", + "custom_value": "yellow" + }, + { + "attribute_id": "222222", + "custom_value": "small", + "sku_img": { + "id": "asdfasdfasdfasdf" + } + } + ], + "stock_infos": [ + { + "warehouse_id": "3333333", + "available_stock": 200 + } + ], + "seller_sku": "test1", + "original_price": "100" + }, + { + "sales_attributes": [ + { + "attribute_id": "100000", + "custom_value": "red" + }, + { + "attribute_id": "100007", + "custom_value": "big", + "sku_img": { + "id": "asdfasdfasdfasdfsda" + } + } + ], + "stock_infos": [ + { + "warehouse_id": "4444444", + "available_stock": 0 + } + ], + "seller_sku": "test2", + "original_price": "100" + } + ] + } + }, + "response": { + "status": 200, + "body": { + "id": "2222332323", + "seller_sku": "bbbbb", + "sales_attributes": [ + { + "attribute_id": "33333", + "value_id": "123124123123" + }, + { + "attribute_id": "44444", + "value_id": "1312312312412" + } + ] + } + }, + "want": { + "id": "2222332323", + "seller_sku": "bbbbb", + "sales_attributes": [ + { + "attribute_id": "33333", + "value_id": "123124123123" + }, + { + "attribute_id": "44444", + "value_id": "1312312312412" + } + ] + }, + "want_err": false + } +] \ No newline at end of file diff --git a/testdata/product/deactivate_products.json b/testdata/product/deactivate_products.json new file mode 100644 index 0000000..2246111 --- /dev/null +++ b/testdata/product/deactivate_products.json @@ -0,0 +1,52 @@ +[ + { + "name": "Official example", + "args": { + "app_key": "12abcd", + "app_secret": "123", + "access_token": "abc1c123-3128-aa17-125e-2d2d51ab814fa", + "shop_id": "123456", + "product_id": [ + "123123123123", + "123123123", + "123123123", + "1231231231" + ] + }, + "request": { + "method": "POST", + "url": "https://open-api.tiktokglobalshop.com/api/products/inactivated_products", + "headers": {}, + "query": "access_token=abc1c123-3128-aa17-125e-2d2d51ab814fa&app_key=12abcd&shop_id=123456&sign=56d278da67aca36b5efe1bc7c4d164be0f9e2b8b6312d1af41911ab55b5c46f8×tamp=1600000000", + "body": { + "product_ids": [ + "123123123123", + "123123123", + "123123123", + "1231231231" + ] + } + }, + "response": { + "status": 200, + "body": { + "code": 0, + "message": "Success", + "request_id": "123123123", + "data": { + "failed_product_ids": [ + "123123123", + "12312312312" + ] + } + } + }, + "want": { + "failed_product_ids": [ + "123123123", + "12312312312" + ] + }, + "want_err": false + } +] \ No newline at end of file diff --git a/testdata/product/delete_products.json b/testdata/product/delete_products.json new file mode 100644 index 0000000..6b20c99 --- /dev/null +++ b/testdata/product/delete_products.json @@ -0,0 +1,52 @@ +[ + { + "name": "Official example", + "args": { + "app_key": "12abcd", + "app_secret": "123", + "access_token": "abc1c123-3128-aa17-125e-2d2d51ab814fa", + "shop_id": "123456", + "product_id": [ + "12312312123", + "123123123", + "123123123", + "1231231231" + ] + }, + "request": { + "method": "DELETE", + "url": "https://open-api.tiktokglobalshop.com/api/products", + "headers": {}, + "query": "access_token=abc1c123-3128-aa17-125e-2d2d51ab814fa&app_key=12abcd&shop_id=123456&sign=0bd65a4d92ab3328d0237c1714eb8664616efc55dfb41a7223f48517eb2e1358×tamp=1600000000", + "body": { + "product_ids": [ + "12312312123", + "123123123", + "123123123", + "1231231231" + ] + } + }, + "response": { + "status": 200, + "body": { + "code": 0, + "message": "Success", + "request_id": "123123123", + "data": { + "failed_product_ids": [ + "123123123", + "123123123" + ] + } + } + }, + "want": { + "failed_product_ids": [ + "123123123", + "123123123" + ] + }, + "want_err": false + } +] \ No newline at end of file diff --git a/testdata/product/edit_product.json b/testdata/product/edit_product.json new file mode 100644 index 0000000..e69de29 diff --git a/testdata/product/get_product_detail.json b/testdata/product/get_product_detail.json new file mode 100644 index 0000000..f4b515f --- /dev/null +++ b/testdata/product/get_product_detail.json @@ -0,0 +1,431 @@ +[ + { + "name": "Official example", + "args": { + "app_key": "12abcd", + "app_secret": "123", + "access_token": "abc1c123-3128-aa17-125e-2d2d51ab814fa", + "shop_id": "123456", + "product_id": "123123123123123" + }, + "request": { + "method": "GET", + "url": "https://open-api.tiktokglobalshop.com/api/products/details", + "headers": {}, + "query": "access_token=abc1c123-3128-aa17-125e-2d2d51ab814fa&app_key=12abcd&product_id=123123123123123&shop_id=123456&sign=3c859ee8285fde6816f31aff7cca15dd75e6cc9061d6bb32c22ccf8429c931f8×tamp=1600000000" + }, + "response": { + "status": 200, + "body": { + "code": 0, + "message": "Success", + "request_id": "123123123", + "data": { + "product_id": "123123123", + "product_status": 4, + "product_name": "aaaa", + "category_list": [ + { + "id": "123123", + "parent_id": "0", + "local_display_name": "aaaa", + "is_leaf": false + }, + { + "id": "123123123", + "parent_id": "1111", + "local_display_name": "bbb", + "is_leaf": false + }, + { + "id": "123123123", + "parent_id": "2222", + "local_display_name": "cccc", + "is_leaf": false + }, + { + "id": "123123123", + "parent_id": "3333", + "local_display_name": "ddddd", + "is_leaf": true + } + ], + "brand": { + "id": "123123123", + "name": "aaa", + "status": 1 + }, + "images": [ + { + "height": 600, + "width": 600, + "thumb_url_list": [ + "https://sadfasdfasdfasdfasdf", + "https://sadfasdfasdfadsf" + ], + "id": "asdfasdfasdf", + "url_list": [ + "https://asdfasdfasdf", + "https://afsdfasdfasdf" + ] + }, + { + "height": 720, + "width": 720, + "thumb_url_list": [ + "https://asdfasdfasdfasdfas", + "https://asdfasfasdfasfasdf" + ], + "id": "afsdfasdfasdf", + "url_list": [ + "https://asdfasdfasdfas", + "https://fasdfasdfasdf" + ] + }, + { + "height": 1080, + "width": 1080, + "thumb_url_list": [ + "https://asdfasdfasdf", + "https://asdfasdfasdf" + ], + "id": "asdfasdfasdf", + "url_list": [ + "https://asdfasfdasdfasf", + "https://asdfasdfasdfsadf" + ] + } + ], + "description": "aaaa", + "warranty_period": { + "warranty_id": 2, + "warranty_description": "2 months" + }, + "warranty_policy": "warranty policy test", + "package_length": 30, + "package_width": 20, + "package_height": 30, + "package_weight": "5", + "skus": [ + { + "id": "123123123", + "seller_sku": "aaaa", + "price": { + "original_price": "1200", + "currency": "IDR" + }, + "stock_infos": [ + { + "warehouse_id": "123123123", + "available_stock": 160 + } + ], + "sales_attributes": [ + { + "id": "123123123", + "name": "aaa", + "value_id": "123123123", + "value_name": "red", + "sku_img": { + "height": 600, + "width": 600, + "thumb_url_list": [ + "https://asdfasdfasdfasdfa", + "https://asdfasdfasdfasdf" + ], + "id": "asdfasdfasdfasdf", + "url_list": [ + "https://asdfasdfasdfasf", + "https://asdfasdfasdfasdf" + ] + } + } + ] + }, + { + "id": "123123123", + "seller_sku": "bbbb", + "price": { + "original_price": "1600", + "currency": "IDR" + }, + "stock_infos": [ + { + "warehouse_id": "123123123", + "available_stock": 3000 + } + ], + "sales_attributes": [ + { + "id": "123123123", + "name": "aaaa", + "value_id": "123123123", + "value_name": "black", + "sku_img": { + "height": 1080, + "width": 1080, + "thumb_url_list": [ + "https://sadfasdfasdfasdfa", + "https://asdfasfasdfasdf" + ], + "id": "asdfasdfasdfasdf", + "url_list": [ + "https://asdfasdfasdf?", + "https://asdfasdfasdfasdf" + ] + } + } + ] + } + ], + "product_certifications": [ + { + "id": "123123123", + "title": "aaaaa", + "images": [ + { + "height": 1080, + "width": 1080, + "thumb_url_list": [ + "https://asdfasdfasdfasd", + "https://asdfasdfasdfasd" + ], + "id": "asdfasdfasdfasdfasdf", + "url_list": [ + "https://afsdfasdfasdfasdf", + "https://asdfasdfasdfasdfsdaf" + ] + } + ], + "files": [ + { + "id": "asdfadsfasdfasd", + "list": [ + "http://asdfasdfasdf", + "http://asdfasdfasdfa" + ], + "name": "test1.pdf", + "type": "PDF" + }, + { + "id": "asdfasdfasdfasdf", + "list": [ + "http://asdfasdfasdfasdf", + "http://fasdfasdfasdfasdf" + ], + "name": "test2.pdf", + "type": "PDF" + } + ] + } + ], + "is_cod_open": false, + "update_time": 1111111111 + } + } + }, + "want": { + "product_id": "123123123", + "product_status": 4, + "product_name": "aaaa", + "category_list": [ + { + "id": "123123", + "parent_id": "0", + "local_display_name": "aaaa", + "is_leaf": false + }, + { + "id": "123123123", + "parent_id": "1111", + "local_display_name": "bbb", + "is_leaf": false + }, + { + "id": "123123123", + "parent_id": "2222", + "local_display_name": "cccc", + "is_leaf": false + }, + { + "id": "123123123", + "parent_id": "3333", + "local_display_name": "ddddd", + "is_leaf": true + } + ], + "brand": { + "id": "123123123", + "name": "aaa", + "status": 1 + }, + "images": [ + { + "height": 600, + "width": 600, + "thumb_url_list": [ + "https://sadfasdfasdfasdfasdf", + "https://sadfasdfasdfadsf" + ], + "id": "asdfasdfasdf", + "url_list": [ + "https://asdfasdfasdf", + "https://afsdfasdfasdf" + ] + }, + { + "height": 720, + "width": 720, + "thumb_url_list": [ + "https://asdfasdfasdfasdfas", + "https://asdfasfasdfasfasdf" + ], + "id": "afsdfasdfasdf", + "url_list": [ + "https://asdfasdfasdfas", + "https://fasdfasdfasdf" + ] + }, + { + "height": 1080, + "width": 1080, + "thumb_url_list": [ + "https://asdfasdfasdf", + "https://asdfasdfasdf" + ], + "id": "asdfasdfasdf", + "url_list": [ + "https://asdfasfdasdfasf", + "https://asdfasdfasdfsadf" + ] + } + ], + "description": "aaaa", + "warranty_period": { + "warranty_id": 2, + "warranty_description": "2 months" + }, + "warranty_policy": "warranty policy test", + "package_length": 30, + "package_width": 20, + "package_height": 30, + "package_weight": "5", + "skus": [ + { + "id": "123123123", + "seller_sku": "aaaa", + "price": { + "original_price": "1200", + "currency": "IDR" + }, + "stock_infos": [ + { + "warehouse_id": "123123123", + "available_stock": 160 + } + ], + "sales_attributes": [ + { + "id": "123123123", + "name": "aaa", + "value_id": "123123123", + "value_name": "red", + "sku_img": { + "height": 600, + "width": 600, + "thumb_url_list": [ + "https://asdfasdfasdfasdfa", + "https://asdfasdfasdfasdf" + ], + "id": "asdfasdfasdfasdf", + "url_list": [ + "https://asdfasdfasdfasf", + "https://asdfasdfasdfasdf" + ] + } + } + ] + }, + { + "id": "123123123", + "seller_sku": "bbbb", + "price": { + "original_price": "1600", + "currency": "IDR" + }, + "stock_infos": [ + { + "warehouse_id": "123123123", + "available_stock": 3000 + } + ], + "sales_attributes": [ + { + "id": "123123123", + "name": "aaaa", + "value_id": "123123123", + "value_name": "black", + "sku_img": { + "height": 1080, + "width": 1080, + "thumb_url_list": [ + "https://sadfasdfasdfasdfa", + "https://asdfasfasdfasdf" + ], + "id": "asdfasdfasdfasdf", + "url_list": [ + "https://asdfasdfasdf?", + "https://asdfasdfasdfasdf" + ] + } + } + ] + } + ], + "product_certifications": [ + { + "id": "123123123", + "title": "aaaaa", + "images": [ + { + "height": 1080, + "width": 1080, + "thumb_url_list": [ + "https://asdfasdfasdfasd", + "https://asdfasdfasdfasd" + ], + "id": "asdfasdfasdfasdfasdf", + "url_list": [ + "https://afsdfasdfasdfasdf", + "https://asdfasdfasdfasdfsdaf" + ] + } + ], + "files": [ + { + "id": "asdfadsfasdfasd", + "list": [ + "http://asdfasdfasdf", + "http://asdfasdfasdfa" + ], + "name": "test1.pdf", + "type": "PDF" + }, + { + "id": "asdfasdfasdfasdf", + "list": [ + "http://asdfasdfasdfasdf", + "http://fasdfasdfasdfasdf" + ], + "name": "test2.pdf", + "type": "PDF" + } + ] + } + ], + "is_cod_open": false, + "update_time": 1111111111 + }, + "want_err": false + } +] \ No newline at end of file diff --git a/testdata/product/get_product_list.json b/testdata/product/get_product_list.json new file mode 100644 index 0000000..1853a50 --- /dev/null +++ b/testdata/product/get_product_list.json @@ -0,0 +1,204 @@ +[ + { + "name": "Official example", + "args": { + "app_key": "12abcd", + "app_secret": "123", + "access_token": "abc1c123-3128-aa17-125e-2d2d51ab814fa", + "shop_id": "123456", + "request_json": { + "page_size": 50, + "page_number": 1, + "search_status": 0, + "seller_sku_list": [ + "aaaa" + ] + } + }, + "request": { + "method": "POST", + "url": "https://open-api.tiktokglobalshop.com/api/products/search", + "headers": {}, + "query": "access_token=abc1c123-3128-aa17-125e-2d2d51ab814fa&app_key=12abcd&shop_id=123456&sign=daefe57b6b9447f2beb621e7277632e9f14c06537a96fed5444f194e61b86e17×tamp=1600000000", + "body": { + "page_size": 50, + "page_number": 1, + "search_status": 0, + "seller_sku_list": [ + "aaaa" + ] + } + }, + "response": { + "status": 200, + "body": { + "code": 0, + "message": "Success", + "request_id": "123123123", + "data": { + "total": 15677, + "products": [ + { + "id": "123123123", + "name": "aaa", + "status": 4, + "sale_regions": [ + "ID" + ], + "skus": [ + { + "id": "123123123123", + "seller_sku": "bbbb", + "price": { + "original_price": "1200", + "currency": "IDR" + }, + "stock_infos": [ + { + "warehouse_id": "123123123123123", + "available_stock": 161 + } + ] + }, + { + "id": "12312312312", + "seller_sku": "bbbb", + "price": { + "original_price": "1600", + "currency": "IDR" + }, + "stock_infos": [ + { + "warehouse_id": "123123123123", + "available_stock": 200 + } + ] + } + ] + }, + { + "id": "1231231231", + "name": "aaa", + "status": 3, + "sale_regions": [ + "ID" + ], + "skus": [ + { + "id": "123123123123", + "seller_sku": "bbbb", + "price": { + "original_price": "1000", + "currency": "IDR" + }, + "stock_infos": [ + { + "warehouse_id": "123123123123", + "available_stock": 150 + } + ] + }, + { + "id": "12312312312312", + "seller_sku": "bbbb", + "price": { + "original_price": "1500", + "currency": "IDR" + }, + "stock_infos": [ + { + "warehouse_id": "123123123123", + "available_stock": 3000 + } + ] + } + ] + } + ] + } + } + }, + "want": { + "total": 15677, + "products": [ + { + "id": "123123123", + "name": "aaa", + "status": 4, + "sale_regions": [ + "ID" + ], + "skus": [ + { + "id": "123123123123", + "seller_sku": "bbbb", + "price": { + "original_price": "1200", + "currency": "IDR" + }, + "stock_infos": [ + { + "warehouse_id": "123123123123123", + "available_stock": 161 + } + ] + }, + { + "id": "12312312312", + "seller_sku": "bbbb", + "price": { + "original_price": "1600", + "currency": "IDR" + }, + "stock_infos": [ + { + "warehouse_id": "123123123123", + "available_stock": 200 + } + ] + } + ] + }, + { + "id": "1231231231", + "name": "aaa", + "status": 3, + "sale_regions": [ + "ID" + ], + "skus": [ + { + "id": "123123123123", + "seller_sku": "bbbb", + "price": { + "original_price": "1000", + "currency": "IDR" + }, + "stock_infos": [ + { + "warehouse_id": "123123123123", + "available_stock": 150 + } + ] + }, + { + "id": "12312312312312", + "seller_sku": "bbbb", + "price": { + "original_price": "1500", + "currency": "IDR" + }, + "stock_infos": [ + { + "warehouse_id": "123123123123", + "available_stock": 3000 + } + ] + } + ] + } + ] + }, + "want_err": false + } +] \ No newline at end of file diff --git a/testdata/product/recover_product.json b/testdata/product/recover_product.json new file mode 100644 index 0000000..b2f502d --- /dev/null +++ b/testdata/product/recover_product.json @@ -0,0 +1,46 @@ +[ + { + "name": "Official example", + "args": { + "app_key": "12abcd", + "app_secret": "123", + "access_token": "abc1c123-3128-aa17-125e-2d2d51ab814fa", + "shop_id": "123456", + "product_id": [ + "1729385838007780943", + "1729382606675281112" + ] + }, + "request": { + "method": "POST", + "url": "https://open-api.tiktokglobalshop.com/api/products/recover", + "headers": {}, + "query": "access_token=abc1c123-3128-aa17-125e-2d2d51ab814fa&app_key=12abcd&sign=f8c08b8f3e42be12251435fa9d9e137000b957fbe8be3cb6e6b83bb2225e2bc9×tamp=1600000000", + "body": { + "product_ids": [ + "1729385838007780943", + "1729382606675281112" + ] + } + }, + "response": { + "status": 200, + "body": { + "code": 0, + "message": "Success", + "request_id": "123123123", + "data": { + "failed_product_ids": [ + "1729385838007780943" + ] + } + } + }, + "want": { + "failed_product_ids": [ + "1729385838007780943" + ] + }, + "want_err": false + } +] \ No newline at end of file diff --git a/testdata/product/update_price.json b/testdata/product/update_price.json new file mode 100644 index 0000000..17d9b7a --- /dev/null +++ b/testdata/product/update_price.json @@ -0,0 +1,59 @@ +[ + { + "name": "Official example", + "args": { + "app_key": "12abcd", + "app_secret": "123", + "access_token": "abc1c123-3128-aa17-125e-2d2d51ab814fa", + "shop_id": "123456", + "request_json": { + "product_id": "123123123", + "skus": [ + { + "id": "123123123", + "original_price": "1200" + }, + { + "id": "12312312312", + "original_price": "1600" + } + ] + } + }, + "request": { + "method": "PUT", + "url": "https://open-api.tiktokglobalshop.com/api/products/prices", + "headers": {}, + "query": "access_token=abc1c123-3128-aa17-125e-2d2d51ab814fa&app_key=12abcd&shop_id=123456&sign=2ef96993a61f2efd39ff041bd0be3a09bd669c271ccd2076445da196b0ced32c×tamp=1600000000", + "body": { + "product_id": "123123123", + "skus": [ + { + "id": "123123123", + "original_price": "1200" + }, + { + "id": "12312312312", + "original_price": "1600" + } + ] + } + }, + "response": { + "status": 200, + "body": { + "code": 0, + "message": "Success", + "request_id": "123123123", + "data": { + "code": 0, + "message": "Success", + "request_id": "111123123123", + "data": [] + } + } + }, + "want": {}, + "want_err": false + } +] \ No newline at end of file diff --git a/testdata/product/update_stock.json b/testdata/product/update_stock.json new file mode 100644 index 0000000..39149dc --- /dev/null +++ b/testdata/product/update_stock.json @@ -0,0 +1,74 @@ +[ + { + "name": "Official example", + "args": { + "app_key": "12abcd", + "app_secret": "123", + "access_token": "abc1c123-3128-aa17-125e-2d2d51ab814fa", + "shop_id": "123456", + "request_json": { + "product_id": "123123123", + "skus": [ + { + "id": "123123123", + "stock_infos": [ + { + "available_stock": 50, + "warehouse_id": "123123123123" + } + ] + }, + { + "id": "123123123", + "stock_infos": [ + { + "available_stock": 60, + "warehouse_id": "123123123" + } + ] + } + ] + } + }, + "request": { + "method": "PUT", + "url": "https://open-api.tiktokglobalshop.com/api/products/stocks", + "headers": {}, + "query": "access_token=abc1c123-3128-aa17-125e-2d2d51ab814fa&app_key=12abcd&shop_id=123456&sign=44673d90649d50f4eab6d76d2cc172e5f672e5d6ceb3cf088d8a1d1d2602cb82×tamp=1600000000", + "body": { + "product_id": "123123123", + "skus": [ + { + "id": "123123123", + "stock_infos": [ + { + "available_stock": 50, + "warehouse_id": "123123123123" + } + ] + }, + { + "id": "123123123", + "stock_infos": [ + { + "available_stock": 60, + "warehouse_id": "123123123" + } + ] + } + ] + } + }, + "response": { + "status": 200, + "body": { + "code": 0, + "message": "Success", + "request_id": "12312312312", + "data": {} + } + }, + "want": {}, + "want_err": false + } +] \ No newline at end of file