package server import ( "bytes" "context" "io" "log/slog" "net/http" "net/http/httptest" "testing" "vocat/internal/store" "vocat/internal/vowifi" vowifiruntime "vocat/internal/vowifi/runtime" ) type fakeVoWiFiController struct { state vowifi.State enabled []bool reconnects int err error } func (controller *fakeVoWiFiController) State(string) (vowifi.State, error) { return controller.state, controller.err } func (controller *fakeVoWiFiController) RequestEnabled( _ string, enabled bool, ) (vowifi.State, error) { controller.enabled = append(controller.enabled, enabled) return controller.state, controller.err } func (controller *fakeVoWiFiController) RequestReconnect(string) (vowifi.State, error) { controller.reconnects++ return controller.state, controller.err } func TestShouldDeferModemSMSSync(t *testing.T) { tests := []struct { name string state vowifi.State err error want bool }{ { name: "cellular session", state: vowifi.State{Phase: vowifi.PhaseIdle}, }, { name: "vowifi SIM setup", state: vowifi.State{Enabled: true, Phase: vowifi.PhaseSIMReady}, want: true, }, { name: "vowifi IMS registration", state: vowifi.State{Enabled: true, Phase: vowifi.PhaseIMSReady}, want: true, }, { name: "stable vowifi catch-up", state: vowifi.State{Enabled: true, Phase: vowifi.PhaseSMSReady, SMSReady: true}, }, { name: "failed vowifi cellular fallback", state: vowifi.State{Enabled: true, Phase: vowifi.PhaseFailed}, }, { name: "unknown vowifi state", state: vowifi.State{Enabled: true, Phase: vowifi.PhaseSIMReady}, err: context.Canceled, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if got := shouldDeferModemSMSSync(test.state, test.err); got != test.want { t.Fatalf("shouldDeferModemSMSSync() = %v, want %v", got, test.want) } }) } } func TestVoWiFiEnableUpdatesPolicyAndQueuesRuntime(t *testing.T) { database, err := store.Open(context.Background(), ":memory:") if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = database.Close() }) config := store.Device{ID: "ec20", Name: "EC20"} if err := database.UpsertDevice(context.Background(), config); err != nil { t.Fatal(err) } controller := &fakeVoWiFiController{ state: vowifi.State{DeviceID: "ec20", Phase: vowifi.PhaseIdle}, } server := &Server{ store: database, vowifi: controller, logger: slog.New(slog.NewTextHandler(io.Discard, nil)), maxRequestBodyBytes: 4096, } request := httptest.NewRequest( http.MethodPatch, "/api/devices/ec20/vowifi", bytes.NewBufferString(`{"enabled":true}`), ) request.Header.Set("Content-Type", "application/json") response := httptest.NewRecorder() server.handleVoWiFiEnabled(response, request, config, true) if response.Code != http.StatusAccepted { t.Fatalf("status = %d, body = %s", response.Code, response.Body.String()) } if len(controller.enabled) != 1 || !controller.enabled[0] { t.Fatalf("queued enables = %#v", controller.enabled) } stored, err := database.Device(context.Background(), "ec20") if err != nil { t.Fatal(err) } if !stored.VoWiFiEnabled { t.Fatal("VoWiFi policy was not persisted") } } func TestVoWiFiRepeatedEnableWhileStartingIsAccepted(t *testing.T) { database, err := store.Open(context.Background(), ":memory:") if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = database.Close() }) config := store.Device{ID: "ec20", Name: "EC20", VoWiFiEnabled: true} if err := database.UpsertDevice(context.Background(), config); err != nil { t.Fatal(err) } controller := &fakeVoWiFiController{ // A reconnect briefly enters Stopping/Enabled=false while the persisted // policy remains enabled. Repeating "enable" is still the same intent. state: vowifi.State{DeviceID: "ec20", Phase: vowifi.PhaseStopping, Enabled: false}, err: vowifiruntime.ErrOperationInProgress, } server := &Server{ store: database, vowifi: controller, logger: slog.New(slog.NewTextHandler(io.Discard, nil)), maxRequestBodyBytes: 4096, } request := httptest.NewRequest( http.MethodPatch, "/api/devices/ec20/vowifi", bytes.NewBufferString(`{"enabled":true}`), ) request.Header.Set("Content-Type", "application/json") response := httptest.NewRecorder() server.handleVoWiFiEnabled(response, request, config, true) if response.Code != http.StatusAccepted { t.Fatalf("status = %d, body = %s", response.Code, response.Body.String()) } stored, err := database.Device(context.Background(), "ec20") if err != nil { t.Fatal(err) } if !stored.VoWiFiEnabled { t.Fatal("idempotent enable reverted the desired policy") } } func TestVoWiFiReconnectRequiresEnabledPolicy(t *testing.T) { database, err := store.Open(context.Background(), ":memory:") if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = database.Close() }) controller := &fakeVoWiFiController{} server := &Server{ store: database, vowifi: controller, logger: slog.New(slog.NewTextHandler(io.Discard, nil)), } request := httptest.NewRequest( http.MethodPost, "/api/devices/ec20/vowifi/actions/reconnect", nil, ) response := httptest.NewRecorder() server.handleVoWiFiReconnect( response, request, store.Device{ID: "ec20", Name: "EC20"}, true, ) if response.Code != http.StatusConflict { t.Fatalf("status = %d, body = %s", response.Code, response.Body.String()) } if controller.reconnects != 0 { t.Fatalf("reconnects = %d", controller.reconnects) } }