Initial commit: wb-mock skeleton
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
wb-mock
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
60
AGENTS.md
Normal file
60
AGENTS.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# AGENTS.md — WB Mock Server — Project Context
|
||||||
|
|
||||||
|
> **Read this first.** Append what you did at the end under **Session Log**.
|
||||||
|
|
||||||
|
## 1. What this is
|
||||||
|
|
||||||
|
Mock server emulating Wildberries Seller API for integration testing. Returns hardcoded fixture data.
|
||||||
|
|
||||||
|
| Layer | Tech |
|
||||||
|
|-------|------|
|
||||||
|
| Language | Go 1.25, `net/http` stdlib |
|
||||||
|
| Deployment | Docker + systemd on ioffe |
|
||||||
|
| Auth | Optional Api-Key validation (WB Authorization header) |
|
||||||
|
| Config | Env vars: `PORT`, `WB_API_KEY` |
|
||||||
|
|
||||||
|
## 2. Endpoints (TBD by agent)
|
||||||
|
|
||||||
|
| Method | Path | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| GET | `/health` | Health check |
|
||||||
|
|
||||||
|
## 3. Test Data
|
||||||
|
|
||||||
|
| Entity | Count |
|
||||||
|
|--------|-------|
|
||||||
|
| Products | TBD |
|
||||||
|
| Stocks | TBD |
|
||||||
|
| Prices | TBD |
|
||||||
|
|
||||||
|
## 4. Deploy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/rinat/projects/wb-mock
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o wb-mock ./cmd/server
|
||||||
|
scp wb-mock ioffe:/opt/wb-mock/wb-mock
|
||||||
|
ssh ioffe "sudo systemctl restart wb-mock"
|
||||||
|
curl https://wb-mock.px.kinrin.ru/health # expect {"status":"ok"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Key Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build # binary
|
||||||
|
make test # go test -race ./...
|
||||||
|
make docker # Docker build
|
||||||
|
go run ./cmd/server # local on :8080
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Wildberries API Auth
|
||||||
|
|
||||||
|
WB Seller API uses `Authorization: <api_key>` header (no Client-Id).
|
||||||
|
|
||||||
|
## 7. Reference
|
||||||
|
|
||||||
|
Ozon mock sibling project: `/Users/rinat/projects/ozon-mock` — same architecture, 20 endpoints, 31 tests.
|
||||||
|
Use it as a reference for patterns: config loading, fixture generation, handler structure, filter logic.
|
||||||
|
|
||||||
|
## 8. Session Log
|
||||||
|
|
||||||
|
> Append your summary below. Latest first.
|
||||||
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM alpine:3.20
|
||||||
|
|
||||||
|
RUN apk --no-cache add ca-certificates tzdata
|
||||||
|
|
||||||
|
COPY wb-mock /usr/local/bin/wb-mock
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
ENV PORT=8080
|
||||||
|
|
||||||
|
CMD ["/usr/local/bin/wb-mock"]
|
||||||
25
Makefile
Normal file
25
Makefile
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
.PHONY: build test docker run clean vet lint
|
||||||
|
|
||||||
|
build:
|
||||||
|
CGO_ENABLED=0 go build -o wb-mock ./cmd/server
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test -v -race ./...
|
||||||
|
|
||||||
|
vet:
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
lint:
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
run:
|
||||||
|
go run ./cmd/server
|
||||||
|
|
||||||
|
docker:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o wb-mock ./cmd/server
|
||||||
|
docker build -t wb-mock .
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f wb-mock
|
||||||
|
|
||||||
|
all: vet test build
|
||||||
38
cmd/server/main.go
Normal file
38
cmd/server/main.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/rinat/wb-mock/internal/config"
|
||||||
|
"github.com/rinat/wb-mock/internal/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg := config.Load()
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
auth := func(h http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return middleware.Auth(cfg.APIKey, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health
|
||||||
|
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"status":"ok"}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
_ = auth
|
||||||
|
|
||||||
|
// 404 handler
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(`{"code":404,"message":"not found"}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
addr := ":" + cfg.Port
|
||||||
|
log.Printf("Starting wb-mock on %s", addr)
|
||||||
|
log.Fatal(http.ListenAndServe(addr, mux))
|
||||||
|
}
|
||||||
22
internal/config/config.go
Normal file
22
internal/config/config.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Port string
|
||||||
|
APIKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load() *Config {
|
||||||
|
return &Config{
|
||||||
|
Port: getEnv("PORT", "8080"),
|
||||||
|
APIKey: os.Getenv("WB_API_KEY"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnv(key, defaultVal string) string {
|
||||||
|
if val := os.Getenv(key); val != "" {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
22
internal/handlers/handlers.go
Normal file
22
internal/handlers/handlers.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeJSON(w http.ResponseWriter, status int, v any) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(status)
|
||||||
|
json.NewEncoder(w).Encode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeBody(r *http.Request, v any) error {
|
||||||
|
defer r.Body.Close()
|
||||||
|
return json.NewDecoder(r.Body).Decode(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logRequest(r *http.Request) {
|
||||||
|
log.Printf("[wb-mock] %s %s", r.Method, r.URL.Path)
|
||||||
|
}
|
||||||
33
internal/middleware/auth.go
Normal file
33
internal/middleware/auth.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Auth(expectedAPIKey string, next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
apiKey := r.Header.Get("Authorization")
|
||||||
|
|
||||||
|
if apiKey == "" {
|
||||||
|
writeError(w, 401, "missing credentials: Authorization header is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectedAPIKey != "" && apiKey != expectedAPIKey {
|
||||||
|
writeError(w, 403, "invalid api key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeError(w http.ResponseWriter, code int, msg string) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(code)
|
||||||
|
json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"code": code,
|
||||||
|
"message": msg,
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user