90 lines
3.7 KiB
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 }
|