"""One query across every corpus, on disposable SQLite; no network or AI. The point of these tests is the boundary, not the ranking: a search page that had its own idea of who may see what is how private content leaks. """ import unittest from datetime import datetime import test_quiz_builder as fixtures from app.models.article import Article, ArticleSectionIndex from app.models.flashcard import Flashcard, FlashcardDeck from app.models.question import Question from app.routers import search class GlobalSearchTests(unittest.TestCase): def setUp(self): self.bank = fixtures.BuilderTests() self.bank.setUp() self.client = self.bank.client self.client.app.include_router(search.router, prefix='/search') self.db = self.bank.db self.db.add_all([ Article(id=1, slug='febrile-seizures', title='Febrile seizures', summary='A seizure with fever in a young child', content='Intro', sections=[{"id": "a" * 32, "slug": "workup", "title": "Initial workup", "content": "Lumbar puncture"}], status='published', user_id=3), Article(id=2, slug='draft-topic', title='Febrile draft', summary='Unpublished febrile notes', sections=[], status='draft', user_id=3), ]) self.db.add(ArticleSectionIndex(id=1, article_id=1, section_id='a' * 32, title='Initial workup', content='Lumbar puncture in a febrile infant')) self.db.add_all([ FlashcardDeck(id=1, title='Mine', user_id=1), FlashcardDeck(id=2, title='Theirs', user_id=2), ]) self.db.flush() self.db.add_all([ Flashcard(id=1, deck_id=1, front='Febrile seizure duration', back='Under 15 minutes'), Flashcard(id=2, deck_id=2, front='Febrile seizure age', back='6 months to 5 years'), ]) self.db.query(Question).filter(Question.id == 1).update( {"question_text": "A child with a febrile seizure lasting two minutes"}) self.db.query(Question).filter(Question.id == 3).update( {"question_text": "A removed febrile seizure question", "deleted_at": datetime(2026, 1, 1)}) self.db.commit() def tearDown(self): self.bank.tearDown() def find(self, q='febrile', **params): response = self.client.get('/search', params={'q': q, **params}) self.assertEqual(response.status_code, 200, response.text) return response.json() def test_one_query_answers_from_every_corpus(self): self.bank.user = self.bank.owner data = self.find() self.assertEqual(data['query'], 'febrile') self.assertIn(1, [row['id'] for row in data['results']['article']]) self.assertIn(1, [row['id'] for row in data['results']['question']]) self.assertIn(1, [row['id'] for row in data['results']['flashcard']]) self.assertEqual(data['total'], sum(len(v) for v in data['results'].values())) def test_a_section_hit_is_reported_under_its_article(self): self.bank.user = self.bank.owner article = next(row for row in self.find('lumbar')['results']['article'] if row['id'] == 1) # Ten sections of one article are one result with ten places to start, # not ten results burying everything else. self.assertEqual([s['title'] for s in article['sections']], ['Initial workup']) self.assertEqual(article['sections'][0]['section_id'], 'a' * 32) def test_drafts_stay_invisible_to_everyone_but_their_educator(self): self.bank.user = self.bank.owner self.assertNotIn(2, [row['id'] for row in self.find()['results']['article']]) self.bank.user = self.bank.mod self.assertIn(2, [row['id'] for row in self.find()['results']['article']]) def test_removed_questions_and_other_peoples_decks_are_not_searchable(self): self.bank.user = self.bank.peer data = self.find() # Question 3 has been removed from the bank. self.assertNotIn(3, [row['id'] for row in data['results']['question']]) # Deck 1 belongs to the owner; the peer's search must not reach into it. self.assertNotIn(1, [row['id'] for row in data['results']['flashcard']]) def test_a_query_too_short_to_mean_anything_asks_nothing(self): self.bank.user = self.bank.owner data = self.find('f') self.assertEqual(data['total'], 0) self.assertEqual(data['results']['article'], []) def test_kinds_narrows_the_answer(self): self.bank.user = self.bank.owner data = self.find(kinds='article') self.assertEqual(set(data['results']), {'article'}) def test_a_snippet_shows_where_the_match_is_without_markup(self): self.db.query(Article).filter(Article.id == 1).update( {"summary": None, "content": "## Heading\n\nSee ![scan](/uploads/x.png) the **febrile** child."}) self.db.commit() self.bank.user = self.bank.owner snippet = next(row for row in self.find()['results']['article'] if row['id'] == 1)['snippet'] self.assertIn('febrile', snippet) self.assertNotIn('##', snippet) self.assertNotIn('/uploads/', snippet) def test_suggestions_prefer_a_prefix_and_respect_drafts(self): self.bank.user = self.bank.owner titles = [s['title'] for s in self.client.get( '/search/suggest', params={'q': 'febrile'}).json()['suggestions']] self.assertEqual(titles, ['Febrile seizures']) self.bank.user = self.bank.mod titles = [s['title'] for s in self.client.get( '/search/suggest', params={'q': 'febrile'}).json()['suggestions']] self.assertIn('Febrile draft', titles) self.assertEqual(self.client.get('/search/suggest', params={'q': 'f'}).json()['suggestions'], [])