import unittest from unittest.mock import Mock, patch from pathlib import Path import app def paper( title: str, *, year: str = "2024", abstract: str = "", citations: str = "0", ) -> app.PaperResult: return app.PaperResult( title=title, year=year, source="OpenAlex", authors="A. Author", citations=citations, url="https://example.com", abstract=abstract, ) class AppCoreTests(unittest.TestCase): def test_extract_search_query_removes_question_filler(self): query = app._extract_search_query( "What are the main approaches to early cancer detection using MRI?" ) self.assertEqual(query, "approaches early cancer detection mri") def test_rank_results_prefers_relevance_before_year(self): older_relevant = paper( "Cancer detection with MRI", year="2020", abstract="MRI cancer detection screening model", citations="50", ) newer_irrelevant = paper( "Unrelated particle physics survey", year="2025", abstract="Collider measurements", citations="500", ) ranked = app._rank_results( [newer_irrelevant, older_relevant], "cancer detection MRI", ) self.assertEqual(ranked[0], older_relevant) def test_split_search_queries_accepts_multiple_keywords(self): queries = app._split_search_queries("aerosols, cloud feedback\nsatellite rainfall; aerosols") self.assertEqual(queries, ["aerosols", "cloud feedback", "satellite rainfall"]) def test_collect_results_merges_multiple_keyword_searches(self): first = paper("Aerosol cloud interactions", abstract="aerosol cloud") second = paper("Satellite rainfall retrieval", abstract="satellite rainfall") with patch( "app._collect_single_query_results", side_effect=[([first], []), ([second], [])], ) as mocked: results, warnings = app._collect_results("aerosols, satellite rainfall") self.assertEqual(mocked.call_count, 2) self.assertEqual({result.title for result in results}, {first.title, second.title}) self.assertEqual(warnings, []) def test_search_all_sources_reports_multiple_keyword_searches(self): with patch( "app._collect_results", return_value=([paper("Aerosol cloud interactions")], []), ): status, *_ = app.search_all_sources("aerosols, cloud feedback") self.assertIn("2", status) self.assertIn("keyword searches", status) def test_render_result_insights_handles_results(self): panel = app._render_result_insights([paper("Aerosol cloud interactions")]) self.assertIn("Papers", panel) self.assertIn("Top Source", panel) def test_context_builder_respects_budget(self): long_abstract = "cancer detection " * 1000 papers = [paper(f"Paper {index}", abstract=long_abstract) for index in range(20)] context = app._build_synthesis_context(papers) self.assertLessEqual(len(context), app.SYNTHESIS_CONTEXT_CHAR_LIMIT) self.assertLessEqual( app._rough_token_count(context), app.SYNTHESIS_CONTEXT_TOKEN_LIMIT, ) def test_clear_search_returns_all_reset_outputs(self): result = app.clear_search() self.assertEqual(len(result), 28) self.assertEqual(result[0], "Enter a research topic to begin.") self.assertIsNone(result[9]) self.assertEqual(result[10], "") self.assertEqual(result[11], "") self.assertEqual(result[12], "") self.assertEqual(result[14], app.DEFAULT_ASK_ANSWER) self.assertEqual(result[16], app.DEFAULT_LOAD_STATUS) self.assertIsNone(result[18]) self.assertEqual(result[21], app.DEFAULT_PAPER_CHAT_ANSWER) self.assertEqual(result[25], app.DEFAULT_COMPARE_ANSWER) self.assertIn("Literature Constellation", result[26]) def test_pagination_updates_disable_edges(self): papers = [paper(f"Paper {index}") for index in range(app.RESULTS_PER_PAGE + 1)] first_prev, first_next = app._pagination_updates(papers, 0) second_prev, second_next = app._pagination_updates(papers, 1) self.assertFalse(first_prev["interactive"]) self.assertTrue(first_next["interactive"]) self.assertTrue(second_prev["interactive"]) self.assertFalse(second_next["interactive"]) def test_compare_selector_updates_use_display_choices(self): papers = [paper("Paper A"), paper("Paper B")] left, right = app._compare_selector_updates(papers) self.assertEqual(left["value"], "1. Paper A") self.assertEqual(right["value"], "2. Paper B") def test_reconstruct_abstract_orders_openalex_index(self): abstract = app._reconstruct_abstract({"world": [1], "hello": [0]}) self.assertEqual(abstract, "hello world") def test_normalize_doi_strips_doi_url(self): self.assertEqual( app._normalize_doi("https://doi.org/10.1234/ABC"), "10.1234/abc", ) def test_dedupe_prefers_duplicate_with_abstract(self): weak = paper("Deep Learning for Cancer Detection", abstract="") strong = paper( "Deep Learning for Cancer Detection!", abstract="Detailed abstract", citations="3", ) deduped = app._dedupe_results([weak, strong]) self.assertEqual(len(deduped), 1) self.assertEqual(deduped[0].abstract, "Detailed abstract") def test_source_specific_queries(self): self.assertIn("ti:cancer", app._arxiv_search_query("cancer detection with MRI")) self.assertIn( "cancer[Title/Abstract]", app._pubmed_search_query("cancer detection with MRI"), ) def test_rubric_proof_panel_mentions_judging_evidence(self): panel = app._render_rubric_proof() self.assertIn("Backyard AI proof points", panel) self.assertIn("Real professor workflow", panel) self.assertIn("NVIDIA Nemotron fit", panel) self.assertIn(app.MODEL_DISPLAY_NAME, panel) def test_public_model_story_is_nvidia_nemotron(self): self.assertEqual( app.MODEL_ID, "nvidia/Llama-3.1-Nemotron-Nano-8B-v1", ) self.assertEqual(app.MODEL_PROVIDER_BADGE, "Powered by NVIDIA Nemotron on Modal") self.assertIn("Nemotron", app.MODEL_DISPLAY_NAME) def test_modal_default_model_is_nemotron_nano(self): modal_source = Path(app.__file__).with_name("modal_inference.py").read_text( encoding="utf-8", ) self.assertIn( '"nvidia/Llama-3.1-Nemotron-Nano-8B-v1"', modal_source, ) self.assertIn("trust_remote_code=True", modal_source) self.assertIn("enforce_eager=True", modal_source) self.assertIn("Use only the supplied context", modal_source) def test_load_selected_paper_returns_context(self): item = paper("Useful Paper", abstract="A clear abstract about useful results.") paper_text, status, summary, tab_update = app.load_selected_paper(0, [item]) self.assertIn("Useful Paper", paper_text) self.assertIn("Loaded", status) self.assertEqual(summary, "") self.assertEqual(tab_update["selected"], "summarize") def test_summarize_now_loads_tab_without_modal_call(self): item = paper("Useful Paper", abstract="A clear abstract about useful results.") with patch("app.summarize_with_modal") as mocked: paper_text, status, summary, tab_update, *_ = app.load_selected_paper_reset_chat( 0, [item], ) mocked.assert_not_called() self.assertIn("Useful Paper", paper_text) self.assertIn("Loaded", status) self.assertIn("Click Summarize with AI", summary) self.assertEqual(tab_update["selected"], "summarize") def test_row_selection_loads_without_modal_call(self): item = paper("Useful Paper", abstract="A clear abstract about useful results.") with patch("app.summarize_with_modal") as mocked: paper_text, status, summary, tab_update, *_ = app.summarize_row_selection( "0", [item], ) mocked.assert_not_called() self.assertIn("Useful Paper", paper_text) self.assertIn("Loaded", status) self.assertIn("Click Summarize with AI", summary) self.assertEqual(tab_update["selected"], "summarize") def test_export_results_csv_creates_file(self): path = app.export_results_csv([paper("Exportable Paper")]) self.assertIsNotNone(path) with open(path, encoding="utf-8") as handle: content = handle.read() self.assertIn("Exportable Paper", content) def test_combine_paper_context_includes_results_section(self): context = app._combine_paper_context("Abstract text", "Result text") self.assertIn("Abstract text", context) self.assertIn("Results / Findings", context) self.assertIn("Result text", context) def test_export_summary_markdown_includes_results(self): path = app.export_summary_markdown("Abstract text", "Result text", "Summary text") self.assertIsNotNone(path) with open(path, encoding="utf-8") as handle: content = handle.read() self.assertIn("Results / Findings", content) self.assertIn("Result text", content) def test_modal_request_error_uses_response_detail(self): response = Mock() response.json.return_value = {"detail": "Bad input"} exc = app.requests.HTTPError(response=response) self.assertEqual( app._modal_request_error_message(exc, "Modal"), "Modal: Bad input", ) def test_get_first_author_accepts_string_or_list(self): self.assertEqual(app.get_first_author("Ada Lovelace, Alan Turing"), "Ada Lovelace") self.assertEqual(app.get_first_author(["Grace Hopper", "Katherine Johnson"]), "Grace Hopper") def test_search_result_constellation_marks_keyword_fallback(self): graph = app.build_constellation_from_papers( "connectome", [ paper("Functional connectome graph theory", abstract="modularity graph network"), paper("Resting state connectome modularity", abstract="resting fmri modularity"), ], ) self.assertTrue(graph["data_completeness"]["keyword_fallback_used"]) self.assertEqual(graph["data_completeness"]["paper_count"], 2) self.assertIn("nodes", graph) def test_constellation_community_ids_match_nodes(self): graph = app.build_constellation_from_papers( "mixed methods", [ paper("Functional graph modularity", abstract="graph modularity community"), paper("Clinical disease cohort", abstract="clinical disease disorder"), paper("Diffusion tractography", abstract="structural diffusion tractography"), ], ) community_ids = {community["id"] for community in graph["communities"]} self.assertTrue({node["community"] for node in graph["nodes"]}.issubset(community_ids)) def test_constellation_render_has_nonblank_fallback(self): graph = app.build_constellation_from_papers( "connectome", [paper("Functional connectome graph theory", abstract="modularity graph network")], ) html = app._render_constellation_html(graph) self.assertIn("Literature Constellation", html) self.assertIn("CONNECTED LITERATURE MAP", html) self.assertIn("canvas", html) self.assertIn("function showDetail", html) self.assertIn("if (node) showDetail(node)", html) self.assertNotIn("const labeled", html) self.assertNotIn("fillText(label", html) self.assertNotIn("Connectome Constellation", html) def test_compare_prompt_names_nemotron(self): results = [ paper("Paper A", abstract="A studies rainfall with satellite data."), paper("Paper B", abstract="B studies rainfall with station data."), ] with patch("app.synthesize_with_modal", return_value="comparison") as mocked: result = app.compare_papers_with_ai(0, 1, results) self.assertEqual(result, "comparison") prompt = mocked.call_args.args[0] self.assertIn(app.MODEL_DISPLAY_NAME, prompt) self.assertIn("Use only the provided metadata", prompt) def test_export_corpus_zip_includes_graph_json(self): graph = app.build_constellation_from_papers( "connectome", [paper("Functional connectome graph theory", abstract="modularity graph network")], ) path = app.export_corpus_zip(graph) self.assertIsNotNone(path) with app.zipfile.ZipFile(path) as archive: self.assertIn("graph.json", archive.namelist()) self.assertIn("data-completeness.json", archive.namelist()) if __name__ == "__main__": unittest.main()