桌面 GUI 外链修复 + 主题顺序调整(含结构扁平化进行中的工作区基线)
- 集成 tauri-plugin-opener:Cargo/lib.rs/capabilities/npm 四处接线,新增 src/openExternal.ts(桌面走 opener,浏览器回退 window.open,相对路径绝对化) - 关于页 4 个链接、活动栏官网菜单、代理错误横幅 2 处健康检查链接全部改走 openExternal,桌面端可正常跳转 - 设置→外观 主题顺序调整为 深色/浅色/跟随系统 - release 二进制重编并验证(浏览器实测关于页链接可开文档页) - 附带工作区中未提交的扁平化重构现状(另一会话进行中) - homepage 落地页与文档终检通过(8787 / 与 /docs 双 200)
This commit is contained in:
1 parent
6452026fd6
commit
2bb4ef7a95
299 files changed
+6059
-3322
No files matched your search
@@ -0,0 +1,120 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user