diff --git a/tests/test_batch_fixes.py b/tests/test_batch_fixes.py index 907e08d3..625f9dea 100644 --- a/tests/test_batch_fixes.py +++ b/tests/test_batch_fixes.py @@ -15,7 +15,7 @@ REPO = pathlib.Path(__file__).parent.parent def read(rel): - return (REPO / rel).read_text() + return (REPO / rel).read_text(encoding="utf-8") # ── Group A: /root workspace ────────────────────────────────────────────────── @@ -80,7 +80,7 @@ class TestCronSkillCacheInvalidation: def _panels_src(self): return read("static/panels.js") - def test_cache_busted_on_form_open(self): + def test_cache_busted_on_form_open(self, encoding="utf-8"): src = self._panels_src() # toggleCronForm should set cache to null unconditionally # openCronCreate() opens the task create form (renamed from toggleCronForm @@ -94,7 +94,7 @@ class TestCronSkillCacheInvalidation: "before fetching skills" ) - def test_cache_not_guarded_by_if_on_open(self): + def test_cache_not_guarded_by_if_on_open(self, encoding="utf-8"): src = self._panels_src() # openCronCreate must not gate the fetch behind an if(!_cronSkillsCache) guard. m = re.search( diff --git a/tests/test_cron_no_agent_edit.py b/tests/test_cron_no_agent_edit.py index 00ac092e..d6dba655 100644 --- a/tests/test_cron_no_agent_edit.py +++ b/tests/test_cron_no_agent_edit.py @@ -5,7 +5,7 @@ from __future__ import annotations from pathlib import Path ROOT = Path(__file__).resolve().parents[1] -PANELS_JS = (ROOT / "static" / "panels.js").read_text() +PANELS_JS = (ROOT / "static" / "panels.js").read_text(encoding="utf-8") def _function_body(name: str) -> str: diff --git a/tests/test_csv_table_rendering.py b/tests/test_csv_table_rendering.py index 02c1eb52..2069a0cb 100644 --- a/tests/test_csv_table_rendering.py +++ b/tests/test_csv_table_rendering.py @@ -7,7 +7,7 @@ WORKSPACE_JS = Path("static/workspace.js").read_text(encoding="utf-8") def test_csv_extension_regex(): """Verify _CSV_EXTS regex is defined.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert '_CSV_EXTS' in src, "Missing _CSV_EXTS regex" assert '.csv' in src, "CSV regex should match .csv extension" @@ -15,7 +15,7 @@ def test_csv_extension_regex(): def test_csv_fence_block_handler(): """Verify fenced ```csv blocks are handled.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert "lang==='csv'" in src, "Missing csv language detection in fence handler" assert 'csv-table' in src, "Missing csv-table class for fenced CSV rendering" @@ -24,7 +24,7 @@ def test_csv_fence_block_handler(): def test_csv_fence_renders_table_structure(): """Verify fenced CSV blocks produce proper table HTML.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() # Should have thead, tbody, th, td assert '' in src, "CSV table should have " @@ -37,7 +37,7 @@ def test_csv_fence_renders_table_structure(): def test_csv_fence_fallback_for_insufficient_rows(): """Verify CSV with < 2 rows falls back to code block.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() fence_section = src[src.find("lang==='csv'"):src.find("lang==='csv'") + 800] assert 'rows.length>=2' in fence_section, "Should check for at least 2 rows" @@ -48,7 +48,7 @@ def test_csv_fence_fallback_for_insufficient_rows(): def test_csv_media_file_handler(): """Verify MEDIA: CSV files trigger inline loading.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert 'csv-inline-load' in src, "Missing csv-inline-load class for MEDIA: CSV" assert 'csv_loading' in src, "Missing csv_loading i18n key usage" @@ -62,7 +62,7 @@ def test_csv_media_file_handler(): def test_loadCsvInline_function(): """Verify loadCsvInline lazy-load function exists.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert 'function loadCsvInline' in src, "Missing loadCsvInline function" assert 'function buildCsvTablePreview(path, text)' in src, "Missing shared CSV preview helper" @@ -70,7 +70,7 @@ def test_loadCsvInline_function(): def test_csv_inline_max_size(): """Verify CSV inline rendering has a size cap.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert 'const CSV_MAX_SIZE=256*1024' in src, "Should have CSV_MAX_SIZE constant" helper_section = src[src.find('function buildCsvTablePreview'):src.find('function buildCsvTablePreview') + 2000] @@ -79,7 +79,7 @@ def test_csv_inline_max_size(): def test_csv_auto_detect_separator(): """Verify CSV handler auto-detects separator.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() csv_section = src[src.find('function buildCsvTablePreview'):src.find('function buildCsvTablePreview') + 2000] assert 'separators' in csv_section, "Should have separator detection" @@ -89,14 +89,14 @@ def test_csv_auto_detect_separator(): def test_csv_quote_stripping(): """Verify CSV handler strips surrounding quotes from fields.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert "replace(/^[\"']|[\"']$/g,'')" in src, "Should strip quotes from CSV fields" def test_csv_error_handling(): """Verify CSV error and empty data handling.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() csv_section = src[src.find('function buildCsvTablePreview'):src.find('function loadCsvInline') + 1000] assert 'csv_error' in csv_section, "Should use csv_error i18n on fetch failure" @@ -110,7 +110,7 @@ def test_csv_error_handling(): def test_csv_loadCsvInline_called_after_render(): """Verify loadCsvInline is called by the consolidated post-render pass.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() # Behavior assertion (not a brittle rAF-literal match): the post-render pass # is scheduled a frame later, now routed through _postProcessWithAnchorSuppression @@ -144,7 +144,7 @@ def test_csv_loadCsvInline_called_after_render(): def test_csv_line_ending_normalization(): """Verify CSV handler normalizes line endings.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() csv_section = src[src.find('function buildCsvTablePreview'):src.find('function buildCsvTablePreview') + 2000] assert '\\r\\n' in csv_section, "Should handle \\r\\n line endings" @@ -153,7 +153,7 @@ def test_csv_line_ending_normalization(): def test_csv_i18n_keys(): """Verify CSV i18n keys exist in all 7 locales.""" - with open('static/i18n.js') as f: + with open('static/i18n.js', encoding="utf-8") as f: src = f.read() required_keys = ['csv_loading', 'csv_too_large', 'csv_no_data', 'csv_error'] for key in required_keys: @@ -163,7 +163,7 @@ def test_csv_i18n_keys(): def test_csv_css_classes(): """Verify CSV table CSS classes are defined.""" - with open('static/style.css') as f: + with open('static/style.css', encoding="utf-8") as f: src = f.read() required_classes = ['csv-table-wrap', 'csv-table', 'csv-table th', 'csv-table td'] for cls in required_classes: @@ -174,7 +174,7 @@ def test_csv_css_classes(): def test_csv_not_matched_by_image_exts(): """Verify .csv is NOT in _IMAGE_EXTS.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() match = re.search(r"const _IMAGE_EXTS=/([^/]+)/i", src) assert match @@ -187,7 +187,7 @@ def test_csv_preview_preserves_edit_flow(): workspace Edit affordance that .csv had when it fell through to the code preview. csv mode must be editable, the preview must cache raw content for the textarea, and a save must re-render the table (not markdown).""" - with open('static/workspace.js') as f: + with open('static/workspace.js', encoding="utf-8") as f: src = f.read() # csv mode is editable assert "_previewCurrentMode==='csv'" in src, "csv mode should be editable / handled in workspace edit flow" diff --git a/tests/test_excalidraw_inline_embed.py b/tests/test_excalidraw_inline_embed.py index 73c83f87..f65c4aac 100644 --- a/tests/test_excalidraw_inline_embed.py +++ b/tests/test_excalidraw_inline_embed.py @@ -4,7 +4,7 @@ import re def test_excalidraw_extension_regex(): """Verify _EXCALIDRAW_EXTS regex is defined.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert '_EXCALIDRAW_EXTS' in src, "Missing _EXCALIDRAW_EXTS regex" assert '.excalidraw' in src, "Excalidraw regex should match .excalidraw" @@ -12,7 +12,7 @@ def test_excalidraw_extension_regex(): def test_excalidraw_media_handler(): """Verify MEDIA: .excalidraw files trigger inline loading.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert 'excalidraw-inline-load' in src, "Missing excalidraw-inline-load class" assert 'excalidraw_loading' in src, "Missing excalidraw_loading i18n key usage" @@ -20,14 +20,14 @@ def test_excalidraw_media_handler(): def test_loadExcalidrawInline_function(): """Verify loadExcalidrawInline lazy-load function exists.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert 'function loadExcalidrawInline' in src, "Missing loadExcalidrawInline function" def test_excalidraw_json_validation(): """Verify Excalidraw handler validates JSON format.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() func = src[src.find('function loadExcalidrawInline'):src.find('function loadExcalidrawInline') + 2000] assert 'JSON.parse' in func, "Should parse JSON" @@ -37,7 +37,7 @@ def test_excalidraw_json_validation(): def test_excalidraw_size_cap(): """Verify Excalidraw inline rendering has a size cap.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() func = src[src.find('function loadExcalidrawInline'):src.find('function loadExcalidrawInline') + 2000] assert 'EXCALIDRAW_MAX_SIZE' in func, "Should have EXCALIDRAW_MAX_SIZE constant" @@ -46,7 +46,7 @@ def test_excalidraw_size_cap(): def test_excalidraw_error_handling(): """Verify Excalidraw error handling.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() func = src[src.find('function loadExcalidrawInline'):src.find('function loadExcalidrawInline') + 3500] assert 'excalidraw_error' in func, "Should use excalidraw_error i18n on fetch failure" @@ -54,7 +54,7 @@ def test_excalidraw_error_handling(): def test_excalidraw_svg_renderer_exists(): """Verify SVG renderer for Excalidraw elements exists.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert 'function _renderExcalidrawCanvases()' in src, "Missing _renderExcalidrawCanvases function" start = src.find('function _renderExcalidrawCanvases()') @@ -66,7 +66,7 @@ def test_excalidraw_svg_renderer_exists(): def test_excalidraw_renders_element_types(): """Verify SVG renderer handles common Excalidraw element types.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() start = src.find('function _renderExcalidrawCanvases()') end = src.find('// ── PDF inline preview', start) @@ -78,7 +78,7 @@ def test_excalidraw_renders_element_types(): def test_excalidraw_arrow_marker(): """Verify SVG renderer includes arrow marker definition.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() start = src.find('function _renderExcalidrawCanvases()') end = src.find('// ── PDF inline preview', start) @@ -89,7 +89,7 @@ def test_excalidraw_arrow_marker(): def test_excalidraw_bounds_calculation(): """Verify SVG renderer calculates viewBox from element bounds.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() start = src.find('function _renderExcalidrawCanvases()') end = src.find('// ── PDF inline preview', start) @@ -101,7 +101,7 @@ def test_excalidraw_bounds_calculation(): def test_excalidraw_empty_elements(): """Verify empty diagrams show a message.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() start = src.find('function _renderExcalidrawCanvases()') end = src.find('// ── PDF inline preview', start) @@ -112,7 +112,7 @@ def test_excalidraw_empty_elements(): def test_excalidraw_download_link(): """Verify Excalidraw embed includes download link.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() func = src[src.find('function loadExcalidrawInline'):src.find('function loadExcalidrawInline') + 2000] assert 'excalidraw-open-link' in func, "Should include open/download link" @@ -121,7 +121,7 @@ def test_excalidraw_download_link(): def test_excalidraw_called_after_render(): """Verify loadExcalidrawInline is called by the consolidated post-render pass.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() # Behavior assertion (#5338): post-render is now scheduled through # _postProcessWithAnchorSuppression (which still calls postProcessRenderedMessages). @@ -139,7 +139,7 @@ def test_excalidraw_called_after_render(): def test_excalidraw_embed_wrap_structure(): """Verify Excalidraw embed uses proper container structure.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert 'excalidraw-embed-wrap' in src, "Missing excalidraw-embed-wrap container" assert 'excalidraw-canvas' in src, "Missing excalidraw-canvas div" @@ -148,7 +148,7 @@ def test_excalidraw_embed_wrap_structure(): def test_excalidraw_i18n_keys(): """Verify Excalidraw i18n keys exist in all 7 locales.""" - with open('static/i18n.js') as f: + with open('static/i18n.js', encoding="utf-8") as f: src = f.read() required_keys = [ 'excalidraw_loading', 'excalidraw_too_large', 'excalidraw_invalid', @@ -162,7 +162,7 @@ def test_excalidraw_i18n_keys(): def test_excalidraw_css_classes(): """Verify Excalidraw CSS classes are defined.""" - with open('static/style.css') as f: + with open('static/style.css', encoding="utf-8") as f: src = f.read() required_classes = [ 'excalidraw-embed-wrap', 'excalidraw-canvas', 'excalidraw-svg', @@ -189,7 +189,7 @@ def test_excalidraw_css_classes(): # be coerced via Number()/isFinite gates so they cannot carry strings. def _excalidraw_render_block(): - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() start = src.find('function _renderExcalidrawCanvases') assert start != -1, '_renderExcalidrawCanvases not found' diff --git a/tests/test_issue1096_copy_buttons.py b/tests/test_issue1096_copy_buttons.py index a504ef89..8d70d8ce 100644 --- a/tests/test_issue1096_copy_buttons.py +++ b/tests/test_issue1096_copy_buttons.py @@ -3,12 +3,12 @@ import re def _src(name: str) -> str: - with open(f"static/{name}") as f: + with open(f"static/{name}", encoding="utf-8") as f: return f.read() def _py_src() -> str: - with open("api/helpers.py") as f: + with open("api/helpers.py", encoding="utf-8") as f: return f.read() diff --git a/tests/test_issue1438_fence_anchoring.py b/tests/test_issue1438_fence_anchoring.py index 530a9707..f6b9aacd 100644 --- a/tests/test_issue1438_fence_anchoring.py +++ b/tests/test_issue1438_fence_anchoring.py @@ -36,7 +36,7 @@ import pytest from tests.test_sprint16 import render_md # Python mirror of renderMd() -UI_JS = (Path(__file__).resolve().parent.parent / "static" / "ui.js").read_text() +UI_JS = (Path(__file__).resolve().parent.parent / "static" / "ui.js").read_text(encoding="utf-8") def _strip_pre_blocks(out: str) -> str: diff --git a/tests/test_issue1488_composer_voice_buttons.py b/tests/test_issue1488_composer_voice_buttons.py index 134b0990..5e1dc335 100644 --- a/tests/test_issue1488_composer_voice_buttons.py +++ b/tests/test_issue1488_composer_voice_buttons.py @@ -20,7 +20,7 @@ import re def _src(name: str) -> str: - with open(f"static/{name}") as f: + with open(f"static/{name}", encoding="utf-8") as f: return f.read() diff --git a/tests/test_issue1743_model_picker_race.py b/tests/test_issue1743_model_picker_race.py index 33413902..51d48124 100644 --- a/tests/test_issue1743_model_picker_race.py +++ b/tests/test_issue1743_model_picker_race.py @@ -3,7 +3,7 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] -UI_JS = (ROOT / "static" / "ui.js").read_text() +UI_JS = (ROOT / "static" / "ui.js").read_text(encoding="utf-8") def _body_between(src: str, start: str, end: str) -> str: diff --git a/tests/test_issue1796_error_toasts.py b/tests/test_issue1796_error_toasts.py index d8ed25cc..7fd9194a 100644 --- a/tests/test_issue1796_error_toasts.py +++ b/tests/test_issue1796_error_toasts.py @@ -1,8 +1,8 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] -UI_JS = (ROOT / "static" / "ui.js").read_text() -STYLE_CSS = (ROOT / "static" / "style.css").read_text() +UI_JS = (ROOT / "static" / "ui.js").read_text(encoding="utf-8") +STYLE_CSS = (ROOT / "static" / "style.css").read_text(encoding="utf-8") def test_error_toast_default_duration_is_substantially_longer_than_info_toasts(): diff --git a/tests/test_issue2508_session_pin_cap.py b/tests/test_issue2508_session_pin_cap.py index 385da1f4..04c14b60 100644 --- a/tests/test_issue2508_session_pin_cap.py +++ b/tests/test_issue2508_session_pin_cap.py @@ -11,9 +11,9 @@ from tests._pytest_port import BASE, TEST_STATE_DIR ROOT = pathlib.Path(__file__).resolve().parent.parent -ROUTES_PY = (ROOT / "api" / "routes.py").read_text() -SESSIONS_JS = (ROOT / "static" / "sessions.js").read_text() -STYLE_CSS = (ROOT / "static" / "style.css").read_text() +ROUTES_PY = (ROOT / "api" / "routes.py").read_text(encoding="utf-8") +SESSIONS_JS = (ROOT / "static" / "sessions.js").read_text(encoding="utf-8") +STYLE_CSS = (ROOT / "static" / "style.css").read_text(encoding="utf-8") def post(path, body=None): diff --git a/tests/test_issue2569_model_provider_picker.py b/tests/test_issue2569_model_provider_picker.py index f46ebce5..53c95342 100644 --- a/tests/test_issue2569_model_provider_picker.py +++ b/tests/test_issue2569_model_provider_picker.py @@ -2,7 +2,7 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] -UI_JS = (ROOT / "static" / "ui.js").read_text() +UI_JS = (ROOT / "static" / "ui.js").read_text(encoding="utf-8") def test_temporary_configured_model_option_carries_provider_badge(): diff --git a/tests/test_issue3402_workspace_os_import.py b/tests/test_issue3402_workspace_os_import.py index d39a7f0e..7fa472ea 100644 --- a/tests/test_issue3402_workspace_os_import.py +++ b/tests/test_issue3402_workspace_os_import.py @@ -5,7 +5,7 @@ import subprocess def _src(name: str) -> str: - with open(f"static/{name}") as f: + with open(f"static/{name}", encoding="utf-8") as f: return f.read() diff --git a/tests/test_issue3869_thinking_dots.py b/tests/test_issue3869_thinking_dots.py index 4dfd4599..2f685648 100644 --- a/tests/test_issue3869_thinking_dots.py +++ b/tests/test_issue3869_thinking_dots.py @@ -2,7 +2,7 @@ from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] -UI_JS = (REPO_ROOT / "static" / "ui.js").read_text() +UI_JS = (REPO_ROOT / "static" / "ui.js").read_text(encoding="utf-8") def _function_body(src: str, name: str) -> str: diff --git a/tests/test_issue4356_no_git_update_check.py b/tests/test_issue4356_no_git_update_check.py index 92e93d20..be823d07 100644 --- a/tests/test_issue4356_no_git_update_check.py +++ b/tests/test_issue4356_no_git_update_check.py @@ -52,7 +52,7 @@ def test_check_repo_still_returns_dict_when_dot_git_exists(tmp_path): def test_format_update_target_status_excludes_no_git(): """_formatUpdateTargetStatus returns null for no_git so it doesn't show as update available.""" ui_path = Path(__file__).resolve().parent.parent / 'static' / 'ui.js' - content = ui_path.read_text() + content = ui_path.read_text(encoding="utf-8") assert '_formatUpdateTargetStatus' in content assert 'info.no_git' in content @@ -67,7 +67,7 @@ def test_panels_has_no_git_branch(): target is no_git (which hid the indicator in mixed installs). """ panels_path = Path(__file__).resolve().parent.parent / 'static' / 'panels.js' - content = panels_path.read_text() + content = panels_path.read_text(encoding="utf-8") # Check for the no_git conditional in the update consumer assert 'no_git' in content @@ -82,7 +82,7 @@ def test_panels_has_no_git_branch(): def test_i18n_no_git_key_all_locales(): """settings_update_no_git key appears in all 14 locale blocks in static/i18n.js.""" i18n_path = Path(__file__).resolve().parent.parent / 'static' / 'i18n.js' - content = i18n_path.read_text() + content = i18n_path.read_text(encoding="utf-8") # Count occurrences of the new key (should be exactly 13, one per locale) count = content.count("settings_update_no_git") diff --git a/tests/test_issue470.py b/tests/test_issue470.py index 78893075..1fe50175 100644 --- a/tests/test_issue470.py +++ b/tests/test_issue470.py @@ -19,7 +19,7 @@ import re import html as _html REPO_ROOT = pathlib.Path(__file__).parent.parent -UI_JS = (REPO_ROOT / "static" / "ui.js").read_text() +UI_JS = (REPO_ROOT / "static" / "ui.js").read_text(encoding="utf-8") # ── Helpers ────────────────────────────────────────────────────────────────── diff --git a/tests/test_issue486_487.py b/tests/test_issue486_487.py index 822c7269..4b4b07d5 100644 --- a/tests/test_issue486_487.py +++ b/tests/test_issue486_487.py @@ -21,8 +21,8 @@ import re import html as _html REPO_ROOT = pathlib.Path(__file__).parent.parent -UI_JS = (REPO_ROOT / "static" / "ui.js").read_text() -STYLE_CSS = (REPO_ROOT / "static" / "style.css").read_text() +UI_JS = (REPO_ROOT / "static" / "ui.js").read_text(encoding="utf-8") +STYLE_CSS = (REPO_ROOT / "static" / "style.css").read_text(encoding="utf-8") # ── Helpers ─────────────────────────────────────────────────────────────────── diff --git a/tests/test_issue487b.py b/tests/test_issue487b.py index 18e43749..fa1b4222 100644 --- a/tests/test_issue487b.py +++ b/tests/test_issue487b.py @@ -14,7 +14,7 @@ import pathlib import re REPO_ROOT = pathlib.Path(__file__).parent.parent -UI_JS = (REPO_ROOT / "static" / "ui.js").read_text() +UI_JS = (REPO_ROOT / "static" / "ui.js").read_text(encoding="utf-8") # ── Source-level check ──────────────────────────────────────────────────────── diff --git a/tests/test_issue856_active_session_read_state.py b/tests/test_issue856_active_session_read_state.py index a3dc8f0c..93755716 100644 --- a/tests/test_issue856_active_session_read_state.py +++ b/tests/test_issue856_active_session_read_state.py @@ -3,7 +3,7 @@ from pathlib import Path -MESSAGES_JS = (Path(__file__).resolve().parent.parent / "static" / "messages.js").read_text() +MESSAGES_JS = (Path(__file__).resolve().parent.parent / "static" / "messages.js").read_text(encoding="utf-8") def test_messages_js_defines_active_session_viewed_helper(): diff --git a/tests/test_issue856_pinned_indicator_layout.py b/tests/test_issue856_pinned_indicator_layout.py index 2130441e..5dfaa500 100644 --- a/tests/test_issue856_pinned_indicator_layout.py +++ b/tests/test_issue856_pinned_indicator_layout.py @@ -3,8 +3,8 @@ from pathlib import Path -SESSIONS_JS = (Path(__file__).resolve().parent.parent / "static" / "sessions.js").read_text() -STYLE_CSS = (Path(__file__).resolve().parent.parent / "static" / "style.css").read_text() +SESSIONS_JS = (Path(__file__).resolve().parent.parent / "static" / "sessions.js").read_text(encoding="utf-8") +STYLE_CSS = (Path(__file__).resolve().parent.parent / "static" / "style.css").read_text(encoding="utf-8") def test_pinned_indicator_renders_inside_title_row(): @@ -133,7 +133,7 @@ def test_plain_mouse_hover_does_not_mark_session_row_dragging(): def test_sidebar_uses_local_inflight_state_for_immediate_spinner(): - messages_js = (Path(__file__).resolve().parent.parent / "static" / "messages.js").read_text() + messages_js = (Path(__file__).resolve().parent.parent / "static" / "messages.js").read_text(encoding="utf-8") assert "function _isSessionLocallyStreaming(s)" in SESSIONS_JS assert "isActive && Boolean(S.busy)" in SESSIONS_JS @@ -158,7 +158,7 @@ def test_date_group_caret_expanded_down_collapsed_right(): def test_apperror_path_calls_render_session_list(): """apperror handler must call renderSessionList() to clear the streaming indicator immediately rather than waiting for the 5s streaming poll interval.""" - messages_js = (Path(__file__).resolve().parent.parent / "static" / "messages.js").read_text() + messages_js = (Path(__file__).resolve().parent.parent / "static" / "messages.js").read_text(encoding="utf-8") apperror_idx = messages_js.find("source.addEventListener('apperror'") assert apperror_idx != -1, "apperror handler not found in messages.js" warning_idx = messages_js.find("source.addEventListener('warning'", apperror_idx) diff --git a/tests/test_kanban_view_toggle.py b/tests/test_kanban_view_toggle.py index e169daef..37a7aaf4 100644 --- a/tests/test_kanban_view_toggle.py +++ b/tests/test_kanban_view_toggle.py @@ -14,10 +14,10 @@ import pytest ROOT = Path(__file__).resolve().parents[1] -INDEX_HTML = (ROOT / "static" / "index.html").read_text() -PANELS_JS = (ROOT / "static" / "panels.js").read_text() -STYLE_CSS = (ROOT / "static" / "style.css").read_text() -I18N_JS = (ROOT / "static" / "i18n.js").read_text() +INDEX_HTML = (ROOT / "static" / "index.html").read_text(encoding="utf-8") +PANELS_JS = (ROOT / "static" / "panels.js").read_text(encoding="utf-8") +STYLE_CSS = (ROOT / "static" / "style.css").read_text(encoding="utf-8") +I18N_JS = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8") def test_kanban_header_exposes_accessible_view_toggle(): diff --git a/tests/test_model_selection_refresh_persistence.py b/tests/test_model_selection_refresh_persistence.py index 361cea94..09f12e3a 100644 --- a/tests/test_model_selection_refresh_persistence.py +++ b/tests/test_model_selection_refresh_persistence.py @@ -9,9 +9,9 @@ next ``loadSession()`` before ``syncTopbar()`` projects server metadata. from pathlib import Path ROOT = Path(__file__).resolve().parents[1] -BOOT_JS = (ROOT / "static" / "boot.js").read_text() -SESSIONS_JS = (ROOT / "static" / "sessions.js").read_text() -UI_JS = (ROOT / "static" / "ui.js").read_text() +BOOT_JS = (ROOT / "static" / "boot.js").read_text(encoding="utf-8") +SESSIONS_JS = (ROOT / "static" / "sessions.js").read_text(encoding="utf-8") +UI_JS = (ROOT / "static" / "ui.js").read_text(encoding="utf-8") def _body_between(src: str, start: str, end: str) -> str: diff --git a/tests/test_pdf_html_preview.py b/tests/test_pdf_html_preview.py index c0a3c8ca..3343b901 100644 --- a/tests/test_pdf_html_preview.py +++ b/tests/test_pdf_html_preview.py @@ -10,12 +10,12 @@ import pytest def _read_js(name): - with open(os.path.join('static', name)) as f: + with open(os.path.join('static', name), encoding="utf-8") as f: return f.read() def _read_css(): - with open(os.path.join('static', 'style.css')) as f: + with open(os.path.join('static', 'style.css'), encoding="utf-8") as f: return f.read() @@ -316,7 +316,7 @@ class TestI18nKeys: HTML_KEYS = ['html_loading', 'html_too_large', 'html_error', 'html_open_full', 'html_sandbox_label'] def _find_locale_block(self, locale): - with open('static/i18n.js') as f: + with open('static/i18n.js', encoding="utf-8") as f: content = f.read() start = content.find(f"'{locale}':") if start < 0: diff --git a/tests/test_run_journal_frontend_static.py b/tests/test_run_journal_frontend_static.py index ed94f720..e63b084a 100644 --- a/tests/test_run_journal_frontend_static.py +++ b/tests/test_run_journal_frontend_static.py @@ -4,9 +4,9 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[1] -MESSAGES_SRC = (ROOT / "static" / "messages.js").read_text() -SESSIONS_SRC = (ROOT / "static" / "sessions.js").read_text() -UI_SRC = (ROOT / "static" / "ui.js").read_text() +MESSAGES_SRC = (ROOT / "static" / "messages.js").read_text(encoding="utf-8") +SESSIONS_SRC = (ROOT / "static" / "sessions.js").read_text(encoding="utf-8") +UI_SRC = (ROOT / "static" / "ui.js").read_text(encoding="utf-8") def _function_body(src: str, signature: str) -> str: diff --git a/tests/test_run_journal_routes.py b/tests/test_run_journal_routes.py index efc4324b..d5a40626 100644 --- a/tests/test_run_journal_routes.py +++ b/tests/test_run_journal_routes.py @@ -6,7 +6,7 @@ import queue ROOT = Path(__file__).resolve().parents[1] -ROUTES_SRC = (ROOT / "api" / "routes.py").read_text() +ROUTES_SRC = (ROOT / "api" / "routes.py").read_text(encoding="utf-8") def test_stream_status_exposes_replay_summary(): diff --git a/tests/test_session_batch_select.py b/tests/test_session_batch_select.py index 7794a45a..e82cc3d5 100644 --- a/tests/test_session_batch_select.py +++ b/tests/test_session_batch_select.py @@ -4,7 +4,7 @@ import re def test_batch_select_state_variables(): """Verify batch select state variables are declared.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: src = f.read() assert '_sessionSelectMode' in src, "Missing _sessionSelectMode variable" assert '_selectedSessions' in src, "Missing _selectedSessions variable" @@ -13,7 +13,7 @@ def test_batch_select_state_variables(): def test_batch_select_functions_exist(): """Verify all batch select functions are defined.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: src = f.read() required_funcs = [ 'toggleSessionSelectMode', @@ -31,7 +31,7 @@ def test_batch_select_functions_exist(): def test_batch_select_checkbox_rendering(): """Verify checkbox is rendered when in select mode.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: src = f.read() assert 'session-select-cb' in src, "Missing session-select-cb class" assert 'session-select-cb-wrapper' in src, "Missing session-select-cb-wrapper class" @@ -40,7 +40,7 @@ def test_batch_select_checkbox_rendering(): def test_batch_select_intercepts_navigation(): """Verify select mode intercepts session navigation.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: src = f.read() assert "_sessionSelectMode" in src # Should have early return when in select mode @@ -50,7 +50,7 @@ def test_batch_select_intercepts_navigation(): def test_batch_checkbox_sets_selection_without_row_double_toggle(): """Clicking the checkbox itself must not also trigger row-level toggling.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: src = f.read() assert 'function setSessionSelected(sid, selected)' in src, \ "Checkbox changes should set explicit state instead of toggling blindly" @@ -64,7 +64,7 @@ def test_batch_checkbox_sets_selection_without_row_double_toggle(): def test_batch_select_escape_handler(): """Verify Escape key exits select mode.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: src = f.read() assert "e.key==='Escape'&&_sessionSelectMode" in src, \ "Should have Escape key handler for select mode" @@ -72,7 +72,7 @@ def test_batch_select_escape_handler(): def test_batch_select_toggle_button(): """Verify select mode toggle button is rendered.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: src = f.read() assert 'session-select-toggle' in src, "Missing session-select-toggle class" assert 'toggleSessionSelectMode' in src, "Missing toggleSessionSelectMode call" @@ -80,7 +80,7 @@ def test_batch_select_toggle_button(): def test_batch_select_bar_element(): """Verify batch action bar DOM element is created.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: src = f.read() assert 'batchActionBar' in src, "Missing batchActionBar element" assert 'batch-action-bar' in src, "Missing batch-action-bar CSS class" @@ -89,7 +89,7 @@ def test_batch_select_bar_element(): def test_batch_action_bar_overrides_css_hidden_state(): """Selected sessions must make the fixed action bar visible.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: src = f.read() assert "if(count>0){_renderBatchActionBar();}" in src, \ "Updating selected count must render action buttons, not just reveal an empty bar" @@ -107,9 +107,9 @@ def test_batch_action_bar_overrides_css_hidden_state(): def test_batch_action_bar_is_sidebar_inline_not_global_footer(): """Batch actions should appear in the session list, not over the composer.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: js = f.read() - with open('static/style.css') as f: + with open('static/style.css', encoding="utf-8") as f: css = f.read() assert "list.appendChild(batchBar)" in js, \ "Batch action bar should be rendered inside the session list" @@ -123,9 +123,9 @@ def test_batch_action_bar_is_sidebar_inline_not_global_footer(): def test_batch_project_picker_is_anchored_to_batch_actions(): """Batch move project picker should open inside the sidebar action bar.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: js = f.read() - with open('static/style.css') as f: + with open('static/style.css', encoding="utf-8") as f: css = f.read() assert "const bar=$('batchActionBar');if(!bar)return;" in js, \ "Batch project picker should anchor to the batch action bar" @@ -143,7 +143,7 @@ def test_batch_project_picker_is_anchored_to_batch_actions(): def test_streaming_zero_message_sessions_stay_visible_after_reload(): """In-flight sessions may have zero saved messages during reload recovery.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: src = f.read() assert "_isSessionEffectivelyStreaming(s)" in src, \ "Streaming sessions must bypass the zero-message sidebar filter" @@ -155,7 +155,7 @@ def test_streaming_zero_message_sessions_stay_visible_after_reload(): def test_boot_does_not_drop_zero_message_inflight_session(): """Reloading /session/ during a running turn must keep the session open.""" - with open('static/boot.js') as f: + with open('static/boot.js', encoding="utf-8") as f: src = f.read() assert "const _restoredInFlight = S.session && (" in src, \ "Boot must detect restored in-flight sessions before ephemeral cleanup" @@ -169,7 +169,7 @@ def test_boot_does_not_drop_zero_message_inflight_session(): def test_batch_select_i18n_keys(): """Verify all batch select i18n keys exist in all locales.""" - with open('static/i18n.js') as f: + with open('static/i18n.js', encoding="utf-8") as f: src = f.read() required_keys = [ 'session_select_mode', @@ -202,7 +202,7 @@ def test_batch_select_i18n_keys(): def test_i18n_string_placeholder_interpolation_supported(): """String-valued translations with {0} placeholders should interpolate args.""" - with open('static/i18n.js') as f: + with open('static/i18n.js', encoding="utf-8") as f: src = f.read() assert "String(val).replace(/\\{(\\d+)\\}/g" in src, \ "t() must interpolate {0}-style placeholders for string-valued translations" @@ -212,7 +212,7 @@ def test_i18n_string_placeholder_interpolation_supported(): def test_batch_select_css_exists(): """Verify batch select CSS classes are defined.""" - with open('static/style.css') as f: + with open('static/style.css', encoding="utf-8") as f: src = f.read() required_classes = [ 'session-select-toggle', @@ -233,7 +233,7 @@ def test_batch_select_css_exists(): def test_batch_select_mode_flags(): """Verify select mode properly toggles state.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: src = f.read() # toggleSessionSelectMode should flip the flag assert '_sessionSelectMode=!_sessionSelectMode' in src, \ @@ -247,7 +247,7 @@ def test_batch_select_mode_flags(): def test_batch_delete_uses_confirm_dialog(): """Verify batch delete shows confirmation dialog.""" - with open('static/sessions.js') as f: + with open('static/sessions.js', encoding="utf-8") as f: src = f.read() # The delete handler should call showConfirmDialog with batch message assert "session_batch_delete_confirm" in src, \ diff --git a/tests/test_sprint38.py b/tests/test_sprint38.py index 8cb2ce89..427252a0 100644 --- a/tests/test_sprint38.py +++ b/tests/test_sprint38.py @@ -7,8 +7,8 @@ and the streaming render path (messages.js _streamDisplay logic). import pathlib REPO_ROOT = pathlib.Path(__file__).parent.parent -UI_JS = (REPO_ROOT / "static" / "ui.js").read_text() -MSG_JS = (REPO_ROOT / "static" / "messages.js").read_text() +UI_JS = (REPO_ROOT / "static" / "ui.js").read_text(encoding="utf-8") +MSG_JS = (REPO_ROOT / "static" / "messages.js").read_text(encoding="utf-8") # ── ui.js: static render path ──────────────────────────────────────────────── diff --git a/tests/test_sprint40.py b/tests/test_sprint40.py index 6a39bc16..a39b003d 100644 --- a/tests/test_sprint40.py +++ b/tests/test_sprint40.py @@ -16,8 +16,8 @@ from unittest.mock import patch import api.onboarding as mod REPO_ROOT = pathlib.Path(__file__).parent.parent -I18N_JS = (REPO_ROOT / "static" / "i18n.js").read_text() -ONBOARDING_JS = (REPO_ROOT / "static" / "onboarding.js").read_text() +I18N_JS = (REPO_ROOT / "static" / "i18n.js").read_text(encoding="utf-8") +ONBOARDING_JS = (REPO_ROOT / "static" / "onboarding.js").read_text(encoding="utf-8") # ── Backend: _build_setup_catalog ────────────────────────────────────────── @@ -152,7 +152,7 @@ class TestOAuthOnboardingJs(unittest.TestCase): def test_style_css_has_oauth_card_rules(self): """style.css must contain the .onboarding-oauth-card rules.""" - css = (REPO_ROOT / "static" / "style.css").read_text() + css = (REPO_ROOT / "static" / "style.css").read_text(encoding="utf-8") self.assertIn("onboarding-oauth-card", css) self.assertIn("onboarding-oauth-ready", css) self.assertIn("onboarding-oauth-pending", css) diff --git a/tests/test_sprint40_ui_polish.py b/tests/test_sprint40_ui_polish.py index 6f26aad7..5743ceba 100644 --- a/tests/test_sprint40_ui_polish.py +++ b/tests/test_sprint40_ui_polish.py @@ -18,9 +18,9 @@ if str(_REPO_ROOT) not in sys.path: sys.path.insert(0, str(_REPO_ROOT)) REPO_ROOT = _REPO_ROOT -STYLE_CSS = (REPO_ROOT / "static" / "style.css").read_text() -SESSIONS_JS = (REPO_ROOT / "static" / "sessions.js").read_text() -PANELS_JS = (REPO_ROOT / "static" / "panels.js").read_text() +STYLE_CSS = (REPO_ROOT / "static" / "style.css").read_text(encoding="utf-8") +SESSIONS_JS = (REPO_ROOT / "static" / "sessions.js").read_text(encoding="utf-8") +PANELS_JS = (REPO_ROOT / "static" / "panels.js").read_text(encoding="utf-8") try: from api import config as _api_config @@ -101,7 +101,7 @@ class TestGatewaySessionNullModel(unittest.TestCase): """api/models.py must not use `or 'unknown'` for the model field so that a NULL model in state.db is returned as None (falsy) to the frontend rather than the truthy string 'unknown'.""" - models_src = (REPO_ROOT / "api" / "models.py").read_text() + models_src = (REPO_ROOT / "api" / "models.py").read_text(encoding="utf-8") # Ensure the old fallback pattern is gone self.assertNotIn( "'model': row['model'] or 'unknown'", @@ -113,7 +113,7 @@ class TestGatewaySessionNullModel(unittest.TestCase): def test_gateway_watcher_null_model_returns_none_not_unknown(self): """api/gateway_watcher.py must not use `or 'unknown'` for the model field so that a NULL model in state.db is returned as None (falsy).""" - gw_src = (REPO_ROOT / "api" / "gateway_watcher.py").read_text() + gw_src = (REPO_ROOT / "api" / "gateway_watcher.py").read_text(encoding="utf-8") self.assertNotIn( "'model': row['model'] or 'unknown'", gw_src, @@ -124,8 +124,8 @@ class TestGatewaySessionNullModel(unittest.TestCase): def test_gateway_session_model_uses_none_fallback(self): """Both source files must use `row['model'] or None` (explicit None fallback) for the model field assignment.""" - models_src = (REPO_ROOT / "api" / "models.py").read_text() - gw_src = (REPO_ROOT / "api" / "gateway_watcher.py").read_text() + models_src = (REPO_ROOT / "api" / "models.py").read_text(encoding="utf-8") + gw_src = (REPO_ROOT / "api" / "gateway_watcher.py").read_text(encoding="utf-8") self.assertIn( "'model': row['model'] or None,", models_src, diff --git a/tests/test_sprint41.py b/tests/test_sprint41.py index eee0898c..a4ca9672 100644 --- a/tests/test_sprint41.py +++ b/tests/test_sprint41.py @@ -12,10 +12,10 @@ import re import unittest REPO_ROOT = pathlib.Path(__file__).parent.parent -CSS = (REPO_ROOT / "static" / "style.css").read_text() -HTML = (REPO_ROOT / "static" / "index.html").read_text() -MESSAGES_JS = (REPO_ROOT / "static" / "messages.js").read_text() -STREAMING_PY = (REPO_ROOT / "api" / "streaming.py").read_text() +CSS = (REPO_ROOT / "static" / "style.css").read_text(encoding="utf-8") +HTML = (REPO_ROOT / "static" / "index.html").read_text(encoding="utf-8") +MESSAGES_JS = (REPO_ROOT / "static" / "messages.js").read_text(encoding="utf-8") +STREAMING_PY = (REPO_ROOT / "api" / "streaming.py").read_text(encoding="utf-8") # ── streaming.py: title auto-generation condition ───────────────────────── diff --git a/tests/test_sprint42.py b/tests/test_sprint42.py index 9c027fac..17a28ddc 100644 --- a/tests/test_sprint42.py +++ b/tests/test_sprint42.py @@ -19,7 +19,7 @@ import unittest from unittest import mock REPO_ROOT = pathlib.Path(__file__).parent.parent -STREAMING_PY = (REPO_ROOT / "api" / "streaming.py").read_text() +STREAMING_PY = (REPO_ROOT / "api" / "streaming.py").read_text(encoding="utf-8") # ── Shared helpers for sprint-42 additional tests ──────────────────────────── @@ -717,7 +717,7 @@ def test_cleanTitle_is_let_not_const(): # ── Sprint 42 additional tests: thinking panel persistence (#427) ──────── def test_streaming_persists_reasoning_in_session(): """streaming.py must accumulate reasoning and patch assistant messages.""" - src = (REPO / 'api' / 'streaming.py').read_text() + src = (REPO / 'api' / 'streaming.py').read_text(encoding="utf-8") # #3587: per-message reasoning segments replaced the flat _reasoning_text accumulator assert "_reasoning_segments" in src, \ @@ -752,7 +752,7 @@ def test_streaming_persists_reasoning_in_session(): def test_done_handler_patches_reasoning_field(): """messages.js done SSE handler must patch reasoningText onto the last assistant message.""" - src = (REPO / 'static' / 'messages.js').read_text() + src = (REPO / 'static' / 'messages.js').read_text(encoding="utf-8") # The persistence comment must be present inside the done handler assert "Persist reasoning trace for Worklog Thinking Cards" in src, \ @@ -778,7 +778,7 @@ def test_done_handler_patches_reasoning_field(): def test_rendermessages_keeps_reasoning_metadata_out_of_worklog_display(): """ui.js renderMessages must not promote provider reasoning metadata into Worklog prose.""" - src = (REPO / 'static' / 'ui.js').read_text() + src = (REPO / 'static' / 'ui.js').read_text(encoding="utf-8") sig_fn = src.split("function _messageHasReasoningPayload(m)", 1)[1].split("function", 1)[0] assert 'm.reasoning' in sig_fn, \ @@ -801,7 +801,7 @@ def test_streaming_restores_prior_reasoning_metadata_after_followup(): history before saving the session, including reinserting dropped reasoning-only assistant segments. """ - src = (REPO / 'api' / 'streaming.py').read_text() + src = (REPO / 'api' / 'streaming.py').read_text(encoding="utf-8") assert "def _restore_reasoning_metadata(" in src, \ "streaming.py must define a helper to restore prior reasoning metadata" assert "_next_context_messages" in src and "s.context_messages" in src, \ @@ -814,7 +814,7 @@ def test_streaming_restores_prior_reasoning_metadata_after_followup(): def test_routes_restores_prior_reasoning_metadata_after_followup(): """The non-streaming route path must preserve prior reasoning metadata too.""" - src = (REPO / 'api' / 'routes.py').read_text() + src = (REPO / 'api' / 'routes.py').read_text(encoding="utf-8") assert "_restore_reasoning_metadata" in src, \ "routes.py must import reasoning metadata restoration helper" assert "_next_context_messages" in src and "s.context_messages" in src, \ diff --git a/tests/test_sprint43.py b/tests/test_sprint43.py index 7a379325..627dfc4f 100644 --- a/tests/test_sprint43.py +++ b/tests/test_sprint43.py @@ -18,16 +18,16 @@ import sys import unittest REPO_ROOT = pathlib.Path(__file__).parent.parent -GATEWAY_WATCHER_PY = (REPO_ROOT / "api" / "gateway_watcher.py").read_text() -CONFIG_PY = (REPO_ROOT / "api" / "config.py").read_text() -BOOTSTRAP_PY = (REPO_ROOT / "bootstrap.py").read_text() -SERVER_PY = (REPO_ROOT / "server.py").read_text() -ROUTES_PY = (REPO_ROOT / "api" / "routes.py").read_text() -AUTH_PY = (REPO_ROOT / "api" / "auth.py").read_text() -PROFILES_PY = (REPO_ROOT / "api" / "profiles.py").read_text() -STREAMING_PY = (REPO_ROOT / "api" / "streaming.py").read_text() -WORKSPACE_PY = (REPO_ROOT / "api" / "workspace.py").read_text() -STATE_SYNC_PY = (REPO_ROOT / "api" / "state_sync.py").read_text() +GATEWAY_WATCHER_PY = (REPO_ROOT / "api" / "gateway_watcher.py").read_text(encoding="utf-8") +CONFIG_PY = (REPO_ROOT / "api" / "config.py").read_text(encoding="utf-8") +BOOTSTRAP_PY = (REPO_ROOT / "bootstrap.py").read_text(encoding="utf-8") +SERVER_PY = (REPO_ROOT / "server.py").read_text(encoding="utf-8") +ROUTES_PY = (REPO_ROOT / "api" / "routes.py").read_text(encoding="utf-8") +AUTH_PY = (REPO_ROOT / "api" / "auth.py").read_text(encoding="utf-8") +PROFILES_PY = (REPO_ROOT / "api" / "profiles.py").read_text(encoding="utf-8") +STREAMING_PY = (REPO_ROOT / "api" / "streaming.py").read_text(encoding="utf-8") +WORKSPACE_PY = (REPO_ROOT / "api" / "workspace.py").read_text(encoding="utf-8") +STATE_SYNC_PY = (REPO_ROOT / "api" / "state_sync.py").read_text(encoding="utf-8") # ── B324: MD5 usedforsecurity=False ───────────────────────────────────────── diff --git a/tests/test_sprint48.py b/tests/test_sprint48.py index 2de27e23..5aa403cf 100644 --- a/tests/test_sprint48.py +++ b/tests/test_sprint48.py @@ -15,7 +15,7 @@ REPO = pathlib.Path(__file__).parent.parent def read(rel): - return (REPO / rel).read_text() + return (REPO / rel).read_text(encoding="utf-8") # ── Bug #702 — XML tool-call leak on DeepSeek ──────────────────────────────── diff --git a/tests/test_svg_audio_video_rendering.py b/tests/test_svg_audio_video_rendering.py index 40475917..d5e7309d 100644 --- a/tests/test_svg_audio_video_rendering.py +++ b/tests/test_svg_audio_video_rendering.py @@ -4,7 +4,7 @@ import re def test_media_extension_regexes_exist(): """Verify SVG/audio/video extension regexes are defined.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert '_SVG_EXTS' in src, "Missing _SVG_EXTS regex" assert '_AUDIO_EXTS' in src, "Missing _AUDIO_EXTS regex" @@ -19,7 +19,7 @@ def test_media_extension_regexes_exist(): def test_svg_rendered_before_image_catch_all(): """Verify SVG handler for URLs runs before the catch-all image handler.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() # Find positions of SVG vs image catch-all in the URL section svg_url_match = src.find("SVG URLs") @@ -33,7 +33,7 @@ def test_svg_rendered_before_image_catch_all(): def test_local_svg_inline_rendering(): """Verify local SVG files render as inline image.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert "msg-media-svg" in src, "Missing msg-media-svg CSS class for SVG rendering" # Should have at least 2 SVG handlers (URL + local) @@ -43,7 +43,7 @@ def test_local_svg_inline_rendering(): def test_local_audio_inline_rendering(): """Verify local audio files render as inline player.""" - with open('static/ui.js') as f: + with open('static/ui.js', encoding="utf-8") as f: src = f.read() assert "msg-media-audio" in src, "Missing msg-media-audio CSS class" assert "