Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions tests/test_run/camera/test_camera_http_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,52 @@ def test_do_post_unknown_path_sends_404(self):
assert handler._error_code == 404


# ---------------------------------------------------------------------------
# VideoStreamingHandler.serve_player - Push AV Stream Verification template
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestServePlayerPushAVTemplate:
"""Regression tests for issue #1051: the Push AV Server returns each stream's
uploaded files under valid_uploads/error_uploads (list of {file_path, reasons?}),
not the legacy files/valid_files/invalid_files shape. The rendered template's
JS must read the current field names or the video player stays blank even
when the DUT has successfully uploaded content."""

def _render(self):
handler = _make_handler(
path="/",
server_attrs={
"prompt_options": {"PASS": 1, "FAIL": 2},
"prompt_text": "Verify the video stream",
"is_push_av_verification": True,
"push_av_server_url": "https://192.168.0.53:1234",
},
)
handler.serve_player()
return handler.wfile.getvalue().decode("utf-8")

def test_renders_without_template_error(self):
html_content = self._render()
assert "Template error" not in html_content
assert "<!DOCTYPE html>" in html_content or "<html>" in html_content

def test_reads_valid_and_error_uploads_fields(self):
html_content = self._render()
assert "stream.valid_uploads" in html_content
assert "stream.error_uploads" in html_content
assert "file_path" in html_content

def test_no_longer_relies_solely_on_legacy_file_fields(self):
"""The old field names may still appear as a fallback, but the current
server field names must be checked first."""
html_content = self._render()
valid_uploads_idx = html_content.index("stream.valid_uploads")
valid_files_idx = html_content.index("stream.valid_files")
assert valid_uploads_idx < valid_files_idx


# ---------------------------------------------------------------------------
# VideoStreamingHandler.handle_response
# ---------------------------------------------------------------------------
Expand Down
42 changes: 36 additions & 6 deletions th_cli/test_run/camera/push_av_stream_verification.html
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,19 @@
nonConformingSection.style.display = 'none';
}}

// Push AV Server returns each stream's uploaded files as valid_uploads/error_uploads,
// each entry being an object with a file_path (and reasons, for error_uploads).
// Older server responses used plain filename arrays under files/valid_files/invalid_files -
// keep supporting those too so this page degrades gracefully against either shape.
function getStreamFilePaths(stream) {{
if (stream.valid_uploads || stream.error_uploads) {{
const validPaths = (stream.valid_uploads || []).map(u => u?.file_path).filter(Boolean);
const invalidPaths = (stream.error_uploads || []).map(u => u?.file_path).filter(Boolean);
return [...validPaths, ...invalidPaths];
}}
return stream.files || [...(stream.valid_files || []), ...(stream.invalid_files || [])];
}}

function selectStream(index) {{
if (!streamsData || !streamsData[index]) {{
console.error('Stream data not found for index:', index);
Expand All @@ -535,8 +548,7 @@
const stream = streamsData[index];
selectedStreamId = stream.id || index;

// Combine valid_files and invalid_files (or use files if present)
const allFiles = stream.files || [...(stream.valid_files || []), ...(stream.invalid_files || [])];
const allFiles = getStreamFilePaths(stream);

// Update selected styling
document.querySelectorAll('.stream-item').forEach(item => {{
Expand Down Expand Up @@ -582,9 +594,14 @@
const streamNum = parseInt(stream.id) + 1;
streamContentsTitle.textContent = `Stream ${{streamNum}} Contents`;

// Combine valid and invalid files (or use files if present)
const validFiles = stream.valid_files || [];
const invalidFiles = stream.invalid_files || [];
const hasUploadsShape = !!(stream.valid_uploads || stream.error_uploads);
const validFiles = hasUploadsShape
? (stream.valid_uploads || []).map(u => u?.file_path).filter(Boolean)
: (stream.valid_files || []);
const invalidUploads = hasUploadsShape ? (stream.error_uploads || []) : [];
const invalidFiles = hasUploadsShape
? invalidUploads.map(u => u?.file_path).filter(Boolean)
: (stream.invalid_files || []);
Comment thread
rquidute marked this conversation as resolved.
const allFiles = stream.files || [...validFiles, ...invalidFiles];

// Display files
Expand All @@ -611,13 +628,26 @@

// Display conformance status
if (invalidFiles.length > 0) {{
nonConformingContent.innerHTML = `<div style="color: #c62828; text-align: center;">Found ${{invalidFiles.length}} non-conforming file(s)</div>`;
const reasons = invalidUploads
.flatMap(u => u?.reasons || [])
.filter(Boolean);
Comment thread
rquidute marked this conversation as resolved.
const reasonsHtml = reasons.length > 0
? `<br><small>${{reasons.map(r => html_escape(r)).join('<br>')}}</small>`
: '';
nonConformingContent.innerHTML =
`<div style="color: #c62828; text-align: center;">Found ${{invalidFiles.length}} non-conforming file(s)${{reasonsHtml}}</div>`;
}} else {{
nonConformingContent.innerHTML = '<div class="conforming-message">All files conform to Matter Spec.</div>';
}}
nonConformingSection.style.display = 'block';
}}

function html_escape(str) {{
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}}

let currentDashPlayer = null; // Track current player instance

function playStream(streamUrl) {{
Expand Down
Loading