ci / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 48s
ci / api (push) Successful in 51s
ci / recommender (push) Canceled after 2s
ci / ts (packages + web) (push) Canceled after 0s
ci / docker (образы собираются — их сервер собирает сам при обновлении) (apps/web/Dockerfile, web, ) (push) Canceled after 0s
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/api/Dockerfile, api, api) (push) Canceled after 0s
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/api/Dockerfile, storage-agent, storage-agent) (push) Canceled after 0s
ci / docker (образы собираются — их сервер собирает сам при обновлении) (services/recommender/Dockerfile, recommender, ) (push) Canceled after 0s
ci / mobile (Metro-бандл Android) (push) Canceled after 0s
Security and robustness: - storage-agent rejects an empty INTERNAL_TOKEN (config notEmpty + middleware) - api takes the client IP only from X-Real-IP (chi RealIP trusted True-Client-IP) - /readyz reports ok/fail and logs the cause; checks run in parallel with a timeout - storage-agent shuts down gracefully; shared httpx.NewServer/Serve with IdleTimeout - goose migrations take a Postgres advisory lock - recommender readyz: per-check timeout, redis socket timeouts Clients: - Android release allows cleartext HTTP for LAN servers (expo-build-properties) - checkServer times out after 10s; connect button is disabled while checking Deploy and CI: - install.sh hands cache/uploads/updates/models to the non-root service users - update.sh refuses downgrades unless PIPU_ALLOW_DOWNGRADE=1 - recommender/analyzer get only the env they need; required secrets fail fast - nginx security headers and CSP; strip True-Client-IP - dev compose binds ports to 127.0.0.1; postgres/redis images pinned - version.mjs fails on a malformed explicit tag instead of releasing 0.0.0-dev - contract check catches untracked generated files - final releases fail without a release keystore; changelog skips rc tags Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
2.5 KiB
Go
82 lines
2.5 KiB
Go
package system_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"pipu/api/internal/server"
|
|
"pipu/api/internal/system"
|
|
)
|
|
|
|
func newServer(checks map[string]system.Checker) http.Handler {
|
|
h := system.NewHandler(system.Info{
|
|
Version: "1.2.3",
|
|
SupportedLocales: []string{"ru", "en"},
|
|
MinAndroidVersionCode: 10203,
|
|
}, nil, checks)
|
|
return server.NewRouter(server.Deps{System: h})
|
|
}
|
|
|
|
func get(t *testing.T, h http.Handler, path string) (*httptest.ResponseRecorder, map[string]any) {
|
|
t.Helper()
|
|
rec := httptest.NewRecorder()
|
|
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, path, nil))
|
|
var body map[string]any
|
|
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body), rec.Body.String())
|
|
return rec, body
|
|
}
|
|
|
|
func TestServerInfo(t *testing.T) {
|
|
rec, body := get(t, newServer(nil), "/api/v1/server-info")
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
assert.Equal(t, "pipu-music", body["name"])
|
|
assert.Equal(t, "1.2.3", body["version"])
|
|
assert.Equal(t, "v1", body["api_version"])
|
|
assert.Equal(t, []any{"ru", "en"}, body["supported_locales"])
|
|
assert.EqualValues(t, 10203, body["min_android_version_code"])
|
|
}
|
|
|
|
func TestHealthz(t *testing.T) {
|
|
rec, body := get(t, newServer(nil), "/healthz")
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
assert.Equal(t, "ok", body["status"])
|
|
}
|
|
|
|
func TestReadyz(t *testing.T) {
|
|
ok := system.CheckerFunc(func(context.Context) error { return nil })
|
|
down := system.CheckerFunc(func(context.Context) error { return errors.New("connection refused") })
|
|
|
|
t.Run("all dependencies up", func(t *testing.T) {
|
|
rec, body := get(t, newServer(map[string]system.Checker{"postgres": ok, "redis": ok}), "/readyz")
|
|
|
|
assert.Equal(t, http.StatusOK, rec.Code)
|
|
assert.Equal(t, "ok", body["status"])
|
|
assert.Equal(t, map[string]any{"postgres": "ok", "redis": "ok"}, body["checks"])
|
|
})
|
|
|
|
t.Run("one dependency down", func(t *testing.T) {
|
|
rec, body := get(t, newServer(map[string]system.Checker{"postgres": ok, "redis": down}), "/readyz")
|
|
|
|
assert.Equal(t, http.StatusServiceUnavailable, rec.Code)
|
|
assert.Equal(t, "degraded", body["status"])
|
|
assert.Equal(t, map[string]any{"postgres": "ok", "redis": "fail"}, body["checks"])
|
|
})
|
|
}
|
|
|
|
func TestUnknownRouteIsProblemJSON(t *testing.T) {
|
|
rec, body := get(t, newServer(nil), "/api/v1/nope")
|
|
|
|
assert.Equal(t, http.StatusNotFound, rec.Code)
|
|
assert.Equal(t, "application/problem+json", rec.Header().Get("Content-Type"))
|
|
assert.Equal(t, "common.not_found", body["code"])
|
|
}
|