| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- 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)
- }
- }
|