snapshot of https://github.com/ActivityWatch/activitywatch.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
Research feed publisher tests / test (push) Failing after 3s
Release / Pre-flight checks (push) Failing after 0s
Release / Research Edition — patch and packaging smoke tests (push) Has been skipped
Release / Build Qt artifacts (false, 22, ubuntu-22.04, 3.9, ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) }}, false, false) (push) Has been skipped
Release / Build Qt artifacts (false, 22, ubuntu-22.04, 3.9, false, false, false) (push) Has been skipped
Release / Build Qt artifacts (manylinux_2_28 — glibc 2.28 ABI floor) (22, 3.9) (push) Has been skipped
Release / Generate release notes (push) Has been skipped
Release / Create dev release tag (push) Has been skipped
CodeQL / Analyze (javascript) (push) Failing after 1s
CodeQL / Analyze (python) (push) Failing after 1s
Release / Build Qt artifacts (false, 22, windows-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, macos-15-intel, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, macos-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, ubuntu-24.04, 3.9, ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) }}, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, ubuntu-24.04, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, ubuntu-24.04-arm, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, windows-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Build Qt artifacts (false, 22, macos-15-intel, 3.9, false, false, false) (push) Has been cancelled
Release / Build Qt artifacts (false, 22, macos-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Publish draft release (push) Has been cancelled
Some checks failed
Research feed publisher tests / test (push) Failing after 3s
Release / Pre-flight checks (push) Failing after 0s
Release / Research Edition — patch and packaging smoke tests (push) Has been skipped
Release / Build Qt artifacts (false, 22, ubuntu-22.04, 3.9, ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) }}, false, false) (push) Has been skipped
Release / Build Qt artifacts (false, 22, ubuntu-22.04, 3.9, false, false, false) (push) Has been skipped
Release / Build Qt artifacts (manylinux_2_28 — glibc 2.28 ABI floor) (22, 3.9) (push) Has been skipped
Release / Generate release notes (push) Has been skipped
Release / Create dev release tag (push) Has been skipped
CodeQL / Analyze (javascript) (push) Failing after 1s
CodeQL / Analyze (python) (push) Failing after 1s
Release / Build Qt artifacts (false, 22, windows-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, macos-15-intel, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, macos-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, ubuntu-24.04, 3.9, ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) }}, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, ubuntu-24.04, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, ubuntu-24.04-arm, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, windows-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Build Qt artifacts (false, 22, macos-15-intel, 3.9, false, false, false) (push) Has been cancelled
Release / Build Qt artifacts (false, 22, macos-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Publish draft release (push) Has been cancelled
This commit is contained in:
223
scripts/tests/test_patch_research_edition_config.py
Normal file
223
scripts/tests/test_patch_research_edition_config.py
Normal file
@@ -0,0 +1,223 @@
|
||||
import ast
|
||||
import importlib.util
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
SCRIPT = Path(__file__).parents[1] / "patch_research_edition_config.py"
|
||||
SPEC = importlib.util.spec_from_file_location("patch_research_edition_config", SCRIPT)
|
||||
assert SPEC and SPEC.loader
|
||||
patcher = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(patcher)
|
||||
|
||||
|
||||
# Current config.py reads both maps. The Research Edition patch deliberately
|
||||
# leaves the app map empty so approved application names survive capture.
|
||||
RUNTIME_LOOKUPS = """
|
||||
|
||||
def parse_args():
|
||||
parsed_args.research_category_map = dict(config.get("research_category_map", {}))
|
||||
parsed_args.research_app_category_map = dict(config.get("research_app_category_map", {}))
|
||||
"""
|
||||
|
||||
# Layout before aw-watcher-window#137: the research tables live in
|
||||
# default_config, section-prefixed.
|
||||
PRE_137 = (
|
||||
'''default_config = """
|
||||
[aw-watcher-window]
|
||||
poll_time = 1.0
|
||||
research_enabled = false
|
||||
|
||||
[aw-watcher-window.research_category_map]
|
||||
|
||||
[aw-watcher-window.research_app_category_map]
|
||||
""".strip()
|
||||
'''
|
||||
+ RUNTIME_LOOKUPS
|
||||
)
|
||||
|
||||
# Layout since aw-watcher-window#137: research knobs moved to their own template
|
||||
# so they are not persisted into every fresh install's config, and a comment
|
||||
# documents the release-time rewrite -- including the literal flag text.
|
||||
POST_137 = (
|
||||
'''default_config = """
|
||||
[aw-watcher-window]
|
||||
poll_time = 1.0
|
||||
""".strip()
|
||||
|
||||
# The Research Edition release build rewrites the flag below with
|
||||
# sed -i 's/^research_enabled = false$/research_enabled = true/'
|
||||
# Keep that line at column 0 and byte-identical.
|
||||
research_defaults = """
|
||||
research_enabled = false
|
||||
""".strip()
|
||||
'''
|
||||
+ RUNTIME_LOOKUPS
|
||||
)
|
||||
|
||||
|
||||
def _string_constant(source: str, name: str) -> str:
|
||||
"""Return the value of a module-level string assignment, unwrapping .strip()."""
|
||||
for node in ast.parse(source).body:
|
||||
if (
|
||||
isinstance(node, ast.Assign)
|
||||
and getattr(node.targets[0], "id", None) == name
|
||||
):
|
||||
value = node.value
|
||||
if isinstance(value, ast.Call): # `""" ... """.strip()`
|
||||
value = value.func.value
|
||||
return ast.literal_eval(value)
|
||||
raise AssertionError(f"{name} not found")
|
||||
|
||||
|
||||
def test_pre_137_layout_injects_browser_map_and_preserves_app_names():
|
||||
result = patcher.patch_config(PRE_137)
|
||||
|
||||
ast.parse(result)
|
||||
|
||||
section = tomllib.loads(_string_constant(result, "default_config"))[
|
||||
"aw-watcher-window"
|
||||
]
|
||||
assert section["research_enabled"] is True
|
||||
assert section["research_category_map"]["svenskaspel.se"] == "Sensitive / Excluded"
|
||||
assert section["research_app_category_map"] == {}
|
||||
|
||||
|
||||
def test_post_137_layout_injects_only_browser_map_into_research_defaults():
|
||||
"""The tables must land in research_defaults, with unprefixed headers.
|
||||
|
||||
research_defaults is parsed standalone and merged into the
|
||||
[aw-watcher-window] section key by key, so a section-prefixed header there
|
||||
would produce a nested `aw-watcher-window` key that nothing reads.
|
||||
"""
|
||||
result = patcher.patch_config(POST_137)
|
||||
|
||||
ast.parse(result)
|
||||
|
||||
defaults = tomllib.loads(_string_constant(result, "research_defaults"))
|
||||
assert "aw-watcher-window" not in defaults
|
||||
assert defaults["research_enabled"] is True
|
||||
assert defaults["research_category_map"]["svenskaspel.se"] == "Sensitive / Excluded"
|
||||
# An empty table is emitted so upgrades from an earlier Research Edition
|
||||
# (which may have had this map populated) explicitly clear it.
|
||||
assert defaults["research_app_category_map"] == {}
|
||||
|
||||
|
||||
def test_post_137_emits_explicit_empty_app_map_to_clear_legacy_entries():
|
||||
"""The patched research_defaults must include an explicit empty app-category table.
|
||||
|
||||
An upgrade from an earlier Research Edition may have a populated
|
||||
research_app_category_map in the user's saved config. Because research_defaults
|
||||
is merged into [aw-watcher-window] key-by-key, omitting the table would leave
|
||||
legacy entries intact, so application names would continue to be replaced with
|
||||
categories, defeating the primary behavior change. The explicit empty section
|
||||
header ensures the watcher's merge logic resets the key.
|
||||
"""
|
||||
result = patcher.patch_config(POST_137)
|
||||
|
||||
defaults_str = _string_constant(result, "research_defaults")
|
||||
# The literal section header must appear so older installs see an explicit reset.
|
||||
assert "[research_app_category_map]" in defaults_str
|
||||
assert tomllib.loads(defaults_str)["research_app_category_map"] == {}
|
||||
|
||||
|
||||
def test_patched_config_matches_the_pinned_watcher_storage_contract():
|
||||
"""Approved app names survive, while browser content and every URL do not."""
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
config_path = root / "aw-watcher-window/aw_watcher_window/config.py"
|
||||
filter_path = root / "aw-watcher-window/aw_watcher_window/research_filter.py"
|
||||
if not config_path.is_file() or not filter_path.is_file():
|
||||
pytest.skip("aw-watcher-window not checked out")
|
||||
|
||||
patched = patcher.patch_config(config_path.read_text(encoding="utf-8"))
|
||||
defaults = tomllib.loads(_string_constant(patched, "research_defaults"))
|
||||
|
||||
spec = importlib.util.spec_from_file_location("research_filter", filter_path)
|
||||
assert spec and spec.loader
|
||||
research_filter = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(research_filter)
|
||||
|
||||
word = research_filter.transform(
|
||||
{
|
||||
"app": "Microsoft Word",
|
||||
"title": "confidential-draft.docx",
|
||||
"url": "file:///private/confidential-draft.docx",
|
||||
},
|
||||
defaults["research_category_map"],
|
||||
defaults.get("research_app_category_map"),
|
||||
)
|
||||
safari = research_filter.transform(
|
||||
{
|
||||
"app": "Safari",
|
||||
"title": "Confidential draft - Google Docs",
|
||||
"url": "https://docs.google.com/document/private-id",
|
||||
},
|
||||
defaults["research_category_map"],
|
||||
defaults.get("research_app_category_map"),
|
||||
)
|
||||
|
||||
assert word == {"app": "Microsoft Word"}
|
||||
assert safari == {"app": "Safari", "title": "Work & Productivity"}
|
||||
|
||||
|
||||
def test_flag_rewrite_is_line_anchored_and_spares_the_sed_comment():
|
||||
"""The #137 comment contains the literal flag text, and precedes the real flag.
|
||||
|
||||
An unanchored replace patches the comment and leaves research_enabled = false
|
||||
-- a green build shipping a Research Edition with research silently disabled.
|
||||
"""
|
||||
result = patcher.patch_config(POST_137)
|
||||
|
||||
assert "s/^research_enabled = false$/research_enabled = true/" in result
|
||||
assert (
|
||||
tomllib.loads(_string_constant(result, "research_defaults"))["research_enabled"]
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
def test_app_map_runtime_support_is_not_required_for_name_preserving_build():
|
||||
without_lookup = POST_137.replace(
|
||||
'config.get("research_app_category_map"', 'config.get("something_else"'
|
||||
)
|
||||
|
||||
result = patcher.patch_config(without_lookup)
|
||||
|
||||
defaults = tomllib.loads(_string_constant(result, "research_defaults"))
|
||||
# An empty app-map table is always emitted to clear legacy populated maps;
|
||||
# the watcher ignores it if it does not read research_app_category_map.
|
||||
assert defaults["research_app_category_map"] == {}
|
||||
|
||||
|
||||
def test_fails_closed_when_flag_is_missing():
|
||||
with pytest.raises(ValueError, match="research_enabled = false"):
|
||||
patcher.patch_config(
|
||||
f'default_config = """\n[aw-watcher-window]\n"""{RUNTIME_LOOKUPS}'
|
||||
)
|
||||
|
||||
|
||||
def test_fails_closed_on_ambiguous_flag():
|
||||
"""Two line-anchored flags mean an unknown layout -- refuse rather than guess."""
|
||||
ambiguous = POST_137.replace(
|
||||
"research_enabled = false\n",
|
||||
"research_enabled = false\nresearch_enabled = false\n",
|
||||
1,
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="found 2"):
|
||||
patcher.patch_config(ambiguous)
|
||||
|
||||
|
||||
def test_pre_137_layout_does_not_require_app_section():
|
||||
missing_app_table = PRE_137.replace(
|
||||
"\n[aw-watcher-window.research_app_category_map]\n", "\n"
|
||||
)
|
||||
|
||||
result = patcher.patch_config(missing_app_table)
|
||||
|
||||
section = tomllib.loads(_string_constant(result, "default_config"))[
|
||||
"aw-watcher-window"
|
||||
]
|
||||
assert section["research_enabled"] is True
|
||||
assert "research_app_category_map" not in section
|
||||
Reference in New Issue
Block a user