tuic_journal_test.go 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. package job
  2. import (
  3. "os"
  4. "path/filepath"
  5. "testing"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
  8. "github.com/mhsanaei/3x-ui/v3/internal/tuic"
  9. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  10. )
  11. func TestTuicShutdownJournalSurvivesDatabaseFailureAndReplaysOnce(t *testing.T) {
  12. dir := t.TempDir()
  13. t.Setenv("XUI_DB_FOLDER", dir)
  14. dbtest.InitDB(t, filepath.Join(dir, "x-ui.db"))
  15. db := database.GetDB()
  16. row := xray.ClientTraffic{Email: "journal@x", Enable: true}
  17. if err := db.Create(&row).Error; err != nil {
  18. t.Fatal(err)
  19. }
  20. batch := tuicTrafficBatch{ID: "shutdown-batch", Deltas: []tuic.ClientTrafficDelta{{Email: row.Email, TrafficID: row.Id, Up: 123, Down: 456}}}
  21. path, err := storeTuicBatch(batch)
  22. if err != nil {
  23. t.Fatal(err)
  24. }
  25. if err := db.Exec(`CREATE TRIGGER fail_tuic_write BEFORE UPDATE ON client_traffics BEGIN SELECT RAISE(FAIL, 'disk fault'); END`).Error; err != nil {
  26. t.Fatal(err)
  27. }
  28. job := NewTuicJob()
  29. if err := job.FlushStoppedTraffic(); err == nil {
  30. t.Fatal("failed traffic update must return an error")
  31. }
  32. if _, err := os.Stat(path); err != nil {
  33. t.Fatalf("durable recovery file missing: %v", err)
  34. }
  35. var receipts int64
  36. if err := db.Table("tuic_traffic_receipts").Count(&receipts).Error; err == nil && receipts != 0 {
  37. t.Fatal("failed transaction committed a receipt")
  38. }
  39. if err := db.Exec("DROP TRIGGER fail_tuic_write").Error; err != nil {
  40. t.Fatal(err)
  41. }
  42. // A new job has no knowledge of the previous process's in-memory deltas.
  43. recovered := NewTuicJob()
  44. if err := recovered.replayTuicJournal(); err != nil {
  45. t.Fatal(err)
  46. }
  47. // Recreate a stale file, as if file removal was lost after DB commit.
  48. if _, err := storeTuicBatch(batch); err != nil {
  49. t.Fatal(err)
  50. }
  51. if err := recovered.replayTuicJournal(); err != nil {
  52. t.Fatal(err)
  53. }
  54. if err := db.Where("id = ?", row.Id).First(&row).Error; err != nil {
  55. t.Fatal(err)
  56. }
  57. if row.Up != 123 || row.Down != 456 {
  58. t.Fatalf("replay billed %d/%d", row.Up, row.Down)
  59. }
  60. entries, err := os.ReadDir(tuicJournalDir())
  61. if err != nil || len(entries) != 0 {
  62. t.Fatalf("journal not drained: %v %v", entries, err)
  63. }
  64. }