api_token_test.go 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. package panel
  2. import (
  3. "errors"
  4. "testing"
  5. "gorm.io/gorm"
  6. "github.com/mhsanaei/3x-ui/v3/internal/config"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  9. )
  10. var errInjectedTokenCreate = errors.New("injected token create failure")
  11. func TestApiTokenCreatedAtSeconds(t *testing.T) {
  12. tests := []struct {
  13. name string
  14. in int64
  15. want int64
  16. }{
  17. {name: "seconds", in: 1_782_485_394, want: 1_782_485_394},
  18. {name: "legacy milliseconds", in: 1_782_485_394_270, want: 1_782_485_394},
  19. {name: "unset", in: 0, want: 0},
  20. }
  21. for _, tt := range tests {
  22. t.Run(tt.name, func(t *testing.T) {
  23. if got := apiTokenCreatedAtSeconds(tt.in); got != tt.want {
  24. t.Fatalf("apiTokenCreatedAtSeconds(%d) = %d, want %d", tt.in, got, tt.want)
  25. }
  26. })
  27. }
  28. }
  29. func TestRecreateByNamePreservesTokenWhenReplacementFails(t *testing.T) {
  30. t.Setenv("XUI_DB_FOLDER", t.TempDir())
  31. if err := database.InitDB(config.GetDBPath()); err != nil {
  32. t.Fatalf("init db: %v", err)
  33. }
  34. t.Cleanup(func() { _ = database.CloseDB() })
  35. svc := ApiTokenService{}
  36. first, err := svc.RecreateByName("cli-fallback")
  37. if err != nil {
  38. t.Fatalf("first recreate: %v", err)
  39. }
  40. db := database.GetDB()
  41. const callback = "test:fail-token-replacement"
  42. if err := db.Callback().Create().Before("gorm:create").Register(callback, func(tx *gorm.DB) {
  43. if token, ok := tx.Statement.Dest.(*model.ApiToken); ok && token.Name == "cli-fallback" {
  44. tx.AddError(errInjectedTokenCreate)
  45. }
  46. }); err != nil {
  47. t.Fatalf("register callback: %v", err)
  48. }
  49. t.Cleanup(func() { _ = db.Callback().Create().Remove(callback) })
  50. if _, err := svc.RecreateByName("cli-fallback"); !errors.Is(err, errInjectedTokenCreate) {
  51. t.Fatalf("recreate error = %v, want %v", err, errInjectedTokenCreate)
  52. }
  53. var row model.ApiToken
  54. if err := db.Where("name = ?", "cli-fallback").First(&row).Error; err != nil {
  55. t.Fatalf("load preserved token: %v", err)
  56. }
  57. if !svc.Match(first.Token) {
  58. t.Fatal("original token was revoked after replacement failure")
  59. }
  60. }
  61. func TestRecreateByNameKeepsOneToken(t *testing.T) {
  62. t.Setenv("XUI_DB_FOLDER", t.TempDir())
  63. if err := database.InitDB(config.GetDBPath()); err != nil {
  64. t.Fatalf("init db: %v", err)
  65. }
  66. t.Cleanup(func() { _ = database.CloseDB() })
  67. svc := ApiTokenService{}
  68. first, err := svc.RecreateByName("cli-fallback")
  69. if err != nil {
  70. t.Fatalf("first recreate: %v", err)
  71. }
  72. second, err := svc.RecreateByName("cli-fallback")
  73. if err != nil {
  74. t.Fatalf("second recreate: %v", err)
  75. }
  76. if first.Token == second.Token {
  77. t.Fatal("second call returned the same plaintext, want a rotated token")
  78. }
  79. var count int64
  80. if err := database.GetDB().Model(model.ApiToken{}).Where("name = ?", "cli-fallback").Count(&count).Error; err != nil {
  81. t.Fatalf("count: %v", err)
  82. }
  83. if count != 1 {
  84. t.Fatalf("token rows = %d, want 1", count)
  85. }
  86. }