Files
logan 2bb4ef7a95 桌面 GUI 外链修复 + 主题顺序调整(含结构扁平化进行中的工作区基线)
- 集成 tauri-plugin-opener:Cargo/lib.rs/capabilities/npm 四处接线,新增 src/openExternal.ts(桌面走 opener,浏览器回退 window.open,相对路径绝对化)
- 关于页 4 个链接、活动栏官网菜单、代理错误横幅 2 处健康检查链接全部改走 openExternal,桌面端可正常跳转
- 设置→外观 主题顺序调整为 深色/浅色/跟随系统
- release 二进制重编并验证(浏览器实测关于页链接可开文档页)
- 附带工作区中未提交的扁平化重构现状(另一会话进行中)
- homepage 落地页与文档终检通过(8787 / 与 /docs 双 200)
2026-10-16 03:03:36 +08:00

121 lines
4.7 KiB
Go

package api
import (
"encoding/json"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"github.com/gin-gonic/gin"
"opsxapi/server/internal/mocksrv"
)
func TestLocalMockLifecycleAndPeerBoundary(t *testing.T) {
gin.SetMode(gin.TestMode)
a, err := New(filepath.Join(t.TempDir(), "mock.db"))
if err != nil {
t.Fatal(err)
}
defer a.Store().Close()
r := gin.New()
_ = r.SetTrustedProxies(nil)
a.Register(r)
mocksrv.New(a.Store()).Register(r)
callWithBearer := func(method, path, body, peer, bearer string) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, path, strings.NewReader(body))
req.RemoteAddr = peer
req.Header.Set("Content-Type", "application/json")
if bearer != "" {
req.Header.Set("Authorization", "Bearer "+bearer)
}
req.Header.Set("X-Forwarded-For", "127.0.0.1")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
call := func(method, path, body, peer string) *httptest.ResponseRecorder {
return callWithBearer(method, path, body, peer, "")
}
if w := call("GET", "/api/mock/local", "", "192.168.1.5:3000"); w.Code != 403 {
t.Fatalf("remote management: %d", w.Code)
}
payload := `{"endpoints":[{"method":"POST","path":"/demo","status":201,"contentType":"application/json","body":"{\"ok\":true}","delayMs":0}],"ttlSeconds":0}`
if w := call("PUT", "/api/mock/local", payload, "127.0.0.1:3000"); w.Code != 200 {
t.Fatal(w.Body.String())
}
w := call("GET", "/api/mock/local", "", "127.0.0.1:3000")
var status struct {
Token string `json:"token"`
Active bool `json:"active"`
Endpoints []mocksrv.Endpoint `json:"endpoints"`
}
if err = json.Unmarshal(w.Body.Bytes(), &status); err != nil {
t.Fatal(err)
}
if !status.Active || len(status.Endpoints) != 1 {
t.Fatal(w.Body.String())
}
path := "/mock/" + status.Token + "/demo"
if w = call("POST", path, "", "192.168.1.5:3000"); w.Code != 201 {
t.Fatal(w.Code, w.Body.String())
}
if w = call("PUT", "/api/mock/local/access", `{"enabled":true,"token":"test-secret-1234567890"}`, "192.168.1.5:3000"); w.Code != 403 {
t.Fatalf("remote access control: %d", w.Code)
}
if w = call("PUT", "/api/mock/local/access", `{"enabled":true,"token":"short"}`, "127.0.0.1:3000"); w.Code != 400 {
t.Fatalf("short token accepted: %d", w.Code)
}
if w = call("PUT", "/api/mock/local/access", `{"enabled":true,"token":"test-secret-1234567890"}`, "127.0.0.1:3000"); w.Code != 200 {
t.Fatal(w.Body.String())
}
if w = call("GET", "/api/mock/local", "", "127.0.0.1:3000"); w.Code != 200 || !strings.Contains(w.Body.String(), `"bearerRequired":true`) || strings.Contains(w.Body.String(), "test-secret-1234567890") {
t.Fatalf("status leaked secret or wrong access state: %s", w.Body.String())
}
if w = call("POST", path, "", "192.168.1.5:3000"); w.Code != 401 || w.Header().Get("WWW-Authenticate") == "" {
t.Fatalf("missing bearer: %d", w.Code)
}
if w = callWithBearer("POST", path, "", "192.168.1.5:3000", "wrong-secret-1234567890"); w.Code != 401 {
t.Fatalf("wrong bearer: %d", w.Code)
}
if w = callWithBearer("POST", path, "", "192.168.1.5:3000", "test-secret-1234567890"); w.Code != 201 {
t.Fatalf("correct bearer: %d %s", w.Code, w.Body.String())
}
if w = call("PUT", "/api/mock/local/access", `{"enabled":true,"token":"replacement-1234567890"}`, "127.0.0.1:3000"); w.Code != 200 {
t.Fatal(w.Body.String())
}
if w = callWithBearer("POST", path, "", "192.168.1.5:3000", "test-secret-1234567890"); w.Code != 401 {
t.Fatalf("old bearer still works after replacement: %d", w.Code)
}
if w = callWithBearer("POST", path, "", "192.168.1.5:3000", "replacement-1234567890"); w.Code != 201 {
t.Fatalf("replacement bearer: %d", w.Code)
}
if w = call("PUT", "/api/mock/local/access", `{"enabled":false}`, "127.0.0.1:3000"); w.Code != 200 {
t.Fatal(w.Body.String())
}
if w = call("POST", path, "", "192.168.1.5:3000"); w.Code != 201 {
t.Fatalf("disabled bearer still required: %d", w.Code)
}
if w = call("PATCH", "/api/mock/local", `{"enabled":false}`, "192.168.1.5:3000"); w.Code != 403 {
t.Fatal("remote toggle allowed")
}
if w = call("PATCH", "/api/mock/local", `{"enabled":false}`, "127.0.0.1:3000"); w.Code != 200 {
t.Fatal(w.Body.String())
}
if w = call("POST", path, "", "192.168.1.5:3000"); w.Code != 503 {
t.Fatal("disabled mock still responds", w.Code)
}
if w = call("PATCH", "/api/mock/local", `{"enabled":true}`, "127.0.0.1:3000"); w.Code != 200 {
t.Fatal(w.Body.String())
}
if w = call("POST", path, "", "192.168.1.5:3000"); w.Code != 201 {
t.Fatal("toggle lost mock data", w.Code)
}
if w = call("DELETE", "/api/mock/local", "", "127.0.0.1:3000"); w.Code != 200 {
t.Fatal(w.Body.String())
}
if w = call("POST", path, "", "192.168.1.5:3000"); w.Code != 404 {
t.Fatalf("revoked token: %d", w.Code)
}
}