store_test.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. package integration
  2. import (
  3. "context"
  4. "encoding/json"
  5. "testing"
  6. "time"
  7. "vocat/internal/device"
  8. "vocat/internal/store"
  9. "vocat/internal/vowifi"
  10. )
  11. func testStore(t *testing.T) *store.Store {
  12. t.Helper()
  13. database, err := store.Open(context.Background(), ":memory:")
  14. if err != nil {
  15. t.Fatal(err)
  16. }
  17. t.Cleanup(func() { _ = database.Close() })
  18. return database
  19. }
  20. func TestProxyResolverUsesDeviceBinding(t *testing.T) {
  21. database := testStore(t)
  22. if err := database.UpsertDevice(context.Background(), store.Device{ID: "ec20", Name: "EC20"}); err != nil {
  23. t.Fatal(err)
  24. }
  25. if err := database.UpsertUpstreamProxy(context.Background(), store.UpstreamProxy{
  26. ID: "clash",
  27. Name: "Clash",
  28. Addr: "192.168.2.143:7897",
  29. Enabled: true,
  30. Password: "must-not-be-lost",
  31. Username: "proxy-user",
  32. }); err != nil {
  33. t.Fatal(err)
  34. }
  35. if err := database.UpsertDeviceProxyBinding(context.Background(), store.DeviceProxyBinding{
  36. DeviceID: "ec20",
  37. UpstreamProxyID: "clash",
  38. }); err != nil {
  39. t.Fatal(err)
  40. }
  41. route, err := (ProxyResolver{Store: database}).Resolve(
  42. context.Background(),
  43. vowifi.ProxyRequest{DeviceID: "ec20", HomeMCC: "234", HomeMNC: "15"},
  44. )
  45. if err != nil {
  46. t.Fatal(err)
  47. }
  48. if route.Mode != vowifi.ProxyModeSOCKS5 ||
  49. route.Address != "192.168.2.143:7897" ||
  50. route.Username != "proxy-user" ||
  51. route.Password != "must-not-be-lost" {
  52. t.Fatalf("route = %#v", route)
  53. }
  54. }
  55. func TestProxyResolverDoesNotUseCountryRuleWithoutDeviceBinding(t *testing.T) {
  56. database := testStore(t)
  57. if err := database.UpsertUpstreamProxy(context.Background(), store.UpstreamProxy{
  58. ID: "legacy", Name: "Legacy", Addr: "127.0.0.1:1080", Enabled: true,
  59. }); err != nil {
  60. t.Fatal(err)
  61. }
  62. if err := database.UpsertCountryRule(context.Background(), store.CountryRule{
  63. CountryCode: "GB", CountryName: "United Kingdom", UpstreamProxyID: "legacy", Enabled: true,
  64. }); err != nil {
  65. t.Fatal(err)
  66. }
  67. route, err := (ProxyResolver{Store: database}).Resolve(
  68. context.Background(),
  69. vowifi.ProxyRequest{DeviceID: "ec20", HomeMCC: "234"},
  70. )
  71. if err != nil {
  72. t.Fatal(err)
  73. }
  74. if route.Mode != vowifi.ProxyModeDirect {
  75. t.Fatalf("route = %#v, want direct", route)
  76. }
  77. }
  78. func TestPhoneStoreRejectsUntrustedSource(t *testing.T) {
  79. database := testStore(t)
  80. phones := PhoneStore{Store: database, DeviceID: "ec20"}
  81. record := vowifi.PhoneRecord{
  82. ICCID: "89441000400311061404",
  83. Number: "+447700900123",
  84. Source: "imsi_guess",
  85. UpdatedAt: time.Now(),
  86. }
  87. if err := phones.SaveAssociatedNumber(context.Background(), record); err == nil {
  88. t.Fatal("untrusted source was accepted")
  89. }
  90. record.Source = vowifi.PhoneSourcePAssociatedURI
  91. if err := phones.SaveAssociatedNumber(context.Background(), record); err != nil {
  92. t.Fatal(err)
  93. }
  94. got, err := database.PhoneAssociation(context.Background(), record.ICCID)
  95. if err != nil {
  96. t.Fatal(err)
  97. }
  98. if got.Number != record.Number || got.Source != record.Source {
  99. t.Fatalf("association = %#v", got)
  100. }
  101. }
  102. func TestStateProjectorRestoresVerifiedNumber(t *testing.T) {
  103. database := testStore(t)
  104. if err := database.UpsertDevice(context.Background(), store.Device{
  105. ID: "ec20",
  106. Name: "EC20",
  107. }); err != nil {
  108. t.Fatal(err)
  109. }
  110. if err := database.UpsertPhoneAssociation(context.Background(), store.PhoneAssociation{
  111. ICCID: "89441000400311061404",
  112. DeviceID: "ec20",
  113. Number: "+447700900123",
  114. Source: vowifi.PhoneSourcePAssociatedURI,
  115. }); err != nil {
  116. t.Fatal(err)
  117. }
  118. projector := StateProjector{
  119. Store: database,
  120. Devices: staticDeviceReader{
  121. iccid: "89441000400311061404",
  122. imsi: "234159598901845",
  123. },
  124. }
  125. if err := projector.Save(context.Background(), vowifi.State{
  126. DeviceID: "ec20",
  127. Phase: vowifi.PhaseIdle,
  128. UpdatedAt: time.Now().UTC(),
  129. }); err != nil {
  130. t.Fatal(err)
  131. }
  132. runtime, err := database.VoWiFiRuntime(context.Background(), "ec20")
  133. if err != nil {
  134. t.Fatal(err)
  135. }
  136. if runtime.LocalPhone != "+447700900123" ||
  137. runtime.PhoneNumberSource != vowifi.PhoneSourcePAssociatedURI {
  138. t.Fatalf("runtime phone = %q (%q)", runtime.LocalPhone, runtime.PhoneNumberSource)
  139. }
  140. var tunnel map[string]any
  141. if err := json.Unmarshal(runtime.Tunnel, &tunnel); err != nil {
  142. t.Fatal(err)
  143. }
  144. }
  145. func TestStateProjectorPreservesConcreteDataplaneMode(t *testing.T) {
  146. database := testStore(t)
  147. if err := database.UpsertDevice(context.Background(), store.Device{
  148. ID: "ec25",
  149. Name: "EC25",
  150. }); err != nil {
  151. t.Fatal(err)
  152. }
  153. projector := StateProjector{Store: database}
  154. if err := projector.Save(context.Background(), vowifi.State{
  155. DeviceID: "ec25",
  156. Phase: vowifi.PhaseIMSReady,
  157. TunnelReady: true,
  158. IMSReady: true,
  159. TunnelName: "vocat-swu-ec25",
  160. DataplaneMode: "userspace",
  161. UpdatedAt: time.Now().UTC(),
  162. }); err != nil {
  163. t.Fatal(err)
  164. }
  165. runtime, err := database.VoWiFiRuntime(context.Background(), "ec25")
  166. if err != nil {
  167. t.Fatal(err)
  168. }
  169. if runtime.DataplaneMode != "userspace" {
  170. t.Fatalf("dataplane mode = %q, want userspace", runtime.DataplaneMode)
  171. }
  172. var tunnel map[string]any
  173. if err := json.Unmarshal(runtime.Tunnel, &tunnel); err != nil {
  174. t.Fatal(err)
  175. }
  176. if tunnel["dataplane_mode"] != "userspace" {
  177. t.Fatalf("tunnel dataplane mode = %#v", tunnel["dataplane_mode"])
  178. }
  179. }
  180. type staticDeviceReader struct {
  181. iccid string
  182. imsi string
  183. }
  184. func (reader staticDeviceReader) Get(string) (device.Device, error) {
  185. return device.Device{
  186. Snapshot: &device.Snapshot{
  187. ICCID: reader.iccid,
  188. IMSI: reader.imsi,
  189. },
  190. }, nil
  191. }