client_ips_prune_test.go 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. package service
  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. )
  9. // Rows for clients absent from the online scan are never rewritten, so the
  10. // sweep is the only thing standing between them and indefinite retention.
  11. func TestPruneStaleClientIpsExpiresUnobservedRows(t *testing.T) {
  12. setupClientIpTestDB(t)
  13. db := database.GetDB()
  14. now := time.Now().Unix()
  15. stale := now - clientIpStaleAfterSeconds - 300
  16. fresh := now - 60
  17. mkNode := func(entries ...model.ClientIpEntry) string {
  18. t.Helper()
  19. b, err := json.Marshal(entries)
  20. if err != nil {
  21. t.Fatalf("marshal node ips: %v", err)
  22. }
  23. return string(b)
  24. }
  25. seed := []any{
  26. &model.InboundClientIps{ClientEmail: "offline", Ips: marshalIps(t, clientIpEntry{IP: "198.51.100.7", Timestamp: stale})},
  27. &model.InboundClientIps{ClientEmail: "mixed", Ips: marshalIps(t,
  28. clientIpEntry{IP: "198.51.100.8", Timestamp: stale},
  29. clientIpEntry{IP: "203.0.113.9", Timestamp: fresh})},
  30. &model.NodeClientIp{NodeGuid: "g1", Email: "node-offline", Ips: mkNode(model.ClientIpEntry{IP: "198.51.100.9", Timestamp: stale})},
  31. &model.NodeClientIp{NodeGuid: "g1", Email: "node-fresh", Ips: mkNode(model.ClientIpEntry{IP: "203.0.113.10", Timestamp: fresh})},
  32. }
  33. for _, row := range seed {
  34. if err := db.Create(row).Error; err != nil {
  35. t.Fatalf("seed %T: %v", row, err)
  36. }
  37. }
  38. if err := (&InboundService{}).PruneStaleClientIps(); err != nil {
  39. t.Fatalf("PruneStaleClientIps: %v", err)
  40. }
  41. if _, exists := readClientIps(t, "offline"); exists {
  42. t.Fatal("fully stale inbound_client_ips row must be deleted")
  43. }
  44. got, exists := readClientIps(t, "mixed")
  45. if !exists {
  46. t.Fatal("row with a fresh entry must survive")
  47. }
  48. if len(got) != 1 || got["203.0.113.9"] != fresh {
  49. t.Fatalf("mixed row = %v, want only 203.0.113.9@%d", got, fresh)
  50. }
  51. var nodeRows []model.NodeClientIp
  52. if err := db.Where("node_guid = ?", "g1").Find(&nodeRows).Error; err != nil {
  53. t.Fatalf("read node rows: %v", err)
  54. }
  55. if len(nodeRows) != 1 || nodeRows[0].Email != "node-fresh" {
  56. t.Fatalf("node rows after prune = %+v, want only node-fresh", nodeRows)
  57. }
  58. var kept []model.ClientIpEntry
  59. if err := json.Unmarshal([]byte(nodeRows[0].Ips), &kept); err != nil || len(kept) != 1 || kept[0].IP != "203.0.113.10" {
  60. t.Fatalf("node-fresh ips = %q (err %v), want 203.0.113.10 kept", nodeRows[0].Ips, err)
  61. }
  62. }