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