"""Settings: deep merge, secret masking, and the environment seed.""" from __future__ import annotations from app.services import pomodoro, settings_service from app.settings_schema import SECRET_MASK def test_a_preset_change_resizes_the_running_interval(session, settings): row = pomodoro.start(session, settings, kind="work") assert row.planned_seconds == settings.pomodoro.work_minutes * 60 updated = settings_service.apply_patch( session, {"pomodoro": {"work_minutes": 85, "short_break_minutes": 10}} ) current = pomodoro.current_session(session) assert current.id == row.id assert current.planned_seconds == 85 * 60 # Elapsed time is wall-clock, not stored — resizing never has to touch it. assert pomodoro.elapsed_seconds(current) >= 0 assert updated.pomodoro.work_minutes == 85 def test_a_patch_unrelated_to_durations_leaves_the_running_interval_alone(session, settings): row = pomodoro.start(session, settings, kind="work") settings_service.apply_patch(session, {"pomodoro": {"daily_goal_pomodoros": 12}}) current = pomodoro.current_session(session) assert current.id == row.id assert current.planned_seconds == row.planned_seconds def test_patch_merges_without_clobbering_siblings(session): settings_service.apply_patch(session, {"pomodoro": {"work_minutes": 50}}) updated = settings_service.apply_patch( session, {"pomodoro": {"daily_goal_pomodoros": 12}} ) assert updated.pomodoro.work_minutes == 50 assert updated.pomodoro.daily_goal_pomodoros == 12 assert updated.pomodoro.short_break_minutes == 5 # untouched default def test_secrets_are_masked_on_the_way_out(session): settings_service.apply_patch( session, {"sources": {"todoist": {"enabled": True, "api_token": "super-secret"}}}, ) masked = settings_service.mask_secrets( settings_service.load_settings(session).model_dump(mode="json") ) assert masked["sources"]["todoist"]["api_token"] == SECRET_MASK def test_sending_the_mask_back_keeps_the_stored_secret(session): settings_service.apply_patch( session, {"sources": {"todoist": {"api_token": "super-secret"}}} ) settings_service.apply_patch( session, {"sources": {"todoist": {"api_token": SECRET_MASK, "enabled": True}}}, ) settings = settings_service.load_settings(session) assert settings.sources.todoist.api_token == "super-secret" assert settings.sources.todoist.enabled is True def test_a_real_new_secret_replaces_the_old_one(session): settings_service.apply_patch(session, {"sources": {"todoist": {"api_token": "old"}}}) settings_service.apply_patch(session, {"sources": {"todoist": {"api_token": "new"}}}) assert settings_service.load_settings(session).sources.todoist.api_token == "new" def test_environment_seeds_the_sources(monkeypatch): monkeypatch.setenv("JIRA_BASE_URL", "https://example.atlassian.net/") monkeypatch.setenv("JIRA_EMAIL", "me@example.com") monkeypatch.setenv("JIRA_API_TOKEN", "token") monkeypatch.setenv("TODOIST_API_TOKEN", "todoist-token") monkeypatch.setenv("TODOIST_PROJECTS", "Inbox, Учёба") from app.config import env_settings env_settings.cache_clear() try: settings = settings_service.settings_from_env() finally: env_settings.cache_clear() assert settings.sources.jira.enabled assert settings.sources.jira.base_url == "https://example.atlassian.net" assert settings.sources.todoist.projects == ["Inbox", "Учёба"] def test_listeners_fire_on_save(session): seen = [] settings_service.on_change(lambda new: seen.append(new.pomodoro.work_minutes)) settings_service.apply_patch(session, {"pomodoro": {"work_minutes": 33}}) assert 33 in seen