"""Run: PYTHONPATH=app pytest -q tests/test_calendar.py""" from datetime import date, datetime, timedelta from zoneinfo import ZoneInfo import pytest from services import ha_views, alfred_intents as intents PARIS=ZoneInfo('Europe/Paris') NOW=datetime(2026,10,25,18,0,tzinfo=PARIS) @pytest.mark.parametrize('utterance,start,end',[ ('J’ai quelque chose de prévu demain ?',date(2026,10,26),date(2026,10,27)), ('Montre mon planning sur 3 jours',date(2026,10,25),date(2026,10,28)), ('Que j’ai le 8 novembre ?',date(2026,11,8),date(2026,11,9)), ('Je veux mon agenda du mois',date(2026,10,1),date(2026,11,1)), ('Qu’est-ce que j’ai ce matin ?',date(2026,10,25),date(2026,10,25)), ]) def test_voice_period(utterance,start,end): a,b,view=intents.period(utterance,NOW) assert a.date()==start if 'matin' not in utterance:assert b.date()==end else:assert (a.hour,b.hour)==(6,12) @pytest.mark.parametrize('utterance,kind',[ ('Montre mon planning sur 7 jours','calendar'), ('J’ai des choses de prévu le 8 novembre ?','calendar'), ('Ajoute un rendez-vous dentiste demain à 15h','create'), ('Supprime mon rendez-vous demain','delete'), ('Affiche les caméras extérieures','cameras'), ('Montre les températures','temperatures'), ('Reviens à l’accueil','home'), ('Génère-moi une image de poulet',None), ]) def test_intent(utterance,kind): result=intents.classify(utterance) assert (result or {}).get('kind')==kind def test_event_adapter(monkeypatch): def fake_request(method,path,**kwargs): if path=='/api/calendars':return [{'entity_id':'calendar.family','name':'Famille'}] if path=='/api/states':return [{'entity_id':'calendar.family','attributes':{'supported_features':3}}] if path.startswith('/api/calendars/'): return [{'summary':'Dentiste','uid':'abc123','start':{'dateTime':'2026-10-25T10:00:00+01:00'}, 'end':{'dateTime':'2026-10-25T11:00:00+01:00'}}] monkeypatch.setattr(ha_views,'_request',fake_request) data=ha_views.get_events(datetime(2026,10,25,tzinfo=PARIS),datetime(2026,10,26,tzinfo=PARIS)) assert data['events'][0]['title']=='Dentiste' assert data['events'][0]['id']=='abc123' assert data['events'][0]['can_delete'] is True def test_recurring_no_uid_cannot_delete(monkeypatch): def fake_request(method,path,**kwargs): if path=='/api/calendars':return [{'entity_id':'calendar.a','name':'A'}] if path=='/api/states':return [{'entity_id':'calendar.a','attributes':{'supported_features':0}}] if path.startswith('/api/calendars/'): return [{'summary':'Test','start':'2026-10-26','end':'2026-10-27'}] monkeypatch.setattr(ha_views,'_request',fake_request) data=ha_views.get_events(datetime(2026,10,25,tzinfo=PARIS),datetime(2026,10,27,tzinfo=PARIS)) assert data['events'][0]['all_day'] is True assert data['events'][0]['can_delete'] is False def test_create_event_calls_service(monkeypatch): calls=[] monkeypatch.setattr(ha_views,'list_calendars',lambda:[{'entity_id':'calendar.family','can_create':True,'can_delete':True}]) monkeypatch.setattr(ha_views,'_request',lambda method,path,**kwargs:calls.append((method,path,kwargs)) or None) start=datetime(2026,11,8,15,0,tzinfo=PARIS) assert ha_views.create_event('calendar.family','Dentiste',start,start+timedelta(hours=1))['ok'] assert calls[0][1]=='/api/services/calendar/create_event' assert calls[0][2]['payload']['entity_id']=='calendar.family' def test_delete_uses_websocket(monkeypatch): import websocket monkeypatch.setattr(ha_views,'list_calendars',lambda:[{'entity_id':'calendar.family','can_create':True,'can_delete':True}]) monkeypatch.setattr(ha_views,'_cfg',lambda:('http://ha.example:8123','test-token')) class FakeWS: messages=[] def __init__(self):self.replies=iter(['{"type":"auth_required"}','{"type":"auth_ok"}','{"id":1,"success":true}']) def recv(self):return next(self.replies) def send(self,m):self.messages.append(m) def close(self):pass ws=FakeWS() monkeypatch.setattr(websocket,'create_connection',lambda url,timeout:ws) ha_views.delete_event('calendar.family','event1') assert 'calendar/event/delete' in ws.messages[1] assert 'event1' in ws.messages[1]