Initial release prep
This commit is contained in:
parent
c9b9d892f4
commit
92395b2b4b
100 changed files with 405 additions and 401 deletions
|
|
@ -9,13 +9,13 @@ from sqlalchemy import select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.models import ArxivQueryCacheEntry
|
||||
from app.services.domains.arxiv.cache import (
|
||||
from app.services.arxiv.cache import (
|
||||
build_query_fingerprint,
|
||||
get_cached_feed,
|
||||
run_with_inflight_dedupe,
|
||||
set_cached_feed,
|
||||
)
|
||||
from app.services.domains.arxiv.types import ArxivEntry, ArxivFeed, ArxivOpenSearchMeta
|
||||
from app.services.arxiv.types import ArxivEntry, ArxivFeed, ArxivOpenSearchMeta
|
||||
|
||||
|
||||
def _sample_feed(arxiv_id: str = "1234.5678") -> ArxivFeed:
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import httpx
|
|||
import pytest
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.services.domains.arxiv import client as arxiv_client_module
|
||||
from app.services.domains.arxiv.client import ArxivClient
|
||||
from app.services.domains.arxiv.errors import ArxivClientValidationError, ArxivRateLimitError
|
||||
from app.services.domains.arxiv.rate_limit import ArxivCooldownStatus
|
||||
from app.services.arxiv import client as arxiv_client_module
|
||||
from app.services.arxiv.client import ArxivClient
|
||||
from app.services.arxiv.errors import ArxivClientValidationError, ArxivRateLimitError
|
||||
from app.services.arxiv.rate_limit import ArxivCooldownStatus
|
||||
|
||||
_CLIENT_FEED_XML = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||
|
|
@ -160,7 +160,7 @@ async def test_client_logs_cache_hit_and_miss(
|
|||
def _capture_log(_msg: str, *args, **kwargs) -> None:
|
||||
logged.append({"event": _msg, **(kwargs.get("extra") or {})})
|
||||
|
||||
monkeypatch.setattr("app.services.domains.arxiv.client.logger.info", _capture_log)
|
||||
monkeypatch.setattr("app.services.arxiv.client.logger.info", _capture_log)
|
||||
client = ArxivClient(request_fn=_request_fn, cache_enabled=True)
|
||||
await client.search(query="ti:test-cache")
|
||||
await client.search(query="ti:test-cache")
|
||||
|
|
@ -196,7 +196,7 @@ async def test_request_feed_skips_live_call_when_global_cooldown_is_active(
|
|||
|
||||
monkeypatch.setattr(arxiv_client_module, "get_arxiv_cooldown_status", _cooldown_status)
|
||||
monkeypatch.setattr(arxiv_client_module, "run_with_global_arxiv_limit", _unexpected_limit_call)
|
||||
monkeypatch.setattr("app.services.domains.arxiv.client.logger.warning", _capture_warning)
|
||||
monkeypatch.setattr("app.services.arxiv.client.logger.warning", _capture_warning)
|
||||
|
||||
with pytest.raises(ArxivRateLimitError):
|
||||
await arxiv_client_module._request_arxiv_feed(
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ from types import SimpleNamespace
|
|||
|
||||
import pytest
|
||||
|
||||
from app.services.domains.arxiv import application as arxiv_application
|
||||
from app.services.domains.arxiv import gateway as arxiv_gateway
|
||||
from app.services.domains.arxiv.types import ArxivEntry, ArxivFeed, ArxivOpenSearchMeta
|
||||
from app.services.arxiv import application as arxiv_application
|
||||
from app.services.arxiv import gateway as arxiv_gateway
|
||||
from app.services.arxiv.types import ArxivEntry, ArxivFeed, ArxivOpenSearchMeta
|
||||
from app.settings import settings
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ from __future__ import annotations
|
|||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from app.services.domains.arxiv.guards import arxiv_skip_reason_for_item
|
||||
from app.services.domains.publication_identifiers.types import DisplayIdentifier
|
||||
from app.services.domains.publications.types import PublicationListItem
|
||||
from app.services.arxiv.guards import arxiv_skip_reason_for_item
|
||||
from app.services.publication_identifiers.types import DisplayIdentifier
|
||||
from app.services.publications.types import PublicationListItem
|
||||
|
||||
|
||||
def _item(
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from app.services.domains.arxiv.errors import ArxivParseError
|
||||
from app.services.domains.arxiv.parser import parse_arxiv_feed
|
||||
from app.services.arxiv.errors import ArxivParseError
|
||||
from app.services.arxiv.parser import parse_arxiv_feed
|
||||
|
||||
_VALID_FEED_XML = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ from sqlalchemy import select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.models import ArxivRuntimeState
|
||||
from app.services.domains.arxiv.constants import ARXIV_RUNTIME_STATE_KEY
|
||||
from app.services.domains.arxiv.errors import ArxivRateLimitError
|
||||
from app.services.domains.arxiv.rate_limit import get_arxiv_cooldown_status, run_with_global_arxiv_limit
|
||||
from app.services.arxiv.constants import ARXIV_RUNTIME_STATE_KEY
|
||||
from app.services.arxiv.errors import ArxivRateLimitError
|
||||
from app.services.arxiv.rate_limit import get_arxiv_cooldown_status, run_with_global_arxiv_limit
|
||||
from app.settings import settings
|
||||
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ async def test_arxiv_rate_limit_logs_request_scheduled_and_completed(
|
|||
def _capture_log(_msg: str, *args, **kwargs) -> None:
|
||||
logged.append({"event": _msg, **(kwargs.get("extra") or {})})
|
||||
|
||||
monkeypatch.setattr("app.services.domains.arxiv.rate_limit.logger.info", _capture_log)
|
||||
monkeypatch.setattr("app.services.arxiv.rate_limit.logger.info", _capture_log)
|
||||
await run_with_global_arxiv_limit(fetch=_fetch, source_path="search")
|
||||
|
||||
scheduled = [entry for entry in logged if entry.get("event") == "arxiv.request_scheduled"]
|
||||
|
|
@ -128,7 +128,7 @@ async def test_arxiv_rate_limit_logs_cooldown_activation(
|
|||
def _capture_warning(_msg: str, *args, **kwargs) -> None:
|
||||
logged_warning.append({"event": _msg, **(kwargs.get("extra") or {})})
|
||||
|
||||
monkeypatch.setattr("app.services.domains.arxiv.rate_limit.logger.warning", _capture_warning)
|
||||
monkeypatch.setattr("app.services.arxiv.rate_limit.logger.warning", _capture_warning)
|
||||
with pytest.raises(ArxivRateLimitError):
|
||||
await run_with_global_arxiv_limit(fetch=_fetch, source_path="lookup_ids")
|
||||
finally:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from app.services.domains.openalex.types import OpenAlexWork
|
||||
from app.services.openalex.types import OpenAlexWork
|
||||
|
||||
|
||||
def test_parse_openalex_work_from_api_dict() -> None:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from app.services.domains.openalex.matching import find_best_match
|
||||
from app.services.domains.openalex.types import OpenAlexWork
|
||||
from app.services.openalex.matching import find_best_match
|
||||
from app.services.openalex.types import OpenAlexWork
|
||||
|
||||
|
||||
def test_find_best_match_exact_title():
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||
import pytest
|
||||
|
||||
from app.db.models import ScholarPublication
|
||||
from app.services.domains.publications import dedup as dedup_service
|
||||
from app.services.domains.publications.dedup import (
|
||||
from app.services.publications import dedup as dedup_service
|
||||
from app.services.publications.dedup import (
|
||||
NearDuplicateCluster,
|
||||
NearDuplicateMember,
|
||||
find_identifier_duplicate_pairs,
|
||||
|
|
@ -87,15 +87,15 @@ async def test_merge_duplicate_publication_migrates_links_and_identifiers() -> N
|
|||
|
||||
with (
|
||||
patch(
|
||||
"app.services.domains.publications.dedup._load_publication",
|
||||
"app.services.publications.dedup._load_publication",
|
||||
new=AsyncMock(side_effect=[winner, dup]),
|
||||
),
|
||||
patch(
|
||||
"app.services.domains.publications.dedup._migrate_scholar_links",
|
||||
"app.services.publications.dedup._migrate_scholar_links",
|
||||
new=AsyncMock(),
|
||||
) as mock_links,
|
||||
patch(
|
||||
"app.services.domains.publications.dedup._migrate_identifiers",
|
||||
"app.services.publications.dedup._migrate_identifiers",
|
||||
new=AsyncMock(),
|
||||
) as mock_identifiers,
|
||||
):
|
||||
|
|
@ -112,7 +112,7 @@ async def test_merge_duplicate_publication_rejects_missing_publications() -> Non
|
|||
|
||||
with (
|
||||
patch(
|
||||
"app.services.domains.publications.dedup._load_publication",
|
||||
"app.services.publications.dedup._load_publication",
|
||||
new=AsyncMock(side_effect=[None, None]),
|
||||
),
|
||||
pytest.raises(ValueError),
|
||||
|
|
@ -160,7 +160,7 @@ async def test_migrate_scholar_links_drops_conflicts() -> None:
|
|||
@pytest.mark.asyncio
|
||||
async def test_sweep_returns_zero_when_no_pairs() -> None:
|
||||
with patch(
|
||||
"app.services.domains.publications.dedup.find_identifier_duplicate_pairs",
|
||||
"app.services.publications.dedup.find_identifier_duplicate_pairs",
|
||||
new=AsyncMock(return_value=[]),
|
||||
):
|
||||
session = AsyncMock()
|
||||
|
|
@ -174,11 +174,11 @@ async def test_sweep_returns_zero_when_no_pairs() -> None:
|
|||
async def test_sweep_returns_merge_count() -> None:
|
||||
with (
|
||||
patch(
|
||||
"app.services.domains.publications.dedup.find_identifier_duplicate_pairs",
|
||||
"app.services.publications.dedup.find_identifier_duplicate_pairs",
|
||||
new=AsyncMock(return_value=[(1, 2), (3, 4)]),
|
||||
),
|
||||
patch(
|
||||
"app.services.domains.publications.dedup.merge_duplicate_publication",
|
||||
"app.services.publications.dedup.merge_duplicate_publication",
|
||||
new=AsyncMock(),
|
||||
) as mock_merge,
|
||||
):
|
||||
|
|
@ -194,11 +194,11 @@ async def test_sweep_returns_merge_count() -> None:
|
|||
async def test_sweep_merges_each_dup_only_once() -> None:
|
||||
with (
|
||||
patch(
|
||||
"app.services.domains.publications.dedup.find_identifier_duplicate_pairs",
|
||||
"app.services.publications.dedup.find_identifier_duplicate_pairs",
|
||||
new=AsyncMock(return_value=[(1, 2), (1, 2)]),
|
||||
),
|
||||
patch(
|
||||
"app.services.domains.publications.dedup.merge_duplicate_publication",
|
||||
"app.services.publications.dedup.merge_duplicate_publication",
|
||||
new=AsyncMock(),
|
||||
) as mock_merge,
|
||||
):
|
||||
|
|
@ -227,7 +227,7 @@ async def test_find_near_duplicate_clusters_groups_similar_titles() -> None:
|
|||
assert second is not None
|
||||
|
||||
with patch(
|
||||
"app.services.domains.publications.dedup._load_near_duplicate_candidates",
|
||||
"app.services.publications.dedup._load_near_duplicate_candidates",
|
||||
new=AsyncMock(return_value=[first, second]),
|
||||
):
|
||||
clusters = await find_near_duplicate_clusters(AsyncMock())
|
||||
|
|
@ -255,7 +255,7 @@ async def test_find_near_duplicate_clusters_skips_unrelated_titles() -> None:
|
|||
assert second is not None
|
||||
|
||||
with patch(
|
||||
"app.services.domains.publications.dedup._load_near_duplicate_candidates",
|
||||
"app.services.publications.dedup._load_near_duplicate_candidates",
|
||||
new=AsyncMock(return_value=[first, second]),
|
||||
):
|
||||
clusters = await find_near_duplicate_clusters(AsyncMock())
|
||||
|
|
@ -277,7 +277,7 @@ async def test_merge_near_duplicate_cluster_merges_non_winner_members() -> None:
|
|||
)
|
||||
|
||||
with patch(
|
||||
"app.services.domains.publications.dedup.merge_duplicate_publication",
|
||||
"app.services.publications.dedup.merge_duplicate_publication",
|
||||
new=AsyncMock(),
|
||||
) as mock_merge:
|
||||
merged = await merge_near_duplicate_cluster(AsyncMock(), cluster=cluster)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from types import SimpleNamespace
|
|||
|
||||
import pytest
|
||||
|
||||
from app.services.domains.crossref import application as crossref_app
|
||||
from app.services.crossref import application as crossref_app
|
||||
|
||||
|
||||
def _item(*, title: str, year: int | None, scholar_label: str = "Shinya Yamanaka"):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from app.services.domains.doi.normalize import first_doi_from_texts, normalize_doi
|
||||
from app.services.doi.normalize import first_doi_from_texts, normalize_doi
|
||||
|
||||
|
||||
def test_normalize_doi_extracts_and_lowercases() -> None:
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from app.services.domains.ingestion.fingerprints import (
|
||||
from app.services.ingestion.fingerprints import (
|
||||
_dedupe_publication_candidates,
|
||||
canonical_title_for_dedup,
|
||||
fuzzy_titles_match,
|
||||
normalize_title,
|
||||
)
|
||||
from app.services.domains.scholar.parser import PublicationCandidate
|
||||
from app.services.scholar.parser import PublicationCandidate
|
||||
|
||||
|
||||
def _candidate(
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ from types import SimpleNamespace
|
|||
|
||||
import pytest
|
||||
|
||||
from app.services.domains.arxiv.errors import ArxivRateLimitError
|
||||
from app.services.domains.ingestion.application import ScholarIngestionService
|
||||
from app.services.domains.publication_identifiers import application as identifier_service
|
||||
from app.services.arxiv.errors import ArxivRateLimitError
|
||||
from app.services.ingestion.application import ScholarIngestionService
|
||||
from app.services.publication_identifiers import application as identifier_service
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from app.services.domains.ingestion.application import ScholarIngestionService
|
||||
from app.services.domains.ingestion.types import RunProgress, ScholarProcessingOutcome
|
||||
from app.services.ingestion.application import ScholarIngestionService
|
||||
from app.services.ingestion.types import RunProgress, ScholarProcessingOutcome
|
||||
|
||||
|
||||
def _outcome(*, scholar_profile_id: int, outcome_label: str) -> ScholarProcessingOutcome:
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ from sqlalchemy import select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.models import Publication, PublicationIdentifier
|
||||
from app.services.domains.arxiv import application as arxiv_service
|
||||
from app.services.domains.publication_identifiers import application as identifier_service
|
||||
from app.services.domains.publication_identifiers.types import IdentifierKind
|
||||
from app.services.domains.publications.types import UnreadPublicationItem
|
||||
from app.services.arxiv import application as arxiv_service
|
||||
from app.services.publication_identifiers import application as identifier_service
|
||||
from app.services.publication_identifiers.types import IdentifierKind
|
||||
from app.services.publications.types import UnreadPublicationItem
|
||||
|
||||
|
||||
def test_derive_display_identifier_prefers_doi_over_arxiv() -> None:
|
||||
|
|
@ -47,7 +47,7 @@ def test_derive_display_identifier_uses_pmcid_when_present() -> None:
|
|||
|
||||
|
||||
def test_normalize_arxiv_id_handles_urls() -> None:
|
||||
from app.services.domains.publication_identifiers.normalize import normalize_arxiv_id
|
||||
from app.services.publication_identifiers.normalize import normalize_arxiv_id
|
||||
|
||||
assert normalize_arxiv_id("https://arxiv.org/abs/1504.08025") == "1504.08025"
|
||||
assert normalize_arxiv_id("http://arxiv.org/pdf/1504.08025v2.pdf") == "1504.08025v2"
|
||||
|
|
@ -59,7 +59,7 @@ def test_normalize_arxiv_id_handles_urls() -> None:
|
|||
|
||||
|
||||
def test_normalize_arxiv_id_handles_raw_text() -> None:
|
||||
from app.services.domains.publication_identifiers.normalize import normalize_arxiv_id
|
||||
from app.services.publication_identifiers.normalize import normalize_arxiv_id
|
||||
|
||||
assert normalize_arxiv_id("1504.08025v1") == "1504.08025v1"
|
||||
assert normalize_arxiv_id("arXiv:1504.08025") == "1504.08025"
|
||||
|
|
@ -69,7 +69,7 @@ def test_normalize_arxiv_id_handles_raw_text() -> None:
|
|||
|
||||
|
||||
def test_normalize_pmcid_handles_urls_and_text() -> None:
|
||||
from app.services.domains.publication_identifiers.normalize import normalize_pmcid
|
||||
from app.services.publication_identifiers.normalize import normalize_pmcid
|
||||
|
||||
assert normalize_pmcid("https://pmc.ncbi.nlm.nih.gov/articles/PMC2175868/") == "PMC2175868"
|
||||
assert normalize_pmcid("http://www.ncbi.nlm.nih.gov/pmc/articles/PMC1234567") == "PMC1234567"
|
||||
|
|
@ -79,7 +79,7 @@ def test_normalize_pmcid_handles_urls_and_text() -> None:
|
|||
|
||||
|
||||
def test_normalize_pmid_handles_urls() -> None:
|
||||
from app.services.domains.publication_identifiers.normalize import normalize_pmid
|
||||
from app.services.publication_identifiers.normalize import normalize_pmid
|
||||
|
||||
assert normalize_pmid("https://pubmed.ncbi.nlm.nih.gov/12345678/") == "12345678"
|
||||
assert normalize_pmid("http://pubmed.ncbi.nlm.nih.gov/12345678") == "12345678"
|
||||
|
|
@ -117,11 +117,11 @@ async def test_discover_and_sync_skips_arxiv_when_crossref_finds_doi(
|
|||
raise AssertionError("arXiv should be skipped after strong DOI evidence.")
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.services.domains.crossref.application.discover_doi_for_publication",
|
||||
"app.services.crossref.application.discover_doi_for_publication",
|
||||
_fake_crossref,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.services.domains.arxiv.application.discover_arxiv_id_for_publication",
|
||||
"app.services.arxiv.application.discover_arxiv_id_for_publication",
|
||||
_fail_arxiv,
|
||||
)
|
||||
|
||||
|
|
@ -158,11 +158,11 @@ async def test_discover_and_sync_skips_arxiv_for_low_quality_title(
|
|||
return "1234.5678"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.services.domains.crossref.application.discover_doi_for_publication",
|
||||
"app.services.crossref.application.discover_doi_for_publication",
|
||||
_fake_crossref,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.services.domains.arxiv.application.discover_arxiv_id_for_publication",
|
||||
"app.services.arxiv.application.discover_arxiv_id_for_publication",
|
||||
_fake_arxiv,
|
||||
)
|
||||
|
||||
|
|
@ -190,11 +190,11 @@ async def test_discover_and_sync_calls_arxiv_when_item_is_eligible(
|
|||
return "2501.00001v2"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.services.domains.crossref.application.discover_doi_for_publication",
|
||||
"app.services.crossref.application.discover_doi_for_publication",
|
||||
_fake_crossref,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"app.services.domains.arxiv.application.discover_arxiv_id_for_publication",
|
||||
"app.services.arxiv.application.discover_arxiv_id_for_publication",
|
||||
_fake_arxiv,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ from types import SimpleNamespace
|
|||
import pytest
|
||||
|
||||
from app.db.models import PublicationPdfJob
|
||||
from app.services.domains.publications import pdf_queue
|
||||
from app.services.domains.publications.pdf_resolution_pipeline import PipelineOutcome
|
||||
from app.services.domains.unpaywall.application import OaResolutionOutcome
|
||||
from app.services.publications import pdf_queue
|
||||
from app.services.publications.pdf_resolution_pipeline import PipelineOutcome
|
||||
from app.services.unpaywall.application import OaResolutionOutcome
|
||||
|
||||
|
||||
def _job(
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ from types import SimpleNamespace
|
|||
|
||||
import pytest
|
||||
|
||||
from app.services.domains.arxiv.errors import ArxivRateLimitError
|
||||
from app.services.domains.publication_identifiers.types import DisplayIdentifier
|
||||
from app.services.domains.publications import pdf_resolution_pipeline as pipeline
|
||||
from app.services.domains.unpaywall.application import OaResolutionOutcome
|
||||
from app.services.arxiv.errors import ArxivRateLimitError
|
||||
from app.services.publication_identifiers.types import DisplayIdentifier
|
||||
from app.services.publications import pdf_resolution_pipeline as pipeline
|
||||
from app.services.unpaywall.application import OaResolutionOutcome
|
||||
|
||||
|
||||
def _row(*, display_identifier: DisplayIdentifier | None = None) -> SimpleNamespace:
|
||||
|
|
@ -165,7 +165,7 @@ async def test_arxiv_outcome_skips_when_strong_doi_identifier(
|
|||
raise AssertionError("arXiv lookup should be skipped when DOI evidence is strong.")
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.services.domains.arxiv.application.discover_arxiv_id_for_publication",
|
||||
"app.services.arxiv.application.discover_arxiv_id_for_publication",
|
||||
_fail_discover,
|
||||
)
|
||||
outcome = await pipeline._arxiv_outcome(row, request_email="user@example.com")
|
||||
|
|
@ -183,7 +183,7 @@ async def test_arxiv_outcome_skips_when_title_quality_is_low(
|
|||
raise AssertionError("arXiv lookup should be skipped for low-quality titles.")
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.services.domains.arxiv.application.discover_arxiv_id_for_publication",
|
||||
"app.services.arxiv.application.discover_arxiv_id_for_publication",
|
||||
_fail_discover,
|
||||
)
|
||||
outcome = await pipeline._arxiv_outcome(row, request_email="user@example.com")
|
||||
|
|
@ -198,7 +198,7 @@ async def test_arxiv_outcome_calls_arxiv_when_eligible(
|
|||
return "1234.5678"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.services.domains.arxiv.application.discover_arxiv_id_for_publication",
|
||||
"app.services.arxiv.application.discover_arxiv_id_for_publication",
|
||||
_fake_discover,
|
||||
)
|
||||
row = _row()
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ from __future__ import annotations
|
|||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from app.services.domains.ingestion import scheduler as scheduler_module
|
||||
from app.services.domains.ingestion.application import ScholarIngestionService
|
||||
from app.services.ingestion import scheduler as scheduler_module
|
||||
from app.services.ingestion.application import ScholarIngestionService
|
||||
from app.settings import settings
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
from app.db.models import UserSetting
|
||||
from app.services.domains.ingestion import safety as run_safety
|
||||
from app.services.ingestion import safety as run_safety
|
||||
|
||||
|
||||
def test_apply_run_safety_outcome_triggers_blocked_cooldown() -> None:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from app.services.domains.runs.application import extract_run_summary
|
||||
from app.services.runs.application import extract_run_summary
|
||||
|
||||
|
||||
def test_extract_run_summary_includes_extended_metrics() -> None:
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from app.services.domains.scholar.parser import (
|
||||
from app.services.scholar.parser import (
|
||||
ParseState,
|
||||
ScholarDomInvariantError,
|
||||
parse_author_search_page,
|
||||
parse_profile_page,
|
||||
)
|
||||
from app.services.domains.scholar.source import FetchResult
|
||||
from app.services.scholar.source import FetchResult
|
||||
|
||||
|
||||
def _fixture(name: str) -> str:
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ from __future__ import annotations
|
|||
import pytest
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.services.domains.scholar.parser import ParseState
|
||||
from app.services.domains.scholar.source import FetchResult
|
||||
from app.services.domains.scholars import application as scholar_service
|
||||
from app.services.scholar.parser import ParseState
|
||||
from app.services.scholar.source import FetchResult
|
||||
from app.services.scholars import application as scholar_service
|
||||
|
||||
pytestmark = [pytest.mark.integration, pytest.mark.db]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from app.services.domains.scholar import rate_limit as scholar_rate_limit
|
||||
from app.services.domains.scholar.source import FetchResult, LiveScholarSource, _build_profile_url
|
||||
from app.services.scholar import rate_limit as scholar_rate_limit
|
||||
from app.services.scholar.source import FetchResult, LiveScholarSource, _build_profile_url
|
||||
|
||||
|
||||
def test_build_profile_url_includes_pagesize_for_initial_page() -> None:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from app.services.domains.unpaywall import pdf_discovery
|
||||
from app.services.unpaywall import pdf_discovery
|
||||
|
||||
|
||||
def test_looks_like_pdf_url_detects_path_and_query_variants() -> None:
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ from datetime import UTC, datetime
|
|||
|
||||
import pytest
|
||||
|
||||
from app.services.domains.publication_identifiers.types import DisplayIdentifier
|
||||
from app.services.domains.publications.types import PublicationListItem
|
||||
from app.services.domains.unpaywall import application as unpaywall_app
|
||||
from app.services.publication_identifiers.types import DisplayIdentifier
|
||||
from app.services.publications.types import PublicationListItem
|
||||
from app.services.unpaywall import application as unpaywall_app
|
||||
|
||||
|
||||
class _DummyAsyncClient:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from app.services.domains.settings.application import (
|
||||
from app.services.settings.application import (
|
||||
DEFAULT_NAV_VISIBLE_PAGES,
|
||||
HARD_MIN_REQUEST_DELAY_SECONDS,
|
||||
HARD_MIN_RUN_INTERVAL_MINUTES,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue