Files
NexusGate/panel-go/auth_test.go
aaaamoooo 3951134940
CI / test (push) Canceled after 0s
CI / anytls-smoke (push) Canceled after 0s
feat: Go port of the panel in panel-go/ — drop-in replacement for server.js
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.
2026-10-20 08:21:54 +00:00

115 lines
3.1 KiB
Go

package main
import (
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestParseCookies(t *testing.T) {
got := ParseCookies("a=1; b=two; ng_session=abc%20def; empty=; =x; novalue; a=last")
want := map[string]string{
"a": "last", // later duplicates overwrite, like Object.fromEntries
"b": "two",
"ng_session": "abc def",
"empty": "",
}
if len(got) != len(want) {
t.Fatalf("ParseCookies = %v, want %v", got, want)
}
for k, v := range want {
if got[k] != v {
t.Errorf("cookie %q = %q, want %q", k, got[k], v)
}
}
if _, ok := got[""]; ok {
t.Errorf("empty key must be filtered out: %v", got)
}
if _, ok := got["novalue"]; ok {
t.Errorf("pair without '=' must be skipped: %v", got)
}
}
func TestParseCookiesDecodingAndEdges(t *testing.T) {
if got := ParseCookies(""); len(got) != 0 {
t.Errorf("empty header = %v, want empty", got)
}
got := ParseCookies("a%20b=c%3Dd; spaced = value ")
if got["a b"] != "c=d" {
t.Errorf("decoded key/value with '=' in value = %v", got)
}
if got["spaced"] != "value" {
t.Errorf("trimmed pair = %v", got)
}
// Malformed escape: pair is skipped (documented deviation from JS throw).
got = ParseCookies("bad=%zz; good=1")
if _, ok := got["bad"]; ok {
t.Errorf("malformed pair should be skipped: %v", got)
}
if got["good"] != "1" {
t.Errorf("good pair after malformed = %v", got)
}
// '+' is literal in decodeURIComponent semantics, not a space.
got = ParseCookies("plus=a+b")
if got["plus"] != "a+b" {
t.Errorf("plus handling = %q, want %q", got["plus"], "a+b")
}
}
func requestWithSession(id string) *http.Request {
r := httptest.NewRequest("GET", "/", nil)
if id != "" {
r.Header.Set("Cookie", "ng_session="+id)
}
return r
}
func TestSessionManagerLifecycle(t *testing.T) {
m := NewSessionManager(12)
if m.TTL != 12*time.Hour {
t.Fatalf("TTL = %v, want 12h", m.TTL)
}
s := m.Create("user-1")
if s.ID == "" || s.CSRF == "" || s.UserID != "user-1" {
t.Fatalf("created session = %+v", s)
}
if !s.ExpiresAt.After(time.Now()) {
t.Fatalf("ExpiresAt not in future: %v", s.ExpiresAt)
}
got := m.Get(requestWithSession(s.ID))
if got != s {
t.Fatalf("Get = %+v, want the created session", got)
}
if m.Get(requestWithSession("nope")) != nil {
t.Errorf("Get unknown id should be nil")
}
if m.Get(requestWithSession("")) != nil {
t.Errorf("Get without cookie should be nil")
}
m.Delete(requestWithSession(s.ID))
if m.Get(requestWithSession(s.ID)) != nil {
t.Errorf("Get after Delete should be nil")
}
}
func TestSessionManagerExpiryAndPrune(t *testing.T) {
m := NewSessionManager(1)
live := m.Create("live")
dead := m.Create("dead")
dead.ExpiresAt = time.Now().Add(-time.Minute)
if m.Get(requestWithSession(dead.ID)) != nil {
t.Errorf("expired session should read as nil")
}
// Expired session is also deleted by Get.
dead2 := m.Create("dead2")
dead2.ExpiresAt = time.Now().Add(-time.Minute)
m.Prune()
if m.Get(requestWithSession(dead2.ID)) != nil {
t.Errorf("pruned session should be gone")
}
if m.Get(requestWithSession(live.ID)) != live {
t.Errorf("live session must survive Prune")
}
}