wifi_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. package wifi
  2. import (
  3. "os"
  4. "path/filepath"
  5. "runtime"
  6. "testing"
  7. db "imuslab.com/arozos/mod/database"
  8. )
  9. // newTestDatabase creates a temporary bolt database for use in tests.
  10. // The caller is responsible for calling the returned cleanup function.
  11. func newTestDatabase(t *testing.T) (*db.Database, func()) {
  12. t.Helper()
  13. tmpDir := t.TempDir()
  14. dbPath := filepath.Join(tmpDir, "test.db")
  15. // Create the file so the database opener can find it.
  16. f, err := os.Create(dbPath)
  17. if err != nil {
  18. t.Fatalf("failed to create temp db file: %v", err)
  19. }
  20. f.Close()
  21. database, err := db.NewDatabase(dbPath, false)
  22. if err != nil {
  23. t.Fatalf("NewDatabase() error: %v", err)
  24. }
  25. cleanup := func() {
  26. // Nothing extra needed; t.TempDir() handles directory removal.
  27. _ = database
  28. }
  29. return database, cleanup
  30. }
  31. // ---------------------------------------------------------------------------
  32. // NewWiFiManager
  33. // ---------------------------------------------------------------------------
  34. // TestNewWiFiManager verifies that the constructor returns a non-nil manager
  35. // and creates the expected database table.
  36. func TestNewWiFiManager(t *testing.T) {
  37. database, cleanup := newTestDatabase(t)
  38. defer cleanup()
  39. wm := NewWiFiManager(database, false, "/etc/wpa_supplicant/wpa_supplicant.conf", "wlan0")
  40. if wm == nil {
  41. t.Fatal("NewWiFiManager() returned nil")
  42. }
  43. // Verify the wifi table was created.
  44. if !database.TableExists("wifi") {
  45. t.Error("NewWiFiManager() did not create 'wifi' table in the database")
  46. }
  47. }
  48. // TestNewWiFiManagerSudoMode verifies that sudo_mode is stored correctly.
  49. func TestNewWiFiManagerSudoMode(t *testing.T) {
  50. database, cleanup := newTestDatabase(t)
  51. defer cleanup()
  52. wm := NewWiFiManager(database, true, "/tmp/wpa.conf", "wlan1")
  53. if wm == nil {
  54. t.Fatal("NewWiFiManager() returned nil")
  55. }
  56. if !wm.sudo_mode {
  57. t.Error("sudo_mode should be true")
  58. }
  59. }
  60. // TestNewWiFiManagerFields verifies that all constructor arguments are stored.
  61. func TestNewWiFiManagerFields(t *testing.T) {
  62. database, cleanup := newTestDatabase(t)
  63. defer cleanup()
  64. wpaPath := "/etc/wpa_supplicant.conf"
  65. wlanName := "wlan2"
  66. wm := NewWiFiManager(database, false, wpaPath, wlanName)
  67. if wm.wpa_supplicant_path != wpaPath {
  68. t.Errorf("wpa_supplicant_path = %q, expected %q", wm.wpa_supplicant_path, wpaPath)
  69. }
  70. if wm.wan_interface_name != wlanName {
  71. t.Errorf("wan_interface_name = %q, expected %q", wm.wan_interface_name, wlanName)
  72. }
  73. if wm.database != database {
  74. t.Error("database pointer not stored correctly")
  75. }
  76. }
  77. // ---------------------------------------------------------------------------
  78. // GetWirelessInterfaces
  79. // ---------------------------------------------------------------------------
  80. // TestGetWirelessInterfaces verifies that GetWirelessInterfaces does not error
  81. // on the current platform. On Linux it may return an empty list if no wireless
  82. // hardware is present (common in CI); on Darwin/FreeBSD it always returns an
  83. // empty list (stub implementation). On Windows it calls netsh; errors from
  84. // missing netsh are tolerated.
  85. func TestGetWirelessInterfaces(t *testing.T) {
  86. database, cleanup := newTestDatabase(t)
  87. defer cleanup()
  88. wm := NewWiFiManager(database, false, "", "")
  89. switch runtime.GOOS {
  90. case "linux":
  91. ifaces, err := wm.GetWirelessInterfaces()
  92. if err != nil {
  93. t.Logf("GetWirelessInterfaces() returned error (may be expected in CI without iw): %v", err)
  94. return
  95. }
  96. // ifaces may be empty (no wireless hardware) or contain interface names.
  97. for _, iface := range ifaces {
  98. if iface == "" {
  99. t.Error("GetWirelessInterfaces() returned an empty string in the interface list")
  100. }
  101. }
  102. case "darwin", "freebsd":
  103. // Stub implementations always return an empty slice with no error.
  104. ifaces, err := wm.GetWirelessInterfaces()
  105. if err != nil {
  106. t.Errorf("GetWirelessInterfaces() unexpected error on %s: %v", runtime.GOOS, err)
  107. }
  108. if len(ifaces) != 0 {
  109. t.Errorf("GetWirelessInterfaces() on %s should return empty slice, got %v", runtime.GOOS, ifaces)
  110. }
  111. case "windows":
  112. // May fail in CI if running without wireless hardware; that's acceptable.
  113. _, err := wm.GetWirelessInterfaces()
  114. if err != nil {
  115. t.Logf("GetWirelessInterfaces() on Windows returned error (may be expected in CI): %v", err)
  116. }
  117. default:
  118. t.Skipf("GetWirelessInterfaces not tested on %s", runtime.GOOS)
  119. }
  120. }
  121. // ---------------------------------------------------------------------------
  122. // Platform-specific capability tests
  123. // ---------------------------------------------------------------------------
  124. // TestSetInterfacePowerUnsupported verifies that SetInterfacePower returns an
  125. // error on platforms that do not support the operation (Darwin, FreeBSD, Windows).
  126. func TestSetInterfacePowerUnsupported(t *testing.T) {
  127. switch runtime.GOOS {
  128. case "darwin", "freebsd", "windows":
  129. // Intentionally left without t.Skip: these platforms should return an error.
  130. case "linux":
  131. t.Skip("skipping unsupported-platform test on Linux")
  132. default:
  133. t.Skipf("platform %s not tested", runtime.GOOS)
  134. }
  135. database, cleanup := newTestDatabase(t)
  136. defer cleanup()
  137. wm := NewWiFiManager(database, false, "", "")
  138. err := wm.SetInterfacePower("wlan0", true)
  139. if err == nil {
  140. t.Errorf("SetInterfacePower() on %s should return an error, got nil", runtime.GOOS)
  141. }
  142. }
  143. // TestGetInterfacePowerStatusUnsupported verifies that GetInterfacePowerStatuts
  144. // returns an error on Darwin, FreeBSD, and Windows.
  145. func TestGetInterfacePowerStatusUnsupported(t *testing.T) {
  146. switch runtime.GOOS {
  147. case "darwin", "freebsd", "windows":
  148. // These platforms should return an error.
  149. case "linux":
  150. t.Skip("skipping unsupported-platform test on Linux")
  151. default:
  152. t.Skipf("platform %s not tested", runtime.GOOS)
  153. }
  154. database, cleanup := newTestDatabase(t)
  155. defer cleanup()
  156. wm := NewWiFiManager(database, false, "", "")
  157. _, err := wm.GetInterfacePowerStatuts("wlan0")
  158. if err == nil {
  159. t.Errorf("GetInterfacePowerStatuts() on %s should return an error, got nil", runtime.GOOS)
  160. }
  161. }
  162. // TestScanNearbyWiFiUnsupported verifies that ScanNearbyWiFi returns an error
  163. // on Darwin and FreeBSD (stub implementations).
  164. func TestScanNearbyWiFiUnsupported(t *testing.T) {
  165. switch runtime.GOOS {
  166. case "darwin", "freebsd":
  167. // These platforms should return an error.
  168. default:
  169. t.Skipf("skipping ScanNearbyWiFi unsupported test on %s", runtime.GOOS)
  170. }
  171. database, cleanup := newTestDatabase(t)
  172. defer cleanup()
  173. wm := NewWiFiManager(database, false, "", "")
  174. results, err := wm.ScanNearbyWiFi("wlan0")
  175. if err == nil {
  176. t.Errorf("ScanNearbyWiFi() on %s should return an error, got nil", runtime.GOOS)
  177. }
  178. if results == nil {
  179. t.Error("ScanNearbyWiFi() should return an empty (non-nil) slice on error")
  180. }
  181. }
  182. // TestConnectWiFiUnsupported verifies that ConnectWiFi returns an error on
  183. // Darwin, FreeBSD, and Windows stub implementations.
  184. func TestConnectWiFiUnsupported(t *testing.T) {
  185. switch runtime.GOOS {
  186. case "darwin", "freebsd", "windows":
  187. // These platforms should return an error.
  188. case "linux":
  189. t.Skip("skipping unsupported platform ConnectWiFi test on Linux")
  190. default:
  191. t.Skipf("platform %s not tested", runtime.GOOS)
  192. }
  193. database, cleanup := newTestDatabase(t)
  194. defer cleanup()
  195. wm := NewWiFiManager(database, false, "", "")
  196. result, err := wm.ConnectWiFi("TestSSID", "password", "", "")
  197. if err == nil {
  198. t.Errorf("ConnectWiFi() on %s should return an error, got nil", runtime.GOOS)
  199. }
  200. if result == nil {
  201. t.Error("ConnectWiFi() should return a non-nil result even on error")
  202. }
  203. }
  204. // TestRemoveWifiUnsupported verifies that RemoveWifi returns an error on
  205. // Darwin, FreeBSD, and Windows stub implementations.
  206. func TestRemoveWifiUnsupported(t *testing.T) {
  207. switch runtime.GOOS {
  208. case "darwin", "freebsd", "windows":
  209. // These platforms should return an error.
  210. case "linux":
  211. t.Skip("skipping unsupported platform RemoveWifi test on Linux")
  212. default:
  213. t.Skipf("platform %s not tested", runtime.GOOS)
  214. }
  215. database, cleanup := newTestDatabase(t)
  216. defer cleanup()
  217. wm := NewWiFiManager(database, false, "", "")
  218. err := wm.RemoveWifi("TestSSID")
  219. if err == nil {
  220. t.Errorf("RemoveWifi() on %s should return an error, got nil", runtime.GOOS)
  221. }
  222. }
  223. // ---------------------------------------------------------------------------
  224. // Linux-specific helper function tests
  225. // ---------------------------------------------------------------------------
  226. // TestFileExistsLinux verifies the fileExists helper.
  227. func TestFileExistsLinux(t *testing.T) {
  228. if runtime.GOOS != "linux" {
  229. t.Skip("fileExists helper is in linux-only source; test runs on Linux")
  230. }
  231. // A path that exists.
  232. if !fileExists("/proc/version") {
  233. t.Error("fileExists(\"/proc/version\") should return true on Linux")
  234. }
  235. // A path that does not exist.
  236. if fileExists("/nonexistent_path_xyz_12345") {
  237. t.Error("fileExists(\"/nonexistent_path_xyz_12345\") should return false")
  238. }
  239. }
  240. // TestFileInDirLinux verifies the fileInDir helper.
  241. func TestFileInDirLinux(t *testing.T) {
  242. if runtime.GOOS != "linux" {
  243. t.Skip("fileInDir helper is in linux-only source; test runs on Linux")
  244. }
  245. // The file is inside the directory.
  246. if !fileInDir("/tmp/foo/bar.txt", "/tmp/foo") {
  247. t.Error("fileInDir should return true when file is inside directory")
  248. }
  249. // The file is outside the directory.
  250. if fileInDir("/etc/passwd", "/tmp") {
  251. t.Error("fileInDir should return false when file is outside directory")
  252. }
  253. }
  254. // TestPkgExistsLinux verifies the pkg_exists helper.
  255. func TestPkgExistsLinux(t *testing.T) {
  256. if runtime.GOOS != "linux" {
  257. t.Skip("pkg_exists helper is in linux-only source; test runs on Linux")
  258. }
  259. // "sh" should always exist on Linux.
  260. if !pkg_exists("sh") {
  261. t.Error("pkg_exists(\"sh\") should return true on Linux")
  262. }
  263. // A package that almost certainly doesn't exist.
  264. if pkg_exists("this_package_does_not_exist_xyz") {
  265. t.Error("pkg_exists(\"this_package_does_not_exist_xyz\") should return false")
  266. }
  267. }
  268. // TestGetSignalLevelEstimation verifies the bar-to-dBm mapping.
  269. func TestGetSignalLevelEstimation(t *testing.T) {
  270. if runtime.GOOS != "linux" {
  271. t.Skip("getSignalLevelEstimation is in linux-only source; test runs on Linux")
  272. }
  273. database, cleanup := newTestDatabase(t)
  274. defer cleanup()
  275. wm := NewWiFiManager(database, false, "", "")
  276. cases := []struct {
  277. bar string
  278. expected string
  279. }{
  280. {"▂▄▆█", "-45 dBm[Estimated]"},
  281. {"▂▄▆_", "-55 dBm[Estimated]"},
  282. {"▂▄__", "-75 dBm[Estimated]"},
  283. {"▂___", "-85 dBm[Estimated]"},
  284. {"____", "-95 dBm[Estimated]"},
  285. {"", "-95 dBm[Estimated]"},
  286. }
  287. for _, tc := range cases {
  288. got := wm.getSignalLevelEstimation(tc.bar)
  289. if got != tc.expected {
  290. t.Errorf("getSignalLevelEstimation(%q) = %q, expected %q", tc.bar, got, tc.expected)
  291. }
  292. }
  293. }