fix: restore recording playback and default to 30-day retention

This commit is contained in:
Codex
2026-10-06 22:20:00 +08:00
parent 91915b9d90
commit 59bd83403f
17 changed files with 160 additions and 20 deletions
+59
View File
@@ -257,6 +257,7 @@ class WebTests(unittest.TestCase):
self.assertEqual(command[command.index('-rc_mode') + 1], 'CQP')
self.assertIn('h264_vaapi', command)
self.assertIn('pipe:1', command)
self.assertEqual(command[command.index('-analyzeduration') + 1], '1000000')
def test_ptz_requires_both_space_and_control_permission(self):
app.save_object('sites', dict(id='permitted', name='Permitted'))
@@ -378,6 +379,64 @@ class WebTests(unittest.TestCase):
app.media_json('/list?path=example', playback=True)
self.assertTrue(error.fp.closed)
def test_first_recording_missing_directory_is_empty_only_for_requested_camera(self):
name = 'cam_' + 'a' * 16 + '_main'
path = '/list?path=' + name
expected = 'lstat ' + str(app.RECORDINGS / name) + ': no such file or directory'
for message, empty in [(expected, True), (expected.replace(name, 'other'), False),
(expected.replace('no such file or directory', 'permission denied'), False),
('invalid start timestamp', False)]:
error = app.urllib.error.HTTPError('http://localhost/list', 400, 'Bad Request', {},
io.BytesIO(json.dumps({'error': message}).encode()))
with self.subTest(message=message), patch('app.urllib.request.urlopen', side_effect=error):
if empty:
self.assertEqual(app.media_json(path, playback=True), [])
else:
with self.assertRaises(app.Problem):
app.media_json(path, playback=True)
self.assertTrue(error.fp.closed)
def test_playback_disables_reverse_proxy_buffering(self):
with patch.object(playback, 'stream', return_value=io.BytesIO(b'test-mp4')):
code, headers, body = self.request('/media/playback?camera='+'a'*16+'&start=2026-01-01T00:00:00Z&duration=10')
self.assertEqual(code, 200)
self.assertEqual(headers['X-Accel-Buffering'], 'no')
self.assertEqual(headers['Cache-Control'], 'no-store')
self.assertEqual(body, b'test-mp4')
def test_default_retention_is_30_days_and_keeps_space_guards(self):
self.assertEqual(app.DEFAULT_STORAGE, dict(retentionDays=30, maxGB=40, reserveGB=8))
camera = dict(id='e'*16, enabled=True, record=True)
def setting(key, default=None):
return 'test-secret' if key == 'mediaSecret' else default
with tempfile.TemporaryDirectory() as folder, patch.object(app, 'CONFIG', app.Path(folder)/'media.json'), \
patch.object(app, 'objects', return_value=[camera]), patch.object(app, 'setting', side_effect=setting), \
patch.object(app, 'source', return_value='rtsp://test/stream'), patch.dict(app.STORAGE, paused=False):
app.write_media_config()
config=json.loads(app.CONFIG.read_text())
main=config['paths'][app.stream_name(camera)]
self.assertEqual(main['recordDeleteAfter'], '720h')
self.assertTrue(main['record'])
self.assertFalse(config['paths'][app.stream_name(camera, 'sub')]['record'])
app.STORAGE['paused']=True
app.write_media_config()
self.assertFalse(json.loads(app.CONFIG.read_text())['paths'][app.stream_name(camera)]['record'])
def test_recording_status_requires_recent_writes_and_distinguishes_protection(self):
camera=dict(id='e'*16, enabled=True, record=True)
with patch.dict(app.STORAGE, paused=False), patch.dict(app.RECENT_RECORDS, {}, clear=True):
self.assertEqual(app.recording_state(camera, True, True), 'waiting')
app.RECENT_RECORDS[app.stream_name(camera)]=time.time()
self.assertEqual(app.recording_state(camera, True, True), 'recording')
self.assertEqual(app.recording_state(dict(camera,record=False), True, True), 'off')
self.assertEqual(app.recording_state(dict(camera,enabled=False), True, True), 'disabled')
self.assertEqual(app.recording_state(camera, False, True), 'offline')
self.assertEqual(app.recording_state(camera, True, False), 'unavailable')
app.RECENT_RECORDS[app.stream_name(camera)]=time.time()-60
self.assertEqual(app.recording_state(camera, True, True), 'waiting')
app.STORAGE['paused']=True
self.assertEqual(app.recording_state(camera, True, True), 'paused')
def family_save(self, kind, data):
return app.FAMILY.save(kind, dict(data, revision=app.setting('familyRevision', 0)), self.admin)