Files
yovision/Sense/server/app/sense/liveview/service_test.go

90 lines
3.7 KiB
Go

package liveview
import (
"context"
"encoding/json"
"net/http/httptest"
"strings"
"testing"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func testDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
statements := []string{
`CREATE TABLE sense_devices (id text primary key, name text, location text, status text)`,
`CREATE TABLE sense_admission_profiles (device_id text, token text, name text, kind text, width integer, height integer, encoding text, verification_status text, stream_uri text)`,
`CREATE TABLE sense_media_routes (id text primary key, device_id text, profile_token text, path text, desired text, actual text, detail text, readers integer)`,
`INSERT INTO sense_devices VALUES ('device-1','东门摄像机','教学楼东门','active')`,
`INSERT INTO sense_admission_profiles VALUES ('device-1','main','主码流','main',1920,1080,'H264','ready','rtsp://camera.example/live')`,
`INSERT INTO sense_media_routes VALUES ('device-1:main','device-1','main','sense_012345','running','waiting','等待播放器连接并按需拉流',0)`,
}
for _, statement := range statements {
if err = db.Exec(statement).Error; err != nil {
t.Fatal(err)
}
}
return db
}
func TestListIsPaginatedSearchableAndSecretFree(t *testing.T) {
service := NewService(testDB(t), NewStore())
items, total, err := service.List(context.Background(), PageRequest{Keyword: "东门", PageIndex: 1, PageSize: 10})
if err != nil || total != 1 || len(items) != 1 || items[0].ProfileKind != "main" {
t.Fatalf("items=%+v total=%d err=%v", items, total, err)
}
encoded, _ := json.Marshal(items)
if strings.Contains(string(encoded), "rtsp://") || strings.Contains(string(encoded), "sense_012345") {
t.Fatalf("response leaked private media data: %s", encoded)
}
}
func TestSessionIsOwnerBoundShortLivedAndUsesBrowserHost(t *testing.T) {
store := NewStore()
now := time.Date(2026, 8, 14, 10, 0, 0, 0, time.UTC)
store.now = func() time.Time { return now }
service := NewService(testDB(t), store)
request := httptest.NewRequest("POST", "http://192.0.2.20:18080/api/v1/liveview/sessions", nil)
session, err := service.Create(context.Background(), 7, "device-1:main", request)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(session.PlayerURL, "/api/v1/liveview/player/view_") || strings.Contains(session.PlayerURL, "sense_012345") {
t.Fatalf("unsafe player URL: %s", session.PlayerURL)
}
if _, err = service.Get(context.Background(), 8, session.ID); !errorsIs(err, ErrSessionExpired) {
t.Fatalf("another owner accessed session: %v", err)
}
target, err := service.PlayerTarget(session.ID)
if err != nil || target != "http://192.0.2.20:8889/sense_012345?controls=true&muted=true&autoplay=true" {
t.Fatalf("target=%q err=%v", target, err)
}
now = now.Add(sessionTTL)
if _, err = service.PlayerTarget(session.ID); !errorsIs(err, ErrSessionExpired) {
t.Fatalf("expired session remained valid: %v", err)
}
}
func TestPlaybackBaseRejectsCredentials(t *testing.T) {
if _, err := validatePlaybackBase("http://invalid-user@127.0.0.1:8889"); err == nil {
t.Fatal("expected credential-bearing base URL to be rejected")
}
}
func TestPlaybackStatusKeepsActionableFailuresDistinct(t *testing.T) {
for input, want := range map[string]string{"waiting": "waiting", "credential_unavailable": "authentication_failed", "path_missing": "stream_not_found", "process_unavailable": "service_unavailable", "unexpected": "offline"} {
if got := playbackStatus(input); got != want {
t.Fatalf("playbackStatus(%q)=%q want %q", input, got, want)
}
}
}
func errorsIs(err, target error) bool { return err == target }