package main import ( "encoding/json" "strings" "testing" "frame" "gocv.io/x/gocv" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" ) const testClipsRoom = id.RoomID("!clips:server") const testDetsRoom = id.RoomID("!dets:server") const testViewRoom = id.RoomID("!view:server") // testClip builds a clip with the given motion percentage (via Comparisons) // and optional detections. Width/Height of 10x10 makes PixelsChanged == the // motion percentage directly. func testClip(motionPercent int, detections bool) *frame.Clip { clp := &frame.Clip{ Width: 10, Height: 10, } if motionPercent > 0 { clp.Comparisons = []frame.Comparison{{PixelsChanged: motionPercent, Threshold: 30}} } if detections { clp.Detections = []map[string][]frame.Detection{ {"person": {{DetectionTitle: "person", DetectionCertainty: 0.95}}}, } } return clp } func sameRooms(got, want []id.RoomID) bool { if len(got) != len(want) { return false } for i := range got { if got[i] != want[i] { return false } } return true } func TestRouteTargets(t *testing.T) { tests := []struct { name string motion int hasDet bool want []id.RoomID }{ {"no motion, no detections", 0, false, nil}, {"no motion, detections present", 0, true, nil}, {"motion, no detections", 5, false, []id.RoomID{testClipsRoom}}, {"motion with detections", 5, true, []id.RoomID{testClipsRoom, testDetsRoom}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := routeTargets(testClip(tt.motion, tt.hasDet), testClipsRoom, testDetsRoom) if !sameRooms(got, tt.want) { t.Errorf("routeTargets = %v, want %v", got, tt.want) } }) } } func TestHasDetections(t *testing.T) { tests := []struct { name string dets []map[string][]frame.Detection want bool }{ {"nil", nil, false}, {"empty frame map", []map[string][]frame.Detection{{}}, false}, {"empty frame slices", []map[string][]frame.Detection{{"person": {}}}, false}, {"one detection", []map[string][]frame.Detection{{"person": {{DetectionTitle: "person"}}}}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { clp := &frame.Clip{Detections: tt.dets} if got := hasDetections(clp); got != tt.want { t.Errorf("hasDetections = %v, want %v", got, tt.want) } }) } } func TestVideoContent(t *testing.T) { c := videoContent("aabb-100.mp4", id.MustParseContentURI("mxc://server/mediaid"), 2048) if c.MsgType != event.MsgVideo { t.Errorf("MsgType = %v, want MsgVideo", c.MsgType) } if c.Body != "aabb-100.mp4" { t.Errorf("Body = %v, want aabb-100.mp4", c.Body) } if c.URL != "mxc://server/mediaid" { t.Errorf("URL = %v, want mxc://server/mediaid", c.URL) } if c.Info == nil || c.Info.MimeType != "video/mp4" { t.Errorf("Info.MimeType = %v, want video/mp4", c.Info.MimeType) } if c.Info == nil || c.Info.Size != 2048 { t.Errorf("Info.Size = %v, want 2048", c.Info.Size) } } func TestMetadataContent(t *testing.T) { meta := `{"source_data":"oko--front"}` c := metadataContent(id.EventID("$videoevent"), meta) if c.MsgType != event.MsgText { t.Errorf("MsgType = %v, want MsgText", c.MsgType) } if c.Body != meta { t.Errorf("Body = %v, want %v", c.Body, meta) } if c.RelatesTo == nil || c.RelatesTo.InReplyTo == nil { t.Fatalf("RelatesTo.InReplyTo not set") } if got := c.RelatesTo.InReplyTo.EventID; got != "$videoevent" { t.Errorf("InReplyTo.EventID = %v, want $videoevent", got) } } func TestBuildClipMetadata(t *testing.T) { clp := frame.Clip{ PixelMats: [][]byte{{1, 2, 3}}, Width: 2, Height: 2, Types: gocv.MatTypeCV8UC1, Channels: 1, Guids: [][]byte{{0xaa, 0xbb}}, Timestamps: []uint64{100}, SourceData: "oko--front_door", Detections: []map[string][]frame.Detection{ {"person": {{DetectionTitle: "person", DetectionCertainty: 0.9}}}, }, Comparisons: []frame.Comparison{{PixelsChanged: 1, Threshold: 30}}, } data, err := buildClipMetadata(&clp, 30.0) if err != nil { t.Fatalf("buildClipMetadata: %v", err) } var meta map[string]interface{} if err := json.Unmarshal([]byte(data), &meta); err != nil { t.Fatalf("output is not valid JSON: %v", err) } if meta["source_data"] != "oko--front_door" { t.Errorf("source_data = %v, want oko--front_door", meta["source_data"]) } if meta["motion_percent"] != float64(25) { t.Errorf("motion_percent = %v, want 25 (1 of 4 pixels)", meta["motion_percent"]) } if meta["frame_count"] != float64(1) { t.Errorf("frame_count = %v, want 1", meta["frame_count"]) } if meta["fps"] != float64(30) { t.Errorf("fps = %v, want 30", meta["fps"]) } guids, ok := meta["guids"].([]interface{}) if !ok || len(guids) != 1 || guids[0] != "aabb" { t.Errorf("guids = %v, want hex aabb", meta["guids"]) } dets, ok := meta["detections"].([]interface{}) if !ok || len(dets) != 1 { t.Errorf("detections not preserved in metadata") } } func TestViewCaption(t *testing.T) { clp := frame.Clip{ PixelMats: [][]byte{{1, 2, 3}, {4, 5, 6}}, Width: 10, Height: 10, SourceData: "oko--front_door", Comparisons: []frame.Comparison{ {PixelsChanged: 10, Threshold: 30}, {PixelsChanged: 20, Threshold: 30}, }, } // minimal, human-readable, and free of JSON got := viewCaption(&clp, 15.5) want := "oko--front_door · 2 frames · 15.5 fps · motion 20%" if got != want { t.Errorf("viewCaption = %q, want %q", got, want) } for _, bad := range []string{"{", "}", `"`, "motion_percent", "source_data"} { if strings.Contains(got, bad) { t.Errorf("viewCaption contains JSON-ish token %q: %q", bad, got) } } }