Files
ozon-mock/internal/handlers/handlers_test.go
Rinat 50dc046369
Some checks failed
deploy / deploy (push) Failing after 2s
test / test (push) Failing after 1s
v2.0: massive test data (104 products, 41 postings, 20 returns, 20 payouts, 25 txs, 3 companies)
- Remove unused gin dependency from go.mod
- Remove credential logging from startup
- Remove dead parseOfferIDs function
- Add method check to GET /v1/product/list
- Complete all filter implementations (since, to, status, product_id, return_id, dir, analytics, page)
- CancelPosting now validates posting existence (404 for unknown)
- Implement MOCK_STOCKS_JSON file loading
- Add Phase 2 endpoints: v3/product/import, v1/product/info/description,
  v1/product/info/attributes, v2/category/tree, v3/category/attribute,
  v3/category/attribute/values
- Generate 104 products/stocks/prices across 3 companies:
  TechGiant (TG-0001..TG-0035), FashionLine (FL-0001..FL-0035), HomeStyle (HS-0001..HS-0034)
- 41 postings, 20 returns, 20 payouts, 25 transactions
- 31 tests (all PASS with race detector)
- Add Makefile
2026-07-17 14:38:53 +03:00

420 lines
12 KiB
Go

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))
}
}