mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-13 03:13:43 +08:00
- Implemented a new `unsupportedBackend` in `backend_stub.go` to handle unsupported platforms. - Created a `Service` struct in `service.go` to manage interactions with smart cards, including session management and identity reading. - Added methods for reading identity, checking readiness, and authenticating with USIM applications. - Introduced a `scriptedCard` for testing purposes in `service_test.go` to simulate card responses. - Defined necessary types and error handling in `types.go` for better clarity and usability. - Developed a `PCSCAdapter` in `vowifi/pcsc_adapter.go` to integrate PC/SC service with Wi-Fi calling functionalities. - Added a new SVG icon for USB SIM readers in `public/sim-reader.svg`. - Implemented tests to ensure correct functionality and error handling in various scenarios.
121 lines
5.1 KiB
Go
121 lines
5.1 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"vocat/internal/store"
|
|
"vocat/internal/vowifi"
|
|
)
|
|
|
|
const testProfileICCID = "89441000400128014257"
|
|
|
|
func newProfileBindingTestServer(t *testing.T) (*Server, *store.Store, *fakeVoWiFiController) {
|
|
t.Helper()
|
|
database, err := store.Open(context.Background(), ":memory:")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = database.Close() })
|
|
if err := database.UpsertDevice(context.Background(), store.Device{ID: "ec20", Name: "EC20", VoWiFiEnabled: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, upstream := range []store.UpstreamProxy{
|
|
{ID: "route-1", Name: "Route 1", Addr: "127.0.0.1:1080", Enabled: true},
|
|
{ID: "route-2", Name: "Route 2", Addr: "127.0.0.1:1081", Enabled: true},
|
|
} {
|
|
if err := database.UpsertUpstreamProxy(context.Background(), upstream); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
controller := &fakeVoWiFiController{state: vowifi.State{DeviceID: "ec20", ICCID: testProfileICCID, Enabled: true}}
|
|
return &Server{store: database, vowifi: controller, logger: slog.New(slog.NewTextHandler(io.Discard, nil)), maxRequestBodyBytes: 16 << 10}, database, controller
|
|
}
|
|
|
|
func profileBindingRequest(t *testing.T, server *Server, method, body string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
request := httptest.NewRequest(method, "/api/upstream-proxy-profile-bindings", bytes.NewBufferString(body))
|
|
request.Header.Set("Content-Type", "application/json")
|
|
response := httptest.NewRecorder()
|
|
server.handleProfileProxyBindings(response, request)
|
|
return response
|
|
}
|
|
|
|
func TestProfileProxyBindingPersistsAndReconnectsOnlyCurrentICCID(t *testing.T) {
|
|
server, database, controller := newProfileBindingTestServer(t)
|
|
response := profileBindingRequest(t, server, http.MethodPost, `{
|
|
"upstream_proxy_id":"route-1",
|
|
"bindings":[
|
|
{"device_id":"ec20","iccid":"89441000400128014257","profile_name":"Vodafone UK","state_text":"Enabled"},
|
|
{"device_id":"ec20","iccid":"89104100000028106378","profile_name":"TIM"}
|
|
]
|
|
}`)
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("POST status = %d, body = %s", response.Code, response.Body.String())
|
|
}
|
|
binding, err := database.DeviceProxyBinding(context.Background(), testProfileICCID)
|
|
if err != nil || binding.UpstreamProxyID != "route-1" || binding.ProfileName != "Vodafone UK" {
|
|
t.Fatalf("binding = %+v, %v", binding, err)
|
|
}
|
|
if controller.reconnects != 1 {
|
|
t.Fatalf("reconnects = %d, want only the current ICCID to reconnect", controller.reconnects)
|
|
}
|
|
|
|
response = profileBindingRequest(t, server, http.MethodDelete, `{"upstream_proxy_id":"route-1","iccids":["89441000400128014257","89104100000028106378"]}`)
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("DELETE status = %d, body = %s", response.Code, response.Body.String())
|
|
}
|
|
if _, err := database.DeviceProxyBinding(context.Background(), testProfileICCID); err != store.ErrNotFound {
|
|
t.Fatalf("binding after delete error = %v, want ErrNotFound", err)
|
|
}
|
|
if controller.reconnects != 2 {
|
|
t.Fatalf("reconnects after delete = %d, want 2", controller.reconnects)
|
|
}
|
|
}
|
|
|
|
func TestProfileProxyBindingRejectsSameICCIDOnDifferentProxy(t *testing.T) {
|
|
server, database, _ := newProfileBindingTestServer(t)
|
|
first := profileBindingRequest(t, server, http.MethodPost, `{"upstream_proxy_id":"route-1","bindings":[{"device_id":"ec20","iccid":"89441000400128014257","profile_name":"Profile"}]}`)
|
|
if first.Code != http.StatusOK {
|
|
t.Fatalf("initial bind status = %d, body = %s", first.Code, first.Body.String())
|
|
}
|
|
second := profileBindingRequest(t, server, http.MethodPost, `{"upstream_proxy_id":"route-2","bindings":[{"device_id":"ec20","iccid":"89441000400128014257","profile_name":"Profile"}]}`)
|
|
if second.Code != http.StatusConflict {
|
|
t.Fatalf("rebind status = %d, want 409, body = %s", second.Code, second.Body.String())
|
|
}
|
|
binding, err := database.DeviceProxyBinding(context.Background(), testProfileICCID)
|
|
if err != nil || binding.UpstreamProxyID != "route-1" {
|
|
t.Fatalf("binding after rejected rebind = %+v, %v", binding, err)
|
|
}
|
|
}
|
|
|
|
func TestProfileProxyBindingSupportsPCSCReader(t *testing.T) {
|
|
server, database, controller := newProfileBindingTestServer(t)
|
|
readerICCID := "89104100000028106378"
|
|
if err := database.UpsertDevice(context.Background(), store.Device{
|
|
ID: "reader-1", Name: "USB SIM Reader", DeviceType: store.DeviceTypeUSBSIMReader,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
controller.state = vowifi.State{DeviceID: "reader-1", ICCID: readerICCID, Enabled: true}
|
|
response := profileBindingRequest(t, server, http.MethodPost, `{
|
|
"upstream_proxy_id":"route-1",
|
|
"bindings":[{"device_id":"reader-1","iccid":"89104100000028106378","profile_name":"Reader Profile"}]
|
|
}`)
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("POST status = %d, body = %s", response.Code, response.Body.String())
|
|
}
|
|
binding, err := database.DeviceProxyBinding(context.Background(), readerICCID)
|
|
if err != nil || binding.DeviceID != "reader-1" || binding.UpstreamProxyID != "route-1" {
|
|
t.Fatalf("reader binding = %+v, %v", binding, err)
|
|
}
|
|
if controller.reconnects != 1 {
|
|
t.Fatalf("reader reconnects = %d, want 1", controller.reconnects)
|
|
}
|
|
}
|