perf(tests): copy a migrated template DB instead of migrating per test

Most tests opened a throwaway panel DB with database.InitDB, which runs the
full AutoMigrate + seed on an empty file every time: ~230ms, and ~850ms under
-race because GORM's reflection-heavy migration is what the detector slows
most. internal/web/service does this in ~550 of its 830 tests, so the CI race
job spent ~10 of its ~14.6 minutes re-migrating empty databases.

internal/database/dbtest.InitDB migrates once per test process, then hands
each test its own copy of that file (~130ms under -race) and registers the
CloseDB cleanup. The copy then goes through InitDB like a panel restart, so
every test still starts from the state a fresh install has. Tests that reopen
an existing file, migrate a hand-built legacy DB or target Postgres keep
calling database.InitDB.

Locally under -race: internal/web/service 626s (last CI run) -> 114s,
internal/sub 246s -> 35s.
This commit is contained in:
MHSanaei
2026-09-27 03:04:50 +02:00
parent 33a469315a
commit 12d51d7195
82 changed files with 289 additions and 451 deletions
+2 -2
View File
@@ -100,8 +100,8 @@ question it already answers.
subtests and `t.Helper()` on helpers. An assertion must pin the exact value,
typed error or emitted string — `err != nil` and `len(x) > 0` are findings,
not nits. Prefer real dependencies: a throwaway DB via
`database.InitDB(filepath.Join(t.TempDir(), "x-ui.db"))` with `t.Cleanup`, and
`httptest` for HTTP. `internal/sub`'s `initSubDB(t)` is the template.
`dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))`
(`internal/database/dbtest`), and `httptest` for HTTP. `internal/sub`'s `initSubDB(t)` is the template.
A test must FAIL without its fix; one that passes either way certifies
nothing and then gets cited as proof the fix works.
+5 -3
View File
@@ -154,9 +154,11 @@ file locations when it can answer in one hop.
- Stdlib `testing` only (no testify). Table-driven, `t.Run` subtests,
`t.Helper()` on helpers. Assert the exact value / typed error / emitted
string, never just `err != nil`. Prefer real deps over mocks: throwaway DB via
`database.InitDB(filepath.Join(t.TempDir(), "x-ui.db"))` +
`t.Cleanup(func() { _ = database.CloseDB() })`; `httptest` for HTTP.
`internal/sub`'s `initSubDB(t)` is the template.
`dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))`
(`internal/database/dbtest`: copies a once-migrated template and registers
`CloseDB` cleanup; a fresh `database.InitDB` costs ~7x more, ~850ms under
`-race`); `httptest` for HTTP. Keep `database.InitDB` for reopening a file or
migrating a hand-built legacy DB. `internal/sub`'s `initSubDB(t)` is the template.
- Code must pass `golangci-lint run` (gofumpt + goimports formatting): `make lint`.
- Postgres, xray-gRPC-e2e and scale tests `t.Skip` unless `XUI_TEST_PG_DSN`,
`XUI_DB_TYPE`+`XUI_DB_DSN`, `XRAY_E2E_BINARY` or `XUI_SCALE_TEST` is set — a
+1 -1
View File
@@ -253,7 +253,7 @@ Tests live next to the code (`foo.go` ↔ `foo_test.go`); frontend specs and gol
- **Stdlib `testing` only** — no testify. Table-driven with `t.Run` subtests and `t.Helper()` on helpers.
- **Assert the contract, not internals.** Pin the exact value / typed error / emitted string — not `err != nil` or `len > 0`. A test that still passes when the behavior is broken is worse than no test.
- **Real dependencies over mocks.** Get a throwaway DB with `database.InitDB(filepath.Join(t.TempDir(), "x-ui.db"))` + `t.Cleanup(func() { _ = database.CloseDB() })` (Windows-safe), and use `httptest` servers for HTTP. The `internal/sub` suite's `initSubDB(t)` is the template.
- **Real dependencies over mocks.** Get a throwaway DB with `dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))` from `internal/database/dbtest`: it copies a once-migrated template (migrating from scratch per test is ~7x slower, worst under `-race`) and closes the DB before `t.TempDir` cleanup (Windows-safe). Keep `database.InitDB` for reopening an existing file or migrating a hand-built legacy DB. Use `httptest` servers for HTTP. The `internal/sub` suite's `initSubDB(t)` is the template.
### Running
+2 -4
View File
@@ -9,6 +9,7 @@ import (
"github.com/mhsanaei/3x-ui/v3/internal/config"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/service/panel"
)
@@ -16,10 +17,7 @@ import (
func newTokenCLIEnv(t *testing.T) {
t.Helper()
t.Setenv("XUI_DB_FOLDER", t.TempDir())
if err := database.InitDB(config.GetDBPath()); err != nil {
t.Fatalf("init db: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, config.GetDBPath())
}
func tokenNames(t *testing.T) []string {
+67
View File
@@ -0,0 +1,67 @@
// Package dbtest opens throwaway panel databases for tests. Migrating a new
// SQLite file costs ~850ms under -race; copying a migrated template ~130ms.
package dbtest
import (
"os"
"path/filepath"
"sync"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/config"
"github.com/mhsanaei/3x-ui/v3/internal/database"
)
var migrated struct {
once sync.Once
data []byte
err error
}
// InitDB opens a new, fully migrated panel database at path and closes it when
// t ends. Reopen an existing file with database.InitDB instead.
func InitDB(t testing.TB, path string) {
t.Helper()
if config.GetDBKind() != "postgres" {
if _, err := os.Stat(path); err == nil {
t.Fatalf("dbtest.InitDB would overwrite existing %s; reopen it with database.InitDB", path)
}
data, err := migratedTemplate()
if err != nil {
t.Fatalf("build template database: %v", err)
}
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
t.Fatalf("create database dir: %v", err)
}
if err := os.WriteFile(path, data, 0o600); err != nil {
t.Fatalf("copy template database: %v", err)
}
}
if err := database.InitDB(path); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
}
func migratedTemplate() ([]byte, error) {
migrated.once.Do(func() {
dir, err := os.MkdirTemp("", "xui-dbtest-")
if err != nil {
migrated.err = err
return
}
defer os.RemoveAll(dir)
path := filepath.Join(dir, "template.db")
if err := database.InitDB(path); err != nil {
migrated.err = err
return
}
// Closing the last connection checkpoints the WAL into the main file.
if err := database.CloseDB(); err != nil {
migrated.err = err
return
}
migrated.data, migrated.err = os.ReadFile(path)
})
return migrated.data, migrated.err
}
+36
View File
@@ -0,0 +1,36 @@
package dbtest
import (
"path/filepath"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
func TestInitDBGivesEachTestItsOwnDatabase(t *testing.T) {
t.Run("first test writes", func(t *testing.T) {
InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
if err := database.GetDB().Create(&model.Setting{Key: "dbtestProbe", Value: "first"}).Error; err != nil {
t.Fatalf("write probe: %v", err)
}
})
t.Run("next test starts clean", func(t *testing.T) {
InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
var leaked int64
if err := database.GetDB().Model(&model.Setting{}).Where("key = ?", "dbtestProbe").Count(&leaked).Error; err != nil {
t.Fatalf("count probe: %v", err)
}
if leaked != 0 {
t.Fatalf("database holds %d probe rows written by the previous test; copies must not share state", leaked)
}
var admins int64
if err := database.GetDB().Model(&model.User{}).Count(&admins).Error; err != nil {
t.Fatalf("count users: %v", err)
}
if admins != 1 {
t.Fatalf("users = %d, want the 1 seeded admin a fresh install has", admins)
}
})
}
+2 -6
View File
@@ -6,17 +6,13 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
func initSubDB(t *testing.T) {
t.Helper()
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
// Close the handle before t.TempDir cleanup so Windows doesn't refuse to
// remove the still-open sqlite file.
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
}
// The subscription page's Copy URL must be built from the same host the
+2 -5
View File
@@ -6,17 +6,14 @@ import (
"path/filepath"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
)
// #6559: the Master panel must send a stable X-HWID when fetching external
// subscriptions, otherwise an HWID-limited donor answers 404.
func TestFetchSendsStableHwid(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
var gotHwid string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+3 -8
View File
@@ -13,6 +13,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -302,10 +303,7 @@ func TestExpandEntryCacheHitWritesNothing(t *testing.T) {
func TestFetchUsesConfiguredExternalSubUserAgent(t *testing.T) {
resetSubscriptionCache(t)
if err := database.InitDB(filepath.Join(t.TempDir(), "ua.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "ua.db"))
const customUA = "Happ/4.2.1"
if err := database.GetDB().Create(&model.Setting{
@@ -333,10 +331,7 @@ func TestFetchUsesConfiguredExternalSubUserAgent(t *testing.T) {
func TestFetchFallsBackToDefaultExternalSubUserAgent(t *testing.T) {
resetSubscriptionCache(t)
if err := database.InitDB(filepath.Join(t.TempDir(), "ua-default.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "ua-default.db"))
var gotUA string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+2 -4
View File
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -15,10 +16,7 @@ func seedSubDB(t *testing.T) {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
// seedSubInbound creates a VLESS inbound with one client wired into the
+2 -4
View File
@@ -12,6 +12,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -27,10 +28,7 @@ func initHwidSubRouter(t *testing.T, limit int) (*gin.Engine, string) {
}
t.Setenv("XUI_DB_FOLDER", tmp)
if err := database.InitDB(filepath.Join(tmp, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(tmp, "x-ui.db"))
const subID = "sub-hwid-route"
const email = "route@example.com"
+2 -4
View File
@@ -9,6 +9,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
@@ -19,10 +20,7 @@ func initMutDB(t *testing.T) {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
// --- json_service.go:40 — rules are merged into routing only when non-empty ---
+2 -4
View File
@@ -17,6 +17,7 @@ import (
yaml "github.com/goccy/go-yaml"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
)
func mergeRemoteClashRulesYAML(base map[string]any, raw string) error {
@@ -607,10 +608,7 @@ func TestRemoteRoutingHTTPClientRejectsLoopback(t *testing.T) {
func TestRemoteRoutingPersistedLoadRetriesAfterDatabaseBecomesReady(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "x-ui.db")
if err := database.InitDB(dbPath); err != nil {
t.Fatalf("init db: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, dbPath)
deeplink, err := normalizeHappRouting([]byte(`{"Name":"persisted-after-ready"}`))
if err != nil {
+3 -8
View File
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -18,10 +19,7 @@ import (
func TestGetSubs_DuplicateSettingsClients_Deduped(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
const subId = "sub-dup"
const email = "dup@example.com"
@@ -76,10 +74,7 @@ func TestGetSubs_DuplicateSettingsClients_Deduped(t *testing.T) {
func TestMatchingClients_DedupsCaseInsensitiveEmail(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
const subId = "s1"
const uuid = "11111111-2222-4333-8444-555555555555"
+2 -6
View File
@@ -8,6 +8,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
@@ -15,12 +16,7 @@ import (
func setupInfoNodeTestDB(t *testing.T) {
t.Helper()
if err := database.InitDB(t.TempDir() + "/test_infonode.db"); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() {
_ = database.CloseDB()
})
dbtest.InitDB(t, t.TempDir()+"/test_infonode.db")
db := database.GetDB()
if err := db.AutoMigrate(
&model.Inbound{},
+2 -4
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
@@ -17,10 +18,7 @@ import (
func TestStatsForClient_OrphanedInboundIdFallback(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
const email = "old-client@example.com"
const total = int64(100) * gb
+2 -4
View File
@@ -6,6 +6,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -16,10 +17,7 @@ import (
func TestGetSubs_OrdersBySubSortIndexThenId(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
const subId = "sub-sort"
db := database.GetDB()
+2 -4
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
@@ -12,10 +13,7 @@ import (
func TestAggregateTrafficByEmails_FallsBackToClientLimits(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
const email = "node-client@example.com"
const totalBytes = int64(300) * 1024 * 1024 * 1024
+2 -4
View File
@@ -16,6 +16,7 @@ import (
"github.com/mhsanaei/3x-ui/v3/internal/config"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
@@ -38,10 +39,7 @@ func setupScaleSubDB(t *testing.T) {
}
switch strings.ToLower(strings.TrimSpace(os.Getenv("XUI_SCALE_TEST"))) {
case "1", "true", "yes":
if err := database.InitDB(filepath.Join(t.TempDir(), "scale.db")); err != nil {
t.Fatalf("InitDB(sqlite): %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "scale.db"))
return
}
t.Skip("set XUI_SCALE_TEST=1 (sqlite) or XUI_DB_TYPE=postgres + XUI_DB_DSN (postgres) to run the scale benchmark")
+2 -4
View File
@@ -15,6 +15,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
"github.com/mhsanaei/3x-ui/v3/internal/web/session"
@@ -31,10 +32,7 @@ func newAPIAuthTestEngine(t *testing.T) (*gin.Engine, *APIController) {
gin.SetMode(gin.TestMode)
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
engine := gin.New()
store := cookie.NewStore([]byte("api-auth-test-secret"))
engine.Use(sessions.Sessions("3x-ui", store))
@@ -11,6 +11,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/entity"
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
@@ -22,10 +23,7 @@ func seedPartlyApplyingClient(t *testing.T, email string, basePort int) (healthy
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
ids := make([]int, 0, 2)
@@ -129,10 +127,7 @@ func TestDeleteHandlerFlagsRestartOnPartialApply(t *testing.T) {
func TestImportHandlerFlagsRestartWhenTrafficRestoreFails(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
ib := &model.Inbound{
UserId: 1, Enable: true, Port: 43340, Tag: "in-import-partial",
@@ -12,6 +12,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
@@ -45,10 +46,7 @@ func TestClientRenewalPreviewHTTP(t *testing.T) {
{name: "negative count", zone: "UTC", count: -1, invalid: true, wantError: "renewal preview reset and resetCount must not be negative\n"},
} {
t.Run(tt.name, func(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
db := database.GetDB()
if err := db.Create(&model.Setting{Key: "timeLocation", Value: tt.zone}).Error; err != nil {
t.Fatal(err)
+2 -4
View File
@@ -14,6 +14,7 @@ import (
"github.com/op/go-logging"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
"github.com/mhsanaei/3x-ui/v3/internal/web/entity"
@@ -25,10 +26,7 @@ func newHostTestDB(t *testing.T) {
gin.SetMode(gin.TestMode)
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
type hostEnvelope struct {
@@ -12,6 +12,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
@@ -23,10 +24,7 @@ func TestNodeSyncPushSkipsOperatorTLSGuard(t *testing.T) {
gin.SetMode(gin.TestMode)
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
prev := runtime.GetManager()
runtime.SetManager(runtime.NewManager(runtime.LocalDeps{APIPort: func() int { return 0 }, SetNeedRestart: func() {}}))
t.Cleanup(func() { runtime.SetManager(prev) })
@@ -16,6 +16,7 @@ import (
"gorm.io/gorm"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/locale"
)
@@ -25,10 +26,7 @@ func newNodeCredentialTestEngine(t *testing.T) *gin.Engine {
gin.SetMode(gin.TestMode)
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
engine := gin.New()
engine.Use(func(c *gin.Context) {
+5 -15
View File
@@ -12,6 +12,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
"github.com/mhsanaei/3x-ui/v3/internal/web/locale"
@@ -57,10 +58,7 @@ func TestValidateRegex(t *testing.T) {
func TestAPITokenMutationRoutesEnforceExpectedScope(t *testing.T) {
t.Setenv("XUI_DB_FOLDER", t.TempDir())
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
row := &model.ApiToken{Name: "route-scope", Token: crypto.HashTokenSHA256("token"), Enabled: true, Scope: model.ApiScopeNodeSync}
if err := database.GetDB().Create(row).Error; err != nil {
t.Fatalf("seed token: %v", err)
@@ -95,10 +93,7 @@ func TestAPITokenMutationRoutesEnforceExpectedScope(t *testing.T) {
// rebind the authenticator without presenting a current code.
func TestUpdateSettingRequiresCodeToReplaceTwoFactorToken(t *testing.T) {
t.Setenv("XUI_DB_FOLDER", t.TempDir())
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
settingService := service.SettingService{}
if err := settingService.SetTwoFactorToken("ORIGINALSECRET234567"); err != nil {
@@ -195,13 +190,8 @@ func TestTestDiscordEndpoint(t *testing.T) {
// Setup DB
t.Setenv("XUI_DB_FOLDER", t.TempDir())
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() {
_ = database.CloseDB()
SetDiscordService(nil)
})
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
t.Cleanup(func() { SetDiscordService(nil) })
settingService := service.SettingService{}
svc := discord.NewDiscordService(settingService)
+2 -5
View File
@@ -11,16 +11,13 @@ import (
"github.com/gin-gonic/gin"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
)
func setupSubBalancerRouter(t *testing.T) *gin.Engine {
t.Helper()
t.Setenv("XUI_DB_FOLDER", t.TempDir())
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
gin.SetMode(gin.TestMode)
router := gin.New()
NewSubBalancerController(router.Group("/panel/api"))
@@ -13,6 +13,7 @@ import (
"gorm.io/gorm"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/locale"
)
@@ -21,10 +22,7 @@ func TestWarpIntervalReportsClockPersistenceFailure(t *testing.T) {
gin.SetMode(gin.TestMode)
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
for _, setting := range []*model.Setting{
{Key: "warpUpdateInterval", Value: "0"},
@@ -12,6 +12,7 @@ import (
"github.com/op/go-logging"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
)
@@ -47,15 +48,7 @@ func setupIntegrationDB(t *testing.T) {
log.SetFlags(origLogFlags)
})
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("database.InitDB failed: %v", err)
}
// LIFO cleanup order: this runs before t.TempDir's own cleanup.
t.Cleanup(func() {
if err := database.CloseDB(); err != nil {
t.Logf("database.CloseDB warning: %v", err)
}
})
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
// enforceIpLimitForTest runs the same two steps processObserved does: select
+2 -4
View File
@@ -9,6 +9,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -197,10 +198,7 @@ func TestPartitionLiveIps_ConcurrentLiveIpsSortedAscending(t *testing.T) {
func TestGetInboundByEmailFallbackIgnoresProtocolScalarFields(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
inbound := &model.Inbound{
UserId: 1,
@@ -15,6 +15,7 @@ import (
"github.com/mhsanaei/3x-ui/v3/internal/config"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
)
@@ -35,10 +36,7 @@ func setupScaleJobDB(t *testing.T) {
}
switch strings.ToLower(strings.TrimSpace(os.Getenv("XUI_SCALE_TEST"))) {
case "1", "true", "yes":
if err := database.InitDB(filepath.Join(t.TempDir(), "scale.db")); err != nil {
t.Fatalf("InitDB(sqlite): %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "scale.db"))
return
}
t.Skip("set XUI_SCALE_TEST=1 (sqlite) or XUI_DB_TYPE=postgres + XUI_DB_DSN (postgres) to run the scale benchmark")
+2 -4
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
)
@@ -13,10 +14,7 @@ func initLdapJobDB(t *testing.T) {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
func TestBuildClient_ConvertsDefaultTotalGBToBytes(t *testing.T) {
@@ -11,6 +11,7 @@ import (
"github.com/op/go-logging"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
@@ -53,10 +54,7 @@ func TestHeartbeatDropsSubNodesOfNodesItNoLongerProbes(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
xuilogger.InitLogger(logging.ERROR)
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
runtime.SetManager(runtime.NewManager(runtime.LocalDeps{APIPort: func() int { return 0 }, SetNeedRestart: func() {}}))
t.Cleanup(func() { runtime.SetManager(nil) })
@@ -16,6 +16,7 @@ import (
"github.com/op/go-logging"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
@@ -26,10 +27,7 @@ import (
// whole table made every node store and echo back the entire fleet's IPs.
func TestNodeTrafficSyncPushesOnlyHostedClientIps(t *testing.T) {
xuilogger.InitLogger(logging.ERROR)
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
service.StartTrafficWriter()
t.Cleanup(service.StopTrafficWriter)
runtime.SetManager(runtime.NewManager(runtime.LocalDeps{APIPort: func() int { return 0 }, SetNeedRestart: func() {}}))
@@ -7,6 +7,7 @@ import (
"github.com/op/go-logging"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
@@ -18,10 +19,7 @@ import (
// must drop their online sets itself: a disabled node here, a deleted one below.
func TestNodeTrafficSyncDropsOnlineClientsOfUnsyncedNodes(t *testing.T) {
xuilogger.InitLogger(logging.ERROR)
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
runtime.SetManager(runtime.NewManager(runtime.LocalDeps{APIPort: func() int { return 0 }, SetNeedRestart: func() {}}))
t.Cleanup(func() { runtime.SetManager(nil) })
process := xray.NewTestProcess(nil, "")
@@ -13,6 +13,7 @@ import (
"github.com/op/go-logging"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/eventbus"
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
@@ -24,10 +25,7 @@ import (
func goingDownNodes(t *testing.T, n int) {
t.Helper()
xuilogger.InitLogger(logging.ERROR)
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
runtime.SetManager(runtime.NewManager(runtime.LocalDeps{APIPort: func() int { return 0 }, SetNeedRestart: func() {}}))
t.Cleanup(func() { runtime.SetManager(nil) })
srv := httptest.NewServer(nil)
@@ -7,6 +7,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
@@ -15,10 +16,7 @@ func initResetJobDB(t *testing.T) {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
type seededClient struct {
+2 -5
View File
@@ -12,7 +12,7 @@ import (
"github.com/robfig/cron/v3"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/web/global"
)
@@ -44,10 +44,7 @@ func inContractScope(method, path string) bool {
func registeredContractRoutes(t *testing.T) map[string]bool {
t.Helper()
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("init db: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
previous := global.GetWebServer()
s := NewServer()
+2 -4
View File
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -14,10 +15,7 @@ func setupBulkDB(t *testing.T) {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
func clientsSettings(t *testing.T, clients []model.Client) string {
@@ -6,6 +6,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
@@ -16,10 +17,7 @@ import (
func TestResetClientExpiryTimeByEmail_MultiInbound(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -91,10 +89,7 @@ func TestResetClientExpiryTimeByEmail_MultiInbound(t *testing.T) {
func TestSetClientEnableByEmail_MultiInbound(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -14,10 +15,7 @@ import (
func TestEffectiveFlowsByEmails(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
const vision = "xtls-rprx-vision"
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -46,10 +47,7 @@ func TestClientWithInboundFlow_GatesByInboundCapability(t *testing.T) {
func TestFlowIsolation_VisionDoesNotLeakToWsInbound(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -95,10 +93,7 @@ func TestFlowIsolation_VisionDoesNotLeakToWsInbound(t *testing.T) {
func TestEffectiveFlow_NonFlowInboundSyncedLastDoesNotHideVision(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
reality := &model.Inbound{Tag: "vless-reality", Enable: true, Port: 40001, Protocol: model.VLESS, StreamSettings: `{"network":"tcp","security":"reality"}`}
@@ -145,10 +140,7 @@ func TestEffectiveFlow_NonFlowInboundSyncedLastDoesNotHideVision(t *testing.T) {
func TestEffectiveFlow_ClearedFlowStaysCleared(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
reality := &model.Inbound{Tag: "vless-reality", Enable: true, Port: 41001, Protocol: model.VLESS, StreamSettings: `{"network":"tcp","security":"reality"}`}
@@ -191,10 +183,7 @@ func TestEffectiveFlow_ClearedFlowStaysCleared(t *testing.T) {
func TestAttach_PreservesVisionFlowWhenCanonicalColumnZeroed(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -267,10 +256,7 @@ func TestAttach_PreservesVisionFlowWhenCanonicalColumnZeroed(t *testing.T) {
func TestBulkAttach_PreservesVisionFlowWhenCanonicalColumnZeroed(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -6,6 +6,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
)
@@ -13,10 +14,7 @@ import (
func TestSetRemoteTraffic_PreservesPanelLocalGroupAndComment(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -82,10 +80,7 @@ func TestSetRemoteTraffic_PreservesPanelLocalGroupAndComment(t *testing.T) {
func TestSyncInbound_KeepsGroupWhenIncomingEmpty(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -125,10 +120,7 @@ func TestSyncInbound_KeepsGroupWhenIncomingEmpty(t *testing.T) {
func TestClientUpdate_ClearsGroup(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
+2 -4
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -12,10 +13,7 @@ func initClientHwidTestDB(t *testing.T) {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
func seedHwidClient(t *testing.T, limit int) *model.ClientRecord {
+2 -4
View File
@@ -8,6 +8,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"gorm.io/gorm"
@@ -188,10 +189,7 @@ func TestSetClientLimitHwidIsSerializedWithSyncInbound(t *testing.T) {
func BenchmarkSetClientLimitHwidSerialized(b *testing.B) {
dbDir := b.TempDir()
b.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
b.Fatalf("InitDB: %v", err)
}
b.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(b, filepath.Join(dbDir, "x-ui.db"))
StartTrafficWriter()
b.Cleanup(StopTrafficWriter)
db := database.GetDB()
@@ -5,16 +5,14 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
func TestSyncInbound_UpdatesMtprotoSecretAndAdTag(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -5,16 +5,14 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
func TestSyncInbound_PreservesCredentialsAcrossProtocols(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -74,10 +72,7 @@ func TestSyncInbound_PreservesCredentialsAcrossProtocols(t *testing.T) {
func TestSyncInbound_AllowsClearingFlow(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
+2 -5
View File
@@ -11,17 +11,14 @@ import (
"testing"
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
)
func setupTestDB(t *testing.T) service.SettingService {
t.Helper()
dbPath := filepath.Join(t.TempDir(), "x-ui.db")
if err := database.InitDB(dbPath); err != nil {
t.Fatalf("init db: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, dbPath)
return service.SettingService{}
}
+3 -9
View File
@@ -14,7 +14,7 @@ import (
"sync"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
)
@@ -132,10 +132,7 @@ func startFakeSMTPServer(t *testing.T) (string, func() []string) {
}
func TestSendUsesBareAddressFromNameAddrSmtpFrom(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
addr, recordedLines := startFakeSMTPServer(t)
host, portStr, err := net.SplitHostPort(addr)
@@ -182,10 +179,7 @@ func TestSendUsesBareAddressFromNameAddrSmtpFrom(t *testing.T) {
}
func TestConnectionReportsMissingFrom(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
settingService := service.SettingService{}
mustSet := func(name string, err error) {
+2 -4
View File
@@ -18,6 +18,7 @@ import (
"golang.org/x/crypto/chacha20poly1305"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/logger"
)
@@ -30,10 +31,7 @@ func initHappTestDB(t *testing.T) {
if err := os.WriteFile(filepath.Join(dbDir, "config.json"), []byte(`{"log":{}}`), 0o600); err != nil {
t.Fatalf("write Xray config: %v", err)
}
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
func seedHappClient(t *testing.T, subID string) *model.ClientRecord {
@@ -7,6 +7,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"gorm.io/gorm"
@@ -18,10 +19,7 @@ func setupClientIpTestDB(t *testing.T) {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
func marshalIps(t *testing.T, entries ...clientIpEntry) string {
@@ -6,6 +6,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
@@ -26,10 +27,7 @@ import (
func TestAddClientTraffic_MatchesByEmail(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -97,10 +95,7 @@ func TestAddClientTraffic_MatchesByEmail(t *testing.T) {
func TestAdjustTraffics_DelayedStartConvertsDespiteStaleInboundId(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -166,10 +161,7 @@ func TestAdjustTraffics_DelayedStartConvertsDespiteStaleInboundId(t *testing.T)
func TestAddClientTraffic_ExpiryWriteOnlyForConvertedClients(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -6,6 +6,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"gorm.io/gorm"
@@ -95,10 +96,7 @@ func initFlowTestDB(t *testing.T) *gorm.DB {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
return database.GetDB()
}
@@ -6,6 +6,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -16,10 +17,7 @@ import (
func TestRestoreVisionFlowForEligibleInbound(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
const vision = "xtls-rprx-vision"
+4 -12
View File
@@ -9,6 +9,7 @@ import (
"gorm.io/gorm"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
@@ -22,10 +23,7 @@ import (
func TestMigrationRequirements_BackfillsClientTrafficsWithMultiDomainInbound(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
@@ -96,10 +94,7 @@ func TestMigrationRequirements_BackfillsClientTrafficsWithMultiDomainInbound(t *
func TestMigrationRequirementsReturnsAddClientStatFailure(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
first := &model.Inbound{UserId: 1, Tag: "first", Port: 31001, Protocol: model.VLESS, Settings: `{"clients":[{"email":"first@example.test","id":"id-1"}]}`, StreamSettings: `{}`}
if err := db.Create(first).Error; err != nil {
@@ -136,10 +131,7 @@ func TestMigrationRequirementsReturnsAddClientStatFailure(t *testing.T) {
func TestMigrationRequirements_CleansLegacyZeroAddrTag(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
legacy := &model.Inbound{
+3 -8
View File
@@ -5,14 +5,12 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
func TestSetInboundSubSortIndexLeavesSettingsUntouched(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("init db: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
const settings = `{"clients":[{"email":"a@example.test","id":"11111111-1111-1111-1111-111111111111"}]}`
ib := &model.Inbound{UserId: 1, Remark: "r", Port: 21001, Protocol: model.VLESS, Settings: settings, SubSortIndex: 1, Enable: true}
@@ -57,10 +55,7 @@ func TestSetInboundSubSortIndexUsesNarrowNodeUpdate(t *testing.T) {
}
func TestSetInboundSubSortIndexPreservesNegative(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("init db: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
ib := &model.Inbound{UserId: 1, Remark: "r", Port: 21003, Protocol: model.VLESS, Settings: `{"clients":[]}`, SubSortIndex: 1, Enable: true}
if err := database.GetDB().Create(ib).Error; err != nil {
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
@@ -17,10 +18,7 @@ import (
func TestGetClientTrafficTgBot_SettingsSerializationStyles(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
db := database.GetDB()
+2 -5
View File
@@ -12,7 +12,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/crypto/nodetoken"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
piaprotocol "github.com/mhsanaei/3x-ui/v3/internal/pia"
)
@@ -48,10 +48,7 @@ func (f *fakePiaRegistrar) RegisterKey(_ context.Context, server piaprotocol.Wir
func setupPiaService(t *testing.T) *PiaService {
t.Helper()
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
payload := []byte(`{"version":6,"groups":{"wg":[{"name":"wireguard","ports":[1337]}]},"regions":[{"id":"us-east","name":"US East","country":"US","geo":false,"offline":false,"port_forward":true,"servers":{"wg":[{"ip":"198.51.100.10","cn":"useast1"},{"ip":"198.51.100.20","cn":"useast2"}]}},{"id":"de-berlin","name":"Berlin","country":"DE","geo":false,"offline":false,"port_forward":false,"servers":{"wg":[{"ip":"203.0.113.10","cn":"berlin1"}]}}]}`)
svc := NewPiaService()
svc.Auth = fakePiaAuth{token: "tokentokentokentoken12"}
@@ -11,6 +11,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -69,10 +70,7 @@ func withWarpAPIBase(t *testing.T, base string) {
}
func TestChangeWarpIPPreservesLicenseKey(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
const license = "WARPPLLUS-KEY-0123456789abcdefgh" // 32 chars, >= 26 gate
seedWarp(t, license)
@@ -124,10 +122,7 @@ func TestChangeWarpIPPreservesLicenseKey(t *testing.T) {
}
func TestChangeWarpIPKeepsLicenseWhenReapplyFails(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
const license = "WARPPLLUS-KEY-0123456789abcdefgh"
seedWarp(t, license)
@@ -7,14 +7,11 @@ import (
"path/filepath"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
)
func TestDoWarpRequestCapsResponseBody(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
oversize := maxResponseSize + 4096
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -9,6 +9,7 @@ import (
"gorm.io/gorm"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/runtime"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
@@ -18,10 +19,7 @@ func initTrafficTestDB(t *testing.T) *gorm.DB {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
return database.GetDB()
}
@@ -6,6 +6,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
)
@@ -14,14 +15,7 @@ func setupAPITokenTestDB(t *testing.T) {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() {
if err := database.CloseDB(); err != nil {
t.Fatalf("CloseDB: %v", err)
}
})
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
func TestAPITokenScopeExpiryAndExpectedRevoke(t *testing.T) {
+4 -12
View File
@@ -9,6 +9,7 @@ import (
"github.com/mhsanaei/3x-ui/v3/internal/config"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -36,10 +37,7 @@ func TestApiTokenCreatedAtSeconds(t *testing.T) {
func TestRecreateByNamePreservesTokenWhenReplacementFails(t *testing.T) {
t.Setenv("XUI_DB_FOLDER", t.TempDir())
if err := database.InitDB(config.GetDBPath()); err != nil {
t.Fatalf("init db: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, config.GetDBPath())
svc := ApiTokenService{}
first, err := svc.RecreateByName("cli-fallback")
@@ -73,10 +71,7 @@ func TestRecreateByNamePreservesTokenWhenReplacementFails(t *testing.T) {
// and now takes operator input from -tokenName, so it must cap it too.
func TestRecreateByNameRejectsOverlongName(t *testing.T) {
t.Setenv("XUI_DB_FOLDER", t.TempDir())
if err := database.InitDB(config.GetDBPath()); err != nil {
t.Fatalf("init db: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, config.GetDBPath())
const wantErr = "token name must be 64 characters or fewer"
@@ -95,10 +90,7 @@ func TestRecreateByNameRejectsOverlongName(t *testing.T) {
func TestRecreateByNameKeepsOneToken(t *testing.T) {
t.Setenv("XUI_DB_FOLDER", t.TempDir())
if err := database.InitDB(config.GetDBPath()); err != nil {
t.Fatalf("init db: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, config.GetDBPath())
svc := ApiTokenService{}
first, err := svc.RecreateByName("cli-fallback")
+2 -5
View File
@@ -13,7 +13,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/config"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
)
var sponsorTestNow = time.Date(2026, 10, 15, 0, 0, 0, 0, time.UTC)
@@ -131,10 +131,7 @@ func TestActiveSponsorsResolvesLogoAndSlots(t *testing.T) {
func setupSponsorServer(t *testing.T, body string) *atomic.Int32 {
t.Helper()
t.Setenv("XUI_DB_FOLDER", t.TempDir())
if err := database.InitDB(config.GetDBPath()); err != nil {
t.Fatalf("init db: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, config.GetDBPath())
var hits atomic.Int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+2 -8
View File
@@ -10,6 +10,7 @@ import (
"github.com/mhsanaei/3x-ui/v3/internal/amneziawgnet"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
)
@@ -27,14 +28,7 @@ func setupConflictDB(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() {
if err := database.CloseDB(); err != nil {
t.Logf("CloseDB warning: %v", err)
}
})
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
func seedInboundConflict(t *testing.T, tag, listen string, port int, protocol model.Protocol, streamSettings, settings string) {
+2 -4
View File
@@ -11,6 +11,7 @@ import (
"github.com/mhsanaei/3x-ui/v3/internal/config"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
@@ -41,10 +42,7 @@ func setupScaleDB(t *testing.T) {
if dbPath == "" {
dbPath = filepath.Join(t.TempDir(), "scale.db")
}
if err := database.InitDB(dbPath); err != nil {
t.Fatalf("InitDB(sqlite): %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, dbPath)
return
}
@@ -4,7 +4,7 @@ import (
"path/filepath"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
)
// A panel restarts with an empty snapshot until the @2s ticker fires, and a
@@ -12,10 +12,7 @@ import (
func TestCurrentStatusSamplesBeforeFirstTick(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
svc := &ServerService{}
if svc.LastStatus() != nil {
+2 -5
View File
@@ -12,7 +12,7 @@ import (
"sync"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
)
// Loyalsoldier and runetfreedom write "<hash> geoip.dat"; chocolate4u writes
@@ -141,10 +141,7 @@ func geofileTestEnv(t *testing.T, entries map[string]geofileEntry) string {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
binFolder := t.TempDir()
t.Setenv("XUI_BIN_FOLDER", binFolder)
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
)
func TestSniffImportKind(t *testing.T) {
@@ -30,10 +31,7 @@ func TestSniffImportKind(t *testing.T) {
t.Run("panel migration dump", func(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "x-ui.db")
if err := database.InitDB(dbPath); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, dbPath)
dump, err := database.DumpSQLiteToBytes(dbPath)
if err != nil {
t.Fatalf("DumpSQLiteToBytes: %v", err)
@@ -6,15 +6,13 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
)
func TestStageSQLiteUploadRebuildsFromDump(t *testing.T) {
dir := t.TempDir()
dbPath := filepath.Join(dir, "x-ui.db")
if err := database.InitDB(dbPath); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, dbPath)
dump, err := database.DumpSQLiteToBytes(dbPath)
if err != nil {
t.Fatalf("DumpSQLiteToBytes: %v", err)
@@ -7,7 +7,7 @@ import (
"testing"
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
)
// A box with no IPv6 route spends 3s per lookup service, and a status sample
@@ -15,10 +15,7 @@ import (
func TestStatusSampleDoesNotWaitOnPublicIPLookup(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
release := make(chan struct{})
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+2 -4
View File
@@ -9,6 +9,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/util/crypto"
)
@@ -16,10 +17,7 @@ func setupSettingMtlsDB(t *testing.T) *SettingService {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
return &SettingService{}
}
@@ -9,6 +9,7 @@ import (
"github.com/xlzd/gotp"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -52,14 +53,7 @@ func TestResetSettingsRegeneratesSubscriptionPaths(t *testing.T) {
func setupSettingTestDB(t *testing.T) {
t.Helper()
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := database.CloseDB(); err != nil {
t.Fatal(err)
}
})
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
}
func TestGetAllSettingViewRedactsSecrets(t *testing.T) {
+2 -9
View File
@@ -8,7 +8,7 @@ import (
"github.com/op/go-logging"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
)
@@ -20,14 +20,7 @@ func setupSubBalancerDB(t *testing.T) {
subBalancerLoggerOnce.Do(func() { xuilogger.InitLogger(logging.ERROR) })
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() {
if err := database.CloseDB(); err != nil {
t.Logf("CloseDB warning: %v", err)
}
})
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
func TestSubBalancerServiceCRUD(t *testing.T) {
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mymmrac/telego"
@@ -15,10 +16,7 @@ import (
func seedPickerInbounds(t *testing.T, protocols ...model.Protocol) {
t.Helper()
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
for i, protocol := range protocols {
port := 20000 + i
ib := &model.Inbound{Remark: string(protocol), Enable: true, Port: port, Protocol: protocol, Tag: fmt.Sprintf("inbound-%d", port), Settings: `{}`}
@@ -4,7 +4,7 @@ import (
"path/filepath"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mymmrac/telego"
)
@@ -12,10 +12,7 @@ import (
// Regression test: keying the add-client wizard by chat alone left the two
// admins of a group chat filling in one client between them.
func TestAddClientDraftIsPerAdminInGroupChat(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
const (
groupChat = int64(-1001234567890)
@@ -11,7 +11,7 @@ import (
"testing"
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mymmrac/telego"
)
@@ -84,10 +84,7 @@ func lastDraftCard(t *testing.T, texts []string) string {
// Regression test: one package-level draft per bot meant an admin's new client
// was filled in by another chat's steps.
func TestAddClientDraftIsPerChat(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
const (
chatA = int64(7101)
@@ -6,7 +6,7 @@ import (
"strings"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/web/locale"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
@@ -35,10 +35,7 @@ func clientInfoLocalizer(t *testing.T) {
// Regression test: a start-after-first-use client is stored as a negative duration,
// and a disabled one rendered it as a 1969 date.
func TestClientInfoShowsStartAfterFirstUseWhenDisabled(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
clientInfoLocalizer(t)
traffic := &xray.ClientTraffic{
@@ -6,6 +6,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/global"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
@@ -33,10 +34,7 @@ func newLinksCallbackTgbot(t *testing.T, email string) (*Tgbot, func(string) int
swapTestBot(t, mock.URL)
t.Cleanup(mock.Close)
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
inbound := &model.Inbound{
UserId: 1,
@@ -12,6 +12,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
@@ -59,10 +60,7 @@ func seedReportClients(t *testing.T, remark string, emails []string) {
func initReportDB(t *testing.T) *Tgbot {
t.Helper()
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
origRunning := isRunning
t.Cleanup(func() { isRunning = origRunning })
@@ -8,7 +8,7 @@ import (
"sync"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mymmrac/telego"
@@ -58,10 +58,7 @@ func swapTestBot(t *testing.T, url string) {
func newStaleButtonTgbot(t *testing.T) *Tgbot {
t.Helper()
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
return &Tgbot{}
}
+3 -9
View File
@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/eventbus"
)
@@ -61,10 +61,7 @@ func runObservatory(t *testing.T, threshold int, seq []probe) []eventbus.EventTy
}
func TestApplyObservatoryDebounce(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("init db: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
tests := []struct {
name string
@@ -150,10 +147,7 @@ func TestValidObsTag(t *testing.T) {
func TestApplyObservatoryKeepsUnicodeTags(t *testing.T) {
dbDir := t.TempDir()
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
s := &XrayMetricsService{settingService: SettingService{}}
s.applyObservatory(time.Unix(1000, 0), map[string]rawObsEntry{