Files
opsxapi/apps/server/internal/docs/docs_test.go
T
logan dec66db5aa feat: OpsxAPI v1.0 全协议接口工作台
- 六协议调试:HTTP/GraphQL/WebSocket/SSE/Socket.IO/MQTT
- 集合树/多标签/多环境变量/历史记录,localStorage 持久化
- 导入:OpenAPI 2/3/3.1、Postman、Apifox、Apipost、cURL、HAR、
  Insomnia、Hoppscotch、Markdown(自动识别,21 个单测覆盖)
- 导出:OpenAPI 3.0 JSON/YAML、Postman v2.1、Markdown 文档、
  全量备份;单请求生成 cURL/fetch/Python/Go 代码
- Gin 服务:HTTP 代理、NDJSON 流式代理(SSE 透传)、
  WebSocket 隧道(浏览器自定义握手 Header)
- 内置产品官网 /home 与用户文档站 /docs(SSR,随二进制交付)
- lucide 图标体系 + 品牌标识(Logo/favicon),明暗双主题
- 环境变量:彩色徽标切换器、管理弹窗、{{ 自动补全
2026-09-18 00:16:59 +08:00

123 lines
3.3 KiB
Go

package docs
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gin-gonic/gin"
)
func newTestRouter(t *testing.T) *gin.Engine {
t.Helper()
gin.SetMode(gin.TestMode)
r := gin.New()
New().Register(r)
return r
}
func TestDocsIndexRedirect(t *testing.T) {
r := newTestRouter(t)
w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/docs", nil))
if w.Code != http.StatusFound {
t.Fatalf("expect 302, got %d", w.Code)
}
if !strings.HasPrefix(w.Header().Get("Location"), "/docs/") {
t.Fatalf("bad redirect: %s", w.Header().Get("Location"))
}
}
func TestDocsAllPagesRender(t *testing.T) {
h := New()
r := newTestRouter(t)
for _, slug := range h.order {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/docs/"+slug, nil)
r.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("page %s: expect 200, got %d", slug, w.Code)
}
body := w.Body.String()
if !strings.Contains(body, "<title>") || !strings.Contains(body, `class="md"`) {
t.Fatalf("page %s: html structure incomplete", slug)
}
// 正文有渲染出的标题与段落
if !strings.Contains(body, "<h1") {
t.Fatalf("page %s: missing h1", slug)
}
// 侧栏包含全部分组
for _, g := range h.manifest.Groups {
if !strings.Contains(body, g.Name) {
t.Fatalf("page %s: sidebar missing group %s", slug, g.Name)
}
}
}
}
func TestDocsPageContainsTOCAndNav(t *testing.T) {
r := newTestRouter(t)
w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/docs/http", nil))
body := w.Body.String()
if !strings.Contains(body, "sec-1") {
t.Fatal("expect heading anchors")
}
if !strings.Contains(body, "上一页") {
t.Fatal("expect prev link")
}
}
func TestDocsSearchData(t *testing.T) {
r := newTestRouter(t)
w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/docs/search-data.json", nil))
if w.Code != http.StatusOK {
t.Fatalf("expect 200, got %d", w.Code)
}
var entries []SearchEntry
if err := json.Unmarshal(w.Body.Bytes(), &entries); err != nil {
t.Fatalf("invalid json: %v", err)
}
if len(entries) < 10 {
t.Fatalf("expect >= 10 search entries, got %d", len(entries))
}
// MQTT 页应可被检索(标题或正文命中)
found := false
for _, e := range entries {
if e.Slug == "mqtt" && (e.Title == "MQTT" || strings.Contains(e.Text, "MQTT")) {
found = true
}
}
if !found {
t.Fatal("mqtt entry missing searchable text")
}
}
func TestDocsAssetsAnd404(t *testing.T) {
r := newTestRouter(t)
w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/docs/assets/docs.css", nil))
if w.Code != http.StatusOK || !strings.Contains(w.Header().Get("Content-Type"), "text/css") {
t.Fatalf("css asset: %d %s", w.Code, w.Header().Get("Content-Type"))
}
w = httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/docs/assets/docs.js", nil))
if w.Code != http.StatusOK {
t.Fatalf("js asset: %d", w.Code)
}
w = httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/docs/not-exist-page", nil))
if w.Code != http.StatusNotFound {
t.Fatalf("404 page: %d", w.Code)
}
if !strings.Contains(w.Body.String(), "返回文档首页") {
t.Fatal("404 page missing home link")
}
}