Files
VoCat/internal/device/registration_native_qmi_test.go
T
8dc6d4f620 feat: add native OpenStick 410 modem support (#23)
* build: add QMI dependency and license attribution

* feat: add native OpenStick 410 modem support

---------

Co-authored-by: w0x41en <[email protected]>
2026-08-15 23:45:15 +08:00

272 lines
9.3 KiB
Go

package device
import (
"context"
"testing"
"github.com/iniwex5/quectel-qmi-go/pkg/qmi"
"vocat/internal/modem"
)
type fakeNativeQMIRegistrationSession struct {
mode qmi.OperatingMode
serving []*qmi.ServingSystem
selection *qmi.SystemSelectionPreference
setModes []qmi.OperatingMode
setPreferences []qmi.SystemSelectionPreference
registerRequests []qmi.NASInitiateNetworkRegisterRequest
forceSearches int
forceSearchErr error
registerErr error
attachRequests []bool
closeCount int
}
func (session *fakeNativeQMIRegistrationSession) GetOperatingMode(context.Context) (qmi.OperatingMode, error) {
return session.mode, nil
}
func (session *fakeNativeQMIRegistrationSession) SetOperatingMode(_ context.Context, mode qmi.OperatingMode) error {
session.mode = mode
session.setModes = append(session.setModes, mode)
return nil
}
func (session *fakeNativeQMIRegistrationSession) Close() error {
session.closeCount++
return nil
}
func (session *fakeNativeQMIRegistrationSession) GetServingSystem(context.Context) (*qmi.ServingSystem, error) {
if len(session.serving) == 0 {
return &qmi.ServingSystem{RegistrationState: qmi.RegStateSearching}, nil
}
current := session.serving[0]
if len(session.serving) > 1 {
session.serving = session.serving[1:]
}
return current, nil
}
func (session *fakeNativeQMIRegistrationSession) GetSystemSelectionPreference(context.Context) (*qmi.SystemSelectionPreference, error) {
if session.selection == nil {
return &qmi.SystemSelectionPreference{}, nil
}
return session.selection, nil
}
func (session *fakeNativeQMIRegistrationSession) SetSystemSelectionPreference(_ context.Context, pref qmi.SystemSelectionPreference) error {
session.selection = &pref
session.setPreferences = append(session.setPreferences, pref)
return nil
}
func (session *fakeNativeQMIRegistrationSession) InitiateNetworkRegister(_ context.Context, req qmi.NASInitiateNetworkRegisterRequest) error {
session.registerRequests = append(session.registerRequests, req)
return session.registerErr
}
func (session *fakeNativeQMIRegistrationSession) ForceNetworkSearch(context.Context) error {
session.forceSearches++
return session.forceSearchErr
}
func (session *fakeNativeQMIRegistrationSession) AttachDetach(_ context.Context, attached bool) error {
session.attachRequests = append(session.attachRequests, attached)
return nil
}
func TestEnsureNativeQMIRegistrationDrivesNASSequence(t *testing.T) {
session := &fakeNativeQMIRegistrationSession{
mode: qmi.ModeLowPower,
serving: []*qmi.ServingSystem{
{RegistrationState: qmi.RegStateSearching},
{RegistrationState: qmi.RegStateSearching},
{RegistrationState: qmi.RegStateRegistered, PSAttached: false},
{RegistrationState: qmi.RegStateRegistered, PSAttached: true},
},
}
if err := ensureNativeQMIRegistration(context.Background(), session, qmiRegistrationRequestAutomatic(), true); err != nil {
t.Fatalf("ensure native QMI registration: %v", err)
}
if len(session.setModes) != 1 || session.setModes[0] != qmi.ModeOnline {
t.Fatalf("operating mode writes = %#v, want [online]", session.setModes)
}
if len(session.setPreferences) != 1 || !session.setPreferences[0].HasNetworkSelectionPreference ||
session.setPreferences[0].NetworkSelectionPreference != qmi.NASNetworkSelectionAutomatic {
t.Fatalf("selection writes = %#v, want automatic", session.setPreferences)
}
if len(session.registerRequests) != 1 || session.registerRequests[0].Mode != qmi.NASNetworkRegisterAutomatic {
t.Fatalf("registration requests = %#v, want one automatic request", session.registerRequests)
}
if session.forceSearches != 1 {
t.Fatalf("force-search count = %d, want 1", session.forceSearches)
}
if len(session.attachRequests) != 1 || !session.attachRequests[0] {
t.Fatalf("attach requests = %#v, want one attach", session.attachRequests)
}
}
func TestQMIManualRegisterRequestMapsPLMNAndRAT(t *testing.T) {
rat := 7
request, err := qmiManualRegisterRequest("46001", &rat)
if err != nil {
t.Fatalf("manual request: %v", err)
}
if request.Mode != qmi.NASNetworkRegisterManual || request.MCC != 460 || request.MNC != 1 ||
request.IncludesPCSDigit || request.RadioAccessTech != 0x08 || !request.HasChangeDuration ||
request.ChangeDuration != qmi.NASChangeDurationPermanent {
t.Fatalf("manual request = %#v", request)
}
}
func TestQMIManualSelectionPreferenceMapsPLMN(t *testing.T) {
pref, selection, err := qmiManualSelectionPreference("46001")
if err != nil {
t.Fatalf("manual preference: %v", err)
}
if pref.NetworkSelectionPreference != qmi.NASNetworkSelectionManual ||
!pref.HasNetworkSelectionPreference || !pref.HasManualNetworkSelection ||
!pref.HasChangeDuration || pref.ChangeDuration != qmi.NASChangeDurationPermanent {
t.Fatalf("manual preference = %#v", pref)
}
if selection.MCC != 460 || selection.MNC != 1 || selection.IncludesPCSDigit {
t.Fatalf("manual selection = %#v", selection)
}
}
func TestQMIManualSelectionPreferenceMapsRAT(t *testing.T) {
rat := 7
pref, _, err := qmiManualSelectionPreferenceWithRAT("46001", &rat)
if err != nil {
t.Fatalf("manual preference: %v", err)
}
if !pref.HasModePreference || pref.ModePreference != qmi.NASRatModePreferenceLTE {
t.Fatalf("manual preference mode = %#v, want LTE mode preference", pref)
}
}
func TestQMIManualRegisterRequestRejectsUnknownRAT(t *testing.T) {
rat := 1
if _, err := qmiManualRegisterRequest("46001", &rat); err == nil {
t.Fatal("manual request with unknown RAT must fail")
}
}
func TestEnsureNativeQMIRegistrationWaitsForManualTarget(t *testing.T) {
session := &fakeNativeQMIRegistrationSession{
mode: qmi.ModeOnline,
serving: []*qmi.ServingSystem{
{RegistrationState: qmi.RegStateRegistered, PSAttached: true, MCC: 460, MNC: 0},
{RegistrationState: qmi.RegStateSearching},
{RegistrationState: qmi.RegStateRegistered, PSAttached: false, MCC: 460, MNC: 1},
{RegistrationState: qmi.RegStateRegistered, PSAttached: true, MCC: 460, MNC: 1},
},
}
request, err := qmiManualRegisterRequest("46001", nil)
if err != nil {
t.Fatalf("manual request: %v", err)
}
target := qmi.ManualNetworkSelection{MCC: 460, MNC: 1}
if err := ensureNativeQMIRegistrationForTarget(context.Background(), session, request, false, &target); err != nil {
t.Fatalf("ensure manual registration: %v", err)
}
if len(session.registerRequests) != 0 {
t.Fatalf("registration requests = %#v, want force-search-only manual trigger", session.registerRequests)
}
if session.forceSearches != 1 {
t.Fatalf("force-search count = %d, want 1", session.forceSearches)
}
if len(session.attachRequests) != 1 || !session.attachRequests[0] {
t.Fatalf("attach requests = %#v, want one attach", session.attachRequests)
}
}
func TestEnsureNativeQMIRegistrationFallsBackWhenForceSearchUnsupported(t *testing.T) {
session := &fakeNativeQMIRegistrationSession{
serving: []*qmi.ServingSystem{
{RegistrationState: qmi.RegStateRegistered, PSAttached: true, RadioInterface: 8, MCC: 460, MNC: 0},
{RegistrationState: qmi.RegStateSearching},
{RegistrationState: qmi.RegStateRegistered, PSAttached: false, MCC: 460, MNC: 1},
{RegistrationState: qmi.RegStateRegistered, PSAttached: true, MCC: 460, MNC: 1},
},
forceSearchErr: &qmi.QMIError{
Service: qmi.ServiceNAS, MessageID: qmi.NASForceNetworkSearch,
Result: 0x0001, ErrorCode: qmi.QMIErrNotSupported,
},
}
request, err := qmiManualRegisterRequest("46001", nil)
if err != nil {
t.Fatalf("manual request: %v", err)
}
target := qmi.ManualNetworkSelection{MCC: 460, MNC: 1}
if err := ensureNativeQMIRegistrationForTarget(context.Background(), session, request, false, &target); err != nil {
t.Fatalf("ensure manual registration: %v", err)
}
if len(session.registerRequests) != 1 || session.registerRequests[0].RadioAccessTech != 8 {
t.Fatalf("registration requests = %#v, want one LTE fallback request", session.registerRequests)
}
if session.forceSearches != 1 {
t.Fatalf("force-search count = %d, want one unsupported attempt", session.forceSearches)
}
}
func TestNativeQMIRegistrationCyclesEarlyWhenForceSearchUnsupported(t *testing.T) {
if got := nativeQMIRegistrationRadioCycleThreshold(true); got != 3 {
t.Fatalf("unsupported force-search threshold = %d, want 3", got)
}
if got := nativeQMIRegistrationRadioCycleThreshold(false); got != 30 {
t.Fatalf("supported force-search threshold = %d, want 30", got)
}
}
func TestIsNativeQMICandidateRequiresOpenStickWWANPair(t *testing.T) {
tests := []struct {
name string
candidate modem.Candidate
want bool
}{
{
name: "native",
candidate: modem.Candidate{
ID: "wwan0",
QMIControl: "/dev/wwan0qmi0",
},
want: true,
},
{
name: "mhi native discovery id",
candidate: modem.Candidate{
ID: "mhi-wwan0",
QMIControl: "/dev/wwan0qmi0",
},
want: true,
},
{
name: "different control device",
candidate: modem.Candidate{
ID: "wwan0",
QMIControl: "/dev/cdc-wdm0",
},
want: false,
},
{
name: "non native id",
candidate: modem.Candidate{
ID: "usb0",
QMIControl: "/dev/usb0qmi0",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isNativeQMICandidate(tt.candidate); got != tt.want {
t.Fatalf("isNativeQMICandidate() = %v, want %v", got, tt.want)
}
})
}
}