1
0

freedom_finalrules_migration_test.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package database
  2. import (
  3. "encoding/json"
  4. "testing"
  5. )
  6. func TestRewriteFreedomFinalRulesPrivateEgress(t *testing.T) {
  7. hardened := []any{
  8. map[string]any{"action": "block", "ip": []any{"geoip:private"}},
  9. map[string]any{"action": "allow"},
  10. }
  11. tests := []struct {
  12. name string
  13. raw string
  14. wantChanged bool
  15. wantRules []any
  16. }{
  17. {
  18. name: "allow-only default is hardened",
  19. raw: `{"outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs","finalRules":[{"action":"allow"}]},"tag":"direct"}]}`,
  20. wantChanged: true,
  21. wantRules: hardened,
  22. },
  23. {
  24. name: "missing finalRules is hardened",
  25. raw: `{"outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs"},"tag":"direct"}]}`,
  26. wantChanged: true,
  27. wantRules: hardened,
  28. },
  29. {
  30. name: "null finalRules is hardened",
  31. raw: `{"outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs","finalRules":null},"tag":"direct"}]}`,
  32. wantChanged: true,
  33. wantRules: hardened,
  34. },
  35. {
  36. name: "empty finalRules is hardened",
  37. raw: `{"outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs","finalRules":[]},"tag":"direct"}]}`,
  38. wantChanged: true,
  39. wantRules: hardened,
  40. },
  41. {
  42. name: "legacy private-only allow is hardened",
  43. raw: `{"outbounds":[{"protocol":"freedom","settings":{"finalRules":[{"action":"allow","ip":["geoip:private"]}]},"tag":"direct"}]}`,
  44. wantChanged: true,
  45. wantRules: hardened,
  46. },
  47. {
  48. name: "customized rules are preserved",
  49. raw: `{"outbounds":[{"protocol":"freedom","settings":{"finalRules":[{"action":"block","ip":["1.2.3.4"]},{"action":"allow"}]},"tag":"direct"}]}`,
  50. wantChanged: false,
  51. },
  52. {
  53. name: "non-freedom outbounds are ignored",
  54. raw: `{"outbounds":[{"protocol":"blackhole","settings":{},"tag":"blocked"}]}`,
  55. wantChanged: false,
  56. },
  57. {
  58. name: "empty config is untouched",
  59. raw: "",
  60. wantChanged: false,
  61. },
  62. }
  63. for _, tc := range tests {
  64. t.Run(tc.name, func(t *testing.T) {
  65. updated, changed, err := rewriteFreedomFinalRulesPrivateEgress(tc.raw)
  66. if err != nil {
  67. t.Fatalf("unexpected error: %v", err)
  68. }
  69. if changed != tc.wantChanged {
  70. t.Fatalf("changed = %v, want %v", changed, tc.wantChanged)
  71. }
  72. if !tc.wantChanged {
  73. if updated != tc.raw {
  74. t.Fatalf("raw config mutated without change flag:\n%s", updated)
  75. }
  76. return
  77. }
  78. var cfg map[string]any
  79. if err := json.Unmarshal([]byte(updated), &cfg); err != nil {
  80. t.Fatalf("updated config is not valid json: %v", err)
  81. }
  82. outbounds := cfg["outbounds"].([]any)
  83. settings := outbounds[0].(map[string]any)["settings"].(map[string]any)
  84. gotRules, _ := json.Marshal(settings["finalRules"])
  85. wantRules, _ := json.Marshal(tc.wantRules)
  86. if string(gotRules) != string(wantRules) {
  87. t.Fatalf("finalRules = %s, want %s", gotRules, wantRules)
  88. }
  89. })
  90. }
  91. }
  92. func TestRewriteFreedomFinalRulesPreservesSplitRouting(t *testing.T) {
  93. const raw = `{
  94. "outbounds":[{"protocol":"freedom","settings":{"domainStrategy":"AsIs"},"tag":"direct"}],
  95. "routing":{"domainStrategy":"AsIs","rules":[
  96. {"type":"field","domain":["regexp:.*\\.ru$"],"outboundTag":"direct"},
  97. {"type":"field","network":"tcp,udp","outboundTag":"proxy"}
  98. ]}
  99. }`
  100. updated, changed, err := rewriteFreedomFinalRulesPrivateEgress(raw)
  101. if err != nil {
  102. t.Fatalf("rewrite: %v", err)
  103. }
  104. if !changed {
  105. t.Fatal("missing finalRules must be hardened")
  106. }
  107. var before, after map[string]any
  108. if err := json.Unmarshal([]byte(raw), &before); err != nil {
  109. t.Fatalf("decode before: %v", err)
  110. }
  111. if err := json.Unmarshal([]byte(updated), &after); err != nil {
  112. t.Fatalf("decode after: %v", err)
  113. }
  114. beforeRouting, _ := json.Marshal(before["routing"])
  115. afterRouting, _ := json.Marshal(after["routing"])
  116. if string(afterRouting) != string(beforeRouting) {
  117. t.Fatalf("split routing changed:\n got %s\nwant %s", afterRouting, beforeRouting)
  118. }
  119. outbound := after["outbounds"].([]any)[0].(map[string]any)
  120. settings := outbound["settings"].(map[string]any)
  121. if settings["domainStrategy"] != "AsIs" {
  122. t.Fatalf("freedom domainStrategy=%v want AsIs", settings["domainStrategy"])
  123. }
  124. }
  125. func TestRewriteFreedomFinalRulesPrivateEgressInvalidJSON(t *testing.T) {
  126. _, changed, err := rewriteFreedomFinalRulesPrivateEgress("{not json")
  127. if err == nil {
  128. t.Fatal("expected a json error for malformed config")
  129. }
  130. if changed {
  131. t.Fatal("malformed config must not report changed")
  132. }
  133. }