Skip to content

Commit 7bea395

Browse files
authored
Merge pull request #31 from GyeongHoKim/feature/embedded-rtsp
Feature/embedded rtsp
2 parents acbfb28 + f14ecde commit 7bea395

44 files changed

Lines changed: 2743 additions & 714 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/cli/control_test.go

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,28 @@ import (
1616
"github.com/GyeongHoKim/onvif-simulator/internal/simulator"
1717
)
1818

19-
// chdirToTempConfig writes a minimal valid config into a temp dir and chdirs
20-
// there so simulator.New can find it.
21-
func chdirToTempConfig(t *testing.T) (cleanup func()) {
19+
// writeTempConfig writes a minimal valid config into a temp dir and returns
20+
// its absolute path. Callers pass the path to simulator.Options.ConfigPath
21+
// (the explicit override path is the public way for tests to point the
22+
// simulator at a fixture; chdir is no longer relied on).
23+
func writeTempConfig(t *testing.T) (cfgPath string, cleanup func()) {
2224
t.Helper()
2325
dir := t.TempDir()
2426
cfg := config.Config{
25-
Version: 1,
27+
Version: config.CurrentVersion,
2628
Device: config.DeviceConfig{
2729
UUID: "urn:uuid:00000000-0000-4000-8000-000000000001",
2830
Manufacturer: "Test", Model: "SimCam", Serial: "SN-1",
2931
Scopes: []string{"onvif://www.onvif.org/name/test"},
3032
},
31-
Network: config.NetworkConfig{HTTPPort: 18081},
33+
Network: config.NetworkConfig{HTTPPort: 18081, RTSPPort: 0},
3234
Media: config.MediaConfig{Profiles: []config.ProfileConfig{{
3335
Name: "main", Token: "profile_main",
34-
RTSP: "rtsp://127.0.0.1:8554/main",
35-
Encoding: "H264", Width: 1920, Height: 1080, FPS: 30,
36+
// RTSP retained as a passthrough fallback so this fixture covers
37+
// the back-compat path; a fixture variant with MediaFilePath is
38+
// exercised by the rtsp_test.go integration cases.
39+
MediaFilePath: "",
40+
Encoding: "H264", Width: 1920, Height: 1080, FPS: 30,
3641
}}},
3742
Events: config.EventsConfig{
3843
Topics: []config.TopicConfig{
@@ -48,24 +53,18 @@ func chdirToTempConfig(t *testing.T) (cleanup func()) {
4853
if err != nil {
4954
t.Fatalf("marshal: %v", err)
5055
}
51-
if writeErr := os.WriteFile(filepath.Join(dir, config.FileName), data, 0o600); writeErr != nil {
56+
cfgPath = filepath.Join(dir, config.FileName)
57+
if writeErr := os.WriteFile(cfgPath, data, 0o600); writeErr != nil {
5258
t.Fatalf("write: %v", writeErr)
5359
}
54-
prev, getErr := os.Getwd()
55-
if getErr != nil {
56-
t.Fatalf("getwd: %v", getErr)
57-
}
58-
if chErr := os.Chdir(dir); chErr != nil {
59-
t.Fatalf("chdir: %v", chErr)
60-
}
61-
return func() { _ = os.Chdir(prev) } //nolint:errcheck // best-effort restore.
60+
return cfgPath, func() { config.SetPath("") }
6261
}
6362

6463
func TestControlServerEventsAndShutdown(t *testing.T) {
65-
cleanup := chdirToTempConfig(t)
64+
cfgPath, cleanup := writeTempConfig(t)
6665
defer cleanup()
6766

68-
sim, err := simulator.New(simulator.Options{EventBufferSize: 16})
67+
sim, err := simulator.New(simulator.Options{EventBufferSize: 16, ConfigPath: cfgPath})
6968
if err != nil {
7069
t.Fatalf("simulator.New: %v", err)
7170
}
@@ -118,10 +117,10 @@ func TestControlServerEventsAndShutdown(t *testing.T) {
118117
}
119118

120119
func TestControlServerRejectsGET(t *testing.T) {
121-
cleanup := chdirToTempConfig(t)
120+
cfgPath, cleanup := writeTempConfig(t)
122121
defer cleanup()
123122

124-
sim, err := simulator.New(simulator.Options{})
123+
sim, err := simulator.New(simulator.Options{ConfigPath: cfgPath})
125124
if err != nil {
126125
t.Fatalf("simulator.New: %v", err)
127126
}
@@ -151,10 +150,10 @@ func TestControlServerRejectsGET(t *testing.T) {
151150
}
152151

153152
func TestControlServerRejectsBadJSON(t *testing.T) {
154-
cleanup := chdirToTempConfig(t)
153+
cfgPath, cleanup := writeTempConfig(t)
155154
defer cleanup()
156155

157-
sim, err := simulator.New(simulator.Options{})
156+
sim, err := simulator.New(simulator.Options{ConfigPath: cfgPath})
158157
if err != nil {
159158
t.Fatalf("simulator.New: %v", err)
160159
}

cmd/cli/main.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,31 @@ func runConfig(args []string) error {
199199
if len(args) == 0 {
200200
return errConfigSubcommandReq
201201
}
202-
switch args[0] {
202+
sub := args[0]
203+
// Reject unknown subcommands BEFORE config.EnsureExists, otherwise a
204+
// typo'd subcommand would still create the user config directory and
205+
// write a default file.
206+
if sub != "show" && sub != "validate" {
207+
return fmt.Errorf("%w: %q", errConfigUnknownSubcommand, sub)
208+
}
209+
fs := flag.NewFlagSet("config "+sub, flag.ContinueOnError)
210+
cfgPath := fs.String("config", "", "path to onvif-simulator.json (defaults to user config dir)")
211+
if err := fs.Parse(args[1:]); err != nil {
212+
return err
213+
}
214+
resolved, err := config.Resolve(*cfgPath)
215+
if err != nil {
216+
return err
217+
}
218+
// Same ordering rationale as simulator.New: EnsureExists takes the
219+
// path explicitly, so run it first and only mutate the package-level
220+
// active path on success.
221+
if _, err := config.EnsureExists(resolved); err != nil {
222+
return err
223+
}
224+
config.SetPath(resolved)
225+
226+
switch sub {
203227
case "show":
204228
cfg, err := config.Load()
205229
if err != nil {
@@ -214,9 +238,9 @@ func runConfig(args []string) error {
214238
}
215239
fmt.Println("ok")
216240
return nil
217-
default:
218-
return fmt.Errorf("%w: %q", errConfigUnknownSubcommand, args[0])
219241
}
242+
// Unreachable: sub is guaranteed to be show or validate by the guard above.
243+
return nil
220244
}
221245

222246
// ---------- event ---------------------------------------------------------------

cmd/cli/main_test.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,35 @@ func TestRunConfigUnknownSubcommand(t *testing.T) {
8888
}
8989
}
9090

91+
// TestRunConfigUnknownSubcommandNoSideEffects guards against the regression
92+
// where runConfig used to call config.EnsureExists (which mkdir's the user
93+
// config directory and writes a default JSON file) before validating the
94+
// subcommand — meaning a typo'd `onvif-simulator config bogus` would leave
95+
// debris in the operator's real ~/.config or ~/Library/Application Support.
96+
func TestRunConfigUnknownSubcommandNoSideEffects(t *testing.T) {
97+
dir := t.TempDir()
98+
t.Setenv("HOME", dir) // macOS UserConfigDir base
99+
t.Setenv("XDG_CONFIG_HOME", dir) // Linux UserConfigDir base
100+
t.Setenv("AppData", dir) // Windows UserConfigDir base
101+
t.Cleanup(func() { config.SetPath("") })
102+
103+
if err := runConfig([]string{"bogus"}); !errors.Is(err, errConfigUnknownSubcommand) {
104+
t.Fatalf("expected errConfigUnknownSubcommand, got %v", err)
105+
}
106+
107+
entries, err := os.ReadDir(dir)
108+
if err != nil {
109+
t.Fatalf("ReadDir: %v", err)
110+
}
111+
if len(entries) > 0 {
112+
names := make([]string, len(entries))
113+
for i, e := range entries {
114+
names[i] = e.Name()
115+
}
116+
t.Errorf("expected no filesystem side effects, found: %v", names)
117+
}
118+
}
119+
91120
func TestRunEventSubcommandRequired(t *testing.T) {
92121
if err := runEvent(nil); !errors.Is(err, errEventSubcommandReq) {
93122
t.Fatalf("expected errEventSubcommandReq, got %v", err)
@@ -107,19 +136,19 @@ func TestPostSyncEventBadArgs(t *testing.T) {
107136
}
108137

109138
func TestRunConfigShow(t *testing.T) {
110-
cleanup := chdirToTempConfig(t)
139+
cfgPath, cleanup := writeTempConfig(t)
111140
defer cleanup()
112141

113-
if err := runConfig([]string{"show"}); err != nil {
142+
if err := runConfig([]string{"show", "-config", cfgPath}); err != nil {
114143
t.Fatalf("runConfig show: %v", err)
115144
}
116145
}
117146

118147
func TestRunConfigValidate(t *testing.T) {
119-
cleanup := chdirToTempConfig(t)
148+
cfgPath, cleanup := writeTempConfig(t)
120149
defer cleanup()
121150

122-
if err := runConfig([]string{"validate"}); err != nil {
151+
if err := runConfig([]string{"validate", "-config", cfgPath}); err != nil {
123152
t.Fatalf("runConfig validate: %v", err)
124153
}
125154
}

cmd/cli/serve_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
)
1111

1212
func TestRunServeSignalShutdown(t *testing.T) {
13-
cleanup := chdirToTempConfig(t)
13+
cfgPath, cleanup := writeTempConfig(t)
1414
defer cleanup()
1515

1616
// Use a temp HOME/USERPROFILE so writeControlPortFile lands somewhere ephemeral.
@@ -20,7 +20,7 @@ func TestRunServeSignalShutdown(t *testing.T) {
2020

2121
done := make(chan error, 1)
2222
go func() {
23-
done <- runServe(nil)
23+
done <- runServe([]string{"-config", cfgPath})
2424
}()
2525

2626
// Give the server a moment to bind.
@@ -40,13 +40,13 @@ func TestRunServeSignalShutdown(t *testing.T) {
4040
}
4141

4242
func TestRunTUIReturnsTUIError(t *testing.T) {
43-
cleanup := chdirToTempConfig(t)
43+
cfgPath, cleanup := writeTempConfig(t)
4444
defer cleanup()
4545
tmpHome := t.TempDir()
4646
t.Setenv("HOME", tmpHome)
4747
t.Setenv("USERPROFILE", tmpHome) // os.UserHomeDir uses USERPROFILE on Windows
4848

49-
err := runTUI(nil)
49+
err := runTUI([]string{"-config", cfgPath})
5050
if err == nil {
5151
t.Fatal("expected error from runTUI (no TTY in tests)")
5252
}

go.mod

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,22 @@ go 1.26.2
44

55
require (
66
github.com/MicahParks/keyfunc/v3 v3.8.0
7+
github.com/bluenviron/gortsplib/v5 v5.5.2
8+
github.com/bluenviron/mediacommon/v2 v2.8.3
79
github.com/charmbracelet/bubbles v1.0.0
810
github.com/charmbracelet/bubbletea v1.3.10
911
github.com/charmbracelet/lipgloss v1.1.0
1012
github.com/golang-jwt/jwt/v5 v5.3.1
13+
github.com/google/uuid v1.6.0
14+
github.com/pion/rtp v1.10.1
1115
github.com/use-go/onvif v0.0.9
1216
github.com/wailsapp/wails/v2 v2.12.0
1317
)
1418

1519
require (
1620
git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect
1721
github.com/MicahParks/jwkset v0.11.0 // indirect
22+
github.com/abema/go-mp4 v1.5.0 // indirect
1823
github.com/atotto/clipboard v0.1.4 // indirect
1924
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
2025
github.com/beevik/etree v1.1.0 // indirect
@@ -31,7 +36,6 @@ require (
3136
github.com/go-ole/go-ole v1.3.0 // indirect
3237
github.com/godbus/dbus/v5 v5.1.0 // indirect
3338
github.com/gofrs/uuid v3.2.0+incompatible // indirect
34-
github.com/google/uuid v1.6.0 // indirect
3539
github.com/gorilla/websocket v1.5.3 // indirect
3640
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
3741
github.com/juju/errors v0.0.0-20220331221717-b38fca44723b // indirect
@@ -49,6 +53,12 @@ require (
4953
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
5054
github.com/muesli/cancelreader v0.2.2 // indirect
5155
github.com/muesli/termenv v0.16.0 // indirect
56+
github.com/pion/logging v0.2.4 // indirect
57+
github.com/pion/randutil v0.1.0 // indirect
58+
github.com/pion/rtcp v1.2.16 // indirect
59+
github.com/pion/sdp/v3 v3.0.18 // indirect
60+
github.com/pion/srtp/v3 v3.0.10 // indirect
61+
github.com/pion/transport/v4 v4.0.1 // indirect
5262
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
5363
github.com/pkg/errors v0.9.1 // indirect
5464
github.com/rivo/uniseg v0.4.7 // indirect
@@ -60,9 +70,9 @@ require (
6070
github.com/wailsapp/go-webview2 v1.0.22 // indirect
6171
github.com/wailsapp/mimetype v1.4.1 // indirect
6272
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
63-
golang.org/x/crypto v0.33.0 // indirect
64-
golang.org/x/net v0.35.0 // indirect
65-
golang.org/x/sys v0.38.0 // indirect
66-
golang.org/x/text v0.22.0 // indirect
67-
golang.org/x/time v0.9.0 // indirect
73+
golang.org/x/crypto v0.50.0 // indirect
74+
golang.org/x/net v0.53.0 // indirect
75+
golang.org/x/sys v0.43.0 // indirect
76+
golang.org/x/text v0.36.0 // indirect
77+
golang.org/x/time v0.10.0 // indirect
6878
)

0 commit comments

Comments
 (0)