testsAndMisc/python_pkg/focus_policy/tests/test_export.py
Krzysztof kuhy Rudnicki b60cb4972a Clear five python_pkg packages under the cap
android_ui, app_icons, token_audit, focus_policy and random_jpg are now
entirely under 250 lines. Only wsg_grabber's six test modules remain in
python_pkg.

android_ui/driver.py 411 -> 205 took two cuts and a class split: the
element and error types plus the XML parser to _elements.py, and the
text-entry methods to a TextEntryMixin, using the ABC-with-abstract-
methods shape because the tests call them as bound attributes.
app_icons/glyphs.py 291 -> 93 by moving the twelve path fragments out.

Three intermediate failures, all module-level globals that travelled
with their code (_logger, re, _SAME_FIELD_X_TOLERANCE_PX) - the same
class of miss as _make_verifier, which ruff cannot flag.

Counts unchanged: 81, 137, 14, 44, 57.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:14:36 +02:00

161 lines
6.2 KiB
Python

"""Tests for the JSON policy exporter.
The guarantee under test is the one that cannot be recovered from on the device
itself: the enforcing app must never be rendered into a policy that would hide
it. Everything else here is shape, not safety.
"""
from __future__ import annotations
import datetime as dt
from python_pkg.focus_policy.export import (
ENFORCER_PACKAGE,
policy_to_dict,
)
from python_pkg.focus_policy.model import CurfewWindow, FocusPolicy, HomeLocation
_DEFAULT_CURFEW = CurfewWindow(start=dt.time(23, 0), end=dt.time(5, 0))
def _policy(
*,
# The launcher must be allowed or the model rejects the policy outright.
allowed_packages: frozenset[str] = frozenset({"pl.mbank", "com.launcher"}),
night_allowed_packages: frozenset[str] = frozenset({"pl.mbank", "com.launcher"}),
curfew: CurfewWindow | None = _DEFAULT_CURFEW,
never_disable_prefixes: tuple[str, ...] = ("com.android.",),
allowed_prefixes: tuple[str, ...] = (),
) -> FocusPolicy:
"""Build a policy with the enforcer deliberately absent from both lists."""
return FocusPolicy(
home=HomeLocation(
latitude=52.2297,
longitude=21.0122,
radius_m=150.0,
hysteresis_m=30.0,
),
curfew=curfew,
launcher_package="com.launcher",
allowed_packages=allowed_packages,
allowed_prefixes=allowed_prefixes,
night_allowed_packages=night_allowed_packages,
never_disable_prefixes=never_disable_prefixes,
workout_unblock_domains=frozenset({"youtube.com"}),
browser_packages=frozenset({"org.mozilla.firefox"}),
)
def test_enforcer_is_injected_into_both_allowlists() -> None:
"""The app must be allowed even when the source config omits it.
Regression guard for the on-device finding of 2026-08-09: the enforcer was
in neither allowlist, so the decision layer placed it in the hide set on
every enforcing pass, leaving one in-app check as the sole thing preventing
the app from hiding itself.
"""
payload = policy_to_dict(_policy())
assert ENFORCER_PACKAGE in payload["allowed_packages"]
assert ENFORCER_PACKAGE in payload["night_allowed_packages"]
def test_enforcer_is_not_duplicated_when_already_present() -> None:
"""Injection is a set union, so an explicit entry stays single."""
payload = policy_to_dict(
_policy(
allowed_packages=frozenset({"pl.mbank", "com.launcher", ENFORCER_PACKAGE}),
night_allowed_packages=frozenset({"com.launcher", ENFORCER_PACKAGE}),
),
)
assert payload["allowed_packages"].count(ENFORCER_PACKAGE) == 1
assert payload["night_allowed_packages"].count(ENFORCER_PACKAGE) == 1
def test_blockable_system_packages_names_the_distraction_apps() -> None:
"""The sweep is default-deny for system apps; this is the opt-in.
YouTube and Chrome ship with FLAG_SYSTEM, so without this list the Device
Owner sweep skips exactly the apps it exists to block. Play is included
because leaving it reachable makes every other removal a one-tap undo.
"""
blockable = policy_to_dict(_policy())["blockable_system_packages"]
assert "com.google.android.youtube" in blockable
assert "com.android.chrome" in blockable
assert "com.android.vending" in blockable
def test_an_allowed_system_package_stays_sweepable() -> None:
"""An allowed system package must stay in the sweep, or it freezes.
Sweepable is not the same as hideable. This list decides which FLAG_SYSTEM
packages are *eligible for a decision*; ``is_allowed`` is what then
protects them. ``sweepablePackages`` in the Kotlin runner drops any system
package absent from here, so an allowed-but-unsweepable package appears in
neither ``packagesToHide`` nor ``packagesToShow`` and keeps whatever state
it was last left in.
Measured on device: Play was hidden, so subtracting the allowlist here
would have stranded it hidden permanently the moment it was allowlisted.
"""
payload = policy_to_dict(
_policy(
allowed_packages=frozenset(
{"pl.mbank", "com.launcher", "com.android.vending"},
),
),
)
assert "com.android.vending" in payload["blockable_system_packages"]
assert "com.android.vending" in payload["allowed_packages"]
def test_always_blocked_exempts_youtube_and_chrome_from_the_geofence() -> None:
"""The geofence must not become an off switch for these.
Everything else is restored on the AWAY branch, which makes leaving the
house a way to switch enforcement off -- the specific thing Device Owner
was provisioned to remove. Play is deliberately absent: it stays geofenced
so apps can still be installed away from home.
"""
always = policy_to_dict(_policy())["always_blocked_packages"]
assert "com.google.android.youtube" in always
assert "com.google.android.apps.youtube.music" in always
assert "com.android.chrome" in always
assert "com.android.vending" not in always
def test_always_on_vpn_requires_a_sweep_protected_provider() -> None:
"""Pinning a package the enforcer can hide is the worst failure mode.
DISALLOW_CONFIG_VPN pointing at a hidden app costs general connectivity,
not just the filter, so the field is emitted only when the provider is
protected.
"""
unprotected = policy_to_dict(_policy())["always_on_vpn_package"]
protected = policy_to_dict(
_policy(never_disable_prefixes=("com.celzero.bravedns",)),
)["always_on_vpn_package"]
assert unprotected == ""
assert protected == "com.celzero.bravedns"
def test_private_dns_host_is_exported_for_pinning() -> None:
"""The domain rules live on this resolver, not in the VPN app.
Measured on 2026-08-11: RethinkDNS's blocklists can be switched off from
inside that app in a few taps, and no device owner API prevents it. A
pinned Private DNS host moves the rules somewhere the phone cannot edit.
"""
payload = policy_to_dict(_policy())
# Empty until the resolver is reachable from the phone: pinning an
# unreachable host means no DNS at all, and Android refuses it anyway
# (PRIVATE_DNS_SET_ERROR_HOST_NOT_SERVING).
assert payload["private_dns_host"] == ""
assert payload["vpn_lockdown"] is True