Files
silver_pose/v2/internal/monitor/monitor_test.go
T

104 lines
3.2 KiB
Go
Raw Normal View History

2026-07-22 21:20:13 +08:00
package monitor
import (
"context"
"errors"
"testing"
"time"
"silverpose/v2/internal/alert"
"silverpose/v2/internal/fall"
"silverpose/v2/internal/pose"
"silverpose/v2/internal/source"
)
func TestMonitorPublishesLatestCompletedFrameAndOneAlert(t *testing.T) {
frames := make(chan source.Frame, 2)
frames <- source.Frame{BGR: []byte{1, 1, 1, 1, 1, 1}, Width: 2, Height: 1, Sequence: 1}
frames <- source.Frame{BGR: []byte{2, 2, 2, 2, 2, 2}, Width: 2, Height: 1, Sequence: 2}
close(frames)
statuses := make(chan source.Update, 1)
statuses <- source.Update{Status: source.Connected, Message: "视频流已连接"}
close(statuses)
processor := &fakeProcessor{result: fall.FrameResult{
People: []fall.PersonAnalysis{{State: fall.Confirmed}},
Events: []fall.Event{{EventID: "FALL-run-000001", TrackID: "P-0001", ConfigVersion: "cfg-safe", State: fall.Confirmed}},
}}
runtime := &fakeRuntime{output: make([]float32, pose.YOLOPoseOutputValues)}
monitor, err := New(fakeSource{frames: frames, statuses: statuses}, runtime, processor, 0.25, alert.NewDispatcher(t.TempDir(), "lobby"), func() time.Time {
return time.Date(2026, 7, 22, 8, 0, 0, 0, time.UTC)
})
if err != nil {
t.Fatal(err)
}
go monitor.Run(context.Background())
<-monitor.Done()
update, ok := <-monitor.Updates()
if !ok {
t.Fatal("monitor emitted no update")
}
if update.Sequence != 2 || update.Image == nil || update.SourceStatus != source.Connected {
t.Fatalf("update = %#v", update)
}
record, ok := <-monitor.Alerts()
if !ok || !record.Written || record.Event.EventID != "FALL-run-000001" {
t.Fatalf("alert record = %#v, open = %t", record, ok)
}
if !runtime.closed {
t.Fatal("runtime was not closed")
}
}
func TestMonitorDoesNotCreateFallEvidenceOnSourceFailure(t *testing.T) {
frames := make(chan source.Frame)
close(frames)
statuses := make(chan source.Update, 1)
statuses <- source.Update{Status: source.Retrying, Message: "视频流已断开,正在重连"}
close(statuses)
processor := &fakeProcessor{}
monitor, err := New(fakeSource{frames: frames, statuses: statuses}, &fakeRuntime{}, processor, 0.25, alert.NewDispatcher(t.TempDir(), "lobby"), time.Now)
if err != nil {
t.Fatal(err)
}
go monitor.Run(context.Background())
<-monitor.Done()
update := <-monitor.Updates()
if update.SourceStatus != source.Retrying || len(update.Events) != 0 || processor.calls != 0 {
t.Fatalf("update = %#v, process calls = %d", update, processor.calls)
}
}
type fakeSource struct {
frames <-chan source.Frame
statuses <-chan source.Update
}
func (source fakeSource) Frames() <-chan source.Frame { return source.frames }
func (source fakeSource) Statuses() <-chan source.Update { return source.statuses }
type fakeRuntime struct {
output []float32
err error
closed bool
}
func (runtime *fakeRuntime) Run(input []float32) ([]float32, error) {
if len(input) == 0 {
return nil, errors.New("missing input")
}
return runtime.output, runtime.err
}
func (runtime *fakeRuntime) Close() { runtime.closed = true }
type fakeProcessor struct {
result fall.FrameResult
calls int
}
func (processor *fakeProcessor) Process(frame fall.Frame) fall.FrameResult {
processor.calls++
return processor.result
}