package handlers import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" ) func doReq(t *testing.T, h http.HandlerFunc, method, path string, body any) *httptest.ResponseRecorder { t.Helper() var b []byte if body != nil { b, _ = json.Marshal(body) } req := httptest.NewRequest(method, path, bytes.NewReader(b)) req.Header.Set("Content-Type", "application/json") w := httptest.NewRecorder() h(w, req) return w } func assertStatus(t *testing.T, w *httptest.ResponseRecorder, expected int) { t.Helper() if w.Code != expected { t.Errorf("expected status %d, got %d: %s", expected, w.Code, w.Body.String()) } } func assertJSON(t *testing.T, w *httptest.ResponseRecorder, key string, expected any) { t.Helper() var m map[string]any json.Unmarshal(w.Body.Bytes(), &m) if m[key] == nil { t.Errorf("expected key %q in response, got %s", key, w.Body.String()) } } func TestProductStocks(t *testing.T) { w := doReq(t, ProductStocks, "POST", "/v3/product/info/stocks", map[string]any{ "filter": map[string]any{}, }) assertStatus(t, w, 200) assertJSON(t, w, "items", nil) w = doReq(t, ProductStocks, "GET", "/v3/product/info/stocks", nil) assertStatus(t, w, 405) } func TestProductStocks_FilterOfferID(t *testing.T) { w := doReq(t, ProductStocks, "POST", "/v3/product/info/stocks", map[string]any{ "filter": map[string]any{"offer_id": []string{"TG-0001"}}, }) assertStatus(t, w, 200) var resp struct { Items []StockItem `json:"items"` Total int `json:"total"` } json.NewDecoder(w.Body).Decode(&resp) if resp.Total != 1 || resp.Items[0].OfferID != "TG-0001" { t.Errorf("filter by offer_id: total=%d item=%v", resp.Total, resp.Items) } } func TestProductStocks_Limit(t *testing.T) { w := doReq(t, ProductStocks, "POST", "/v3/product/info/stocks", map[string]any{ "limit": 2, }) assertStatus(t, w, 200) var resp struct { Items []StockItem `json:"items"` } json.NewDecoder(w.Body).Decode(&resp) if len(resp.Items) != 2 { t.Errorf("limit: expected 2, got %d", len(resp.Items)) } } func TestProductInfoList(t *testing.T) { w := doReq(t, ProductInfoList, "POST", "/v3/product/info/list", map[string]any{ "filter": map[string]any{}, }) assertStatus(t, w, 200) assertJSON(t, w, "items", nil) } func TestProductInfoList_FilterProductID(t *testing.T) { w := doReq(t, ProductInfoList, "POST", "/v3/product/info/list", map[string]any{ "filter": map[string]any{"product_id": []int64{99000000001}}, }) assertStatus(t, w, 200) var resp struct { Items []ProductInfoItem `json:"items"` } json.NewDecoder(w.Body).Decode(&resp) if len(resp.Items) != 1 || resp.Items[0].ProductID != 99000000001 { t.Errorf("filter by product_id: got %v", resp.Items) } } func TestProductList(t *testing.T) { w := doReq(t, ProductList, "GET", "/v1/product/list", nil) assertStatus(t, w, 200) assertJSON(t, w, "total", nil) w = doReq(t, ProductList, "POST", "/v1/product/list", nil) assertStatus(t, w, 405) } func TestImportPrices(t *testing.T) { w := doReq(t, ImportPrices, "POST", "/v1/product/import/prices", map[string]any{ "items": []map[string]any{ {"offer_id": "SKU-1", "price": 100.0}, }, }) assertStatus(t, w, 200) assertJSON(t, w, "task_id", nil) w = doReq(t, ImportPrices, "POST", "/v1/product/import/prices", map[string]any{"items": []map[string]any{}}) assertStatus(t, w, 400) w = doReq(t, ImportPrices, "POST", "/v1/product/import/prices", map[string]any{ "items": []map[string]any{{"offer_id": "", "price": 100}}, }) assertStatus(t, w, 400) } func TestProductPrices(t *testing.T) { w := doReq(t, ProductPrices, "POST", "/v4/product/info/prices", map[string]any{ "filter": map[string]any{}, }) assertStatus(t, w, 200) assertJSON(t, w, "total", nil) } func TestProductPrices_FilterOfferID(t *testing.T) { w := doReq(t, ProductPrices, "POST", "/v4/product/info/prices", map[string]any{ "filter": map[string]any{"offer_id": []string{"TG-0001"}}, }) assertStatus(t, w, 200) var resp struct { Items []ProductPriceItem `json:"items"` } json.NewDecoder(w.Body).Decode(&resp) if len(resp.Items) != 1 || resp.Items[0].OfferID != "TG-0001" { t.Errorf("filter by offer_id: got %v", resp.Items) } } func TestImportProducts(t *testing.T) { w := doReq(t, ImportProducts, "POST", "/v3/product/import", map[string]any{ "items": []map[string]any{ {"offer_id": "TEST-001", "name": "Test Product", "category_id": 17000000}, }, }) assertStatus(t, w, 200) var resp ImportProductsResponse json.NewDecoder(w.Body).Decode(&resp) if resp.Result.TaskID != 990001 { t.Errorf("expected task_id 990001, got %d", resp.Result.TaskID) } w = doReq(t, ImportProducts, "POST", "/v3/product/import", map[string]any{ "items": []map[string]any{}, }) assertStatus(t, w, 400) w = doReq(t, ImportProducts, "POST", "/v3/product/import", map[string]any{ "items": []map[string]any{{"offer_id": ""}}, }) assertStatus(t, w, 400) } func TestProductDescriptions(t *testing.T) { w := doReq(t, ProductDescriptions, "POST", "/v1/product/info/description", map[string]any{ "filter": map[string]any{"offer_id": []string{"TG-0001"}}, }) assertStatus(t, w, 200) var resp ProductDescriptionResponse json.NewDecoder(w.Body).Decode(&resp) if len(resp.Result) != 1 || resp.Result[0].OfferID != "TG-0001" { t.Errorf("expected 1 result for TG-0001, got %+v", resp.Result) } if resp.Result[0].State != "moderated" { t.Errorf("expected state moderated, got %s", resp.Result[0].State) } } func TestCategoryTree(t *testing.T) { w := doReq(t, CategoryTree, "POST", "/v2/category/tree", map[string]any{"language": "RU"}) assertStatus(t, w, 200) var resp CategoryTreeResponse json.NewDecoder(w.Body).Decode(&resp) if len(resp.Result) != 3 { t.Errorf("expected 3 root categories, got %d", len(resp.Result)) } if resp.Result[0].Title != "Электроника" { t.Errorf("expected Электроника, got %s", resp.Result[0].Title) } } func TestCategoryAttributes(t *testing.T) { w := doReq(t, CategoryAttributes, "POST", "/v3/category/attribute", map[string]any{ "category_id": []int64{17036136}, }) assertStatus(t, w, 200) var resp CategoryAttributesResponse json.NewDecoder(w.Body).Decode(&resp) if len(resp.Result) != 6 { t.Errorf("expected 6 attributes, got %d", len(resp.Result)) } } func TestCategoryAttributeValues(t *testing.T) { w := doReq(t, CategoryAttributeValues, "POST", "/v3/category/attribute/values", map[string]any{ "attribute_id": 85, "category_id": 17036136, }) assertStatus(t, w, 200) var resp CategoryAttributeValuesResponse json.NewDecoder(w.Body).Decode(&resp) if len(resp.Result) != 4 { t.Errorf("expected 4 values, got %d", len(resp.Result)) } } func TestPostingFBSList(t *testing.T) { w := doReq(t, PostingFBSList, "POST", "/v2/posting/fbs/list", map[string]any{}) assertStatus(t, w, 200) assertJSON(t, w, "postings", nil) } func TestPostingFBSList_FilterStatus(t *testing.T) { w := doReq(t, PostingFBSList, "POST", "/v2/posting/fbs/list", map[string]any{ "filter": map[string]any{"status": "delivered"}, }) assertStatus(t, w, 200) var resp struct { Postings []Posting `json:"postings"` } json.NewDecoder(w.Body).Decode(&resp) for _, p := range resp.Postings { if p.Status != "delivered" { t.Errorf("filter status: expected delivered, got %s", p.Status) } } } func TestPostingFBSList_FilterSince(t *testing.T) { w := doReq(t, PostingFBSList, "POST", "/v2/posting/fbs/list", map[string]any{ "filter": map[string]any{"since": "2024-01-16T00:00:00Z"}, }) assertStatus(t, w, 200) var resp struct { Postings []Posting `json:"postings"` } json.NewDecoder(w.Body).Decode(&resp) if len(resp.Postings) < 3 { t.Errorf("filter since: expected at least 3 postings from 2024-01-16, got %d", len(resp.Postings)) } } func TestPostingFBSList_WithAnalytics(t *testing.T) { w := doReq(t, PostingFBSList, "POST", "/v2/posting/fbs/list", map[string]any{ "with": map[string]any{"analytics_data": true}, }) assertStatus(t, w, 200) var resp struct { Postings []Posting `json:"postings"` } json.NewDecoder(w.Body).Decode(&resp) if len(resp.Postings) > 0 && resp.Postings[0].AnalyticsData == nil { t.Errorf("expected analytics_data, got nil") } } func TestPostingFBSList_Asc(t *testing.T) { w := doReq(t, PostingFBSList, "POST", "/v2/posting/fbs/list", map[string]any{ "dir": "asc", }) assertStatus(t, w, 200) } func TestPostingFBSGet(t *testing.T) { w := doReq(t, PostingFBSGet, "POST", "/v2/posting/fbs/get", map[string]any{ "posting_number": "TG-POST-001", }) assertStatus(t, w, 200) assertJSON(t, w, "posting", nil) w = doReq(t, PostingFBSGet, "POST", "/v2/posting/fbs/get", map[string]any{ "posting_number": "NON-EXISTENT", }) assertStatus(t, w, 404) } func TestCancelPosting(t *testing.T) { w := doReq(t, CancelPosting, "POST", "/v1/posting/fbs/cancel", map[string]any{ "posting_number": "TG-POST-001", }) assertStatus(t, w, 200) w = doReq(t, CancelPosting, "POST", "/v1/posting/fbs/cancel", map[string]any{ "posting_number": "", }) assertStatus(t, w, 400) w = doReq(t, CancelPosting, "POST", "/v1/posting/fbs/cancel", map[string]any{ "posting_number": "NON-EXISTENT", }) assertStatus(t, w, 404) } func TestFinanceTransactionList(t *testing.T) { w := doReq(t, FinanceTransactionList, "POST", "/v1/finance/transaction/list", map[string]any{}) assertStatus(t, w, 200) assertJSON(t, w, "transactions", nil) } func TestFinanceTransactionList_FilterType(t *testing.T) { w := doReq(t, FinanceTransactionList, "POST", "/v1/finance/transaction/list", map[string]any{ "filter": map[string]any{"transaction_type": "commission"}, }) assertStatus(t, w, 200) var resp struct { Transactions []Transaction `json:"transactions"` } json.NewDecoder(w.Body).Decode(&resp) if len(resp.Transactions) < 1 || resp.Transactions[0].TransactionType != "commission" { t.Errorf("expected at least 1 commission transaction, got %d", len(resp.Transactions)) } } func TestFinancePayoutList(t *testing.T) { w := doReq(t, FinancePayoutList, "POST", "/v1/finance/payout/list", map[string]any{}) assertStatus(t, w, 200) assertJSON(t, w, "payouts", nil) } func TestFinancePayoutList_FilterSince(t *testing.T) { w := doReq(t, FinancePayoutList, "POST", "/v1/finance/payout/list", map[string]any{ "filter": map[string]any{"since": "2024-01-20T00:00:00Z"}, }) assertStatus(t, w, 200) var resp struct { Payouts []Payout `json:"payouts"` } json.NewDecoder(w.Body).Decode(&resp) if len(resp.Payouts) < 1 { t.Errorf("filter since: expected at least 1 payout, got %d", len(resp.Payouts)) } } func TestReturnsList(t *testing.T) { w := doReq(t, ReturnsList, "POST", "/v1/returns/list", map[string]any{}) assertStatus(t, w, 200) assertJSON(t, w, "result", nil) } func TestReturnsList_FilterStatus(t *testing.T) { w := doReq(t, ReturnsList, "POST", "/v1/returns/list", map[string]any{ "filter": map[string]any{"status": "pending"}, }) assertStatus(t, w, 200) var resp struct { Result []Return `json:"result"` } json.NewDecoder(w.Body).Decode(&resp) if len(resp.Result) < 1 || resp.Result[0].Status != "pending" { t.Errorf("expected at least 1 pending return, got %d", len(resp.Result)) } } func TestReturnsList_FilterReturnID(t *testing.T) { w := doReq(t, ReturnsList, "POST", "/v1/returns/list", map[string]any{ "filter": map[string]any{"return_id": 99000001}, }) assertStatus(t, w, 200) var resp struct { Result []Return `json:"result"` } json.NewDecoder(w.Body).Decode(&resp) if len(resp.Result) != 1 || resp.Result[0].ReturnID != 99000001 { t.Errorf("expected return 99000001, got %d results", len(resp.Result)) } } func TestAnalyticsData(t *testing.T) { w := doReq(t, AnalyticsData, "POST", "/v1/analytics/data", map[string]any{}) assertStatus(t, w, 200) assertJSON(t, w, "data", nil) } func TestTurnoverStocks(t *testing.T) { w := doReq(t, TurnoverStocks, "POST", "/v1/analytics/turnover/stocks", map[string]any{}) assertStatus(t, w, 200) assertJSON(t, w, "items", nil) } func TestProductAttributes(t *testing.T) { w := doReq(t, ProductAttributes, "POST", "/v1/product/info/attributes", map[string]any{ "filter": map[string]any{"offer_id": []string{"TG-0001"}}, }) assertStatus(t, w, 200) var resp ProductAttributesResponse json.NewDecoder(w.Body).Decode(&resp) if len(resp.Result) != 1 { t.Errorf("expected 1 result, got %d", len(resp.Result)) } }