Files
iptvc/app/web/openapi_test.go
T
2026-07-19 19:14:55 +08:00

172 lines
4.7 KiB
Go

package web
import (
"encoding/json"
"net/http"
"strings"
"testing"
)
// TestOpenAPISpec проверяет, что /api/openapi.json отдаёт валидную спецификацию
func TestOpenAPISpec(t *testing.T) {
ts, _ := makeTestServer(t)
resp, err := http.Get(ts.URL + "/api/openapi.json")
if err != nil {
t.Fatalf("GET /api/openapi.json: %s", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "application/json") {
t.Errorf("expected application/json content-type, got %s", ct)
}
var spec map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&spec); err != nil {
t.Fatalf("cannot decode spec: %s", err)
}
// базовые поля OpenAPI 3.0
if v, ok := spec["openapi"].(string); !ok || !strings.HasPrefix(v, "3.0") {
t.Errorf("expected openapi 3.0.x, got %v", spec["openapi"])
}
info, ok := spec["info"].(map[string]interface{})
if !ok {
t.Fatal("missing 'info' section")
}
if _, ok := info["title"].(string); !ok {
t.Error("missing info.title")
}
if _, ok := info["version"].(string); !ok {
t.Error("missing info.version")
}
// сервер должен быть указан
servers, ok := spec["servers"].([]interface{})
if !ok || len(servers) == 0 {
t.Error("expected at least one server")
}
// пути
paths, ok := spec["paths"].(map[string]interface{})
if !ok {
t.Fatal("missing 'paths' section")
}
expectedPaths := []string{
"/api/playlists",
"/api/playlists/{code}",
"/api/playlists/{code}/channels",
"/api/version",
"/api/health",
"/api/stats",
}
for _, p := range expectedPaths {
if _, ok := paths[p]; !ok {
t.Errorf("missing path %s in spec", p)
}
}
// компоненты/схемы
components, ok := spec["components"].(map[string]interface{})
if !ok {
t.Fatal("missing 'components' section")
}
schemas, ok := components["schemas"].(map[string]interface{})
if !ok {
t.Fatal("missing 'components.schemas' section")
}
expectedSchemas := []string{
"PlaylistListResponse",
"PlaylistResponse",
"Playlist",
"ChannelListResponse",
"Channel",
"Group",
"Attribute",
"VersionResponse",
"HealthResponse",
"StatsResponse",
"ErrorResponse",
}
for _, s := range expectedSchemas {
if _, ok := schemas[s]; !ok {
t.Errorf("missing schema %s in components.schemas", s)
}
}
}
// TestSwaggerUI проверяет, что /api/ отдаёт HTML-страницу Swagger UI
// и что CSS/JS-ассеты доступны без внешних зависимостей
func TestSwaggerUI(t *testing.T) {
ts, _ := makeTestServer(t)
// /api → редирект на /api/
noRedirect := &http.Client{CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}}
resp, err := noRedirect.Get(ts.URL + "/api")
if err != nil {
t.Fatalf("GET /api: %s", err)
}
resp.Body.Close()
if resp.StatusCode != http.StatusMovedPermanently {
t.Fatalf("expected 301 redirect, got %d", resp.StatusCode)
}
// /api/ → HTML-страница
resp, err = http.Get(ts.URL + "/api/")
if err != nil {
t.Fatalf("GET /api/: %s", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d", resp.StatusCode)
}
if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "text/html") {
t.Errorf("expected text/html content-type, got %s", ct)
}
body := fetchBody(t, resp)
if !strings.Contains(body, "swagger-ui") {
t.Error("Swagger UI page should contain 'swagger-ui' reference")
}
if !strings.Contains(body, "/api/openapi.json") {
t.Error("Swagger UI page should reference /api/openapi.json")
}
// проверяем, что нет ссылок на CDN
if strings.Contains(body, "cdn.jsdelivr.net") || strings.Contains(body, "unpkg.com") {
t.Error("Swagger UI page should not reference any CDN")
}
// CSS-ассет
respCSS, err := http.Get(ts.URL + "/api/swagger-ui.css")
if err != nil {
t.Fatalf("GET /api/swagger-ui.css: %s", err)
}
defer respCSS.Body.Close()
if respCSS.StatusCode != http.StatusOK {
t.Fatalf("expected 200 for CSS, got %d", respCSS.StatusCode)
}
if ct := respCSS.Header.Get("Content-Type"); !strings.HasPrefix(ct, "text/css") {
t.Errorf("expected text/css content-type, got %s", ct)
}
// JS-ассет
respJS, err := http.Get(ts.URL + "/api/swagger-ui-bundle.js")
if err != nil {
t.Fatalf("GET /api/swagger-ui-bundle.js: %s", err)
}
defer respJS.Body.Close()
if respJS.StatusCode != http.StatusOK {
t.Fatalf("expected 200 for JS, got %d", respJS.StatusCode)
}
if ct := respJS.Header.Get("Content-Type"); !strings.Contains(ct, "javascript") {
t.Errorf("expected javascript content-type, got %s", ct)
}
}