feat: Go port of the panel in panel-go/ — drop-in replacement for server.js
CI / test (push) Canceled after 0s
CI / anytls-smoke (push) Canceled after 0s

Pure-stdlib Go implementation of the whole control plane: same
nexusgate.json schema (unknown fields preserved verbatim, explicit
null/false tri-states modeled), same HTTP API and error strings,
embedded frontend (NG_PUBLIC_DIR override), self-implemented scrypt
auth, X25519 Reality keys, panel-hosted keyStore and heartbeat
reconciliation included. Version stays 0.6.8.

Verified: Go tests 33/33 (scrypt + Reality vectors generated from
the Node implementation, subscription goldens byte-identical),
Node suite 52/52 untouched, differential E2E against the Node
panel 50/50 including two-way DB swap. See panel-go/PARITY.md
and panel-go/INSTALL-GO.md.
This commit is contained in:
aaaamoooo
2026-10-20 08:21:54 +00:00
parent abb1a4344b
commit 3951134940
37 changed files with 12183 additions and 0 deletions
+174
View File
@@ -0,0 +1,174 @@
package main
// Minimal smoke tests for the HTTP layer: healthz, the login gate, session
// round-trip, server creation shape, static + SPA fallback, and the 404
// shape. Uses a throwaway Store in t.TempDir().
import (
"encoding/json"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
)
func newSmokeApp(t *testing.T) *App {
t.Helper()
cookieSecure = false
store := NewStore(filepath.Join(t.TempDir(), "nexusgate.json"))
if err := store.Init(); err != nil {
t.Fatalf("store init: %v", err)
}
_, err := store.Transaction(func(d *Data) (any, error) {
d.Users = append(d.Users, &User{
ID: NewID("usr"),
Username: "admin",
PasswordHash: HashSecret("password12345"),
Role: "owner",
Status: "active",
CreatedAt: NowISO(),
})
return nil, nil
})
if err != nil {
t.Fatalf("seed user: %v", err)
}
app := NewApp(store, NewSessionManager(12), NewOrchestrator(store))
return app
}
func doRequest(app *App, method, path, body string, headers map[string]string) *httptest.ResponseRecorder {
var reader *strings.Reader
if body != "" {
reader = strings.NewReader(body)
} else {
reader = strings.NewReader("")
}
req := httptest.NewRequest(method, path, reader)
for k, v := range headers {
req.Header.Set(k, v)
}
rec := httptest.NewRecorder()
app.ServeHTTP(rec, req)
return rec
}
func decodeBody(t *testing.T, rec *httptest.ResponseRecorder) map[string]any {
t.Helper()
var out map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &out); err != nil {
t.Fatalf("decode %q: %v", rec.Body.String(), err)
}
return out
}
func TestSmokeHealthz(t *testing.T) {
app := newSmokeApp(t)
rec := doRequest(app, "GET", "/healthz", "", nil)
if rec.Code != 200 {
t.Fatalf("healthz status %d", rec.Code)
}
body := decodeBody(t, rec)
if body["ok"] != true || body["version"] != VERSION {
t.Fatalf("healthz body %v", body)
}
}
func TestSmokeLoginFlowAndServerCreate(t *testing.T) {
app := newSmokeApp(t)
// Unauthenticated admin API -> 401 unauthorized shape.
rec := doRequest(app, "GET", "/api/servers", "", nil)
if rec.Code != 401 {
t.Fatalf("unauth status %d", rec.Code)
}
body := decodeBody(t, rec)
if body["error"] != "unauthorized" || body["message"] != "请先登录" {
t.Fatalf("unauth body %v", body)
}
// Wrong password -> 401 invalid_credentials.
rec = doRequest(app, "POST", "/api/auth/login", `{"username":"admin","password":"wrong"}`, nil)
if rec.Code != 401 || decodeBody(t, rec)["error"] != "invalid_credentials" {
t.Fatalf("bad login %d %s", rec.Code, rec.Body.String())
}
// Correct login -> 200, csrf + session cookie.
rec = doRequest(app, "POST", "/api/auth/login", `{"username":"admin","password":"password12345"}`, nil)
if rec.Code != 200 {
t.Fatalf("login status %d body %s", rec.Code, rec.Body.String())
}
body = decodeBody(t, rec)
csrf, _ := body["csrf"].(string)
if csrf == "" {
t.Fatalf("login csrf missing: %v", body)
}
cookie := rec.Header().Get("Set-Cookie")
if !strings.HasPrefix(cookie, "ng_session=") || !strings.Contains(cookie, "HttpOnly") {
t.Fatalf("login cookie %q", cookie)
}
sessionCookie := strings.Split(cookie, ";")[0]
authHeaders := map[string]string{"Cookie": sessionCookie}
// /api/session reflects the login.
rec = doRequest(app, "GET", "/api/session", "", authHeaders)
body = decodeBody(t, rec)
if body["authenticated"] != true || body["csrf"] != csrf {
t.Fatalf("session body %v", body)
}
// Changing request without CSRF -> 403 csrf_failed.
rec = doRequest(app, "POST", "/api/servers", `{"name":"s1","publicAddress":"192.0.2.1"}`, authHeaders)
if rec.Code != 403 || decodeBody(t, rec)["error"] != "csrf_failed" {
t.Fatalf("csrf gate %d %s", rec.Code, rec.Body.String())
}
// Create server -> 201 shape.
createHeaders := map[string]string{"Cookie": sessionCookie, "X-CSRF-Token": csrf}
rec = doRequest(app, "POST", "/api/servers", `{"name":"s1","publicAddress":"192.0.2.1"}`, createHeaders)
if rec.Code != 201 {
t.Fatalf("create server %d %s", rec.Code, rec.Body.String())
}
body = decodeBody(t, rec)
server, _ := body["server"].(map[string]any)
if server == nil || server["name"] != "s1" || server["status"] != "pending" ||
!strings.HasPrefix(server["id"].(string), "srv_") || server["portRangeStart"] != float64(20000) {
t.Fatalf("server shape %v", body)
}
// Authenticated unknown API -> 404 not_found shape.
rec = doRequest(app, "GET", "/api/does-not-exist", "", authHeaders)
if rec.Code != 404 {
t.Fatalf("404 status %d", rec.Code)
}
body = decodeBody(t, rec)
if body["error"] != "not_found" || body["message"] != "接口不存在" {
t.Fatalf("404 body %v", body)
}
}
func TestSmokeStaticAndSPAFallback(t *testing.T) {
app := newSmokeApp(t)
rec := doRequest(app, "GET", "/", "", nil)
if rec.Code != 200 {
t.Fatalf("index status %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), "<") {
t.Fatalf("index body too small: %q", rec.Body.String())
}
if rec.Header().Get("Cache-Control") != "no-store" {
t.Fatalf("index cache-control %q", rec.Header().Get("Cache-Control"))
}
indexBody := rec.Body.String()
rec = doRequest(app, "GET", "/some/client/route", "", nil)
if rec.Code != 200 || rec.Body.String() != indexBody {
t.Fatalf("spa fallback %d", rec.Code)
}
// Unknown /api path is not swallowed by the SPA fallback.
rec = doRequest(app, "GET", "/api/nope", "", nil)
if rec.Code != 401 { // admin gate fires before the 404 for /api/*
t.Fatalf("api unknown status %d", rec.Code)
}
}