11"""Tests for search service."""
22
3- from datetime import datetime
3+ from datetime import datetime , timezone
44
55import pytest
66from sqlalchemy import text
@@ -174,12 +174,12 @@ async def test_after_date(search_service, test_graph):
174174 )
175175 for r in results :
176176 # Handle both string (SQLite) and datetime (Postgres) formats
177- created_at = (
178- r .created_at
179- if isinstance (r .created_at , datetime )
180- else datetime .fromisoformat (r .created_at )
177+ updated_at = (
178+ r .updated_at
179+ if isinstance (r .updated_at , datetime )
180+ else datetime .fromisoformat (r .updated_at )
181181 )
182- assert created_at > past_date
182+ assert updated_at > past_date
183183
184184 # Should not find with future date
185185 future_date = datetime (2030 , 1 , 1 ).astimezone ()
@@ -192,6 +192,71 @@ async def test_after_date(search_service, test_graph):
192192 assert len (results ) == 0
193193
194194
195+ @pytest .mark .asyncio
196+ async def test_after_date_uses_updated_at (search_service ):
197+ """Regression: after_date should filter on updated_at, not created_at.
198+
199+ An entity created before the timeframe but updated within it must appear
200+ in recent-activity results. A stale entity (updated_at also old) must not.
201+ """
202+ cutoff = datetime (2020 , 1 , 1 , tzinfo = timezone .utc )
203+ old_created = datetime (2015 , 6 , 1 , tzinfo = timezone .utc )
204+ recently_updated = datetime (2023 , 3 , 15 , tzinfo = timezone .utc )
205+ stale_updated = datetime (2018 , 6 , 1 , tzinfo = timezone .utc )
206+
207+ project_id = search_service .repository .project_id
208+
209+ # Leave metadata at its None default — SearchIndexRow.to_insert only
210+ # JSON-serializes truthy metadata, so passing {} would slip an
211+ # un-serialized dict into the SQLite bind and raise ProgrammingError.
212+ recently_updated_row = SearchIndexRow (
213+ project_id = project_id ,
214+ id = 99001 ,
215+ type = "entity" ,
216+ file_path = "test/recently_updated.md" ,
217+ title = "Recently Updated Entity" ,
218+ content_snippet = "recently updated content" ,
219+ permalink = "test/recently-updated-entity" ,
220+ created_at = old_created ,
221+ updated_at = recently_updated ,
222+ )
223+ stale_row = SearchIndexRow (
224+ project_id = project_id ,
225+ id = 99002 ,
226+ type = "entity" ,
227+ file_path = "test/stale.md" ,
228+ title = "Stale Entity" ,
229+ content_snippet = "stale content" ,
230+ permalink = "test/stale-entity" ,
231+ created_at = old_created ,
232+ updated_at = stale_updated ,
233+ )
234+
235+ await search_service .repository .index_item (recently_updated_row )
236+ await search_service .repository .index_item (stale_row )
237+
238+ results = await search_service .search (
239+ SearchQuery (after_date = cutoff .isoformat ())
240+ )
241+
242+ permalinks = {r .permalink for r in results }
243+ # recently-updated entity must appear despite old created_at
244+ assert "test/recently-updated-entity" in permalinks
245+ # stale entity must not appear (updated_at is before cutoff)
246+ assert "test/stale-entity" not in permalinks
247+
248+ # results should be ordered newest updated_at first
249+ updated_ats = []
250+ for r in results :
251+ ua = (
252+ r .updated_at
253+ if isinstance (r .updated_at , datetime )
254+ else datetime .fromisoformat (r .updated_at )
255+ )
256+ updated_ats .append (ua .replace (tzinfo = timezone .utc ) if ua .tzinfo is None else ua )
257+ assert updated_ats == sorted (updated_ats , reverse = True )
258+
259+
195260@pytest .mark .asyncio
196261async def test_search_type (search_service , test_graph ):
197262 """Test search filters."""
0 commit comments