Files
NanoKVM-MIRROR/server/service/mcp/remote_test.go
SiYue-ZO 64ce12cc0d feat(mcp): add authenticated remote control backend
Add an MCP Streamable HTTP server with token authentication, screenshot capture, keyboard, and mouse tools.

Introduce shared control-mode and input-control coordination so MCP, PicoClaw, and local HID paths serialize ownership safely.

Integrate PicoClaw gateway/runtime control handoff with PID-managed startup and focused unit coverage.
2026-07-29 15:16:06 +08:00

116 lines
3.1 KiB
Go

package mcpservice
import (
"context"
"errors"
"testing"
)
type recordingHID struct {
keyboard [][]byte
relative [][]byte
absolute [][]byte
failAt int
writes int
onWrite func(int)
}
func (h *recordingHID) record(target *[][]byte, report []byte) error {
h.writes++
if h.onWrite != nil {
h.onWrite(h.writes)
}
*target = append(*target, append([]byte(nil), report...))
if h.failAt > 0 && h.writes == h.failAt {
return errors.New("write failed")
}
return nil
}
func (h *recordingHID) WriteKeyboardReport(report []byte) error {
return h.record(&h.keyboard, report)
}
func (h *recordingHID) WriteRelativeMouseReport(report []byte) error {
return h.record(&h.relative, report)
}
func (h *recordingHID) WriteAbsoluteMouseReport(report []byte) error {
return h.record(&h.absolute, report)
}
func TestRemotePressKeysAlwaysReleases(t *testing.T) {
hid := &recordingHID{}
remote := newRemoteWithHID(hid)
if err := remote.PressKeys(context.Background(), []string{"ShiftLeft", "KeyA"}, 0); err != nil {
t.Fatal(err)
}
if len(hid.keyboard) != 2 || string(hid.keyboard[1]) != string(keyUpReport) {
t.Fatalf("keyboard reports: %v", hid.keyboard)
}
failing := &recordingHID{failAt: 1}
if err := newRemoteWithHID(failing).PressKeys(context.Background(), []string{"KeyA"}, 0); err == nil {
t.Fatal("expected write error")
}
if len(failing.keyboard) != 2 || string(failing.keyboard[1]) != string(keyUpReport) {
t.Fatalf("release not attempted after error: %v", failing.keyboard)
}
}
func TestRemoteClickReleasesButton(t *testing.T) {
hid := &recordingHID{}
if err := newRemoteWithHID(hid).Click(context.Background(), "left", 1, 0); err != nil {
t.Fatal(err)
}
if len(hid.relative) != 2 || hid.relative[0][0] != 1 || hid.relative[1][0] != 0 {
t.Fatalf("relative reports: %v", hid.relative)
}
}
func TestRemoteScrollUsesAbsoluteReportsAndReleasesWheel(t *testing.T) {
hid := &recordingHID{}
if err := newRemoteWithHID(hid).Scroll(context.Background(), 130); err != nil {
t.Fatal(err)
}
if len(hid.relative) != 0 {
t.Fatalf("relative reports = %v, want none", hid.relative)
}
if len(hid.absolute) != 4 {
t.Fatalf("absolute report count = %d, want 4: %v", len(hid.absolute), hid.absolute)
}
x := absoluteCoordinate(0.5)
y := absoluteCoordinate(0.5)
want := [][]byte{
buildAbsolutePointerReport(x, y, 0, 127),
buildAbsolutePointerReport(x, y, 0, 0),
buildAbsolutePointerReport(x, y, 0, 3),
buildAbsolutePointerReport(x, y, 0, 0),
}
for i := range want {
if string(hid.absolute[i]) != string(want[i]) {
t.Fatalf("absolute[%d] = %v, want %v", i, hid.absolute[i], want[i])
}
}
}
func TestRemoteTypeTextCancellationReleasesKeyboard(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
hid := &recordingHID{}
hid.onWrite = func(write int) {
if write == 1 {
cancel()
}
}
_, err := newRemoteWithHID(hid).TypeText(ctx, "A", 0)
if !errors.Is(err, context.Canceled) {
t.Fatalf("error = %v, want canceled", err)
}
if len(hid.keyboard) != 2 || string(hid.keyboard[1]) != string(keyUpReport) {
t.Fatalf("keyboard was not released after cancellation: %v", hid.keyboard)
}
}