wg.lua 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. --[[
  2. tests/wg.lua — Unit tests for wg.lua
  3. Pure helpers + injected-exec integration (no root, no wg CLI needed).
  4. ]]
  5. package.path = package.path .. ";./src/?.lua"
  6. local wg = require("balancerlite.wg")
  7. local passed = 0
  8. local failed = 0
  9. local function check(name, ok, detail)
  10. if ok then
  11. passed = passed + 1
  12. print(" PASS " .. name)
  13. else
  14. failed = failed + 1
  15. print(" FAIL " .. name .. (detail and (" " .. detail) or ""))
  16. end
  17. end
  18. -- -----------------------------------------------------------------
  19. -- TEST 1: q() safe-quoting
  20. -- -----------------------------------------------------------------
  21. print("\n[1] q() safe-quoting")
  22. do
  23. local q = wg.q
  24. check("plain endpoint", q("1.2.3.4:51820") == "'1.2.3.4:51820'")
  25. check("base64 key OK", q("abcDEF123==") == "'abcDEF123=='")
  26. check("slash OK", q("/dev/null") == "'/dev/null'")
  27. check("spaces rejected", q("a b") == nil)
  28. check("quote rejected", q("a'b") == nil)
  29. check("nil rejected", q(nil) == nil)
  30. end
  31. -- -----------------------------------------------------------------
  32. -- TEST 2: build_set_endpoint_cmd disabled
  33. -- -----------------------------------------------------------------
  34. print("\n[2] build_set_endpoint_cmd disabled")
  35. do
  36. local r = wg.build_set_endpoint_cmd({enabled=false, interface="wg0",
  37. public_key="pk", endpoint_a="1.2.3.4:51820"},
  38. {}, "wan-a")
  39. check("ok=true (no-op)", r.ok == true)
  40. check("no cmd", r.cmd == nil)
  41. check("reason set", type(r.reason) == "string" and r.reason:match("disabled"))
  42. end
  43. -- -----------------------------------------------------------------
  44. -- TEST 3: build_set_endpoint_cmd happy path
  45. -- -----------------------------------------------------------------
  46. print("\n[3] build_set_endpoint_cmd happy path")
  47. do
  48. local cfg = { enabled=true, interface="wg0",
  49. public_key="PUBKEY",
  50. endpoint_a="1.2.3.4:51820",
  51. endpoint_b="4.5.6.7:51821" }
  52. local wans = {
  53. { id="wan-a", address="1.2.3.4/24" },
  54. { id="wan-b", address="4.5.6.7/24" },
  55. }
  56. local r = wg.build_set_endpoint_cmd(cfg, wans, "wan-b")
  57. check("ok", r.ok, r.err)
  58. check("cmd present", type(r.cmd) == "string")
  59. check("cmd contains wg set wg0 peer PUBKEY endpoint '4.5.6.7:51821'",
  60. r.cmd == "wg set 'wg0' peer 'PUBKEY' endpoint '4.5.6.7:51821'",
  61. "got: " .. tostring(r.cmd))
  62. end
  63. -- -----------------------------------------------------------------
  64. -- TEST 4: build_set_endpoint_cmd unknown wan
  65. -- -----------------------------------------------------------------
  66. print("\n[4] build_set_endpoint_cmd unknown wan")
  67. do
  68. local cfg = { enabled=true, interface="wg0", public_key="PUBKEY",
  69. endpoint_a="1.2.3.4:51820" }
  70. local r = wg.build_set_endpoint_cmd(cfg, {}, "wan-z")
  71. check("ok=false", r.ok == false)
  72. check("err mentions wan-z", type(r.err) == "string" and r.err:match("wan.z"))
  73. end
  74. -- -----------------------------------------------------------------
  75. -- TEST 5: build_set_endpoint_cmd no endpoint for that WAN
  76. -- -----------------------------------------------------------------
  77. print("\n[5] build_set_endpoint_cmd no endpoint configured")
  78. do
  79. local cfg = { enabled=true, interface="wg0", public_key="PUBKEY",
  80. endpoint_a="", endpoint_b="4.5.6.7:51821" }
  81. local r = wg.build_set_endpoint_cmd(cfg, {}, "wan-a")
  82. check("ok=true (no-op)", r.ok == true)
  83. check("no cmd", r.cmd == nil)
  84. check("reason mentions wan-a", type(r.reason) == "string" and r.reason:match("wan.a"))
  85. end
  86. -- -----------------------------------------------------------------
  87. -- TEST 6: build_syncconf_cmd
  88. -- -----------------------------------------------------------------
  89. print("\n[6] build_syncconf_cmd")
  90. do
  91. local r = wg.build_syncconf_cmd({ enabled=true, interface="wg0" })
  92. check("ok", r.ok)
  93. check("cmd", r.cmd == "wg syncconf 'wg0' /dev/null", "got: " .. tostring(r.cmd))
  94. local r2 = wg.build_syncconf_cmd({ enabled=false })
  95. check("disabled ok", r2.ok and r2.cmd == nil)
  96. end
  97. -- -----------------------------------------------------------------
  98. -- TEST 7: parse_latest_handshake
  99. -- -----------------------------------------------------------------
  100. print("\n[7] parse_latest_handshake")
  101. do
  102. local p = wg.parse_latest_handshake
  103. check("single handshake",
  104. p("ABC123\t42s ago\n") == 42)
  105. check("multiple -> min",
  106. p("A\t100s ago\nB\t5s ago\nC\t60s ago\n") == 5)
  107. check("nil on empty", p("") == nil)
  108. check("nil on garbage", p("no numbers here") == nil)
  109. end
  110. -- -----------------------------------------------------------------
  111. -- TEST 8: set_endpoint with exec injection
  112. -- -----------------------------------------------------------------
  113. print("\n[8] set_endpoint with exec injection")
  114. do
  115. local cmds = {}
  116. local function fake_exec(c) cmds[#cmds+1] = c; return 0 end
  117. local cfg = { enabled=true, interface="wg0",
  118. public_key="PUBKEY",
  119. endpoint_a="1.2.3.4:51820",
  120. endpoint_b="4.5.6.7:51821" }
  121. local W = wg.new(cfg, {}, { exec = fake_exec })
  122. local r = W:set_endpoint("wan-a")
  123. check("ok", r.ok, r.err)
  124. check("2 cmds run (set + syncconf)", #cmds == 2, "got " .. tostring(#cmds))
  125. check("first cmd is wg set", cmds[1]:match("^wg set"))
  126. check("second cmd is wg syncconf", cmds[2]:match("^wg syncconf"))
  127. end
  128. -- -----------------------------------------------------------------
  129. -- TEST 9: set_endpoint disabled (no cmds)
  130. -- -----------------------------------------------------------------
  131. print("\n[9] set_endpoint disabled")
  132. do
  133. local cmds = {}
  134. local function fake_exec(c) cmds[#cmds+1] = c; return 0 end
  135. local W = wg.new({enabled=false, interface="wg0", public_key="PUBKEY"}, {},
  136. { exec = fake_exec })
  137. local r = W:set_endpoint("wan-a")
  138. check("ok", r.ok)
  139. check("no cmds", #cmds == 0)
  140. end
  141. -- -----------------------------------------------------------------
  142. -- TEST 10: set_endpoint failure
  143. -- -----------------------------------------------------------------
  144. print("\n[10] set_endpoint exec failure")
  145. do
  146. local function fake_exec() return 2 end
  147. local cfg = { enabled=true, interface="wg0", public_key="PUBKEY",
  148. endpoint_a="1.2.3.4:51820" }
  149. local W = wg.new(cfg, {}, { exec = fake_exec })
  150. local r = W:set_endpoint("wan-a")
  151. check("ok=false", r.ok == false)
  152. check("err reports rc", type(r.err) == "string" and r.err:match("rc=2"))
  153. end
  154. -- -----------------------------------------------------------------
  155. -- TEST 11: prewarm returns standby endpoint
  156. -- -----------------------------------------------------------------
  157. print("\n[11] prewarm")
  158. do
  159. local W = wg.new({ enabled=true, prewarm_on_standby=true,
  160. endpoint_a="1.2.3.4:51820", endpoint_b="4.5.6.7:51821" }, {})
  161. check("returns endpoint_a", W:prewarm() == "1.2.3.4:51820")
  162. local W2 = wg.new({ enabled=true, prewarm_on_standby=false }, {})
  163. check("disabled returns nil", W2:prewarm() == nil)
  164. local W3 = wg.new({ enabled=true, prewarm_on_standby=true }, {})
  165. check("no endpoint returns nil", W3:prewarm() == nil)
  166. end
  167. -- -----------------------------------------------------------------
  168. -- Done
  169. -- -----------------------------------------------------------------
  170. print(string.format("\n=== wg: %d/%d passed ===", passed, passed + failed))
  171. if failed > 0 then os.exit(1) end