mirror of
https://gitcode.com/JianFeeeee/webui4frpc.git
synced 2026-09-20 00:47:57 +00:00
- 零 frp 源码依赖,单二进制 (Go + Vue3 + VueFlow + Element Plus) - 画布多对多连线,渲染 tcp/udp/http/https frpc 配置 - worker 进程管理:自愈、日志轮转、崩溃退避重启 - frpc 一键安装 (GitHub Releases) + 手动指定路径 - 三页 UI:状态(默认)/连接配置/设置 - 状态页实时节点/转发状态,节点可增删改启停 - 画布冲突检查:端口/域名冲突标红 + 弹窗拦截保存 - backend 单测覆盖 store/render/process/httpapi/install - plan.md + FRPC_FEATURES_AUDIT.md 文档
100 lines
2.6 KiB
Go
100 lines
2.6 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"webui4frpc/internal/process"
|
|
"webui4frpc/internal/store"
|
|
)
|
|
|
|
func newTestHandler(t *testing.T) (*Handler, *httptest.Server) {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
st, err := store.New(filepath.Join(dir, "test.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
pm := process.NewManager(process.Options{
|
|
ConfigsDir: filepath.Join(dir, "configs"),
|
|
LogsDir: filepath.Join(dir, "logs"),
|
|
BinaryPath: func() string { return "" },
|
|
Render: func(string) ([]byte, error) { return []byte(`{}`), nil },
|
|
AutoRestart: func(string) bool { return false },
|
|
RestartInterval: func() int { return 5 },
|
|
})
|
|
|
|
h := &Handler{Store: st, Process: pm, WorkDir: dir, User: "admin", Password: "pw"}
|
|
mux, err := NewServeMux(h)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ts := httptest.NewServer(mux)
|
|
t.Cleanup(ts.Close)
|
|
t.Cleanup(func() { _ = st.Close() })
|
|
return h, ts
|
|
}
|
|
|
|
func TestAuthRequired(t *testing.T) {
|
|
_, ts := newTestHandler(t)
|
|
resp, err := http.Get(ts.URL + "/api/manager/status")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusUnauthorized {
|
|
t.Fatalf("status = %d, want 401", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestCanvasRoundTrip(t *testing.T) {
|
|
_, ts := newTestHandler(t)
|
|
client := ts.Client()
|
|
|
|
body := `{
|
|
"locals": [{"name":"web","ip":"127.0.0.1","port":8080,"protocol":"tcp"}],
|
|
"remotes": [{"name":"srv-a","ip":"1.2.3.4","port":7000,"enabled":true}],
|
|
"links": [{"local":"web","remote":"srv-a","remotePort":8080}]
|
|
}`
|
|
req, _ := http.NewRequest(http.MethodPut, ts.URL+"/api/manager/canvas", bytes.NewBufferString(body))
|
|
req.SetBasicAuth("admin", "pw")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Fatalf("save canvas status = %d", resp.StatusCode)
|
|
}
|
|
|
|
var got canvasData
|
|
if err := json.NewDecoder(resp.Body).Decode(&got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(got.Locals) != 1 || got.Locals[0].Name != "web" {
|
|
t.Fatalf("locals = %+v", got.Locals)
|
|
}
|
|
if len(got.Links) != 1 || got.Links[0].RemotePort != 8080 {
|
|
t.Fatalf("links = %+v", got.Links)
|
|
}
|
|
|
|
// GET again to confirm persistence via store.
|
|
req2, _ := http.NewRequest(http.MethodGet, ts.URL+"/api/manager/canvas", nil)
|
|
req2.SetBasicAuth("admin", "pw")
|
|
resp2, err2 := client.Do(req2)
|
|
if err2 != nil {
|
|
t.Fatal(err2)
|
|
}
|
|
defer resp2.Body.Close()
|
|
var got2 canvasData
|
|
_ = json.NewDecoder(resp2.Body).Decode(&got2)
|
|
if len(got2.Remotes) != 1 || got2.Remotes[0].Name != "srv-a" {
|
|
t.Fatalf("remotes after reload = %+v", got2.Remotes)
|
|
}
|
|
}
|