feat(panel): reskin backend — node sortIndex, presence/speed, /api/system, credentials, subscription aggregation (Node+Go parity)
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParseLinkListBase64AndPlain(t *testing.T) {
|
||||
plain := "vless://11111111-1111-1111-1111-111111111111@a.example.com:443?security=reality#One\n" +
|
||||
"trojan://x@b.example.com:443#Bad\n" +
|
||||
"ss://YWVzLTI1Ni1nY206cGFzcw@b.example.com:8388#Two\n"
|
||||
encoded := base64.StdEncoding.EncodeToString([]byte(plain))
|
||||
items, dropped := ParseLinkList(encoded)
|
||||
if len(items) != 2 || dropped != 1 {
|
||||
t.Fatalf("base64 list: items=%d dropped=%d", len(items), dropped)
|
||||
}
|
||||
if items[0].Name != "One" || items[1].Name != "Two" {
|
||||
t.Fatalf("names: %+v", items)
|
||||
}
|
||||
items2, dropped2 := ParseLinkList(plain)
|
||||
if len(items2) != 2 || dropped2 != 1 {
|
||||
t.Fatalf("plain list: items=%d dropped=%d", len(items2), dropped2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchExternalSourceGuards(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("vless://11111111-1111-1111-1111-111111111111@a.example.com:443#X\n"))
|
||||
}))
|
||||
defer server.Close()
|
||||
source := ExtSource{ID: "src_t", URL: server.URL, Enabled: true}
|
||||
settings := &Settings{}
|
||||
blocked := FetchExternalSource(source, settings)
|
||||
if blocked.OK {
|
||||
t.Fatal("loopback source must be rejected by default")
|
||||
}
|
||||
settings.AllowPrivateSubSources = true
|
||||
allowed := FetchExternalSource(source, settings)
|
||||
if !allowed.OK || len(allowed.Items) != 1 {
|
||||
t.Fatalf("allowPrivate fetch: %+v", allowed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAggregateEntriesOrderAndDedup(t *testing.T) {
|
||||
d := &Data{
|
||||
Nodes: []*Node{
|
||||
{ID: "nod_b", Name: "B", SortIndex: 1, ServerID: "srv_1", Protocol: "shadowsocks-aes256-gcm", Status: "active", Port: 20003},
|
||||
{ID: "nod_a", Name: "A", SortIndex: 0, ServerID: "srv_1", Protocol: "shadowsocks-aes256-gcm", Status: "active", Port: 20001},
|
||||
},
|
||||
Servers: []*Server{{ID: "srv_1", Name: "S", PublicAddress: "s.example.com"}},
|
||||
NodeClients: []*NodeClient{
|
||||
{ID: "ncl_b", NodeID: "nod_b", CustomerID: "cus_1", Status: "active", Credentials: Credentials{RelayPassword: "pw-b"}},
|
||||
{ID: "ncl_a", NodeID: "nod_a", CustomerID: "cus_1", Status: "active", Credentials: Credentials{RelayPassword: "pw-a"}},
|
||||
},
|
||||
SubSourceCache: map[string]*SubSourceCacheEntry{
|
||||
"src_1": {Items: []SubCacheItem{{URI: "ss://YWVzLTI1Ni1nY206cHctYQ@s.example.com:20002#Dup", Name: "Dup"}}},
|
||||
},
|
||||
}
|
||||
okAt := NowISO()
|
||||
d.SubSourceCache["src_1"].OkAt = &okAt
|
||||
customer := &Customer{
|
||||
ID: "cus_1",
|
||||
ManualLinks: []ManualLink{
|
||||
{ID: "lnk_1", URI: "ss://YWVzLTI1Ni1nY206cHctYQ@s.example.com:20002#Whatever", Remark: "手动一号", Enabled: true},
|
||||
},
|
||||
ExternalSources: []ExtSource{{ID: "src_1", URL: "https://example.com/sub", Remark: "机场", Enabled: true}},
|
||||
}
|
||||
entries := AggregateEntries(d, customer)
|
||||
names := []string{}
|
||||
for _, entry := range entries {
|
||||
names = append(names, NameFromURI(entry.ClientURI))
|
||||
}
|
||||
// Own entries first in sortIndex order (A before B although B's
|
||||
// binding is stored first); the manual link follows; the external
|
||||
// item duplicates the manual link (same host/port/credential) and
|
||||
// is dropped.
|
||||
joined := strings.Join(names, ",")
|
||||
if joined != "A,B,手动一号" {
|
||||
t.Fatalf("aggregate order/dedup: %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPresenceWindow(t *testing.T) {
|
||||
now := time.Now()
|
||||
recent := ISOFromTime(now.Add(-60 * time.Second))
|
||||
customer := &Customer{LastOnlineAt: &recent}
|
||||
if !CustomerOnline(customer, now.UnixMilli()) {
|
||||
t.Fatal("customer active 60s ago should be online")
|
||||
}
|
||||
old := ISOFromTime(now.Add(-10 * time.Minute))
|
||||
customer.LastOnlineAt = &old
|
||||
if CustomerOnline(customer, now.UnixMilli()) {
|
||||
t.Fatal("customer active 10m ago should be offline")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostIPsNoPanic(t *testing.T) {
|
||||
HostIPs() // just must not panic on any host
|
||||
}
|
||||
Reference in New Issue
Block a user