Dotfiles_ArchAP/tests/test_night_light_manager.py

110 lines
5.3 KiB
Python

"""Contrats Night Light sur préférences et service simulés, sans localisation réelle."""
import importlib.util
import json
from pathlib import Path
import tempfile
import unittest
from unittest import mock
SOURCE = Path(__file__).resolve().parents[1] / 'bin/.local/bin/night-light-manager.py'
SPEC = importlib.util.spec_from_file_location('night_light_manager', SOURCE)
night = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(night)
class NightLightTest(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.root = Path(self.temp.name)
self.patchers = [mock.patch.object(night, 'CONFIG', self.root / 'preferences.json'),
mock.patch.object(night, 'STATE', self.root / 'effective.json'),
mock.patch.object(night, 'LOCK', self.root / 'wallpaper.lock')]
for patcher in self.patchers:
patcher.start()
self.pref = {'version': 2, 'themeMode': 'manual', 'manualFlavor': 'mocha',
'wallpaperDirectory': '/fixture', 'nightLightMode': 'auto',
'nightTemperature': 3000, 'dayTemperature': 6500,
'scheduleType': 'solar', 'latitude': '12.5', 'longitude': '-8.25'}
night.atomic_json(night.CONFIG, self.pref)
night.atomic_json(night.STATE, {'effectiveFlavor': 'mocha', 'revision': 5})
def tearDown(self):
for patcher in reversed(self.patchers):
patcher.stop()
self.temp.cleanup()
def test_migration_preserves_other_preferences(self):
result = night.normalized(self.pref)
self.assertEqual(result['version'], 3)
self.assertEqual(result['themeMode'], 'manual')
self.assertEqual(result['wallpaperDirectory'], '/fixture')
def test_auto_argv_uses_solar_and_baseline(self):
self.assertEqual(night.argv(self.pref), ['/usr/bin/wlsunset', '-l', '12.5', '-L', '-8.25',
'-t', '3000', '-T', '6500'])
def test_on_argv_uses_manual_schedule_for_force_low(self):
pref = {**self.pref, 'nightLightMode': 'on'}
self.assertEqual(night.argv(pref), ['/usr/bin/wlsunset', '-S', '06:00', '-s', '18:00',
'-t', '3000', '-T', '6500'])
def test_off_has_no_process_argv(self):
self.assertIsNone(night.argv({**self.pref, 'nightLightMode': 'off'}))
def test_invalid_mode_and_temperature(self):
for mode in ('invalid', '', None):
with self.subTest(mode=mode), self.assertRaises(ValueError):
night.normalized({**self.pref, 'nightLightMode': mode})
for value in ('3500', '', None, float('nan'), 2400, 5100, 3550, True):
with self.subTest(value=value), self.assertRaises(ValueError):
night.temperature(value)
def test_fresh_install_has_no_invented_coordinates(self):
self.assertEqual(night.normalized({})['nightLightMode'], 'off')
with self.assertRaisesRegex(ValueError, 'Planification à configurer'):
night.normalized({'nightLightMode': 'auto'})
def test_atomic_write_keeps_previous_on_replace_failure(self):
before = night.CONFIG.read_bytes()
with mock.patch.object(night.os, 'replace', side_effect=OSError('échec')):
with self.assertRaises(OSError):
night.atomic_json(night.CONFIG, {'bad': True})
self.assertEqual(night.CONFIG.read_bytes(), before)
def test_change_rolls_back_preferences_and_state(self):
before_pref = night.CONFIG.read_bytes()
before_state = night.STATE.read_bytes()
with mock.patch.object(night, 'apply_service', side_effect=[RuntimeError('service'), None]) as apply:
with self.assertRaisesRegex(RuntimeError, 'service'):
night.change(mode='off')
self.assertEqual(apply.call_count, 2)
self.assertEqual(night.CONFIG.read_bytes(), before_pref)
self.assertEqual(night.STATE.read_bytes(), before_state)
def test_change_keeps_appearance_fields(self):
with mock.patch.object(night, 'apply_service'), mock.patch.object(night, 'publish'):
result = night.change(mode='off')
self.assertEqual(result['nightLightMode'], 'off')
self.assertEqual(json.loads(night.STATE.read_text())['effectiveFlavor'], 'mocha')
self.assertEqual(json.loads(night.CONFIG.read_text())['manualFlavor'], 'mocha')
def test_reconcile_repairs_missing_service(self):
with mock.patch.object(night, 'verify', side_effect=RuntimeError('absent')), \
mock.patch.object(night, 'unit_state', return_value=(False, 0)), \
mock.patch.object(night, 'wlsunset_pids', return_value=[]), \
mock.patch.object(night, 'apply_service') as apply:
result = night.reconcile()
apply.assert_called_once()
self.assertEqual(result['effectiveNightLightMode'], 'auto')
def test_future_suspension_preserves_preference(self):
pref = night.normalized(self.pref)
state = {'nightLightSuspended': True}
result = night.snapshot(pref, state)
self.assertEqual(result['nightLightMode'], 'auto')
self.assertEqual(result['effectiveNightLightMode'], 'off')
self.assertEqual(night.effective('on', True), 'off')
if __name__ == '__main__':
unittest.main()