Skip to content

Commit dd5a01b

Browse files
committed
fix; review findings
1 parent d08ca43 commit dd5a01b

3 files changed

Lines changed: 30 additions & 8 deletions

File tree

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ jobs:
114114
# Pytest fixtures (tests/conftest.py) build a temp workspaceStorage and
115115
# exercise Flask routes via app.test_client(). Only listed files — not
116116
# `pytest tests/` — to avoid re-collecting unittest.TestCase classes above.
117-
run: python -m pytest tests/test_api_endpoints.py tests/test_pdf_export.py -v --tb=short
117+
run: python -m pytest tests/test_api_endpoints.py tests/test_pdf_export.py tests/test_search_helpers.py -v --tb=short
118118

119119
# ── PyInstaller desktop build (Windows only, once per workflow) ────────
120120
# Closes #44. Builds the onedir bundle and smoke-tests --help so the

services/search.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@
2525
from datetime import datetime
2626
from pathlib import Path
2727

28+
__all__ = [
29+
"search_global_storage",
30+
"search_legacy_workspaces",
31+
"search_cli_sessions",
32+
"rank_results",
33+
]
2834
from models import Bubble, Composer, ParseWarningCollector, SchemaError
2935
from services.workspace_db import (
3036
build_composer_id_to_workspace_id,
@@ -120,7 +126,6 @@ def _find_match(
120126

121127

122128
def _build_ws_id_to_name(
123-
workspace_path: str,
124129
workspace_entries: list[dict],
125130
) -> dict[str, str]:
126131
"""Map workspace folder IDs to human-readable display names.
@@ -204,7 +209,7 @@ def search_global_storage(
204209
results: list[dict] = []
205210
try:
206211
workspace_entries = collect_workspace_entries(workspace_path)
207-
ws_id_to_name = _build_ws_id_to_name(workspace_path, workspace_entries)
212+
ws_id_to_name = _build_ws_id_to_name(workspace_entries)
208213
composer_id_to_ws = build_composer_id_to_workspace_id(
209214
workspace_path, workspace_entries
210215
)
@@ -435,7 +440,7 @@ def search_legacy_workspaces(
435440
"workspaceFolder": workspace_folder,
436441
"chatId": tab.get("tabId"),
437442
"chatTitle": ct or f"Chat {(tab.get('tabId') or '')[:8]}",
438-
"timestamp": tab.get("lastSendTime") or datetime.now().isoformat(),
443+
"timestamp": tab.get("lastSendTime") or 0,
439444
"matchingText": matching_text,
440445
"type": "chat",
441446
})
@@ -554,14 +559,17 @@ def search_cli_sessions(
554559
def rank_results(results: list[dict]) -> list[dict]:
555560
"""Sort *results* by timestamp descending.
556561
557-
Handles both integer epoch-ms timestamps and ISO 8601 strings so the
558-
three source types (composer, chat, cli_agent) sort together correctly.
562+
All three source types use epoch-millisecond integers, except
563+
``search_legacy_workspaces`` which may emit ISO 8601 strings for the
564+
``lastSendTime`` field. ISO strings are converted to epoch-ms so
565+
cross-source comparisons are made in the same unit.
559566
"""
560567
def _ts(r: dict) -> float:
561568
t = r.get("timestamp", 0)
562569
if isinstance(t, str):
563570
try:
564-
return datetime.fromisoformat(t.replace("Z", "+00:00")).timestamp()
571+
# .timestamp() → epoch-seconds; ×1000 → epoch-ms to match ints
572+
return datetime.fromisoformat(t.replace("Z", "+00:00")).timestamp() * 1000
565573
except Exception:
566574
return 0.0
567575
return float(t) if t else 0.0

tests/test_search_helpers.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
from models import ParseWarningCollector
2424
from services.search import (
25-
_build_exclusion_searchable,
2625
_extract_snippet,
2726
_find_match,
2827
rank_results,
@@ -149,6 +148,21 @@ def test_missing_timestamp_treated_as_zero(self):
149148
# Missing timestamp entry sorts last
150149
assert "timestamp" not in ranked[-1]
151150

151+
def test_mixed_epoch_ms_and_iso_string_sort_by_recency(self):
152+
# composer/CLI results use integer epoch-ms (~1.715e12);
153+
# legacy chat results may carry an ISO string from lastSendTime.
154+
# A chat from 2025-01 must rank above a composer from 2024-05 when
155+
# both are in the same result set.
156+
results = [
157+
{"timestamp": 1_715_000_000_000, "type": "composer"}, # 2024-05
158+
{"timestamp": "2025-01-01T00:00:00Z", "type": "chat"}, # 2025-01
159+
]
160+
ranked = rank_results(results)
161+
assert ranked[0]["type"] == "chat", (
162+
"2025-01 chat must outrank 2024-05 composer; "
163+
f"got order: {[r['type'] for r in ranked]}"
164+
)
165+
152166

153167
# ---------------------------------------------------------------------------
154168
# Fixtures — minimal SQLite databases for integration-style unit tests

0 commit comments

Comments
 (0)