Viney Claude Fable 5 commited on
Commit
beec002
Β·
1 Parent(s): 861beb0

test: add migration + translation cache tests (briefs_db) and segment-map unit tests (translate)

Browse files

23 tests total: language migration, save/get/invalidate translations,
_collect_segments coverage (prose/list/nested/exclusions/empty), _set_at_path
edge cases, and language_directive drift guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files changed (2) hide show
  1. tests/test_briefs_db.py +59 -5
  2. tests/test_translate.py +113 -0
tests/test_briefs_db.py CHANGED
@@ -13,7 +13,9 @@ def test_save_and_get_round_trip():
13
  brief = {"ticker": "AAPL", "company_name": "Apple Inc.", "what_changed": ["Revenue up 5%"]}
14
  save_brief("AAPL", brief)
15
  result = get_brief("AAPL")
16
- assert result == brief
 
 
17
 
18
 
19
  def test_get_brief_returns_none_for_unknown_ticker():
@@ -25,14 +27,15 @@ def test_save_brief_overwrites_on_same_ticker():
25
  from storage.briefs_db import save_brief, get_brief
26
  save_brief("AAPL", {"v": 1})
27
  save_brief("AAPL", {"v": 2})
28
- assert get_brief("AAPL") == {"v": 2}
 
29
 
30
 
31
  def test_save_brief_normalizes_ticker_to_uppercase():
32
  from storage.briefs_db import save_brief, get_brief
33
  save_brief("aapl", {"company_name": "Apple"})
34
- assert get_brief("AAPL") == {"company_name": "Apple"}
35
- assert get_brief("aapl") == {"company_name": "Apple"}
36
 
37
 
38
  def test_list_tickers_returns_all_saved():
@@ -53,4 +56,55 @@ def test_init_db_is_idempotent():
53
  init_db()
54
  init_db()
55
  save_brief("AAPL", {"x": 1})
56
- assert get_brief("AAPL") == {"x": 1}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  brief = {"ticker": "AAPL", "company_name": "Apple Inc.", "what_changed": ["Revenue up 5%"]}
14
  save_brief("AAPL", brief)
15
  result = get_brief("AAPL")
16
+ # get_brief injects a default "language" key; strip it for the equality check
17
+ result_without_lang = {k: v for k, v in result.items() if k != "language"}
18
+ assert result_without_lang == brief
19
 
20
 
21
  def test_get_brief_returns_none_for_unknown_ticker():
 
27
  from storage.briefs_db import save_brief, get_brief
28
  save_brief("AAPL", {"v": 1})
29
  save_brief("AAPL", {"v": 2})
30
+ result = get_brief("AAPL")
31
+ assert result["v"] == 2
32
 
33
 
34
  def test_save_brief_normalizes_ticker_to_uppercase():
35
  from storage.briefs_db import save_brief, get_brief
36
  save_brief("aapl", {"company_name": "Apple"})
37
+ assert get_brief("AAPL")["company_name"] == "Apple"
38
+ assert get_brief("aapl")["company_name"] == "Apple"
39
 
40
 
41
  def test_list_tickers_returns_all_saved():
 
56
  init_db()
57
  init_db()
58
  save_brief("AAPL", {"x": 1})
59
+ assert get_brief("AAPL")["x"] == 1
60
+
61
+
62
+ def test_migration_adds_language_column():
63
+ """init_db() migrates an old briefs table that lacks the language column."""
64
+ import sqlite3
65
+ import storage.briefs_db as m
66
+ # Create old-schema row manually (no language column)
67
+ with sqlite3.connect(m.DB_PATH) as conn:
68
+ conn.execute("DROP TABLE IF EXISTS briefs")
69
+ conn.execute("""
70
+ CREATE TABLE briefs (
71
+ ticker TEXT PRIMARY KEY,
72
+ filing_date TEXT,
73
+ brief_json TEXT NOT NULL,
74
+ saved_at TEXT NOT NULL
75
+ )
76
+ """)
77
+ conn.execute("INSERT INTO briefs VALUES ('AAPL', '2025-01-01', '{\"ticker\": \"AAPL\"}', '2025-01-01T00:00:00+00:00')")
78
+ m.init_db()
79
+ with sqlite3.connect(m.DB_PATH) as conn:
80
+ cols = {r[1] for r in conn.execute("PRAGMA table_info(briefs)")}
81
+ assert "language" in cols
82
+ row = conn.execute("SELECT language FROM briefs WHERE ticker = 'AAPL'").fetchone()
83
+ assert row[0] == "English"
84
+
85
+
86
+ def test_save_brief_persists_language():
87
+ from storage.briefs_db import save_brief, get_brief
88
+ save_brief("AAPL", {"ticker": "AAPL", "language": "French", "what_matters_most": "Bonjour"})
89
+ result = get_brief("AAPL")
90
+ assert result["language"] == "French"
91
+
92
+
93
+ def test_save_and_get_translation_round_trip():
94
+ from storage.briefs_db import save_brief, save_translation, get_translation
95
+ save_brief("AAPL", {"ticker": "AAPL", "language": "English"})
96
+ translated = {"ticker": "AAPL", "language": "French", "what_matters_most": "Bonjour"}
97
+ save_translation("AAPL", "French", translated)
98
+ result = get_translation("AAPL", "French")
99
+ assert result == translated
100
+ assert get_translation("AAPL", "German") is None
101
+
102
+
103
+ def test_save_brief_invalidates_translations():
104
+ from storage.briefs_db import save_brief, save_translation, get_translation
105
+ save_brief("AAPL", {"ticker": "AAPL", "language": "English"})
106
+ save_translation("AAPL", "French", {"ticker": "AAPL", "language": "French"})
107
+ assert get_translation("AAPL", "French") is not None
108
+ # Re-save brief β†’ should delete cached translations
109
+ save_brief("AAPL", {"ticker": "AAPL", "language": "English", "what_matters_most": "Updated"})
110
+ assert get_translation("AAPL", "French") is None
tests/test_translate.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # tests/test_translate.py
2
+ """Tests for agent/translate.py β€” pure functions only, no LLM calls."""
3
+ import copy
4
+ import pytest
5
+ from agent.translate import _collect_segments, _set_at_path
6
+ from agent.prompts import PROSE_FIELDS, PROSE_LIST_FIELDS
7
+
8
+
9
+ # ── _collect_segments ──────────────────────────────────────────────────────────
10
+
11
+ def test_collect_segments_prose_field():
12
+ brief = {"what_matters_most": "Revenue grew strongly."}
13
+ segs = _collect_segments(brief)
14
+ assert len(segs) == 1
15
+ path, text = segs[0]
16
+ assert path == ("what_matters_most",)
17
+ assert text == "Revenue grew strongly."
18
+
19
+
20
+ def test_collect_segments_excludes_evidence_snippet():
21
+ brief = {"evidence_snippet": "verbatim quote here", "headline": "Q3 beat"}
22
+ segs = _collect_segments(brief)
23
+ paths = [p for p, _ in segs]
24
+ assert ("evidence_snippet",) not in paths
25
+ assert ("headline",) in paths
26
+
27
+
28
+ def test_collect_segments_skips_quarter_deltas_subtree():
29
+ brief = {
30
+ "what_matters_most": "Growth accelerated.",
31
+ "quarter_deltas": {"headline": "Should NOT be collected", "text": "Also skip"},
32
+ }
33
+ segs = _collect_segments(brief)
34
+ texts = [t for _, t in segs]
35
+ assert "Growth accelerated." in texts
36
+ assert "Should NOT be collected" not in texts
37
+ assert "Also skip" not in texts
38
+
39
+
40
+ def test_collect_segments_prose_list_field():
41
+ brief = {"what_to_watch": ["Watch margins closely.", "Monitor capex spend."]}
42
+ segs = _collect_segments(brief)
43
+ assert len(segs) == 2
44
+ paths = [p for p, _ in segs]
45
+ assert ("what_to_watch", 0) in paths
46
+ assert ("what_to_watch", 1) in paths
47
+
48
+
49
+ def test_collect_segments_nested_dict():
50
+ brief = {
51
+ "bull_points": [
52
+ {"text": "Revenue beat expectations.", "source": "10-K", "reliability": "HIGH"}
53
+ ]
54
+ }
55
+ segs = _collect_segments(brief)
56
+ paths = [p for p, _ in segs]
57
+ assert ("bull_points", 0, "text") in paths
58
+ # source and reliability are enum fields, should not be collected
59
+ assert ("bull_points", 0, "source") not in paths
60
+ assert ("bull_points", 0, "reliability") not in paths
61
+
62
+
63
+ def test_collect_segments_skips_empty_strings():
64
+ brief = {"what_matters_most": "", "headline": "Has content"}
65
+ segs = _collect_segments(brief)
66
+ paths = [p for p, _ in segs]
67
+ assert ("what_matters_most",) not in paths
68
+ assert ("headline",) in paths
69
+
70
+
71
+ def test_collect_segments_nested_key_quote():
72
+ brief = {"key_quote": {"text": "This is the key quote.", "source": "transcript"}}
73
+ segs = _collect_segments(brief)
74
+ paths = [p for p, _ in segs]
75
+ assert ("key_quote", "text") in paths
76
+ assert ("key_quote", "source") not in paths
77
+
78
+
79
+ # ── _set_at_path ──────────────────────────────────────────────────────────────
80
+
81
+ def test_set_at_path_top_level():
82
+ obj = {"what_matters_most": "English text"}
83
+ _set_at_path(obj, ("what_matters_most",), "Texte franΓ§ais")
84
+ assert obj["what_matters_most"] == "Texte franΓ§ais"
85
+
86
+
87
+ def test_set_at_path_nested():
88
+ obj = {"bull_points": [{"text": "English", "reliability": "HIGH"}]}
89
+ _set_at_path(obj, ("bull_points", 0, "text"), "FranΓ§ais")
90
+ assert obj["bull_points"][0]["text"] == "FranΓ§ais"
91
+ assert obj["bull_points"][0]["reliability"] == "HIGH" # untouched
92
+
93
+
94
+ def test_set_at_path_missing_key_is_silent():
95
+ obj = {"bull_points": []}
96
+ _set_at_path(obj, ("bull_points", 0, "text"), "FranΓ§ais")
97
+ # Should not raise; list is empty so index 0 doesn't exist
98
+
99
+
100
+ def test_set_at_path_empty_path_is_silent():
101
+ obj = {"x": 1}
102
+ _set_at_path(obj, (), "value")
103
+ assert obj == {"x": 1} # unchanged
104
+
105
+
106
+ # ── drift guard ──────────────────────────────────────────────────────────────
107
+
108
+ def test_prose_fields_in_language_directive():
109
+ """Every name in PROSE_FIELDS | PROSE_LIST_FIELDS appears in language_directive output."""
110
+ from agent.prompts import language_directive
111
+ directive = language_directive("French")
112
+ for field in PROSE_FIELDS | PROSE_LIST_FIELDS:
113
+ assert field in directive, f"Field '{field}' missing from language_directive"