Files
webui4frpc/internal/store/store_test.go
JianFeeeee d83e5d8e70 fix(render,store): frpc user 支持 + 修正 useEncryption/useCompression 渲染位置
- store.Remote 新增 user 字段(DB 列名 usr,避开保留字;migrate() 自动加列),
  渲染为 frpc 全局 user。面板型 frps(如 ChhChongGou 实例)强制要求 user 登录,
  缺失时服务端返回 panel authentication unavailable: panel HTTP status 403
- ★ 修复渲染 bug:useEncryption/useCompression/bandwidthLimit 必须内嵌在 proxy 的
  transport 对象(frpc ProxyTransport)。此前渲染在 proxy 顶层,frpc 默认
  --strict-config 直接拒载:unknown field "useEncryption"。任何启用加密/压缩
  的转发都无法启动。poolCount 无 proxy 级表达(官方 ProxyTransport 无此字段),
  保留在 store 但不再渲染
- 前端:RemoteNode 画布节点与 StatusView 表单加 user 输入(画布此前会静默
  清掉未声明的字段),带『面板型 frps 必填』提示
- 测试:user 往返(含迁移路径)、渲染含/不含 user、transport 内嵌与顶层
  非法键回归守卫
2026-10-23 09:49:04 +08:00

121 lines
3.5 KiB
Go

package store
import (
"path/filepath"
"testing"
)
func TestLocalRemoteLinkRoundTrip(t *testing.T) {
dir := t.TempDir()
st, err := New(filepath.Join(dir, "test.db"))
if err != nil {
t.Fatal(err)
}
defer st.Close()
if err := st.UpsertLocal(Local{Name: "web", IP: "127.0.0.1", Port: 8080, Protocol: "tcp"}); err != nil {
t.Fatal(err)
}
if err := st.UpsertRemote(Remote{Name: "srv-a", IP: "1.2.3.4", Port: 7000, Token: "tok", Enabled: true}); err != nil {
t.Fatal(err)
}
if err := st.ReplaceLinks([]Link{{Local: "web", Remote: "srv-a", RemotePort: 8080}}); err != nil {
t.Fatal(err)
}
locals, _ := st.ListLocals()
if len(locals) != 1 || locals[0].Name != "web" {
t.Fatalf("locals = %+v", locals)
}
remotes, _ := st.ListRemotes()
if len(remotes) != 1 || remotes[0].Token != "tok" {
t.Fatalf("remotes = %+v", remotes)
}
fwds, err := st.LinksForRemote("srv-a")
if err != nil || len(fwds) != 1 {
t.Fatalf("forwards = %+v err=%v", fwds, err)
}
if fwds[0].LocalPort != 8080 || fwds[0].RemotePort != 8080 {
t.Fatalf("forward = %+v", fwds[0])
}
}
func TestReplaceLinksClearsOld(t *testing.T) {
dir := t.TempDir()
st, _ := New(filepath.Join(dir, "test.db"))
defer st.Close()
_ = st.UpsertLocal(Local{Name: "a", IP: "127.0.0.1", Port: 1, Protocol: "tcp"})
_ = st.UpsertLocal(Local{Name: "b", IP: "127.0.0.1", Port: 2, Protocol: "tcp"})
_ = st.UpsertRemote(Remote{Name: "r", IP: "1.2.3.4", Port: 7000, Enabled: true})
if err := st.ReplaceLinks([]Link{{Local: "a", Remote: "r", RemotePort: 1}}); err != nil {
t.Fatal(err)
}
if err := st.ReplaceLinks([]Link{{Local: "b", Remote: "r", RemotePort: 2}}); err != nil {
t.Fatal(err)
}
links, _ := st.ListLinks()
if len(links) != 1 || links[0].Local != "b" {
t.Fatalf("links = %+v", links)
}
}
// `usr` is a reserved SQL keyword in several dialects, so the remotes column is
// named `usr`. This guards the mapping in both directions, including the
// migrate() path that adds the column to pre-existing databases.
func TestRemoteUserRoundTrip(t *testing.T) {
st, err := New(filepath.Join(t.TempDir(), "test.db"))
if err != nil {
t.Fatal(err)
}
defer st.Close()
// A remote written without user (existing installs) must read back empty.
if err := st.UpsertRemote(Remote{Name: "plain", IP: "1.2.3.4", Port: 7000, Enabled: true}); err != nil {
t.Fatal(err)
}
if got, ok := st.GetRemote("plain"); !ok || got.User != "" {
t.Fatalf("plain remote user = %q ok=%v, want empty", got.User, ok)
}
want := Remote{Name: "panel", IP: "103.40.13.126", Port: 59999, Token: "tk", User: "e8f52adfaf0e5c95", Enabled: true}
if err := st.UpsertRemote(want); err != nil {
t.Fatal(err)
}
got, ok := st.GetRemote("panel")
if !ok || got.User != want.User {
t.Fatalf("user not persisted: %+v", got)
}
list, err := st.ListRemotes()
if err != nil {
t.Fatal(err)
}
var found bool
for _, r := range list {
if r.Name == "panel" {
found = true
if r.User != want.User {
t.Fatalf("ListRemotes user = %q, want %q", r.User, want.User)
}
}
}
if !found {
t.Fatal("panel remote missing from ListRemotes")
}
// Updating in place must not drop the column, and clearing it must work.
want.Token = "tk2"
if err := st.UpsertRemote(want); err != nil {
t.Fatal(err)
}
if got, _ := st.GetRemote("panel"); got.User != want.User || got.Token != "tk2" {
t.Fatalf("upsert lost fields: %+v", got)
}
want.User = ""
if err := st.UpsertRemote(want); err != nil {
t.Fatal(err)
}
if got, _ := st.GetRemote("panel"); got.User != "" {
t.Fatalf("user not cleared: %q", got.User)
}
}