204 lines
5.5 KiB
Go
204 lines
5.5 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|