Files
3x-ui-agent/internal/web/service/panel/api_token_test.go
T
Sanaei f727d04f65
Release 3X-UI for Docker / build (push) Canceled after 0s
Release 3X-UI / build (386) (push) Canceled after 0s
Release 3X-UI / build (amd64) (push) Canceled after 0s
Release 3X-UI / build (arm64) (push) Canceled after 0s
Release 3X-UI / build (armv5) (push) Canceled after 0s
Release 3X-UI / build (armv6) (push) Canceled after 0s
Release 3X-UI / build (armv7) (push) Canceled after 0s
Release 3X-UI / build (s390x) (push) Canceled after 0s
Release 3X-UI / Build for Windows (amd64) (push) Canceled after 0s
Release 3X-UI / Publish rolling dev release (push) Canceled after 0s
v3.7.0
2026-08-24 15:07:15 +02:00

99 lines
2.8 KiB
Go

package panel
import (
"errors"
"testing"
"gorm.io/gorm"
"github.com/mhsanaei/3x-ui/v3/internal/config"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
var errInjectedTokenCreate = errors.New("injected token create failure")
func TestApiTokenCreatedAtSeconds(t *testing.T) {
tests := []struct {
name string
in int64
want int64
}{
{name: "seconds", in: 1_782_485_394, want: 1_782_485_394},
{name: "legacy milliseconds", in: 1_782_485_394_270, want: 1_782_485_394},
{name: "unset", in: 0, want: 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := apiTokenCreatedAtSeconds(tt.in); got != tt.want {
t.Fatalf("apiTokenCreatedAtSeconds(%d) = %d, want %d", tt.in, got, tt.want)
}
})
}
}
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() })
svc := ApiTokenService{}
first, err := svc.RecreateByName("cli-fallback")
if err != nil {
t.Fatalf("first recreate: %v", err)
}
db := database.GetDB()
const callback = "test:fail-token-replacement"
if err := db.Callback().Create().Before("gorm:create").Register(callback, func(tx *gorm.DB) {
if token, ok := tx.Statement.Dest.(*model.ApiToken); ok && token.Name == "cli-fallback" {
tx.AddError(errInjectedTokenCreate)
}
}); err != nil {
t.Fatalf("register callback: %v", err)
}
t.Cleanup(func() { _ = db.Callback().Create().Remove(callback) })
if _, err := svc.RecreateByName("cli-fallback"); !errors.Is(err, errInjectedTokenCreate) {
t.Fatalf("recreate error = %v, want %v", err, errInjectedTokenCreate)
}
var row model.ApiToken
if err := db.Where("name = ?", "cli-fallback").First(&row).Error; err != nil {
t.Fatalf("load preserved token: %v", err)
}
if !svc.Match(first.Token) {
t.Fatal("original token was revoked after replacement failure")
}
}
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() })
svc := ApiTokenService{}
first, err := svc.RecreateByName("cli-fallback")
if err != nil {
t.Fatalf("first recreate: %v", err)
}
second, err := svc.RecreateByName("cli-fallback")
if err != nil {
t.Fatalf("second recreate: %v", err)
}
if first.Token == second.Token {
t.Fatal("second call returned the same plaintext, want a rotated token")
}
var count int64
if err := database.GetDB().Model(model.ApiToken{}).Where("name = ?", "cli-fallback").Count(&count).Error; err != nil {
t.Fatalf("count: %v", err)
}
if count != 1 {
t.Fatalf("token rows = %d, want 1", count)
}
}