Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,496 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/catalog"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/config"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/feeds"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// TestV1DomainResourceCRUD exercises public /api/v1 catalog+feed CRUD with
|
||||
// semi-real merchant data against a live DATABASE_URL (skips when unset).
|
||||
func TestV1DomainResourceCRUD(t *testing.T) {
|
||||
dsn := strings.TrimSpace(os.Getenv("DATABASE_URL"))
|
||||
if dsn == "" {
|
||||
t.Skip("DATABASE_URL not set")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
pg, err := pgxpool.New(ctx, dsn)
|
||||
if err != nil {
|
||||
t.Fatalf("postgres: %v", err)
|
||||
}
|
||||
defer pg.Close()
|
||||
|
||||
companyID := uuid.New()
|
||||
userID := uuid.New()
|
||||
prefix := companyID.String()[:8]
|
||||
_, err = pg.Exec(ctx, `INSERT INTO companies (id, name) VALUES ($1, $2)`,
|
||||
companyID, "merchant-crud-"+prefix)
|
||||
if err != nil {
|
||||
t.Fatalf("seed company: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_, _ = pg.Exec(context.Background(), `DELETE FROM companies WHERE id = $1`, companyID)
|
||||
})
|
||||
|
||||
s := &Server{
|
||||
Config: config.Config{WebOrigin: "http://localhost:5173"},
|
||||
Pool: pg,
|
||||
Catalog: &catalog.Service{Pool: pg},
|
||||
Feeds: &feeds.Service{Pool: pg},
|
||||
}
|
||||
h := mountV1DomainTestRouter(s)
|
||||
|
||||
withTenant := func(r *http.Request) *http.Request {
|
||||
c := context.WithValue(r.Context(), ctxCompanyID, companyID)
|
||||
c = context.WithValue(c, ctxUserID, userID)
|
||||
c = context.WithValue(c, ctxRole, "api")
|
||||
return r.WithContext(c)
|
||||
}
|
||||
do := func(method, path, body string) *httptest.ResponseRecorder {
|
||||
var req *http.Request
|
||||
if body == "" {
|
||||
req = httptest.NewRequest(method, path, nil)
|
||||
} else {
|
||||
req = httptest.NewRequest(method, path, strings.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
rec := httptest.NewRecorder()
|
||||
h.ServeHTTP(rec, withTenant(req))
|
||||
return rec
|
||||
}
|
||||
decode := func(t *testing.T, rec *httptest.ResponseRecorder) map[string]any {
|
||||
t.Helper()
|
||||
var out map[string]any
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
|
||||
t.Fatalf("json status=%d body=%s err=%v", rec.Code, rec.Body.String(), err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// --- Categories CRUD ---
|
||||
catUnique := "electronics-" + prefix
|
||||
rec := do(http.MethodPost, "/api/v1/categories", fmt.Sprintf(
|
||||
`{"name":"Electronics","unique_id":%q,"description":"Consumer electronics for Nordic merchants"}`, catUnique))
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("create category status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
createdCat := decode(t, rec)
|
||||
catData, _ := createdCat["data"].(map[string]any)
|
||||
if catData["unique_id"] != catUnique || catData["name"] != "Electronics" {
|
||||
t.Fatalf("create category data=%v", catData)
|
||||
}
|
||||
catUUID, err := uuid.Parse(fmt.Sprint(catData["id"]))
|
||||
if err != nil {
|
||||
t.Fatalf("category id: %v", err)
|
||||
}
|
||||
|
||||
childUnique := "headphones-" + prefix
|
||||
rec = do(http.MethodPost, "/api/v1/categories/create", fmt.Sprintf(
|
||||
`{"name":"Headphones","unique_id":%q,"parent_id":%q}`, childUnique, catUnique))
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("create child category status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
rec = do(http.MethodGet, "/api/v1/categories?page=1&limit=25&search=Electronics", "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("list categories status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
listCat := decode(t, rec)
|
||||
if _, ok := listCat["data"]; !ok {
|
||||
t.Fatalf("list categories missing data envelope: %v", listCat)
|
||||
}
|
||||
meta, _ := listCat["meta"].(map[string]any)
|
||||
if meta["page"].(float64) != 1 || meta["limit"].(float64) != 25 {
|
||||
t.Fatalf("list categories meta=%v", meta)
|
||||
}
|
||||
|
||||
rec = do(http.MethodGet, "/api/v1/categories/"+catUUID.String(), "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("get category status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
gotCat := decode(t, rec)
|
||||
if _, hasData := gotCat["data"]; hasData {
|
||||
t.Fatalf("GET /categories/{uuid} should be flat dashboard JSON, got envelope: %v", gotCat)
|
||||
}
|
||||
if gotCat["unique_id"] != catUnique {
|
||||
t.Fatalf("get category=%v", gotCat)
|
||||
}
|
||||
|
||||
rec = do(http.MethodPatch, "/api/v1/categories/"+catUUID.String(),
|
||||
`{"name":"Electronics & Audio"}`)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("patch category status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
patchedCat := decode(t, rec)
|
||||
if patchedCat["name"] != "Electronics & Audio" {
|
||||
t.Fatalf("patched category=%v", patchedCat)
|
||||
}
|
||||
|
||||
// --- Attributes CRUD ---
|
||||
rec = do(http.MethodPost, "/api/v1/attributes", fmt.Sprintf(
|
||||
`{"name":"Color","attribute_key":"color_%s","value_type":"string","category_unique_id":%q,"required":true}`,
|
||||
prefix, catUnique))
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("create attribute status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
attrEnv := decode(t, rec)
|
||||
attrData, _ := attrEnv["data"].(map[string]any)
|
||||
if attrData["key"] == nil || attrData["category_unique_id"] != catUnique || attrData["required"] != true {
|
||||
t.Fatalf("create attribute data=%v", attrData)
|
||||
}
|
||||
attrID := fmt.Sprint(attrData["id"])
|
||||
|
||||
rec = do(http.MethodGet, "/api/v1/attributes?page=1&limit=25&categoryId="+catUnique, "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("list attributes status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
listAttr := decode(t, rec)
|
||||
if _, ok := listAttr["data"]; !ok {
|
||||
t.Fatalf("list attributes missing data: %v", listAttr)
|
||||
}
|
||||
|
||||
rec = do(http.MethodPatch, "/api/v1/attributes/"+attrID, `{"name":"Colour"}`)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("patch attribute status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
// --- Feeds CRUD ---
|
||||
// Legacy create: name + item_path without URL (avoids live SSRF DNS for merchant hosts).
|
||||
rec = do(http.MethodPost, "/api/v1/feeds", `{
|
||||
"name":"Main catalog XML",
|
||||
"item_path":"channel/item",
|
||||
"feed_type":"xml",
|
||||
"sync_interval_minutes":60,
|
||||
"is_active":true
|
||||
}`)
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("create feed status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
feedEnv := decode(t, rec)
|
||||
feedData, _ := feedEnv["data"].(map[string]any)
|
||||
if feedData["name"] != "Main catalog XML" || feedData["item_path"] != "channel/item" {
|
||||
t.Fatalf("create feed data=%v", feedData)
|
||||
}
|
||||
if feedData["is_active"] != true {
|
||||
t.Fatalf("create feed is_active should be true after flip, got %v", feedData)
|
||||
}
|
||||
feedID := fmt.Sprint(feedData["id"])
|
||||
|
||||
rec = do(http.MethodGet, "/api/v1/feeds?page=1&limit=25", "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("list feeds status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
listFeeds := decode(t, rec)
|
||||
if _, ok := listFeeds["data"]; !ok {
|
||||
t.Fatalf("list feeds missing data: %v", listFeeds)
|
||||
}
|
||||
|
||||
rec = do(http.MethodGet, "/api/v1/feeds/"+feedID, "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("get feed status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
getFeed := decode(t, rec)
|
||||
getFeedData, _ := getFeed["data"].(map[string]any)
|
||||
if getFeedData["id"] != feedID {
|
||||
t.Fatalf("get feed=%v", getFeed)
|
||||
}
|
||||
|
||||
rec = do(http.MethodPatch, "/api/v1/feeds/"+feedID, `{"name":"Main catalog XML (Nordic)"}`)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("patch feed status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
patchFeed := decode(t, rec)
|
||||
if _, hasData := patchFeed["data"]; hasData {
|
||||
t.Fatalf("PATCH /feeds/{id} should be flat PresentFeed JSON, got envelope: %v", patchFeed)
|
||||
}
|
||||
if patchFeed["name"] != "Main catalog XML (Nordic)" {
|
||||
t.Fatalf("patched feed=%v", patchFeed)
|
||||
}
|
||||
|
||||
rec = do(http.MethodPut, "/api/v1/feeds/"+feedID+"/mappings",
|
||||
`{"mappings":{"title":"g:title","gtin":"g:gtin","description":"g:description"}}`)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("put mappings status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
// --- Export feeds CRUD ---
|
||||
rec = do(http.MethodPost, "/api/v1/export-feeds", `{
|
||||
"name":"Google Shopping XML",
|
||||
"format":"xml",
|
||||
"source_feed_id":`+fmt.Sprintf("%q", feedID)+`
|
||||
}`)
|
||||
if rec.Code != http.StatusCreated {
|
||||
t.Fatalf("create export feed status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
expEnv := decode(t, rec)
|
||||
expData, _ := expEnv["data"].(map[string]any)
|
||||
if expData["name"] != "Google Shopping XML" || expData["format"] != "xml" {
|
||||
t.Fatalf("create export=%v", expData)
|
||||
}
|
||||
if expData["public_token"] == nil || expData["public_token"] == "" {
|
||||
t.Fatalf("export missing public_token: %v", expData)
|
||||
}
|
||||
oldPublicToken := fmt.Sprint(expData["public_token"])
|
||||
expID := fmt.Sprint(expData["id"])
|
||||
|
||||
rec = do(http.MethodPost, "/api/v1/export-feeds/"+expID+"/rotate-token", "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("rotate export token status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
rotated := decode(t, rec)
|
||||
newPublicToken := fmt.Sprint(rotated["public_token"])
|
||||
if newPublicToken == "" || newPublicToken == "<nil>" || newPublicToken == oldPublicToken {
|
||||
t.Fatalf("rotate did not replace public_token old=%q new=%q body=%v", oldPublicToken, newPublicToken, rotated)
|
||||
}
|
||||
if len(newPublicToken) != 64 {
|
||||
t.Fatalf("rotated public_token len=%d want 64", len(newPublicToken))
|
||||
}
|
||||
|
||||
rec = do(http.MethodGet, "/api/v1/export-feeds?page=1&limit=25", "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("list export feeds status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
listExp := decode(t, rec)
|
||||
if _, ok := listExp["data"]; !ok {
|
||||
t.Fatalf("list export missing data: %v", listExp)
|
||||
}
|
||||
|
||||
rec = do(http.MethodGet, "/api/v1/export-feeds/"+expID, "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("get export feed status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
getExp := decode(t, rec)
|
||||
if _, hasData := getExp["data"]; hasData {
|
||||
t.Fatalf("GET /export-feeds/{id} should be flat JSON, got envelope: %v", getExp)
|
||||
}
|
||||
|
||||
rec = do(http.MethodPatch, "/api/v1/export-feeds/"+expID, `{"name":"Google Shopping XML v2","is_active":true}`)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("patch export status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
rec = do(http.MethodPut, "/api/v1/export-feeds/"+expID+"/template",
|
||||
`{"template":{"root":"rss/channel","item":"item","mappings":{"title":"title"}}}`)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("put template status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
// --- Products list + seed get/patch ---
|
||||
rec = do(http.MethodGet, "/api/v1/products?page=1&limit=25", "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("list products status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
prodList := decode(t, rec)
|
||||
if _, ok := prodList["data"]; !ok {
|
||||
t.Fatalf("list products missing data: %v", prodList)
|
||||
}
|
||||
|
||||
productID := uuid.New()
|
||||
_, err = pg.Exec(ctx, `
|
||||
INSERT INTO processed_products (
|
||||
id, company_id, product_id, name, category, description, status,
|
||||
processed_name, processed_description, attributes, processed_attributes, feed_id
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, 'completed',
|
||||
$7, $8, '{}'::jsonb, '{}'::jsonb, $9
|
||||
)`,
|
||||
productID, companyID, "SKU-1001", "Wireless earbuds", catUnique,
|
||||
"Original catalog description",
|
||||
"Acme Wireless Earbuds ANC Black",
|
||||
"Noise-cancelling wireless earbuds with 24h battery life.",
|
||||
uuid.MustParse(feedID),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("seed processed product: %v", err)
|
||||
}
|
||||
|
||||
rec = do(http.MethodGet, "/api/v1/products/"+productID.String(), "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("get product status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
gotProd := decode(t, rec)
|
||||
if _, hasData := gotProd["data"]; hasData {
|
||||
t.Fatalf("GET /products/{id} should be flat JSON, got envelope: %v", gotProd)
|
||||
}
|
||||
if fmt.Sprint(gotProd["product_id"]) != "SKU-1001" {
|
||||
t.Fatalf("get product=%v", gotProd)
|
||||
}
|
||||
|
||||
rec = do(http.MethodPatch, "/api/v1/products/"+productID.String(),
|
||||
`{"processed_name":"Acme Wireless Earbuds ANC Midnight","status":"completed"}`)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("patch product status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
rec = do(http.MethodGet, "/api/v1/products/quality?page=1&limit=25", "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("list quality status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
// --- Campaigns / marketing calendar ---
|
||||
rec = do(http.MethodGet, "/api/v1/campaigns?year=2026", "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("list campaigns status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
camp := decode(t, rec)
|
||||
campData, _ := camp["data"].(map[string]any)
|
||||
if campData["year"].(float64) != 2026 {
|
||||
t.Fatalf("campaigns data=%v", campData)
|
||||
}
|
||||
presets, _ := campData["presets"].([]any)
|
||||
if len(presets) == 0 {
|
||||
t.Fatalf("expected seasonal presets, got %v", campData)
|
||||
}
|
||||
|
||||
rec = do(http.MethodGet, "/api/v1/marketing/calendar?year=2026", "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("marketing calendar status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
cal := decode(t, rec)
|
||||
if _, hasData := cal["data"]; hasData {
|
||||
t.Fatalf("GET /marketing/calendar should be flat JSON per OpenAPI, got envelope: %v", cal)
|
||||
}
|
||||
|
||||
rec = do(http.MethodPost, "/api/v1/campaigns/prepare",
|
||||
`{"preset_id":"black_friday","year":2026,"format":"csv"}`)
|
||||
if rec.Code != http.StatusOK && rec.Code != http.StatusCreated {
|
||||
t.Fatalf("prepare campaign status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
prep := decode(t, rec)
|
||||
prepData, _ := prep["data"].(map[string]any)
|
||||
if prepData["preset_id"] != "black_friday" {
|
||||
t.Fatalf("prepare data=%v", prepData)
|
||||
}
|
||||
|
||||
// --- Deletes (reverse dependency order) ---
|
||||
rec = do(http.MethodDelete, "/api/v1/export-feeds/"+expID, "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("delete export status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
rec = do(http.MethodDelete, "/api/v1/feeds/"+feedID, "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("delete feed status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
delFeed := decode(t, rec)
|
||||
if delFeed["deleted"] != true {
|
||||
t.Fatalf("delete feed response=%v", delFeed)
|
||||
}
|
||||
|
||||
rec = do(http.MethodDelete, "/api/v1/attributes/"+attrID, "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("delete attribute status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
delAttr := decode(t, rec)
|
||||
delAttrData, _ := delAttr["data"].(map[string]any)
|
||||
if delAttrData["message"] != "Attribute deleted successfully" {
|
||||
t.Fatalf("delete attribute=%v", delAttr)
|
||||
}
|
||||
|
||||
rec = do(http.MethodDelete, "/api/v1/categories/"+childUnique, "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("delete child category status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
rec = do(http.MethodDelete, "/api/v1/categories/"+catUnique, "")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("delete category status=%d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
delCat := decode(t, rec)
|
||||
delCatData, _ := delCat["data"].(map[string]any)
|
||||
if delCatData["message"] != "Category deleted successfully" {
|
||||
t.Fatalf("delete category=%v", delCat)
|
||||
}
|
||||
|
||||
// Confirm 404 after delete
|
||||
rec = do(http.MethodGet, "/api/v1/feeds/"+feedID, "")
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("get deleted feed status=%d want 404 body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func mountV1DomainTestRouter(s *Server) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
r.Get("/products", s.handleV1ListProducts)
|
||||
r.Get("/products/quality", s.handleV1ListProductQuality)
|
||||
r.Get("/products/{id}", s.handleGetProduct)
|
||||
r.Patch("/products/{id}", s.handleUpdateProduct)
|
||||
|
||||
r.Get("/marketing/calendar", s.handleGetMarketingCalendar)
|
||||
r.Post("/marketing/calendar/prepare", s.handlePrepareMarketingCalendar)
|
||||
r.Get("/campaigns", s.handleV1ListCampaigns)
|
||||
r.Post("/campaigns/prepare", s.handleV1PrepareCampaign)
|
||||
|
||||
r.Get("/categories", s.handleV1ListCategories)
|
||||
r.Post("/categories", s.handleV1CreateCategory)
|
||||
r.Post("/categories/create", s.handleV1CreateCategory)
|
||||
r.Get("/categories/{id}", s.handleGetCategory)
|
||||
r.Patch("/categories/{id}", s.handleUpdateCategory)
|
||||
r.Delete("/categories/{id}", s.handleV1DeleteCategory)
|
||||
|
||||
r.Get("/attributes", s.handleV1ListAttributes)
|
||||
r.Post("/attributes", s.handleV1CreateAttribute)
|
||||
r.Post("/attributes/create", s.handleV1CreateAttribute)
|
||||
r.Patch("/attributes/{id}", s.handleUpdateAttribute)
|
||||
r.Delete("/attributes/{id}", s.handleV1DeleteAttribute)
|
||||
|
||||
r.Get("/feeds", s.handleV1ListFeeds)
|
||||
r.Post("/feeds", s.handleV1CreateFeed)
|
||||
r.Get("/feeds/{id}", s.handleV1GetFeed)
|
||||
r.Patch("/feeds/{id}", s.handleUpdateFeed)
|
||||
r.Delete("/feeds/{id}", s.handleDeleteFeed)
|
||||
r.Get("/feeds/{id}/mappings", s.handleGetFeedMappings)
|
||||
r.Put("/feeds/{id}/mappings", s.handlePutFeedMappings)
|
||||
|
||||
r.Get("/export-feeds", s.handleV1ListExportFeeds)
|
||||
r.Post("/export-feeds", s.handleV1CreateExportFeed)
|
||||
r.Get("/export-feeds/{id}", s.handleGetExportFeed)
|
||||
r.Patch("/export-feeds/{id}", s.handleUpdateExportFeed)
|
||||
r.Put("/export-feeds/{id}/template", s.handleUpdateExportFeedTemplate)
|
||||
r.Delete("/export-feeds/{id}", s.handleDeleteExportFeed)
|
||||
r.Post("/export-feeds/{id}/rotate-token", s.handleRotateExportFeedPublicToken)
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func TestV1OpenAPIDocumentsDomainCRUDSurface(t *testing.T) {
|
||||
t.Parallel()
|
||||
body := string(v1OpenAPIYAML)
|
||||
needles := []string{
|
||||
"/categories:",
|
||||
"/attributes:",
|
||||
"/feeds:",
|
||||
"/export-feeds:",
|
||||
"/export-feeds/{id}/rotate-token:",
|
||||
"/campaigns:",
|
||||
"/marketing/calendar:",
|
||||
"/products:",
|
||||
"/feeds/{id}/sync-process-sample:",
|
||||
"Flat category JSON",
|
||||
"flat PresentFeed",
|
||||
"flat ProcessedProduct",
|
||||
"CategoryDetail",
|
||||
"FeedDeleted",
|
||||
"FeedMappings",
|
||||
"is_active:",
|
||||
}
|
||||
for _, n := range needles {
|
||||
if !strings.Contains(body, n) {
|
||||
t.Fatalf("openapi missing %q", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user