package api import ( "testing" pb "github.com/antitbone/dwarfctl/proto" "google.golang.org/protobuf/proto" ) // TestModuleIDForCmd verifies that every command range maps to the correct // module_id, matching WsCmd.getModuleId() from the original APK. func TestModuleIDForCmd(t *testing.T) { cases := []struct { name string cmd uint32 wantMod uint32 }{ // Camera Tele (10000–10499) {"tele open camera", CmdTeleOpenCamera, ModuleCameraTele}, {"tele photo", CmdTelePhotograph, ModuleCameraTele}, {"tele get all params", CmdTeleGetAllParams, ModuleCameraTele}, {"tele boundary low", 10000, ModuleCameraTele}, {"tele boundary high-1", 10499, ModuleCameraTele}, // Astro (11000–11499) {"astro calibrate", CmdAstroStartCalibration, ModuleAstro}, {"astro goto dso", CmdAstroStartGotoDSO, ModuleAstro}, {"astro boundary low", 11000, ModuleAstro}, {"astro boundary high-1", 11499, ModuleAstro}, // Camera Wide (12000–12499) {"wide open camera", CmdWideOpenCamera, ModuleCameraWide}, {"wide photo", CmdWidePhotograph, ModuleCameraWide}, {"wide boundary low", 12000, ModuleCameraWide}, {"wide boundary high-1", 12499, ModuleCameraWide}, // System (13000–13299) {"system set time", CmdSystemSetTime, ModuleSystem}, {"system set location", CmdSystemSetLocation, ModuleSystem}, {"system boundary low", 13000, ModuleSystem}, {"system boundary high-1", 13299, ModuleSystem}, // RGB Power (13500–13799) {"rgb on", CmdRGBOn, ModuleRGBPower}, {"reboot", CmdReboot, ModuleRGBPower}, {"rgb boundary low", 13500, ModuleRGBPower}, {"rgb boundary high-1", 13799, ModuleRGBPower}, // Motor (14000–14499) {"motor run", CmdMotorRun, ModuleMotor}, {"motor joystick", CmdMotorJoystick, ModuleMotor}, {"motor boundary low", 14000, ModuleMotor}, {"motor boundary high-1", 14499, ModuleMotor}, // Track (14800–14899) {"track start", CmdTrackStart, ModuleTrack}, {"switch preview", CmdSwitchMainPreview, ModuleTrack}, {"track boundary low", 14800, ModuleTrack}, {"track boundary high-1", 14899, ModuleTrack}, // Focus (15000–15199) {"focus auto", CmdFocusAutoFocus, ModuleFocus}, {"focus boundary low", 15000, ModuleFocus}, {"focus boundary high-1", 15199, ModuleFocus}, // Notify (15200–15499) {"notify track result", NotifyTrackResult, ModuleNotify}, {"notify boundary low", 15200, ModuleNotify}, {"notify boundary high-1", 15499, ModuleNotify}, // Panorama (15500–15599) {"pano start grid", CmdPanoStartGrid, ModulePanorama}, {"pano boundary low", 15500, ModulePanorama}, {"pano boundary high-1", 15599, ModulePanorama}, // ITips (15700–15799) {"itips boundary low", 15700, ModuleITips}, // Shooting Schedule (16100–16399) {"schedule boundary low", 16100, ModuleShootingSchedule}, {"schedule boundary high-1", 16399, ModuleShootingSchedule}, // Task Center (16400–16599) {"task get state", CmdTaskGetDeviceState, ModuleTaskCenter}, {"task boundary low", 16400, ModuleTaskCenter}, {"task boundary high-1", 16599, ModuleTaskCenter}, // Param (16700–16799) {"param boundary low", 16700, ModuleParam}, // Voice Assistant (16800–16899) {"voice boundary low", 16800, ModuleVoiceAssistant}, // Device (17000–17099) {"device boundary low", 17000, ModuleDevice}, {"device boundary high-1", 17099, ModuleDevice}, // Out of range {"unknown cmd 9999", 9999, ModuleNone}, {"unknown cmd 99999", 99999, ModuleNone}, {"cmd 0", 0, ModuleNone}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { got := ModuleIDForCmd(tc.cmd) if got != tc.wantMod { t.Errorf("ModuleIDForCmd(%d) = %d, want %d", tc.cmd, got, tc.wantMod) } }) } } // TestCameraCmdHelper verifies that cameraCmd dispatches correctly between // Tele and Wide command IDs. func TestCameraCmdHelper(t *testing.T) { cases := []struct { tele, wide uint32 cam Camera want uint32 }{ {CmdTeleOpenCamera, CmdWideOpenCamera, CameraTele, CmdTeleOpenCamera}, {CmdTeleOpenCamera, CmdWideOpenCamera, CameraWide, CmdWideOpenCamera}, {10002, 12022, CameraTele, 10002}, {10002, 12022, CameraWide, 12022}, } for _, tc := range cases { got := cameraCmd(tc.tele, tc.wide, tc.cam) if got != tc.want { t.Errorf("cameraCmd(%d,%d,%d) = %d, want %d", tc.tele, tc.wide, tc.cam, got, tc.want) } } } // TestDecodeResponseEmpty verifies that decoding an empty Data field does // not error and leaves the message at its zero value. func TestDecodeResponseEmpty(t *testing.T) { pkt := &pb.WsPacket{Cmd: 10000, Data: nil} msg := &pb.ResGetAllParams{} if err := decodeResponse(pkt, msg); err != nil { t.Errorf("decodeResponse with empty data should not error: %v", err) } } // TestDecodeResponseWithPayload verifies that a response payload decodes // correctly. func TestDecodeResponseWithPayload(t *testing.T) { // Build a ComResponse{code: 0} as the inner payload inner := &pb.ComResponse{Code: 0} data, err := proto.Marshal(inner) if err != nil { t.Fatalf("marshal ComResponse: %v", err) } pkt := &pb.WsPacket{Cmd: 10002, Data: data} msg := &pb.ComResponse{} if err := decodeResponse(pkt, msg); err != nil { t.Fatalf("decodeResponse: %v", err) } if msg.GetCode() != 0 { t.Errorf("code: got %d, want 0", msg.GetCode()) } } // TestDecodeResponseBadData verifies that corrupt data returns an error. func TestDecodeResponseBadData(t *testing.T) { pkt := &pb.WsPacket{Data: []byte{0xff, 0xff, 0xff, 0xff}} msg := &pb.ComResponse{} // Invalid wire format should error (may or may not depending on content, // but definitely garbled). We just ensure it doesn't panic. _ = decodeResponse(pkt, msg) } // TestCommandIDUniqueness verifies that command IDs are unique across all // modules (no accidental collisions). func TestCommandIDUniqueness(t *testing.T) { allCmds := []uint32{ CmdTeleOpenCamera, CmdTeleCloseCamera, CmdTelePhotograph, CmdTeleBurst, CmdTeleStopBurst, CmdTeleStartRecord, CmdTeleStopRecord, CmdTeleSetExp, CmdTeleGetExp, CmdTeleSetGain, CmdTeleGetGain, CmdAstroStartCalibration, CmdAstroStopCalibration, CmdAstroStartGotoDSO, CmdAstroStartGotoSolar, CmdAstroStopGoto, CmdWideOpenCamera, CmdWideCloseCamera, CmdWidePhotograph, CmdSystemSetTime, CmdSystemSetLocation, CmdRGBOn, CmdRGBOff, CmdPowerDown, CmdReboot, CmdMotorRun, CmdMotorStop, CmdMotorJoystick, CmdTrackStart, CmdTrackStop, CmdSwitchMainPreview, CmdFocusAutoFocus, CmdFocusManualSingle, CmdPanoStartGrid, CmdPanoStop, CmdTaskStartTask, CmdTaskStopTask, CmdTaskGetDeviceState, } seen := make(map[uint32]string) for _, cmd := range allCmds { if prev, ok := seen[cmd]; ok { t.Errorf("command ID %d is duplicated (%s vs %s)", cmd, prev, "current") } seen[cmd] = "seen" } } // TestNewTelescope verifies that New() returns a usable Telescope instance. func TestNewTelescope(t *testing.T) { scope := New("test-client") if scope == nil { t.Fatal("New() returned nil") } if scope.t == nil { t.Fatal("telescope transport is nil") } } // TestModuleConstants verify module IDs are non-zero and distinct. func TestModuleConstants(t *testing.T) { modules := []uint32{ ModuleCameraTele, ModuleCameraWide, ModuleAstro, ModuleSystem, ModuleRGBPower, ModuleMotor, ModuleTrack, ModuleFocus, ModuleNotify, ModulePanorama, ModuleITips, ModuleShootingSchedule, ModuleTaskCenter, ModuleParam, ModuleVoiceAssistant, ModuleDevice, } seen := make(map[uint32]bool) for _, m := range modules { if m == 0 { t.Errorf("module constant is 0 (ModuleNone)") } if seen[m] { t.Errorf("module constant %d is duplicated", m) } seen[m] = true } }