File size: 6,827 Bytes
7ddf739
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
from __future__ import annotations

import asyncio

from backend.services import task_service
from backend.services import reminder_dispatcher, reminder_service


def test_health_endpoint(client):
    response = client.get("/health")

    assert response.status_code == 200
    body = response.json()
    assert body["status"] == "ok"
    assert "app" in body


def test_session_lifecycle(client):
    created = client.post("/api/history/sessions")
    assert created.status_code == 200
    session = created.json()

    listed = client.get("/api/history/sessions")
    assert listed.status_code == 200
    assert len(listed.json()) == 1

    fetched = client.get(f"/api/history/sessions/{session['id']}")
    assert fetched.status_code == 200
    assert fetched.json()["id"] == session["id"]

    deleted = client.delete(f"/api/history/sessions/{session['id']}")
    assert deleted.status_code == 200
    assert deleted.json()["deleted"] is True

    missing = client.get(f"/api/history/sessions/{session['id']}")
    assert missing.status_code == 404


def test_task_routes(client):
    created = client.post("/api/tasks", json={"text": "Ship release notes"})
    assert created.status_code == 200
    task_id = created.json()["id"]

    listed = client.get("/api/tasks")
    assert listed.status_code == 200
    assert listed.json()[0]["text"] == "Ship release notes"

    completed = client.patch(f"/api/tasks/{task_id}/complete")
    assert completed.status_code == 200
    assert completed.json()["completed"] is True

    deleted = client.delete(f"/api/tasks/{task_id}")
    assert deleted.status_code == 200
    assert deleted.json()["id"] == task_id


def test_reminder_routes_and_validation(client):
    invalid = client.post("/api/reminders", json={"title": "Review sprint board", "date": "2026-06-01"})
    assert invalid.status_code == 422

    created = client.post(
        "/api/reminders",
        json={"title": "Review sprint board", "note": "Bring blockers", "date": "2020-01-01", "time": "10:00"},
    )
    assert created.status_code == 200
    reminder_id = created.json()["id"]
    assert created.json()["title"] == "Review sprint board"
    assert created.json()["status"] == "overdue"

    listed = client.get("/api/reminders")
    assert listed.status_code == 200
    assert len(listed.json()) == 1
    assert listed.json()[0]["note"] == "Bring blockers"

    due = client.get("/api/reminders/due")
    assert due.status_code == 200
    assert due.json()[0]["id"] == reminder_id

    updated = client.put(
        f"/api/reminders/{reminder_id}",
        json={"title": "Review sprint board updated", "note": "Updated note", "date": "2030-01-01", "time": "11:30"},
    )
    assert updated.status_code == 200
    assert updated.json()["title"] == "Review sprint board updated"
    assert updated.json()["status"] == "scheduled"

    completed = client.patch(f"/api/reminders/{reminder_id}/complete")
    assert completed.status_code == 200
    assert completed.json()["completed"] is True
    assert completed.json()["status"] == "done"

    postponed = client.patch(f"/api/reminders/{reminder_id}/postpone")
    assert postponed.status_code == 200
    assert postponed.json()["completed"] is False

    deleted = client.delete(f"/api/reminders/{reminder_id}")
    assert deleted.status_code == 200


def test_note_routes(client):
    created = client.post("/api/notes", json={"text": "Remember the retro action items", "source": "manual"})
    assert created.status_code == 200
    note_id = created.json()["id"]

    listed = client.get("/api/notes")
    assert listed.status_code == 200
    assert listed.json()[0]["id"] == note_id

    deleted = client.delete(f"/api/notes/{note_id}")
    assert deleted.status_code == 200


def test_chat_commands_history_search_and_save(client):
    created = client.post("/api/chat", json={"message": "add task finish QA audit"})
    assert created.status_code == 200
    payload = created.json()
    session_id = payload["session_id"]
    assert "Task added" in payload["reply"]

    follow_up = client.post("/api/chat", json={"session_id": session_id, "message": "show my tasks"})
    assert follow_up.status_code == 200
    assert "Your tasks" in follow_up.json()["reply"]

    session_detail = client.get(f"/api/history/sessions/{session_id}")
    assert session_detail.status_code == 200
    messages = session_detail.json()["messages"]
    assistant_message = next(message for message in messages if message["role"] == "assistant")

    saved = client.post(f"/api/history/sessions/{session_id}/messages/{assistant_message['id']}/save")
    assert saved.status_code == 200

    saved_messages = client.get("/api/history/saved")
    assert saved_messages.status_code == 200
    assert len(saved_messages.json()) == 1

    results = client.get("/api/history/search", params={"query": "finish QA audit"})
    assert results.status_code == 200
    assert len(results.json()) >= 1


def test_summary_counts(client):
    client.post("/api/tasks", json={"text": "Prepare 1:1 summary"})
    client.post("/api/reminders", json={"title": "Prepare 1:1 summary", "date": "2020-01-01", "time": "10:00"})
    client.post("/api/notes", json={"text": "Growth areas for next quarter", "source": "manual"})

    response = client.get("/api/history/summary")
    assert response.status_code == 200
    summary = response.json()

    assert summary["pending_tasks"] == 1
    assert summary["active_reminders"] == 1
    assert summary["notes"] == 1
    assert summary["due_reminders"] == 1


def test_storage_recovers_from_invalid_json(client):
    task_service.STORE.path.write_text("{invalid json", encoding="utf-8")

    response = client.get("/api/tasks")
    assert response.status_code == 200
    assert response.json() == []


def test_due_reminders_send_to_telegram_once(monkeypatch):
    sent_messages = []

    async def fake_send(reminder):
        sent_messages.append(reminder["title"])
        return True

    monkeypatch.setattr(reminder_dispatcher, "send_reminder_notification", fake_send)
    monkeypatch.setattr(reminder_dispatcher, "telegram_notifications_enabled", lambda: True)

    created = reminder_service.add_reminder(
        "Send standup update",
        reminder_service.datetime.now() - reminder_service.timedelta(minutes=1),
        note="Mention blockers",
    )

    sent_count = asyncio.run(reminder_dispatcher.process_due_reminders())
    assert sent_count == 1
    assert sent_messages == ["Send standup update"]

    updated = reminder_service.get_reminder(created["id"], include_deleted=True)
    assert updated is not None
    assert updated["notified_at"] is not None

    sent_count_again = asyncio.run(reminder_dispatcher.process_due_reminders())
    assert sent_count_again == 0
    assert sent_messages == ["Send standup update"]