refactor(tests): centralize DB query assertions with helpers

Reduce repetitive raw SQL in test bodies by routing scalar and multi-column checks through shared helpers, keeping test intent clearer without changing behavior.
This commit is contained in:
Antigravity
2026-02-17 06:02:46 +00:00
parent d82c099c7f
commit 3d1a8c8963
15 changed files with 288 additions and 419 deletions
@@ -0,0 +1,50 @@
package handler
import (
"testing"
"go-backend/internal/store/repo"
)
func mustLastInsertID(t *testing.T, r *repo.Repository, label string) int64 {
t.Helper()
var id int64
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("read last_insert_rowid for %s: %v", label, err)
}
if id <= 0 {
t.Fatalf("invalid last_insert_rowid for %s: %d", label, id)
}
return id
}
func mustQueryInt(t *testing.T, r *repo.Repository, query string, args ...interface{}) int {
t.Helper()
var v int
if err := r.DB().Raw(query, args...).Row().Scan(&v); err != nil {
t.Fatalf("query int failed: %v (query=%q)", err, query)
}
return v
}
func mustQueryInt64Int64String(t *testing.T, r *repo.Repository, query string, args ...interface{}) (int64, int64, string) {
t.Helper()
var a int64
var b int64
var c string
if err := r.DB().Raw(query, args...).Row().Scan(&a, &b, &c); err != nil {
t.Fatalf("query int64+int64+string failed: %v (query=%q)", err, query)
}
return a, b, c
}
func mustQueryInt64Int64Int(t *testing.T, r *repo.Repository, query string, args ...interface{}) (int64, int64, int) {
t.Helper()
var a int64
var b int64
var c int
if err := r.DB().Raw(query, args...).Row().Scan(&a, &b, &c); err != nil {
t.Fatalf("query int64+int64+int failed: %v (query=%q)", err, query)
}
return a, b, c
}
@@ -128,11 +128,7 @@ func TestPrepareTunnelCreateStateRemoteAutoPortDefersToFederation(t *testing.T)
`, name, name+"-secret", "10.0.0.1", "10.0.0.1", "", portRange, "", "v1", 1, 1, 1, now, now, status, "[::]", "[::]", 0, isRemote, "http://peer", "peer-token", `{"shareId":1}`).Error; execErr != nil { `, name, name+"-secret", "10.0.0.1", "10.0.0.1", "", portRange, "", "v1", 1, 1, 1, now, now, status, "[::]", "[::]", 0, isRemote, "http://peer", "peer-token", `{"shareId":1}`).Error; execErr != nil {
t.Fatalf("insert node %s: %v", name, execErr) t.Fatalf("insert node %s: %v", name, execErr)
} }
var id int64 return mustLastInsertID(t, r, name)
if idErr := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); idErr != nil {
t.Fatalf("node id %s: %v", name, idErr)
}
return id
} }
entryID := insertNode("entry", 1, "31000-31010", 0) entryID := insertNode("entry", 1, "31000-31010", 0)
@@ -188,11 +184,7 @@ func TestPrepareTunnelCreateStateAllowsOfflineRemoteMiddleNode(t *testing.T) {
`, name, name+"-secret", "10.0.0.1", "10.0.0.1", "", portRange, "", "v1", 1, 1, 1, now, now, status, "[::]", "[::]", 0, isRemote, "http://peer", "peer-token", `{"shareId":2}`).Error; execErr != nil { `, name, name+"-secret", "10.0.0.1", "10.0.0.1", "", portRange, "", "v1", 1, 1, 1, now, now, status, "[::]", "[::]", 0, isRemote, "http://peer", "peer-token", `{"shareId":2}`).Error; execErr != nil {
t.Fatalf("insert node %s: %v", name, execErr) t.Fatalf("insert node %s: %v", name, execErr)
} }
var id int64 return mustLastInsertID(t, r, name)
if idErr := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); idErr != nil {
t.Fatalf("node id %s: %v", name, idErr)
}
return id
} }
entryID := insertNode("entry-local", 1, "32000-32010", 0) entryID := insertNode("entry-local", 1, "32000-32010", 0)
@@ -31,10 +31,7 @@ func TestFederationShareCreateRejectsRemoteNode(t *testing.T) {
`, "remote-share-node", "remote-share-secret", "10.10.10.1", "10.10.10.1", "", "20000-20010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0, 1, "http://peer.example", "peer-token", `{"shareId":1}`).Error; err != nil { `, "remote-share-node", "remote-share-secret", "10.10.10.1", "10.10.10.1", "", "20000-20010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0, 1, "http://peer.example", "peer-token", `{"shareId":1}`).Error; err != nil {
t.Fatalf("insert remote node: %v", err) t.Fatalf("insert remote node: %v", err)
} }
var remoteNodeID int64 remoteNodeID := mustLastInsertID(t, r, "remote-share-node")
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&remoteNodeID); err != nil {
t.Fatalf("get remote node id: %v", err)
}
body, err := json.Marshal(createPeerShareRequest{ body, err := json.Marshal(createPeerShareRequest{
Name: "remote-node-share", Name: "remote-node-share",
@@ -69,10 +66,7 @@ func TestFederationShareCreateRejectsRemoteNode(t *testing.T) {
t.Fatalf("expected rejection message %q, got %q", "Only local nodes can be shared", payload.Msg) t.Fatalf("expected rejection message %q, got %q", "Only local nodes can be shared", payload.Msg)
} }
var shareCount int shareCount := mustQueryInt(t, r, `SELECT COUNT(1) FROM peer_share WHERE node_id = ?`, remoteNodeID)
if err := r.DB().Raw(`SELECT COUNT(1) FROM peer_share WHERE node_id = ?`, remoteNodeID).Row().Scan(&shareCount); err != nil {
t.Fatalf("query peer_share count: %v", err)
}
if shareCount != 0 { if shareCount != 0 {
t.Fatalf("expected no share rows for remote node, got %d", shareCount) t.Fatalf("expected no share rows for remote node, got %d", shareCount)
} }
@@ -94,10 +88,7 @@ func TestFederationShareCreateRejectsInvalidAllowedIPs(t *testing.T) {
`, "local-share-node", "local-share-secret", "10.20.30.40", "10.20.30.40", "", "21000-21010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0, 0, "", "", "").Error; err != nil { `, "local-share-node", "local-share-secret", "10.20.30.40", "10.20.30.40", "", "21000-21010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0, 0, "", "", "").Error; err != nil {
t.Fatalf("insert local node: %v", err) t.Fatalf("insert local node: %v", err)
} }
var localNodeID int64 localNodeID := mustLastInsertID(t, r, "local-share-node")
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&localNodeID); err != nil {
t.Fatalf("get local node id: %v", err)
}
body, err := json.Marshal(createPeerShareRequest{ body, err := json.Marshal(createPeerShareRequest{
Name: "local-node-share", Name: "local-node-share",
@@ -133,10 +124,7 @@ func TestFederationShareCreateRejectsInvalidAllowedIPs(t *testing.T) {
t.Fatalf("expected invalid IP message, got %q", payload.Msg) t.Fatalf("expected invalid IP message, got %q", payload.Msg)
} }
var shareCount int shareCount := mustQueryInt(t, r, `SELECT COUNT(1) FROM peer_share WHERE node_id = ?`, localNodeID)
if err := r.DB().Raw(`SELECT COUNT(1) FROM peer_share WHERE node_id = ?`, localNodeID).Row().Scan(&shareCount); err != nil {
t.Fatalf("query peer_share count: %v", err)
}
if shareCount != 0 { if shareCount != 0 {
t.Fatalf("expected no share rows for node, got %d", shareCount) t.Fatalf("expected no share rows for node, got %d", shareCount)
} }
@@ -275,10 +263,7 @@ func TestFederationShareDeleteCleansUpRuntimes(t *testing.T) {
t.Fatalf("insert peer_share_runtime rows: %v", err) t.Fatalf("insert peer_share_runtime rows: %v", err)
} }
var runtimeCount int runtimeCount := mustQueryInt(t, r, `SELECT COUNT(1) FROM peer_share_runtime WHERE share_id = ? AND status = 1`, share.ID)
if err := r.DB().Raw(`SELECT COUNT(1) FROM peer_share_runtime WHERE share_id = ? AND status = 1`, share.ID).Row().Scan(&runtimeCount); err != nil {
t.Fatalf("count active runtimes before: %v", err)
}
if runtimeCount != 2 { if runtimeCount != 2 {
t.Fatalf("expected 2 active runtimes before delete, got %d", runtimeCount) t.Fatalf("expected 2 active runtimes before delete, got %d", runtimeCount)
} }
@@ -304,18 +289,12 @@ func TestFederationShareDeleteCleansUpRuntimes(t *testing.T) {
t.Fatalf("expected response code 0, got %d (%s)", payload.Code, payload.Msg) t.Fatalf("expected response code 0, got %d (%s)", payload.Code, payload.Msg)
} }
var shareCount int shareCount := mustQueryInt(t, r, `SELECT COUNT(1) FROM peer_share WHERE id = ?`, share.ID)
if err := r.DB().Raw(`SELECT COUNT(1) FROM peer_share WHERE id = ?`, share.ID).Row().Scan(&shareCount); err != nil {
t.Fatalf("count peer_share after: %v", err)
}
if shareCount != 0 { if shareCount != 0 {
t.Fatalf("expected peer_share deleted, got %d rows", shareCount) t.Fatalf("expected peer_share deleted, got %d rows", shareCount)
} }
var runtimeCountAfter int runtimeCountAfter := mustQueryInt(t, r, `SELECT COUNT(1) FROM peer_share_runtime WHERE share_id = ?`, share.ID)
if err := r.DB().Raw(`SELECT COUNT(1) FROM peer_share_runtime WHERE share_id = ?`, share.ID).Row().Scan(&runtimeCountAfter); err != nil {
t.Fatalf("count peer_share_runtime after: %v", err)
}
if runtimeCountAfter != 0 { if runtimeCountAfter != 0 {
t.Fatalf("expected all peer_share_runtime rows deleted, got %d", runtimeCountAfter) t.Fatalf("expected all peer_share_runtime rows deleted, got %d", runtimeCountAfter)
} }
@@ -451,26 +430,17 @@ func TestFederationRemoteUsageList(t *testing.T) {
`, "remote-consumer-node", "remote-consumer-secret", "10.30.40.50", "10.30.40.50", "", "31000-31010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0, 1, "http://peer.example", "peer-token", `{"shareId":88,"maxBandwidth":2147483648,"currentFlow":1073741824,"portRangeStart":31000,"portRangeEnd":31010}`).Error; err != nil { `, "remote-consumer-node", "remote-consumer-secret", "10.30.40.50", "10.30.40.50", "", "31000-31010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0, 1, "http://peer.example", "peer-token", `{"shareId":88,"maxBandwidth":2147483648,"currentFlow":1073741824,"portRangeStart":31000,"portRangeEnd":31010}`).Error; err != nil {
t.Fatalf("insert remote node: %v", err) t.Fatalf("insert remote node: %v", err)
} }
var nodeID int64 nodeID := mustLastInsertID(t, r, "remote-consumer-node")
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&nodeID); err != nil {
t.Fatalf("remote node id: %v", err)
}
if err := r.DB().Exec(`INSERT INTO tunnel(name, type, protocol, flow, created_time, updated_time, status, in_ip, inx) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)`, "consumer-tunnel-a", 2, "tls", 1, now, now, 1, "", 0).Error; err != nil { if err := r.DB().Exec(`INSERT INTO tunnel(name, type, protocol, flow, created_time, updated_time, status, in_ip, inx) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)`, "consumer-tunnel-a", 2, "tls", 1, now, now, 1, "", 0).Error; err != nil {
t.Fatalf("insert tunnel a: %v", err) t.Fatalf("insert tunnel a: %v", err)
} }
var tunnelAID int64 tunnelAID := mustLastInsertID(t, r, "consumer-tunnel-a")
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelAID); err != nil {
t.Fatal(err)
}
if err := r.DB().Exec(`INSERT INTO tunnel(name, type, protocol, flow, created_time, updated_time, status, in_ip, inx) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)`, "consumer-tunnel-b", 2, "tls", 1, now, now, 1, "", 0).Error; err != nil { if err := r.DB().Exec(`INSERT INTO tunnel(name, type, protocol, flow, created_time, updated_time, status, in_ip, inx) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)`, "consumer-tunnel-b", 2, "tls", 1, now, now, 1, "", 0).Error; err != nil {
t.Fatalf("insert tunnel b: %v", err) t.Fatalf("insert tunnel b: %v", err)
} }
var tunnelBID int64 tunnelBID := mustLastInsertID(t, r, "consumer-tunnel-b")
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelBID); err != nil {
t.Fatal(err)
}
if err := r.DB().Exec(` if err := r.DB().Exec(`
INSERT INTO federation_tunnel_binding(tunnel_id, node_id, chain_type, hop_inx, remote_url, resource_key, remote_binding_id, allocated_port, status, created_time, updated_time) INSERT INTO federation_tunnel_binding(tunnel_id, node_id, chain_type, hop_inx, remote_url, resource_key, remote_binding_id, allocated_port, status, created_time, updated_time)
+5 -24
View File
@@ -33,20 +33,12 @@ func TestRunStatisticsFlowJobTracksIncrementAndPrunes(t *testing.T) {
h.runStatisticsFlowJob(now) h.runStatisticsFlowJob(now)
var staleCount int staleCount := mustQueryInt(t, r, `SELECT COUNT(1) FROM statistics_flow WHERE created_time < ?`, nowMs-int64((48*time.Hour)/time.Millisecond))
if err := r.DB().Raw(`SELECT COUNT(1) FROM statistics_flow WHERE created_time < ?`, nowMs-int64((48*time.Hour)/time.Millisecond)).Row().Scan(&staleCount); err != nil {
t.Fatalf("query stale statistics rows: %v", err)
}
if staleCount != 0 { if staleCount != 0 {
t.Fatalf("expected stale statistics rows to be pruned, got %d", staleCount) t.Fatalf("expected stale statistics rows to be pruned, got %d", staleCount)
} }
var flow int64 flow, total, hour := mustQueryInt64Int64String(t, r, `SELECT flow, total_flow, time FROM statistics_flow WHERE user_id = 1 ORDER BY id DESC LIMIT 1`)
var total int64
var hour string
if err := r.DB().Raw(`SELECT flow, total_flow, time FROM statistics_flow WHERE user_id = 1 ORDER BY id DESC LIMIT 1`).Row().Scan(&flow, &total, &hour); err != nil {
t.Fatalf("query latest statistics row: %v", err)
}
if flow != 50 { if flow != 50 {
t.Fatalf("expected increment flow 50, got %d", flow) t.Fatalf("expected increment flow 50, got %d", flow)
} }
@@ -100,28 +92,17 @@ func TestRunResetAndExpiryJobResetsFlowAndDisablesExpiredRecords(t *testing.T) {
h.runResetAndExpiryJob(now) h.runResetAndExpiryJob(now)
var userIn, userOut int64 userIn, userOut, userStatus := mustQueryInt64Int64Int(t, r, `SELECT in_flow, out_flow, status FROM user WHERE id = 2`)
var userStatus int
if err := r.DB().Raw(`SELECT in_flow, out_flow, status FROM user WHERE id = 2`).Row().Scan(&userIn, &userOut, &userStatus); err != nil {
t.Fatalf("query user after maintenance: %v", err)
}
if userIn != 0 || userOut != 0 || userStatus != 0 { if userIn != 0 || userOut != 0 || userStatus != 0 {
t.Fatalf("expected user reset+disabled, got in=%d out=%d status=%d", userIn, userOut, userStatus) t.Fatalf("expected user reset+disabled, got in=%d out=%d status=%d", userIn, userOut, userStatus)
} }
var utIn, utOut int64 utIn, utOut, utStatus := mustQueryInt64Int64Int(t, r, `SELECT in_flow, out_flow, status FROM user_tunnel WHERE id = 10`)
var utStatus int
if err := r.DB().Raw(`SELECT in_flow, out_flow, status FROM user_tunnel WHERE id = 10`).Row().Scan(&utIn, &utOut, &utStatus); err != nil {
t.Fatalf("query user_tunnel after maintenance: %v", err)
}
if utIn != 0 || utOut != 0 || utStatus != 0 { if utIn != 0 || utOut != 0 || utStatus != 0 {
t.Fatalf("expected user_tunnel reset+disabled, got in=%d out=%d status=%d", utIn, utOut, utStatus) t.Fatalf("expected user_tunnel reset+disabled, got in=%d out=%d status=%d", utIn, utOut, utStatus)
} }
var forwardStatus int forwardStatus := mustQueryInt(t, r, `SELECT status FROM forward WHERE id = 20`)
if err := r.DB().Raw(`SELECT status FROM forward WHERE id = 20`).Row().Scan(&forwardStatus); err != nil {
t.Fatalf("query forward after maintenance: %v", err)
}
if forwardStatus != 0 { if forwardStatus != 0 {
t.Fatalf("expected forward status=0 after expiry handling, got %d", forwardStatus) t.Fatalf("expected forward status=0 after expiry handling, got %d", forwardStatus)
} }
@@ -0,0 +1,19 @@
package contract
import (
"testing"
"go-backend/internal/store/repo"
)
func mustLastInsertID(t *testing.T, r *repo.Repository, label string) int64 {
t.Helper()
var id int64
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("read last_insert_rowid for %s: %v", label, err)
}
if id <= 0 {
t.Fatalf("invalid last_insert_rowid for %s: %d", label, id)
}
return id
}
@@ -0,0 +1,119 @@
package contract_test
import (
"database/sql"
"testing"
"go-backend/internal/store/repo"
)
func mustLastInsertID(t *testing.T, r *repo.Repository, label string) int64 {
t.Helper()
var id int64
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("read last_insert_rowid for %s: %v", label, err)
}
if id <= 0 {
t.Fatalf("invalid last_insert_rowid for %s: %d", label, id)
}
return id
}
func mustQueryInt(t *testing.T, r *repo.Repository, query string, args ...interface{}) int {
t.Helper()
var v int
if err := r.DB().Raw(query, args...).Row().Scan(&v); err != nil {
t.Fatalf("query int failed: %v (query=%q)", err, query)
}
return v
}
func mustQueryInt64(t *testing.T, r *repo.Repository, query string, args ...interface{}) int64 {
t.Helper()
var v int64
if err := r.DB().Raw(query, args...).Row().Scan(&v); err != nil {
t.Fatalf("query int64 failed: %v (query=%q)", err, query)
}
return v
}
func mustQueryString(t *testing.T, r *repo.Repository, query string, args ...interface{}) string {
t.Helper()
var v string
if err := r.DB().Raw(query, args...).Row().Scan(&v); err != nil {
t.Fatalf("query string failed: %v (query=%q)", err, query)
}
return v
}
func mustQueryInt64Int(t *testing.T, r *repo.Repository, query string, args ...interface{}) (int64, int) {
t.Helper()
var a int64
var b int
if err := r.DB().Raw(query, args...).Row().Scan(&a, &b); err != nil {
t.Fatalf("query int64+int failed: %v (query=%q)", err, query)
}
return a, b
}
func tryQueryString(t *testing.T, r *repo.Repository, query string, args ...interface{}) (string, error) {
t.Helper()
var v string
err := r.DB().Raw(query, args...).Row().Scan(&v)
if err != nil {
return "", err
}
return v, nil
}
func mustQueryNullString(t *testing.T, r *repo.Repository, query string, args ...interface{}) sql.NullString {
t.Helper()
var v sql.NullString
if err := r.DB().Raw(query, args...).Row().Scan(&v); err != nil {
t.Fatalf("query null string failed: %v (query=%q)", err, query)
}
return v
}
func mustQueryTwoNullStrings(t *testing.T, r *repo.Repository, query string, args ...interface{}) (sql.NullString, sql.NullString) {
t.Helper()
var a sql.NullString
var b sql.NullString
if err := r.DB().Raw(query, args...).Row().Scan(&a, &b); err != nil {
t.Fatalf("query two null strings failed: %v (query=%q)", err, query)
}
return a, b
}
func mustQueryNodePorts(t *testing.T, r *repo.Repository, query string, args ...interface{}) map[int64]int {
t.Helper()
rows, err := r.DB().Raw(query, args...).Rows()
if err != nil {
t.Fatalf("query node ports failed: %v (query=%q)", err, query)
}
defer rows.Close()
out := make(map[int64]int)
for rows.Next() {
var nodeID int64
var port int
if err := rows.Scan(&nodeID, &port); err != nil {
t.Fatalf("scan node ports row failed: %v (query=%q)", err, query)
}
out[nodeID] = port
}
if err := rows.Err(); err != nil {
t.Fatalf("iterate node ports rows failed: %v (query=%q)", err, query)
}
return out
}
func tryQueryInt(t *testing.T, r *repo.Repository, query string, args ...interface{}) (int, error) {
t.Helper()
var v int
err := r.DB().Raw(query, args...).Row().Scan(&v)
if err != nil {
return 0, err
}
return v, nil
}
@@ -37,10 +37,7 @@ func TestDiagnosisChainCoverageContracts(t *testing.T) {
`, "diagnose-chain-tunnel", 1.0, 2, "tls", 99999, now, now, 1, nil, 0).Error; err != nil { `, "diagnose-chain-tunnel", 1.0, 2, "tls", 99999, now, now, 1, nil, 0).Error; err != nil {
t.Fatalf("insert tunnel: %v", err) t.Fatalf("insert tunnel: %v", err)
} }
var tunnelID int64 tunnelID := mustLastInsertID(t, r, "diagnose-chain-tunnel")
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelID); err != nil {
t.Fatalf("get tunnel id: %v", err)
}
insertNode := func(name, ip string) int64 { insertNode := func(name, ip string) int64 {
if err := r.DB().Exec(` if err := r.DB().Exec(`
@@ -49,11 +46,7 @@ func TestDiagnosisChainCoverageContracts(t *testing.T) {
`, name, name+"-secret", ip, ip, "", "30000-30010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil { `, name, name+"-secret", ip, ip, "", "30000-30010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil {
t.Fatalf("insert node %s: %v", name, err) t.Fatalf("insert node %s: %v", name, err)
} }
var id int64 return mustLastInsertID(t, r, name)
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("get node id %s: %v", name, err)
}
return id
} }
entryNodeID := insertNode("entry-node", "10.0.1.10") entryNodeID := insertNode("entry-node", "10.0.1.10")
@@ -85,10 +78,7 @@ func TestDiagnosisChainCoverageContracts(t *testing.T) {
`, 2, "normal_user", "chain-forward", tunnelID, "8.8.8.8:53", "fifo", now, now, 0).Error; err != nil { `, 2, "normal_user", "chain-forward", tunnelID, "8.8.8.8:53", "fifo", now, now, 0).Error; err != nil {
t.Fatalf("insert forward: %v", err) t.Fatalf("insert forward: %v", err)
} }
var forwardID int64 forwardID := mustLastInsertID(t, r, "chain-forward")
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&forwardID); err != nil {
t.Fatalf("get forward id: %v", err)
}
userToken, err := auth.GenerateToken(2, "normal_user", 1, secret) userToken, err := auth.GenerateToken(2, "normal_user", 1, secret)
if err != nil { if err != nil {
@@ -259,11 +249,7 @@ func TestDiagnosisUsesFederationRuntimeForRemoteNodes(t *testing.T) {
`, name, name+"-secret", ip, ip, "", "30000-30010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil { `, name, name+"-secret", ip, ip, "", "30000-30010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil {
t.Fatalf("insert local node %s: %v", name, err) t.Fatalf("insert local node %s: %v", name, err)
} }
var id int64 return mustLastInsertID(t, r, name)
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("get local node id %s: %v", name, err)
}
return id
} }
insertRemoteNode := func(name, ip string) int64 { insertRemoteNode := func(name, ip string) int64 {
@@ -273,11 +259,7 @@ func TestDiagnosisUsesFederationRuntimeForRemoteNodes(t *testing.T) {
`, name, name+"-secret", ip, "", "", "31000-31010", "", "", now, now, "[::]", "[::]", 1, remoteServer.URL, remoteToken, `{"shareId": 123}`).Error; err != nil { `, name, name+"-secret", ip, "", "", "31000-31010", "", "", now, now, "[::]", "[::]", 1, remoteServer.URL, remoteToken, `{"shareId": 123}`).Error; err != nil {
t.Fatalf("insert remote node %s: %v", name, err) t.Fatalf("insert remote node %s: %v", name, err)
} }
var id int64 return mustLastInsertID(t, r, name)
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("get remote node id %s: %v", name, err)
}
return id
} }
entryNodeID := insertLocalNode("entry-local", "10.50.0.10") entryNodeID := insertLocalNode("entry-local", "10.50.0.10")
@@ -290,10 +272,7 @@ func TestDiagnosisUsesFederationRuntimeForRemoteNodes(t *testing.T) {
`, "diagnose-remote-tunnel", 1.0, 2, "tls", 99999, now, now, 1, nil, 0).Error; err != nil { `, "diagnose-remote-tunnel", 1.0, 2, "tls", 99999, now, now, 1, nil, 0).Error; err != nil {
t.Fatalf("insert tunnel: %v", err) t.Fatalf("insert tunnel: %v", err)
} }
var tunnelID int64 tunnelID := mustLastInsertID(t, r, "diagnose-remote-tunnel")
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelID); err != nil {
t.Fatalf("get tunnel id: %v", err)
}
if err := r.DB().Exec(` if err := r.DB().Exec(`
INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol) INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol)
@@ -112,10 +112,7 @@ func TestFederationDualPanelMiddleExitAutoPortContract(t *testing.T) {
consumerRouter.ServeHTTP(res, req) consumerRouter.ServeHTTP(res, req)
assertCode(t, res, 0) assertCode(t, res, 0)
var tunnelID int64 tunnelID := mustQueryInt64(t, consumerRepo, `SELECT id FROM tunnel WHERE name = ? ORDER BY id DESC LIMIT 1`, name)
if err := consumerRepo.DB().Raw(`SELECT id FROM tunnel WHERE name = ? ORDER BY id DESC LIMIT 1`, name).Row().Scan(&tunnelID); err != nil {
t.Fatalf("query tunnel id (%s): %v", name, err)
}
if tunnelID <= 0 { if tunnelID <= 0 {
t.Fatalf("invalid tunnel id for %s", name) t.Fatalf("invalid tunnel id for %s", name)
} }
@@ -261,10 +258,7 @@ func TestFederationDualPanelRemoteDiagnosisContract(t *testing.T) {
consumerRouter.ServeHTTP(createRes, createReq) consumerRouter.ServeHTTP(createRes, createReq)
assertCode(t, createRes, 0) assertCode(t, createRes, 0)
var tunnelID int64 tunnelID := mustQueryInt64(t, consumerRepo, `SELECT id FROM tunnel WHERE name = ? ORDER BY id DESC LIMIT 1`, "dual-panel-diagnose-remote")
if err := consumerRepo.DB().Raw(`SELECT id FROM tunnel WHERE name = ? ORDER BY id DESC LIMIT 1`, "dual-panel-diagnose-remote").Row().Scan(&tunnelID); err != nil {
t.Fatalf("query tunnel id: %v", err)
}
if tunnelID <= 0 { if tunnelID <= 0 {
t.Fatalf("invalid tunnel id") t.Fatalf("invalid tunnel id")
} }
@@ -414,10 +408,7 @@ func TestFederationDualPanelRemoteEntryRuntimeContract(t *testing.T) {
consumerRouter.ServeHTTP(res, req) consumerRouter.ServeHTTP(res, req)
assertCode(t, res, 0) assertCode(t, res, 0)
var tunnelID int64 tunnelID := mustQueryInt64(t, consumerRepo, `SELECT id FROM tunnel WHERE name = ? ORDER BY id DESC LIMIT 1`, name)
if err := consumerRepo.DB().Raw(`SELECT id FROM tunnel WHERE name = ? ORDER BY id DESC LIMIT 1`, name).Row().Scan(&tunnelID); err != nil {
t.Fatalf("query tunnel id (%s): %v", name, err)
}
if tunnelID <= 0 { if tunnelID <= 0 {
t.Fatalf("invalid tunnel id for %s", name) t.Fatalf("invalid tunnel id for %s", name)
} }
@@ -455,11 +446,7 @@ func insertContractNode(t *testing.T, r *repo.Repository, name, ip, portRange, s
`, name, secret, ip, ip, "", portRange, "", "v1", 1, 1, 1, now, now, status, "[::]", "[::]", 0).Error; err != nil { `, name, secret, ip, ip, "", portRange, "", "v1", 1, 1, 1, now, now, status, "[::]", "[::]", 0).Error; err != nil {
t.Fatalf("insert node %s: %v", name, err) t.Fatalf("insert node %s: %v", name, err)
} }
var id int64 return mustLastInsertID(t, r, name)
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("node id %s: %v", name, err)
}
return id
} }
func insertPeerShare(t *testing.T, r *repo.Repository, share *repo.PeerShare) int64 { func insertPeerShare(t *testing.T, r *repo.Repository, share *repo.PeerShare) int64 {
@@ -499,10 +486,7 @@ func importRemoteNodeForContract(t *testing.T, router http.Handler, adminToken,
func queryRemoteNodeIDByToken(t *testing.T, r *repo.Repository, token string) int64 { func queryRemoteNodeIDByToken(t *testing.T, r *repo.Repository, token string) int64 {
t.Helper() t.Helper()
var id int64 id := mustQueryInt64(t, r, `SELECT id FROM node WHERE is_remote = 1 AND remote_token = ? ORDER BY id DESC LIMIT 1`, token)
if err := r.DB().Raw(`SELECT id FROM node WHERE is_remote = 1 AND remote_token = ? ORDER BY id DESC LIMIT 1`, token).Row().Scan(&id); err != nil {
t.Fatalf("query remote node by token %s: %v", token, err)
}
if id <= 0 { if id <= 0 {
t.Fatalf("invalid remote node id for token %s", token) t.Fatalf("invalid remote node id for token %s", token)
} }
@@ -511,16 +495,7 @@ func queryRemoteNodeIDByToken(t *testing.T, r *repo.Repository, token string) in
func assertTunnelPortInRange(t *testing.T, r *repo.Repository, tunnelID int64, chainType int, nodeID int64, minPort int, maxPort int) { func assertTunnelPortInRange(t *testing.T, r *repo.Repository, tunnelID int64, chainType int, nodeID int64, minPort int, maxPort int) {
t.Helper() t.Helper()
var port int port := mustQueryInt(t, r, `SELECT port FROM chain_tunnel WHERE tunnel_id = ? AND chain_type = ? AND node_id = ? LIMIT 1`, tunnelID, chainType, nodeID)
err := r.DB().Raw(`
SELECT port
FROM chain_tunnel
WHERE tunnel_id = ? AND chain_type = ? AND node_id = ?
LIMIT 1
`, tunnelID, chainType, nodeID).Row().Scan(&port)
if err != nil {
t.Fatalf("query tunnel=%d chainType=%d node=%d port: %v", tunnelID, chainType, nodeID, err)
}
if port < minPort || port > maxPort { if port < minPort || port > maxPort {
t.Fatalf("expected port in range [%d,%d], got %d", minPort, maxPort, port) t.Fatalf("expected port in range [%d,%d], got %d", minPort, maxPort, port)
} }
@@ -528,10 +503,7 @@ func assertTunnelPortInRange(t *testing.T, r *repo.Repository, tunnelID int64, c
func assertCount(t *testing.T, r *repo.Repository, query string, arg interface{}, expected int) { func assertCount(t *testing.T, r *repo.Repository, query string, arg interface{}, expected int) {
t.Helper() t.Helper()
var got int got := mustQueryInt(t, r, query, arg)
if err := r.DB().Raw(query, arg).Row().Scan(&got); err != nil {
t.Fatalf("count query failed: %v", err)
}
if got != expected { if got != expected {
t.Fatalf("expected count %d, got %d (query: %s, arg: %v)", expected, got, query, arg) t.Fatalf("expected count %d, got %d (query: %s, arg: %v)", expected, got, query, arg)
} }
@@ -641,8 +613,8 @@ func waitNodeStatus(t *testing.T, r *repo.Repository, nodeID int64, expectedStat
t.Helper() t.Helper()
deadline := time.Now().Add(2 * time.Second) deadline := time.Now().Add(2 * time.Second)
for { for {
var status int status, err := tryQueryInt(t, r, `SELECT status FROM node WHERE id = ?`, nodeID)
if err := r.DB().Raw(`SELECT status FROM node WHERE id = ?`, nodeID).Row().Scan(&status); err == nil && status == expectedStatus { if err == nil && status == expectedStatus {
return return
} }
if time.Now().After(deadline) { if time.Now().After(deadline) {
@@ -31,10 +31,7 @@ func TestForwardOwnershipAndScopeContracts(t *testing.T) {
`, "contract-tunnel", 1.0, 1, "tls", 99999, now, now, 1, nil, 0).Error; err != nil { `, "contract-tunnel", 1.0, 1, "tls", 99999, now, now, 1, nil, 0).Error; err != nil {
t.Fatalf("insert tunnel: %v", err) t.Fatalf("insert tunnel: %v", err)
} }
var tunnelID int64 tunnelID := mustLastInsertID(t, repo, "contract-tunnel")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelID); err != nil {
t.Fatalf("get tunnel id: %v", err)
}
if err := repo.DB().Exec(` if err := repo.DB().Exec(`
INSERT INTO node(name, secret, server_ip, server_ip_v4, server_ip_v6, port, interface_name, version, http, tls, socks, created_time, updated_time, status, tcp_listen_addr, udp_listen_addr, inx) INSERT INTO node(name, secret, server_ip, server_ip_v4, server_ip_v6, port, interface_name, version, http, tls, socks, created_time, updated_time, status, tcp_listen_addr, udp_listen_addr, inx)
@@ -42,10 +39,7 @@ func TestForwardOwnershipAndScopeContracts(t *testing.T) {
`, "entry-node", "entry-secret", "10.0.0.10", "10.0.0.10", "", "20000-20010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil { `, "entry-node", "entry-secret", "10.0.0.10", "10.0.0.10", "", "20000-20010", "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil {
t.Fatalf("insert node: %v", err) t.Fatalf("insert node: %v", err)
} }
var entryNodeID int64 entryNodeID := mustLastInsertID(t, repo, "entry-node")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&entryNodeID); err != nil {
t.Fatalf("get node id: %v", err)
}
if err := repo.DB().Exec(` if err := repo.DB().Exec(`
INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol) INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol)
@@ -60,10 +54,7 @@ func TestForwardOwnershipAndScopeContracts(t *testing.T) {
`, 1, "admin_user", "admin-forward", tunnelID, "1.1.1.1:443", "fifo", now, now, 0).Error; err != nil { `, 1, "admin_user", "admin-forward", tunnelID, "1.1.1.1:443", "fifo", now, now, 0).Error; err != nil {
t.Fatalf("insert admin forward: %v", err) t.Fatalf("insert admin forward: %v", err)
} }
var adminForwardID int64 adminForwardID := mustLastInsertID(t, repo, "admin-forward")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&adminForwardID); err != nil {
t.Fatalf("get admin forward id: %v", err)
}
if err := repo.DB().Exec(` if err := repo.DB().Exec(`
INSERT INTO forward(user_id, user_name, name, tunnel_id, remote_addr, strategy, in_flow, out_flow, created_time, updated_time, status, inx) INSERT INTO forward(user_id, user_name, name, tunnel_id, remote_addr, strategy, in_flow, out_flow, created_time, updated_time, status, inx)
@@ -71,10 +62,7 @@ func TestForwardOwnershipAndScopeContracts(t *testing.T) {
`, 2, "normal_user", "user-forward", tunnelID, "8.8.8.8:53", "fifo", now, now, 1).Error; err != nil { `, 2, "normal_user", "user-forward", tunnelID, "8.8.8.8:53", "fifo", now, now, 1).Error; err != nil {
t.Fatalf("insert user forward: %v", err) t.Fatalf("insert user forward: %v", err)
} }
var userForwardID int64 userForwardID := mustLastInsertID(t, repo, "user-forward")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&userForwardID); err != nil {
t.Fatalf("get user forward id: %v", err)
}
userToken, err := auth.GenerateToken(2, "normal_user", 1, secret) userToken, err := auth.GenerateToken(2, "normal_user", 1, secret)
if err != nil { if err != nil {
@@ -217,11 +205,7 @@ func TestForwardSwitchTunnelRollbackOnSyncFailure(t *testing.T) {
`, name, 1.0, 1, "tls", 99999, now, now, 1, nil, inx).Error; err != nil { `, name, 1.0, 1, "tls", 99999, now, now, 1, nil, inx).Error; err != nil {
t.Fatalf("insert tunnel %s: %v", name, err) t.Fatalf("insert tunnel %s: %v", name, err)
} }
var id int64 return mustLastInsertID(t, repo, name)
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("get tunnel id %s: %v", name, err)
}
return id
} }
insertNode := func(name, ip, portRange string, inx int) int64 { insertNode := func(name, ip, portRange string, inx int) int64 {
@@ -231,11 +215,7 @@ func TestForwardSwitchTunnelRollbackOnSyncFailure(t *testing.T) {
`, name, name+"-secret", ip, ip, "", portRange, "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", inx).Error; err != nil { `, name, name+"-secret", ip, ip, "", portRange, "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", inx).Error; err != nil {
t.Fatalf("insert node %s: %v", name, err) t.Fatalf("insert node %s: %v", name, err)
} }
var id int64 return mustLastInsertID(t, repo, name)
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("get node id %s: %v", name, err)
}
return id
} }
tunnelA := insertTunnel("switch-tunnel-a", 0) tunnelA := insertTunnel("switch-tunnel-a", 0)
@@ -275,10 +255,7 @@ func TestForwardSwitchTunnelRollbackOnSyncFailure(t *testing.T) {
`, tunnelA, now, now).Error; err != nil { `, tunnelA, now, now).Error; err != nil {
t.Fatalf("insert forward: %v", err) t.Fatalf("insert forward: %v", err)
} }
var forwardID int64 forwardID := mustLastInsertID(t, repo, "switch-forward")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&forwardID); err != nil {
t.Fatalf("get forward id: %v", err)
}
if err := repo.DB().Exec(`INSERT INTO forward_port(forward_id, node_id, port) VALUES(?, ?, ?)`, forwardID, nodeA, 21001).Error; err != nil { if err := repo.DB().Exec(`INSERT INTO forward_port(forward_id, node_id, port) VALUES(?, ?, ?)`, forwardID, nodeA, 21001).Error; err != nil {
t.Fatalf("insert forward_port: %v", err) t.Fatalf("insert forward_port: %v", err)
@@ -300,19 +277,12 @@ func TestForwardSwitchTunnelRollbackOnSyncFailure(t *testing.T) {
t.Fatalf("expected update failure when node is offline") t.Fatalf("expected update failure when node is offline")
} }
var tunnelAfter int64 tunnelAfter := mustQueryInt64(t, repo, `SELECT tunnel_id FROM forward WHERE id = ?`, forwardID)
if err := repo.DB().Raw(`SELECT tunnel_id FROM forward WHERE id = ?`, forwardID).Row().Scan(&tunnelAfter); err != nil {
t.Fatalf("query forward tunnel_id: %v", err)
}
if tunnelAfter != tunnelA { if tunnelAfter != tunnelA {
t.Fatalf("expected tunnel rollback to %d, got %d", tunnelA, tunnelAfter) t.Fatalf("expected tunnel rollback to %d, got %d", tunnelA, tunnelAfter)
} }
var nodeAfter int64 nodeAfter, portAfter := mustQueryInt64Int(t, repo, `SELECT node_id, port FROM forward_port WHERE forward_id = ? LIMIT 1`, forwardID)
var portAfter int
if err := repo.DB().Raw(`SELECT node_id, port FROM forward_port WHERE forward_id = ? LIMIT 1`, forwardID).Row().Scan(&nodeAfter, &portAfter); err != nil {
t.Fatalf("query forward_port: %v", err)
}
if nodeAfter != nodeA || portAfter != 21001 { if nodeAfter != nodeA || portAfter != 21001 {
t.Fatalf("expected forward_port rollback to node=%d port=21001, got node=%d port=%d", nodeA, nodeAfter, portAfter) t.Fatalf("expected forward_port rollback to node=%d port=21001, got node=%d port=%d", nodeA, nodeAfter, portAfter)
} }
@@ -341,10 +311,7 @@ func TestForwardBatchChangeTunnelRollbackOnSyncFailure(t *testing.T) {
`, now, now).Error; err != nil { `, now, now).Error; err != nil {
t.Fatalf("insert tunnel A: %v", err) t.Fatalf("insert tunnel A: %v", err)
} }
var tunnelA int64 tunnelA := mustLastInsertID(t, repo, "batch-switch-tunnel-a")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelA); err != nil {
t.Fatal(err)
}
if err := repo.DB().Exec(` if err := repo.DB().Exec(`
INSERT INTO tunnel(name, traffic_ratio, type, protocol, flow, created_time, updated_time, status, in_ip, inx) INSERT INTO tunnel(name, traffic_ratio, type, protocol, flow, created_time, updated_time, status, in_ip, inx)
@@ -352,10 +319,7 @@ func TestForwardBatchChangeTunnelRollbackOnSyncFailure(t *testing.T) {
`, now, now).Error; err != nil { `, now, now).Error; err != nil {
t.Fatalf("insert tunnel B: %v", err) t.Fatalf("insert tunnel B: %v", err)
} }
var tunnelB int64 tunnelB := mustLastInsertID(t, repo, "batch-switch-tunnel-b")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelB); err != nil {
t.Fatal(err)
}
if err := repo.DB().Exec(` if err := repo.DB().Exec(`
INSERT INTO node(name, secret, server_ip, server_ip_v4, server_ip_v6, port, interface_name, version, http, tls, socks, created_time, updated_time, status, tcp_listen_addr, udp_listen_addr, inx) INSERT INTO node(name, secret, server_ip, server_ip_v4, server_ip_v6, port, interface_name, version, http, tls, socks, created_time, updated_time, status, tcp_listen_addr, udp_listen_addr, inx)
@@ -363,10 +327,7 @@ func TestForwardBatchChangeTunnelRollbackOnSyncFailure(t *testing.T) {
`, now, now).Error; err != nil { `, now, now).Error; err != nil {
t.Fatalf("insert node A: %v", err) t.Fatalf("insert node A: %v", err)
} }
var nodeA int64 nodeA := mustLastInsertID(t, repo, "batch-switch-node-a")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&nodeA); err != nil {
t.Fatal(err)
}
if err := repo.DB().Exec(` if err := repo.DB().Exec(`
INSERT INTO node(name, secret, server_ip, server_ip_v4, server_ip_v6, port, interface_name, version, http, tls, socks, created_time, updated_time, status, tcp_listen_addr, udp_listen_addr, inx) INSERT INTO node(name, secret, server_ip, server_ip_v4, server_ip_v6, port, interface_name, version, http, tls, socks, created_time, updated_time, status, tcp_listen_addr, udp_listen_addr, inx)
@@ -374,10 +335,7 @@ func TestForwardBatchChangeTunnelRollbackOnSyncFailure(t *testing.T) {
`, now, now).Error; err != nil { `, now, now).Error; err != nil {
t.Fatalf("insert node B: %v", err) t.Fatalf("insert node B: %v", err)
} }
var nodeB int64 nodeB := mustLastInsertID(t, repo, "batch-switch-node-b")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&nodeB); err != nil {
t.Fatal(err)
}
if err := repo.DB().Exec(`INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol) VALUES(?, 1, ?, 23001, 'round', 1, 'tls')`, tunnelA, nodeA).Error; err != nil { if err := repo.DB().Exec(`INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol) VALUES(?, 1, ?, 23001, 'round', 1, 'tls')`, tunnelA, nodeA).Error; err != nil {
t.Fatalf("insert chain_tunnel A: %v", err) t.Fatalf("insert chain_tunnel A: %v", err)
@@ -399,10 +357,7 @@ func TestForwardBatchChangeTunnelRollbackOnSyncFailure(t *testing.T) {
`, tunnelA, now, now).Error; err != nil { `, tunnelA, now, now).Error; err != nil {
t.Fatalf("insert forward: %v", err) t.Fatalf("insert forward: %v", err)
} }
var forwardID int64 forwardID := mustLastInsertID(t, repo, "batch-switch-forward")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&forwardID); err != nil {
t.Fatal(err)
}
if err := repo.DB().Exec(`INSERT INTO forward_port(forward_id, node_id, port) VALUES(?, ?, ?)`, forwardID, nodeA, 23001).Error; err != nil { if err := repo.DB().Exec(`INSERT INTO forward_port(forward_id, node_id, port) VALUES(?, ?, ?)`, forwardID, nodeA, 23001).Error; err != nil {
t.Fatalf("insert forward_port: %v", err) t.Fatalf("insert forward_port: %v", err)
@@ -432,19 +387,12 @@ func TestForwardBatchChangeTunnelRollbackOnSyncFailure(t *testing.T) {
t.Fatalf("expected failCount=1, got %v", result["failCount"]) t.Fatalf("expected failCount=1, got %v", result["failCount"])
} }
var tunnelAfter int64 tunnelAfter := mustQueryInt64(t, repo, `SELECT tunnel_id FROM forward WHERE id = ?`, forwardID)
if err := repo.DB().Raw(`SELECT tunnel_id FROM forward WHERE id = ?`, forwardID).Row().Scan(&tunnelAfter); err != nil {
t.Fatalf("query forward tunnel_id: %v", err)
}
if tunnelAfter != tunnelA { if tunnelAfter != tunnelA {
t.Fatalf("expected tunnel rollback to %d, got %d", tunnelA, tunnelAfter) t.Fatalf("expected tunnel rollback to %d, got %d", tunnelA, tunnelAfter)
} }
var nodeAfter int64 nodeAfter, portAfter := mustQueryInt64Int(t, repo, `SELECT node_id, port FROM forward_port WHERE forward_id = ? LIMIT 1`, forwardID)
var portAfter int
if err := repo.DB().Raw(`SELECT node_id, port FROM forward_port WHERE forward_id = ? LIMIT 1`, forwardID).Row().Scan(&nodeAfter, &portAfter); err != nil {
t.Fatalf("query forward_port: %v", err)
}
if nodeAfter != nodeA || portAfter != 23001 { if nodeAfter != nodeA || portAfter != 23001 {
t.Fatalf("expected forward_port rollback to node=%d port=23001, got node=%d port=%d", nodeA, nodeAfter, portAfter) t.Fatalf("expected forward_port rollback to node=%d port=23001, got node=%d port=%d", nodeA, nodeAfter, portAfter)
} }
@@ -473,10 +421,7 @@ func TestUserTunnelReassignmentKeepsStableID(t *testing.T) {
`, now, now).Error; err != nil { `, now, now).Error; err != nil {
t.Fatalf("insert tunnel: %v", err) t.Fatalf("insert tunnel: %v", err)
} }
var tunnelID int64 tunnelID := mustLastInsertID(t, repo, "stable-tunnel")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelID); err != nil {
t.Fatal(err)
}
// 1. Assign permission (creates new user_tunnel) // 1. Assign permission (creates new user_tunnel)
// userTunnelBatchAssign expects structure: {userId: 123, tunnels: [{tunnelId: 456, ...}]} // userTunnelBatchAssign expects structure: {userId: 123, tunnels: [{tunnelId: 456, ...}]}
@@ -495,10 +440,7 @@ func TestUserTunnelReassignmentKeepsStableID(t *testing.T) {
t.Fatalf("expected code 0, got %d msg=%q", out.Code, out.Msg) t.Fatalf("expected code 0, got %d msg=%q", out.Code, out.Msg)
} }
var initialID int64 initialID := mustQueryInt64(t, repo, `SELECT id FROM user_tunnel WHERE user_id = 100 AND tunnel_id = ?`, tunnelID)
if err := repo.DB().Raw(`SELECT id FROM user_tunnel WHERE user_id = 100 AND tunnel_id = ?`, tunnelID).Row().Scan(&initialID); err != nil {
t.Fatalf("query initial user_tunnel id: %v", err)
}
// 2. Re-assign permission (should UPDATE, not INSERT) // 2. Re-assign permission (should UPDATE, not INSERT)
reassignPayload := `{"userId":100,"tunnels":[{"tunnelId":` + jsonNumber(tunnelID) + `}]}` reassignPayload := `{"userId":100,"tunnels":[{"tunnelId":` + jsonNumber(tunnelID) + `}]}`
@@ -517,18 +459,12 @@ func TestUserTunnelReassignmentKeepsStableID(t *testing.T) {
} }
// 3. Verify stable ID and no duplicates // 3. Verify stable ID and no duplicates
var count int count := mustQueryInt(t, repo, `SELECT COUNT(1) FROM user_tunnel WHERE user_id = 100 AND tunnel_id = ?`, tunnelID)
if err := repo.DB().Raw(`SELECT COUNT(1) FROM user_tunnel WHERE user_id = 100 AND tunnel_id = ?`, tunnelID).Row().Scan(&count); err != nil {
t.Fatalf("query count: %v", err)
}
if count != 1 { if count != 1 {
t.Fatalf("expected exactly 1 user_tunnel record, got %d", count) t.Fatalf("expected exactly 1 user_tunnel record, got %d", count)
} }
var currentID int64 currentID := mustQueryInt64(t, repo, `SELECT id FROM user_tunnel WHERE user_id = 100 AND tunnel_id = ?`, tunnelID)
if err := repo.DB().Raw(`SELECT id FROM user_tunnel WHERE user_id = 100 AND tunnel_id = ?`, tunnelID).Row().Scan(&currentID); err != nil {
t.Fatalf("query current user_tunnel: %v", err)
}
if currentID != initialID { if currentID != initialID {
t.Fatalf("user_tunnel ID changed from %d to %d (unstable ID!)", initialID, currentID) t.Fatalf("user_tunnel ID changed from %d to %d (unstable ID!)", initialID, currentID)
@@ -28,26 +28,17 @@ func TestGroupUserUnbindRevokesInheritedTunnelPermission(t *testing.T) {
`, now, now).Error; err != nil { `, now, now).Error; err != nil {
t.Fatalf("insert tunnel: %v", err) t.Fatalf("insert tunnel: %v", err)
} }
var tunnelID int64 tunnelID := mustLastInsertID(t, repo, "group-contract-tunnel")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelID); err != nil {
t.Fatalf("read tunnel id: %v", err)
}
if err := repo.DB().Exec(`INSERT INTO user_group(name, created_time, updated_time, status) VALUES('ug-contract', ?, ?, 1)`, now, now).Error; err != nil { if err := repo.DB().Exec(`INSERT INTO user_group(name, created_time, updated_time, status) VALUES('ug-contract', ?, ?, 1)`, now, now).Error; err != nil {
t.Fatalf("insert user_group: %v", err) t.Fatalf("insert user_group: %v", err)
} }
var userGroupID int64 userGroupID := mustLastInsertID(t, repo, "ug-contract")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&userGroupID); err != nil {
t.Fatalf("read user_group id: %v", err)
}
if err := repo.DB().Exec(`INSERT INTO tunnel_group(name, created_time, updated_time, status) VALUES('tg-contract', ?, ?, 1)`, now, now).Error; err != nil { if err := repo.DB().Exec(`INSERT INTO tunnel_group(name, created_time, updated_time, status) VALUES('tg-contract', ?, ?, 1)`, now, now).Error; err != nil {
t.Fatalf("insert tunnel_group: %v", err) t.Fatalf("insert tunnel_group: %v", err)
} }
var tunnelGroupID int64 tunnelGroupID := mustLastInsertID(t, repo, "tg-contract")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelGroupID); err != nil {
t.Fatalf("read tunnel_group id: %v", err)
}
if err := repo.DB().Exec(`INSERT INTO tunnel_group_tunnel(tunnel_group_id, tunnel_id, created_time) VALUES(?, ?, ?)`, tunnelGroupID, tunnelID, now).Error; err != nil { if err := repo.DB().Exec(`INSERT INTO tunnel_group_tunnel(tunnel_group_id, tunnel_id, created_time) VALUES(?, ?, ?)`, tunnelGroupID, tunnelID, now).Error; err != nil {
t.Fatalf("insert tunnel_group_tunnel: %v", err) t.Fatalf("insert tunnel_group_tunnel: %v", err)
@@ -67,15 +58,9 @@ func TestGroupUserUnbindRevokesInheritedTunnelPermission(t *testing.T) {
router.ServeHTTP(bindRes, bindReq) router.ServeHTTP(bindRes, bindReq)
assertCode(t, bindRes, 0) assertCode(t, bindRes, 0)
var userTunnelID int64 userTunnelID := mustQueryInt64(t, repo, `SELECT id FROM user_tunnel WHERE user_id = 200 AND tunnel_id = ?`, tunnelID)
if err := repo.DB().Raw(`SELECT id FROM user_tunnel WHERE user_id = 200 AND tunnel_id = ?`, tunnelID).Row().Scan(&userTunnelID); err != nil {
t.Fatalf("query user_tunnel after bind: %v", err)
}
var grantCount int grantCount := mustQueryInt(t, repo, `SELECT COUNT(1) FROM group_permission_grant WHERE user_tunnel_id = ?`, userTunnelID)
if err := repo.DB().Raw(`SELECT COUNT(1) FROM group_permission_grant WHERE user_tunnel_id = ?`, userTunnelID).Row().Scan(&grantCount); err != nil {
t.Fatalf("query group_permission_grant after bind: %v", err)
}
if grantCount == 0 { if grantCount == 0 {
t.Fatalf("expected non-zero grants after bind") t.Fatalf("expected non-zero grants after bind")
} }
@@ -86,17 +71,12 @@ func TestGroupUserUnbindRevokesInheritedTunnelPermission(t *testing.T) {
router.ServeHTTP(unbindRes, unbindReq) router.ServeHTTP(unbindRes, unbindReq)
assertCode(t, unbindRes, 0) assertCode(t, unbindRes, 0)
if err := repo.DB().Raw(`SELECT COUNT(1) FROM group_permission_grant WHERE user_tunnel_id = ?`, userTunnelID).Row().Scan(&grantCount); err != nil { grantCount = mustQueryInt(t, repo, `SELECT COUNT(1) FROM group_permission_grant WHERE user_tunnel_id = ?`, userTunnelID)
t.Fatalf("query group_permission_grant after unbind: %v", err)
}
if grantCount != 0 { if grantCount != 0 {
t.Fatalf("expected grants revoked after unbind, got %d", grantCount) t.Fatalf("expected grants revoked after unbind, got %d", grantCount)
} }
var userTunnelCount int userTunnelCount := mustQueryInt(t, repo, `SELECT COUNT(1) FROM user_tunnel WHERE id = ?`, userTunnelID)
if err := repo.DB().Raw(`SELECT COUNT(1) FROM user_tunnel WHERE id = ?`, userTunnelID).Row().Scan(&userTunnelCount); err != nil {
t.Fatalf("query user_tunnel after unbind: %v", err)
}
if userTunnelCount != 0 { if userTunnelCount != 0 {
t.Fatalf("expected user_tunnel revoked after unbind, got %d", userTunnelCount) t.Fatalf("expected user_tunnel revoked after unbind, got %d", userTunnelCount)
} }
@@ -120,26 +100,17 @@ func TestGroupPermissionRemoveRevokesInheritedTunnelPermission(t *testing.T) {
`, now, now).Error; err != nil { `, now, now).Error; err != nil {
t.Fatalf("insert tunnel: %v", err) t.Fatalf("insert tunnel: %v", err)
} }
var tunnelID int64 tunnelID := mustLastInsertID(t, repo, "group-remove-tunnel")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelID); err != nil {
t.Fatalf("read tunnel id: %v", err)
}
if err := repo.DB().Exec(`INSERT INTO user_group(name, created_time, updated_time, status) VALUES('ug-remove-contract', ?, ?, 1)`, now, now).Error; err != nil { if err := repo.DB().Exec(`INSERT INTO user_group(name, created_time, updated_time, status) VALUES('ug-remove-contract', ?, ?, 1)`, now, now).Error; err != nil {
t.Fatalf("insert user_group: %v", err) t.Fatalf("insert user_group: %v", err)
} }
var userGroupID int64 userGroupID := mustLastInsertID(t, repo, "ug-remove-contract")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&userGroupID); err != nil {
t.Fatalf("read user_group id: %v", err)
}
if err := repo.DB().Exec(`INSERT INTO tunnel_group(name, created_time, updated_time, status) VALUES('tg-remove-contract', ?, ?, 1)`, now, now).Error; err != nil { if err := repo.DB().Exec(`INSERT INTO tunnel_group(name, created_time, updated_time, status) VALUES('tg-remove-contract', ?, ?, 1)`, now, now).Error; err != nil {
t.Fatalf("insert tunnel_group: %v", err) t.Fatalf("insert tunnel_group: %v", err)
} }
var tunnelGroupID int64 tunnelGroupID := mustLastInsertID(t, repo, "tg-remove-contract")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelGroupID); err != nil {
t.Fatalf("read tunnel_group id: %v", err)
}
adminToken, err := auth.GenerateToken(1, "admin_user", 0, secret) adminToken, err := auth.GenerateToken(1, "admin_user", 0, secret)
if err != nil { if err != nil {
@@ -164,20 +135,11 @@ func TestGroupPermissionRemoveRevokesInheritedTunnelPermission(t *testing.T) {
router.ServeHTTP(assignPermissionRes, assignPermissionReq) router.ServeHTTP(assignPermissionRes, assignPermissionReq)
assertCode(t, assignPermissionRes, 0) assertCode(t, assignPermissionRes, 0)
var permissionID int64 permissionID := mustQueryInt64(t, repo, `SELECT id FROM group_permission WHERE user_group_id = ? AND tunnel_group_id = ?`, userGroupID, tunnelGroupID)
if err := repo.DB().Raw(`SELECT id FROM group_permission WHERE user_group_id = ? AND tunnel_group_id = ?`, userGroupID, tunnelGroupID).Row().Scan(&permissionID); err != nil {
t.Fatalf("query group_permission id: %v", err)
}
var userTunnelID int64 userTunnelID := mustQueryInt64(t, repo, `SELECT id FROM user_tunnel WHERE user_id = 201 AND tunnel_id = ?`, tunnelID)
if err := repo.DB().Raw(`SELECT id FROM user_tunnel WHERE user_id = 201 AND tunnel_id = ?`, tunnelID).Row().Scan(&userTunnelID); err != nil {
t.Fatalf("query user_tunnel after assign: %v", err)
}
var grantCount int grantCount := mustQueryInt(t, repo, `SELECT COUNT(1) FROM group_permission_grant WHERE user_tunnel_id = ?`, userTunnelID)
if err := repo.DB().Raw(`SELECT COUNT(1) FROM group_permission_grant WHERE user_tunnel_id = ?`, userTunnelID).Row().Scan(&grantCount); err != nil {
t.Fatalf("query group_permission_grant after assign: %v", err)
}
if grantCount == 0 { if grantCount == 0 {
t.Fatalf("expected non-zero grants after permission assign") t.Fatalf("expected non-zero grants after permission assign")
} }
@@ -188,25 +150,17 @@ func TestGroupPermissionRemoveRevokesInheritedTunnelPermission(t *testing.T) {
router.ServeHTTP(removeRes, removeReq) router.ServeHTTP(removeRes, removeReq)
assertCode(t, removeRes, 0) assertCode(t, removeRes, 0)
var permissionCount int permissionCount := mustQueryInt(t, repo, `SELECT COUNT(1) FROM group_permission WHERE id = ?`, permissionID)
if err := repo.DB().Raw(`SELECT COUNT(1) FROM group_permission WHERE id = ?`, permissionID).Row().Scan(&permissionCount); err != nil {
t.Fatalf("query group_permission after remove: %v", err)
}
if permissionCount != 0 { if permissionCount != 0 {
t.Fatalf("expected group_permission removed, got %d", permissionCount) t.Fatalf("expected group_permission removed, got %d", permissionCount)
} }
if err := repo.DB().Raw(`SELECT COUNT(1) FROM group_permission_grant WHERE user_tunnel_id = ?`, userTunnelID).Row().Scan(&grantCount); err != nil { grantCount = mustQueryInt(t, repo, `SELECT COUNT(1) FROM group_permission_grant WHERE user_tunnel_id = ?`, userTunnelID)
t.Fatalf("query group_permission_grant after remove: %v", err)
}
if grantCount != 0 { if grantCount != 0 {
t.Fatalf("expected grants removed after permission remove, got %d", grantCount) t.Fatalf("expected grants removed after permission remove, got %d", grantCount)
} }
var userTunnelCount int userTunnelCount := mustQueryInt(t, repo, `SELECT COUNT(1) FROM user_tunnel WHERE id = ?`, userTunnelID)
if err := repo.DB().Raw(`SELECT COUNT(1) FROM user_tunnel WHERE id = ?`, userTunnelID).Row().Scan(&userTunnelCount); err != nil {
t.Fatalf("query user_tunnel after permission remove: %v", err)
}
if userTunnelCount != 0 { if userTunnelCount != 0 {
t.Fatalf("expected user_tunnel revoked after permission remove, got %d", userTunnelCount) t.Fatalf("expected user_tunnel revoked after permission remove, got %d", userTunnelCount)
} }
@@ -94,10 +94,7 @@ func TestOpenAPISubStoreContracts(t *testing.T) {
"contract-tunnel", 1.0, 1, "tls", 1, now, now, 1, nil, 0).Error; err != nil { "contract-tunnel", 1.0, 1, "tls", 1, now, now, 1, nil, 0).Error; err != nil {
t.Fatalf("insert tunnel: %v", err) t.Fatalf("insert tunnel: %v", err)
} }
var tunnelID int64 tunnelID := mustLastInsertID(t, r, "contract-tunnel")
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelID); err != nil {
t.Fatalf("last insert id: %v", err)
}
if err := r.DB().Exec(`INSERT INTO user_tunnel(user_id, tunnel_id, speed_id, num, flow, in_flow, out_flow, flow_reset_time, exp_time, status) VALUES(?, ?, NULL, ?, ?, ?, ?, ?, ?, ?)`, if err := r.DB().Exec(`INSERT INTO user_tunnel(user_id, tunnel_id, speed_id, num, flow, in_flow, out_flow, flow_reset_time, exp_time, status) VALUES(?, ?, NULL, ?, ?, ?, ?, ?, ?, ?)`,
1, tunnelID, 99999, tunnelFlowGB, tunnelInFlow, tunnelOutFlow, 1, tunnelExpTimeMs, 1).Error; err != nil { 1, tunnelID, 99999, tunnelFlowGB, tunnelInFlow, tunnelOutFlow, 1, tunnelExpTimeMs, 1).Error; err != nil {
t.Fatalf("insert user_tunnel: %v", err) t.Fatalf("insert user_tunnel: %v", err)
@@ -317,10 +314,7 @@ func TestBackupExportImportRestoreContracts(t *testing.T) {
`, "backup-forward-tunnel", 1.0, 1, "tls", 0, now, now, 1, "", 88).Error; err != nil { `, "backup-forward-tunnel", 1.0, 1, "tls", 0, now, now, 1, "", 88).Error; err != nil {
t.Fatalf("seed tunnel for forward backup: %v", err) t.Fatalf("seed tunnel for forward backup: %v", err)
} }
var tunnelID int64 tunnelID := mustLastInsertID(t, r, "backup-forward-tunnel")
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelID); err != nil {
t.Fatalf("read tunnel id for forward backup: %v", err)
}
if err := r.DB().Exec(` if err := r.DB().Exec(`
INSERT INTO forward(user_id, user_name, name, tunnel_id, remote_addr, strategy, in_flow, out_flow, created_time, updated_time, status, inx) INSERT INTO forward(user_id, user_name, name, tunnel_id, remote_addr, strategy, in_flow, out_flow, created_time, updated_time, status, inx)
@@ -328,10 +322,7 @@ func TestBackupExportImportRestoreContracts(t *testing.T) {
`, 1, "admin_user", "backup-forward", tunnelID, "127.0.0.1:9000", "fifo", 0, 0, now, now, 1, 88).Error; err != nil { `, 1, "admin_user", "backup-forward", tunnelID, "127.0.0.1:9000", "fifo", 0, 0, now, now, 1, 88).Error; err != nil {
t.Fatalf("seed forward for backup: %v", err) t.Fatalf("seed forward for backup: %v", err)
} }
var forwardID int64 forwardID := mustLastInsertID(t, r, "backup-forward")
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&forwardID); err != nil {
t.Fatalf("read forward id for backup: %v", err)
}
expected := map[int64]int{ expected := map[int64]int{
2001: 21001, 2001: 21001,
@@ -442,24 +433,7 @@ func TestBackupExportImportRestoreContracts(t *testing.T) {
t.Fatalf("expected forwards import code 0, got %d (%s)", out.Code, out.Msg) t.Fatalf("expected forwards import code 0, got %d (%s)", out.Code, out.Msg)
} }
rows, err := r.DB().Raw(`SELECT node_id, port FROM forward_port WHERE forward_id = ? ORDER BY id ASC`, forwardID).Rows() after := mustQueryNodePorts(t, r, `SELECT node_id, port FROM forward_port WHERE forward_id = ? ORDER BY id ASC`, forwardID)
if err != nil {
t.Fatalf("query forward ports after import: %v", err)
}
defer rows.Close()
after := make(map[int64]int)
for rows.Next() {
var nodeID int64
var port int
if err := rows.Scan(&nodeID, &port); err != nil {
t.Fatalf("scan forward_port row: %v", err)
}
after[nodeID] = port
}
if err := rows.Err(); err != nil {
t.Fatalf("iterate forward_port rows: %v", err)
}
if len(after) != len(expected) { if len(after) != len(expected) {
t.Fatalf("expected %d forward ports after import, got %d (%v)", len(expected), len(after), after) t.Fatalf("expected %d forward ports after import, got %d (%v)", len(expected), len(after), after)
@@ -479,10 +453,7 @@ func TestBackupExportImportRestoreContracts(t *testing.T) {
`, "legacy-null-chain", 1.0, 1, "tls", 1000, now, now, 1, nil, 1).Error; err != nil { `, "legacy-null-chain", 1.0, 1, "tls", 1000, now, now, 1, nil, 1).Error; err != nil {
t.Fatalf("seed tunnel for nullable chain export: %v", err) t.Fatalf("seed tunnel for nullable chain export: %v", err)
} }
var tunnelID int64 tunnelID := mustLastInsertID(t, r, "legacy-null-chain")
if err := r.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelID); err != nil {
t.Fatalf("read tunnel id for nullable chain export: %v", err)
}
if err := r.DB().Exec(` if err := r.DB().Exec(`
INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol) INSERT INTO chain_tunnel(tunnel_id, chain_type, node_id, port, strategy, inx, protocol)
@@ -696,25 +667,19 @@ func TestOpenMigratesLegacyNodeDualStackColumns(t *testing.T) {
func readTableColumns(t *testing.T, db *gorm.DB, table string) map[string]bool { func readTableColumns(t *testing.T, db *gorm.DB, table string) map[string]bool {
t.Helper() t.Helper()
rows, err := db.Raw("PRAGMA table_info(" + table + ")").Rows() columnTypes, err := db.Migrator().ColumnTypes(table)
if err != nil { if err != nil {
t.Fatalf("inspect %s columns: %v", table, err) t.Fatalf("inspect %s columns: %v", table, err)
} }
defer rows.Close()
columns := map[string]bool{} columns := map[string]bool{}
for rows.Next() { for _, col := range columnTypes {
var cid, notNull, pk int name := strings.TrimSpace(col.Name())
var name, typ string if name == "" {
var defaultValue sql.NullString continue
if err := rows.Scan(&cid, &name, &typ, &notNull, &defaultValue, &pk); err != nil {
t.Fatalf("scan %s pragma row: %v", table, err)
} }
columns[name] = true columns[name] = true
} }
if err := rows.Err(); err != nil {
t.Fatalf("iterate %s pragma rows: %v", table, err)
}
return columns return columns
} }
@@ -64,17 +64,14 @@ func TestPostgresNodeCreateRepairsMissingIDDefaultContract(t *testing.T) {
_ = r.Close() _ = r.Close()
}) })
var columnDefault sql.NullString columnDefault := mustQueryNullString(t, r, `
if err := r.DB().Raw(`
SELECT column_default SELECT column_default
FROM information_schema.columns FROM information_schema.columns
WHERE table_schema = current_schema() WHERE table_schema = current_schema()
AND table_name = 'node' AND table_name = 'node'
AND column_name = 'id' AND column_name = 'id'
LIMIT 1 LIMIT 1
`).Row().Scan(&columnDefault); err != nil { `)
t.Fatalf("query node.id default: %v", err)
}
if !columnDefault.Valid || !strings.Contains(strings.ToLower(columnDefault.String), "nextval(") { if !columnDefault.Valid || !strings.Contains(strings.ToLower(columnDefault.String), "nextval(") {
t.Fatalf("expected node.id default to be nextval(...), got %q", columnDefault.String) t.Fatalf("expected node.id default to be nextval(...), got %q", columnDefault.String)
} }
@@ -94,10 +91,7 @@ func TestPostgresNodeCreateRepairsMissingIDDefaultContract(t *testing.T) {
router.ServeHTTP(resp, req) router.ServeHTTP(resp, req)
assertCode(t, resp, 0) assertCode(t, resp, 0)
var nodeID int64 nodeID := mustQueryInt64(t, r, `SELECT id FROM node WHERE name = ? ORDER BY id DESC LIMIT 1`, "pg-repair-node")
if err := r.DB().Raw(`SELECT id FROM node WHERE name = ? ORDER BY id DESC LIMIT 1`, "pg-repair-node").Row().Scan(&nodeID); err != nil {
t.Fatalf("query created node: %v", err)
}
if nodeID <= 0 { if nodeID <= 0 {
t.Fatalf("expected positive node id, got %d", nodeID) t.Fatalf("expected positive node id, got %d", nodeID)
} }
@@ -2,7 +2,6 @@ package contract_test
import ( import (
"bytes" "bytes"
"database/sql"
"encoding/json" "encoding/json"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@@ -32,11 +31,7 @@ func TestTunnelCreateRuntimeRollbackContract(t *testing.T) {
`, name, name+"-secret", ip, ip, "", portRange, "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil { `, name, name+"-secret", ip, ip, "", portRange, "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil {
t.Fatalf("insert node %s: %v", name, err) t.Fatalf("insert node %s: %v", name, err)
} }
var id int64 return mustLastInsertID(t, repo, name)
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("get node id %s: %v", name, err)
}
return id
} }
entryID := insertNode("create-entry", "10.20.0.1", "30000-30010") entryID := insertNode("create-entry", "10.20.0.1", "30000-30010")
@@ -62,18 +57,12 @@ func TestTunnelCreateRuntimeRollbackContract(t *testing.T) {
t.Fatalf("expected node-related error, got %q", out.Msg) t.Fatalf("expected node-related error, got %q", out.Msg)
} }
var tunnelCount int tunnelCount := mustQueryInt(t, repo, `SELECT COUNT(1) FROM tunnel WHERE name = ?`, "runtime-rollback-tunnel")
if err := repo.DB().Raw(`SELECT COUNT(1) FROM tunnel WHERE name = ?`, "runtime-rollback-tunnel").Row().Scan(&tunnelCount); err != nil {
t.Fatalf("count tunnel: %v", err)
}
if tunnelCount != 0 { if tunnelCount != 0 {
t.Fatalf("expected tunnel rollback, found %d records", tunnelCount) t.Fatalf("expected tunnel rollback, found %d records", tunnelCount)
} }
var chainCount int chainCount := mustQueryInt(t, repo, `SELECT COUNT(1) FROM chain_tunnel`)
if err := repo.DB().Raw(`SELECT COUNT(1) FROM chain_tunnel`).Row().Scan(&chainCount); err != nil {
t.Fatalf("count chain_tunnel: %v", err)
}
if chainCount != 0 { if chainCount != 0 {
t.Fatalf("expected chain_tunnel rollback, found %d records", chainCount) t.Fatalf("expected chain_tunnel rollback, found %d records", chainCount)
} }
@@ -96,11 +85,7 @@ func TestTunnelUpdateAssignsChainPortsContract(t *testing.T) {
`, name, name+"-secret", ip, ip, "", portRange, "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil { `, name, name+"-secret", ip, ip, "", portRange, "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil {
t.Fatalf("insert node %s: %v", name, err) t.Fatalf("insert node %s: %v", name, err)
} }
var id int64 return mustLastInsertID(t, repo, name)
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("get node id %s: %v", name, err)
}
return id
} }
entryID := insertNode("update-entry", "10.30.0.1", "40000-40010") entryID := insertNode("update-entry", "10.30.0.1", "40000-40010")
@@ -113,10 +98,7 @@ func TestTunnelUpdateAssignsChainPortsContract(t *testing.T) {
`, "update-port-tunnel", 1.0, 1, "tls", 99999, now, now, 1, nil, 0).Error; err != nil { `, "update-port-tunnel", 1.0, 1, "tls", 99999, now, now, 1, nil, 0).Error; err != nil {
t.Fatalf("insert tunnel: %v", err) t.Fatalf("insert tunnel: %v", err)
} }
var tunnelID int64 tunnelID := mustLastInsertID(t, repo, "update-port-tunnel")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelID); err != nil {
t.Fatalf("get tunnel id: %v", err)
}
payload := `{"id":` + jsonInt(tunnelID) + `,"name":"update-port-tunnel","type":2,"flow":99999,"trafficRatio":1.0,"status":1,"inNodeId":[{"nodeId":` + jsonInt(entryID) + `,"protocol":"tls"}],"chainNodes":[[{"nodeId":` + jsonInt(chainID) + `,"protocol":"tls","strategy":"round"}]],"outNodeId":[{"nodeId":` + jsonInt(exitID) + `,"protocol":"tls"}]}` payload := `{"id":` + jsonInt(tunnelID) + `,"name":"update-port-tunnel","type":2,"flow":99999,"trafficRatio":1.0,"status":1,"inNodeId":[{"nodeId":` + jsonInt(entryID) + `,"protocol":"tls"}],"chainNodes":[[{"nodeId":` + jsonInt(chainID) + `,"protocol":"tls","strategy":"round"}]],"outNodeId":[{"nodeId":` + jsonInt(exitID) + `,"protocol":"tls"}]}`
req := httptest.NewRequest(http.MethodPost, "/api/v1/tunnel/update", bytes.NewBufferString(payload)) req := httptest.NewRequest(http.MethodPost, "/api/v1/tunnel/update", bytes.NewBufferString(payload))
@@ -127,26 +109,17 @@ func TestTunnelUpdateAssignsChainPortsContract(t *testing.T) {
router.ServeHTTP(res, req) router.ServeHTTP(res, req)
assertCode(t, res, 0) assertCode(t, res, 0)
var chainPort int chainPort := mustQueryInt(t, repo, `SELECT port FROM chain_tunnel WHERE tunnel_id = ? AND chain_type = 2 LIMIT 1`, tunnelID)
if err := repo.DB().Raw(`SELECT port FROM chain_tunnel WHERE tunnel_id = ? AND chain_type = 2 LIMIT 1`, tunnelID).Row().Scan(&chainPort); err != nil {
t.Fatalf("query chain port: %v", err)
}
if chainPort <= 0 { if chainPort <= 0 {
t.Fatalf("expected chain node port to be assigned, got %d", chainPort) t.Fatalf("expected chain node port to be assigned, got %d", chainPort)
} }
var outPort int outPort := mustQueryInt(t, repo, `SELECT port FROM chain_tunnel WHERE tunnel_id = ? AND chain_type = 3 LIMIT 1`, tunnelID)
if err := repo.DB().Raw(`SELECT port FROM chain_tunnel WHERE tunnel_id = ? AND chain_type = 3 LIMIT 1`, tunnelID).Row().Scan(&outPort); err != nil {
t.Fatalf("query out port: %v", err)
}
if outPort <= 0 { if outPort <= 0 {
t.Fatalf("expected out node port to be assigned, got %d", outPort) t.Fatalf("expected out node port to be assigned, got %d", outPort)
} }
var entryStrategy sql.NullString entryStrategy := mustQueryNullString(t, repo, `SELECT strategy FROM chain_tunnel WHERE tunnel_id = ? AND chain_type = 1 LIMIT 1`, tunnelID)
if err := repo.DB().Raw(`SELECT strategy FROM chain_tunnel WHERE tunnel_id = ? AND chain_type = 1 LIMIT 1`, tunnelID).Row().Scan(&entryStrategy); err != nil {
t.Fatalf("query entry strategy: %v", err)
}
if !entryStrategy.Valid || strings.TrimSpace(entryStrategy.String) == "" { if !entryStrategy.Valid || strings.TrimSpace(entryStrategy.String) == "" {
t.Fatalf("expected entry strategy to be non-null and non-empty") t.Fatalf("expected entry strategy to be non-null and non-empty")
} }
@@ -30,11 +30,7 @@ func TestTunnelCreateWithIPPreferenceContract(t *testing.T) {
`, name, name+"-secret", v4, v4, v6, portRange, "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil { `, name, name+"-secret", v4, v4, v6, portRange, "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil {
t.Fatalf("insert node %s: %v", name, err) t.Fatalf("insert node %s: %v", name, err)
} }
var id int64 return mustLastInsertID(t, repo, name)
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("get node id %s: %v", name, err)
}
return id
} }
entryID := insertDualStackNode("ip-pref-entry", "10.50.0.1", "2001:db8::1", "50000-50010") entryID := insertDualStackNode("ip-pref-entry", "10.50.0.1", "2001:db8::1", "50000-50010")
@@ -61,8 +57,7 @@ func TestTunnelCreateWithIPPreferenceContract(t *testing.T) {
t.Fatalf("decode response: %v", err) t.Fatalf("decode response: %v", err)
} }
var stored string stored, err := tryQueryString(t, repo, `SELECT COALESCE(ip_preference, '') FROM tunnel WHERE name = ?`, "tunnel-"+tc.name)
err := repo.DB().Raw(`SELECT COALESCE(ip_preference, '') FROM tunnel WHERE name = ?`, "tunnel-"+tc.name).Row().Scan(&stored)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
t.Skipf("tunnel not created (nodes offline), skipping DB verification") t.Skipf("tunnel not created (nodes offline), skipping DB verification")
@@ -93,11 +88,7 @@ func TestTunnelUpdateIPPreferenceContract(t *testing.T) {
`, name, name+"-secret", v4, v4, v6, portRange, "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil { `, name, name+"-secret", v4, v4, v6, portRange, "", "v1", 1, 1, 1, now, now, 1, "[::]", "[::]", 0).Error; err != nil {
t.Fatalf("insert node %s: %v", name, err) t.Fatalf("insert node %s: %v", name, err)
} }
var id int64 return mustLastInsertID(t, repo, name)
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("get node id %s: %v", name, err)
}
return id
} }
entryID := insertDualStackNode("upd-entry", "10.60.0.1", "2001:db8:1::1", "60000-60010") entryID := insertDualStackNode("upd-entry", "10.60.0.1", "2001:db8:1::1", "60000-60010")
@@ -109,10 +100,7 @@ func TestTunnelUpdateIPPreferenceContract(t *testing.T) {
`, "update-ip-pref-tunnel", 1.0, 1, "tls", 99999, now, now, 1, nil, 0, "").Error; err != nil { `, "update-ip-pref-tunnel", 1.0, 1, "tls", 99999, now, now, 1, nil, 0, "").Error; err != nil {
t.Fatalf("insert tunnel: %v", err) t.Fatalf("insert tunnel: %v", err)
} }
var tunnelID int64 tunnelID := mustLastInsertID(t, repo, "update-ip-pref-tunnel")
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&tunnelID); err != nil {
t.Fatalf("get tunnel id: %v", err)
}
payload := `{"id":` + jsonInt(tunnelID) + `,"name":"update-ip-pref-tunnel","type":2,"flow":99999,"trafficRatio":1.0,"status":1,"ipPreference":"v6","inNodeId":[{"nodeId":` + jsonInt(entryID) + `,"protocol":"tls"}],"chainNodes":[],"outNodeId":[{"nodeId":` + jsonInt(exitID) + `,"protocol":"tls"}]}` payload := `{"id":` + jsonInt(tunnelID) + `,"name":"update-ip-pref-tunnel","type":2,"flow":99999,"trafficRatio":1.0,"status":1,"ipPreference":"v6","inNodeId":[{"nodeId":` + jsonInt(entryID) + `,"protocol":"tls"}],"chainNodes":[],"outNodeId":[{"nodeId":` + jsonInt(exitID) + `,"protocol":"tls"}]}`
req := httptest.NewRequest(http.MethodPost, "/api/v1/tunnel/update", bytes.NewBufferString(payload)) req := httptest.NewRequest(http.MethodPost, "/api/v1/tunnel/update", bytes.NewBufferString(payload))
@@ -126,10 +114,7 @@ func TestTunnelUpdateIPPreferenceContract(t *testing.T) {
t.Fatalf("decode response: %v", err) t.Fatalf("decode response: %v", err)
} }
var stored string stored := mustQueryString(t, repo, `SELECT COALESCE(ip_preference, '') FROM tunnel WHERE id = ?`, tunnelID)
if err := repo.DB().Raw(`SELECT COALESCE(ip_preference, '') FROM tunnel WHERE id = ?`, tunnelID).Row().Scan(&stored); err != nil {
t.Fatalf("query ip_preference: %v", err)
}
if stored != "v6" { if stored != "v6" {
t.Fatalf("expected ip_preference='v6' after update, got %q", stored) t.Fatalf("expected ip_preference='v6' after update, got %q", stored)
} }
@@ -202,10 +187,7 @@ func TestIPPreferenceColumnDefaultContract(t *testing.T) {
t.Fatalf("insert tunnel without ip_preference: %v", err) t.Fatalf("insert tunnel without ip_preference: %v", err)
} }
var stored string stored := mustQueryString(t, repo, `SELECT COALESCE(ip_preference, '') FROM tunnel WHERE name = ?`, "no-pref-tunnel")
if err := repo.DB().Raw(`SELECT COALESCE(ip_preference, '') FROM tunnel WHERE name = ?`, "no-pref-tunnel").Row().Scan(&stored); err != nil {
t.Fatalf("query ip_preference: %v", err)
}
if stored != "" { if stored != "" {
t.Fatalf("expected default ip_preference='', got %q", stored) t.Fatalf("expected default ip_preference='', got %q", stored)
} }
@@ -214,11 +196,7 @@ func TestIPPreferenceColumnDefaultContract(t *testing.T) {
func TestIPPreferenceColumnMigrationContract(t *testing.T) { func TestIPPreferenceColumnMigrationContract(t *testing.T) {
_, repo := setupContractRouter(t, "contract-jwt-secret") _, repo := setupContractRouter(t, "contract-jwt-secret")
var colCount int colCount := mustQueryInt(t, repo, `SELECT COUNT(*) FROM pragma_table_info('tunnel') WHERE name = 'ip_preference'`)
err := repo.DB().Raw(`SELECT COUNT(*) FROM pragma_table_info('tunnel') WHERE name = 'ip_preference'`).Row().Scan(&colCount)
if err != nil {
t.Fatalf("check column existence: %v", err)
}
if colCount != 1 { if colCount != 1 {
t.Fatalf("expected ip_preference column to exist in tunnel table, found %d", colCount) t.Fatalf("expected ip_preference column to exist in tunnel table, found %d", colCount)
} }
@@ -235,10 +213,7 @@ func TestIPPreferenceCoalesceNullSafety(t *testing.T) {
t.Skipf("DB does not allow NULL ip_preference (NOT NULL constraint): %v", err) t.Skipf("DB does not allow NULL ip_preference (NOT NULL constraint): %v", err)
} }
var stored string stored := mustQueryString(t, repo, `SELECT COALESCE(ip_preference, '') FROM tunnel WHERE name = ?`, "null-pref-tunnel")
if err := repo.DB().Raw(`SELECT COALESCE(ip_preference, '') FROM tunnel WHERE name = ?`, "null-pref-tunnel").Row().Scan(&stored); err != nil {
t.Fatalf("query ip_preference: %v", err)
}
if stored != "" { if stored != "" {
t.Fatalf("COALESCE should convert NULL to empty string, got %q", stored) t.Fatalf("COALESCE should convert NULL to empty string, got %q", stored)
} }
@@ -255,10 +230,7 @@ func TestDualStackNodeIPFieldsStoredContract(t *testing.T) {
t.Fatalf("insert dual-stack node: %v", err) t.Fatalf("insert dual-stack node: %v", err)
} }
var v4, v6 sql.NullString v4, v6 := mustQueryTwoNullStrings(t, repo, `SELECT server_ip_v4, server_ip_v6 FROM node WHERE name = ?`, "ds-verify-node")
if err := repo.DB().Raw(`SELECT server_ip_v4, server_ip_v6 FROM node WHERE name = ?`, "ds-verify-node").Row().Scan(&v4, &v6); err != nil {
t.Fatalf("query node IPs: %v", err)
}
if !v4.Valid || v4.String != "10.70.0.1" { if !v4.Valid || v4.String != "10.70.0.1" {
t.Fatalf("expected server_ip_v4='10.70.0.1', got %v", v4) t.Fatalf("expected server_ip_v4='10.70.0.1', got %v", v4)
} }
@@ -283,10 +255,7 @@ func TestIPPreferenceValidValuesContract(t *testing.T) {
t.Fatalf("insert tunnel with ip_preference=%q: %v", pref, err) t.Fatalf("insert tunnel with ip_preference=%q: %v", pref, err)
} }
var stored string stored := mustQueryString(t, repo, `SELECT COALESCE(ip_preference, '') FROM tunnel WHERE name = ?`, name)
if err := repo.DB().Raw(`SELECT COALESCE(ip_preference, '') FROM tunnel WHERE name = ?`, name).Row().Scan(&stored); err != nil {
t.Fatalf("query ip_preference for %s: %v", name, err)
}
if stored != pref { if stored != pref {
t.Fatalf("expected ip_preference=%q, got %q for %s", pref, stored, name) t.Fatalf("expected ip_preference=%q, got %q for %s", pref, stored, name)
} }
@@ -30,11 +30,7 @@ func TestUserTunnelVisibleListContracts(t *testing.T) {
`, name, 1.0, 1, "tls", 99999, now, now, status, nil, inx).Error; err != nil { `, name, 1.0, 1, "tls", 99999, now, now, status, nil, inx).Error; err != nil {
t.Fatalf("insert tunnel %s: %v", name, err) t.Fatalf("insert tunnel %s: %v", name, err)
} }
var id int64 return mustLastInsertID(t, repo, name)
if err := repo.DB().Raw("SELECT last_insert_rowid()").Row().Scan(&id); err != nil {
t.Fatalf("get tunnel id %s: %v", name, err)
}
return id
} }
enabledA := insertTunnel("enabled-A", 1, 1) enabledA := insertTunnel("enabled-A", 1, 1)