check_client_ip_cas_test.go 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. package job
  2. import (
  3. "encoding/json"
  4. "testing"
  5. "time"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  8. "gorm.io/gorm"
  9. )
  10. // A node sync landing between the scan's read and its write must keep its remote
  11. // IP (#6587); the hook injects that write, since SQLite serializes real writers.
  12. func TestProcessObserved_KeepsNodeSyncWriteThatLandsMidScan(t *testing.T) {
  13. setupIntegrationDB(t)
  14. db := database.GetDB()
  15. const email = "cas-scan@x"
  16. seedLinkedInboundWithClient(t, "cas-scan", email, 3)
  17. now := time.Now().Unix()
  18. seedClientIps(t, email, []IPWithTimestamp{{IP: "198.51.100.1", Timestamp: now - 60}})
  19. nodeBlob, err := json.Marshal([]IPWithTimestamp{
  20. {IP: "198.51.100.1", Timestamp: now - 60},
  21. {IP: "203.0.113.77", Timestamp: now - 5},
  22. })
  23. if err != nil {
  24. t.Fatalf("marshal node blob: %v", err)
  25. }
  26. const callback = "test:client_ip_scan_cas_inject"
  27. injected := false
  28. if err := db.Callback().Update().Before("gorm:update").Register(callback, func(tx *gorm.DB) {
  29. if injected || tx.Statement.Schema == nil || tx.Statement.Schema.Table != "inbound_client_ips" {
  30. return
  31. }
  32. injected = true
  33. if err := tx.Session(&gorm.Session{SkipHooks: true, NewDB: true}).
  34. Model(&model.InboundClientIps{}).
  35. Where("client_email = ?", email).
  36. Update("ips", string(nodeBlob)).Error; err != nil {
  37. _ = tx.AddError(err)
  38. }
  39. }); err != nil {
  40. t.Fatalf("register callback: %v", err)
  41. }
  42. t.Cleanup(func() { _ = db.Callback().Update().Remove(callback) })
  43. NewCheckClientIpJob().processObserved(map[string]map[string]int64{
  44. email: {"198.51.100.1": now},
  45. }, true, true)
  46. if !injected {
  47. t.Fatal("inject callback never fired; the scan's write path is untested")
  48. }
  49. got := ipSet(readClientIps(t, email))
  50. if _, ok := got["203.0.113.77"]; !ok {
  51. t.Fatalf("scan overwrote the node's remote IP (the #6587 lost update): %v", got)
  52. }
  53. if got["198.51.100.1"] != now {
  54. t.Fatalf("scan's own observation missing or stale: %v", got)
  55. }
  56. }