"""Blocking policy: when it is on, and what is on the list.""" from __future__ import annotations from app.services import blocker_service, pomodoro, settings_service def test_disabled_blocker_blocks_nothing(session, settings): pomodoro.start(session, settings, kind="work") policy = blocker_service.policy(session, settings) assert policy["active"] is False and policy["domains"] == [] def test_during_work_mode_follows_the_timer(session, settings): settings.blocker.enabled = True settings.blocker.blocked_domains = ["youtube.com"] settings_service.save_settings(session, settings) assert blocker_service.policy(session, settings)["active"] is False pomodoro.start(session, settings, kind="work") policy = blocker_service.policy(session, settings) assert policy["active"] is True assert "www.youtube.com" in policy["domains"] pomodoro.complete(session, settings, auto_next=False) assert blocker_service.policy(session, settings)["active"] is False def test_breaks_do_not_block_unless_asked(session, settings): settings.blocker.enabled = True settings_service.save_settings(session, settings) pomodoro.start(session, settings, kind="short_break") assert blocker_service.policy(session, settings)["active"] is False settings.blocker.block_during_breaks = True settings_service.save_settings(session, settings) assert blocker_service.policy(session, settings)["active"] is True def test_always_mode_ignores_the_timer(session, settings): settings.blocker.enabled = True settings.blocker.mode = "always" settings_service.save_settings(session, settings) assert blocker_service.policy(session, settings)["active"] is True def test_allowlist_wins_over_the_blocklist(session, settings): settings.blocker.blocked_domains = ["youtube.com"] settings.blocker.allowed_domains = ["m.youtube.com"] hosts = blocker_service.expand_domains(settings) assert "youtube.com" in hosts assert "m.youtube.com" not in hosts def test_events_are_grouped_for_the_report(session, settings): for _ in range(3): blocker_service.record_event(session, settings, kind="domain", target="YouTube.com") blocker_service.record_event(session, settings, kind="app", target="steam") summary = blocker_service.events_for_day(session, settings) assert summary["total"] == 4 assert summary["by_target"][0] == {"target": "youtube.com", "count": 3} def test_masked_subdomains_are_expanded(session, settings): """A hosts file has no wildcards, so `*.x` has to become real names.""" settings.blocker.blocked_domains = ["*.google.com"] settings.blocker.subdomain_expansions = ["www", "mail"] hosts = blocker_service.expand_domains(settings) assert hosts == ["google.com", "www.google.com", "mail.google.com"] def test_a_plain_exception_covers_its_subdomains_like_a_plain_block_does(session, settings): """The two halves of one list have to read the same way. `youtube.com` on the block list catches `music.youtube.com`. For a long time `youtube.com` on the *allow* list caught only itself, because exceptions were glob-matched — so the same text meant a subtree in one box and one hostname in the other, with nothing saying so. """ settings.blocker.blocked_domains = ["*.youtube.com", "reddit.com"] settings.blocker.subdomain_expansions = ["www", "m", "music"] settings.blocker.allowed_domains = ["youtube.com"] hosts = blocker_service.expand_domains(settings) assert hosts == ["reddit.com", "www.reddit.com", "m.reddit.com"] assert blocker_service.is_allowed("music.youtube.com", ["youtube.com"]) assert not blocker_service.is_allowed("notyoutube.com", ["youtube.com"]) def test_a_masked_exception_is_still_a_glob(session, settings): """Which is how you allow something narrower than a whole subtree.""" assert blocker_service.is_allowed("docs.google.com", ["docs.*"]) assert blocker_service.is_allowed("a.docs.google.com", ["*.docs.google.com"]) assert not blocker_service.is_allowed("sheets.google.com", ["docs.*"]) def test_allow_masks_carve_holes_in_block_masks(session, settings): settings.blocker.blocked_domains = ["*.google.com", "reddit.com"] settings.blocker.subdomain_expansions = ["www", "docs"] settings.blocker.allowed_domains = ["docs.*", "m.reddit.com"] hosts = blocker_service.expand_domains(settings) assert "docs.google.com" not in hosts assert "m.reddit.com" not in hosts assert "www.google.com" in hosts and "reddit.com" in hosts def test_patterns_a_hosts_file_cannot_express_are_reported(session, settings): settings.blocker.blocked_domains = ["*gram*", "youtube.com", "*.x.com"] assert blocker_service.unexpandable_masks(settings) == ["*gram*"] hosts = blocker_service.expand_domains(settings) assert "youtube.com" in hosts and "x.com" in hosts def test_app_masks_are_passed_through_untouched(session, settings): """Process matching happens on the desktop, where globs work directly.""" settings.blocker.enabled = True settings.blocker.mode = "always" settings.blocker.blocked_apps = ["steam*", "*discord*"] settings_service.save_settings(session, settings) policy = blocker_service.policy(session, settings) assert policy["apps"] == ["steam*", "*discord*"] def test_a_wildcard_means_every_subdomain(session, settings): """`*` in the expansion list is the user asking for all of them. It used to be refused (it is not a DNS label), which left `*.telegram.org` expanded to just the bare domain with a red banner beside it. Now the labels still expand, the pattern is handed to the clients that can match masks, and only entries that are neither a label nor `*` are refused. """ settings.blocker.blocked_domains = ["*.telegram.org"] settings.blocker.subdomain_expansions = ["*", "web", "", "not a label"] expansion = blocker_service.parse_expansions(settings.blocker.subdomain_expansions) assert expansion.labels == ["web"] assert expansion.wildcard is True assert expansion.rejected == ["not a label"] hosts = blocker_service.expand_domains(settings) assert hosts == ["telegram.org", "web.telegram.org"] assert not any("*" in host for host in hosts) # The pattern comes back too: a hosts file cannot say "every subdomain", so # the desktop app's resolver stub and the phone's URL matcher take it. assert blocker_service.unexpandable_masks(settings) == ["*.telegram.org"] def test_without_a_wildcard_the_pattern_stays_in_the_expansion_list(session, settings): """`*.x.com` alone means the configured labels, not a wildcard — nothing else.""" settings.blocker.blocked_domains = ["*.google.com"] settings.blocker.subdomain_expansions = ["www", "mail"] assert blocker_service.unexpandable_masks(settings) == [] assert blocker_service.expand_domains(settings) == [ "google.com", "www.google.com", "mail.google.com", ] def test_the_policy_reports_expansions_it_had_to_refuse(session, settings): settings.blocker.enabled = True settings.blocker.mode = "always" settings.blocker.blocked_domains = ["*.telegram.org"] settings.blocker.subdomain_expansions = ["*", ".."] result = blocker_service.policy(session, settings) assert result["invalid_expansions"] == [".."] assert result["domains"] == ["telegram.org"] assert result["unexpandable"] == ["*.telegram.org"] def test_the_policy_carries_the_allow_masks_for_the_stub(session, settings): """The desktop app answers DNS for a whole zone, so it needs the exceptions. Without them the resolver stub would sinkhole `music.youtube.com` along with everything else under a wildcarded domain — an exception that works over the hosts file but not over DNS is an exception nobody will trust. """ settings.blocker.enabled = True settings.blocker.mode = "always" settings.blocker.blocked_domains = ["*.youtube.com"] settings.blocker.allowed_domains = [" music.youtube.com ", "*.docs.google.com"] result = blocker_service.policy(session, settings) assert result["allowed"] == ["music.youtube.com", "*.docs.google.com"] assert "music.youtube.com" not in result["domains"] assert "m.youtube.com" in result["all_domains"]