1import pytest23from aiatlas.connectors.labs.google import DEEPMIND_RSS, DOCS, GOOGLE_AI_RSS, GoogleConnector4from aiatlas.sdk.facts import Target5from tests.conftest import claims_of, entity_names, extract_from_fixture, fixture_path678@pytest.fixture9def connector() -> GoogleConnector:10 return GoogleConnector()111213async def test_models_overview(connector):14 target = Target(url=f"{DOCS}/models", doc_type="model_docs", key="models")15 facts = await extract_from_fixture(connector, target, fixture_path("google", "models.html"))16 models = entity_names(facts, "model")17 assert len(models) >= 4018 assert {"Gemini 3.8 Flash", "Gemini 2.5 Pro", "Nano Banana 2", "Veo 3.1", "Gemini Embedding 2"} <= models19 flash = claims_of(facts, "Gemini 3.8 Flash")20 assert flash["api_model_id"] == "gemini-3.8-flash" and flash["status"] == "active" and flash["family"] == "Gemini"21 assert claims_of(facts, "Gemini 3.1 Pro")["status"] == "preview"22 assert claims_of(facts, "Gemini 2.0 Flash")["status"] == "retired"23 ref = next(e for e in facts.entities if e.name == "Nano Banana 2")24 assert ref.identifiers == {"gemini_model_id": "gemini-3.1-flash-image"}25 pages = [t for t in facts.targets if t.doc_type == "model_page"]26 assert len(pages) >= 40 and pages[0].url == f"{DOCS}/models/gemini-3.8-flash"27 # several endpoints under one display name: identity = first endpoint, the others are a claim (never aliases)28 transcribe = next(e for e in facts.entities if e.name == "Gemini 3.5 Transcribe")29 assert transcribe.identifiers == {"gemini_model_id": "gemini-3.5-transcribe"} and "gemini-3.5-transcribe-live" not in transcribe.aliases30 assert claims_of(facts, "Gemini 3.5 Transcribe")["api_aliases"] == ["gemini-3.5-transcribe-live"]31 assert len([e for e in facts.entities if e.name == "Gemini Embedding 2"]) == 132 omni = {e.name: e.identifiers["gemini_model_id"] for e in facts.entities if e.entity_type == "model" and e.name.startswith("Gemini Omni Flash")}33 assert omni == {"Gemini Omni Flash": "gemini-omni-flash"} # the GA endpoint row folds into the card's api_aliases34 assert claims_of(facts, "Gemini Omni Flash")["api_aliases"] == ["gemini-omni-1.1-flash"]35 assert sum(1 for c in facts.claims if c.property == "api_aliases" and c.entity.name == "Gemini Omni Flash") == 136 names = [e.name.lower() for e in facts.entities if e.entity_type == "model"]37 assert len(names) == len(set(names)) # every model ref has a distinct name inside the document38 props = [(c.entity.name, c.property) for c in facts.claims if c.property in ("api_model_id", "context_length")]39 assert len(props) == len(set(props))404142async def test_model_pages(connector):43 target = Target(url=f"{DOCS}/models/gemini-3.8-flash", doc_type="model_page", key="model:gemini-3.8-flash", meta={"api_id": "gemini-3.8-flash"})44 facts = await extract_from_fixture(connector, target, fixture_path("google", "model-gemini-3.8-flash.html"))45 m = claims_of(facts, "Gemini 3.8 Flash")46 assert m["context_length"] == 1_048_576 and m["max_output_tokens"] == 65_53647 assert m["modalities_input"] == ["text", "image", "video", "audio", "pdf"] and m["modalities_output"] == ["text"]48 assert m["tool_calling"] is True and m["structured_output"] is True and m["reasoning"] is True and m["vision"] is True49 assert "Function calling" in m["capabilities"] and "Image generation" not in m["capabilities"]50 assert m["latest_update"] == "2026-09" and m["versions"] == {"stable": ["gemini-3.8-flash"]}51 target = Target(url=f"{DOCS}/models/gemini-2.5-pro", doc_type="model_page", key="model:gemini-2.5-pro", meta={"api_id": "gemini-2.5-pro"})52 facts = await extract_from_fixture(connector, target, fixture_path("google", "model-gemini-2.5-pro.html"))53 pro = claims_of(facts, "Gemini 2.5 Pro")54 assert pro["knowledge_cutoff"] == "2025-01" and pro["latest_update"] == "2025-06" and pro["context_length"] == 1_048_576555657async def test_pricing(connector):58 target = Target(url=f"{DOCS}/pricing", doc_type="pricing", key="pricing")59 facts = await extract_from_fixture(connector, target, fixture_path("google", "pricing.html"))60 by_id = {p.provider_model_id: p for p in facts.prices}61 assert len(by_id) >= 2562 flash = by_id["gemini-3.8-flash"]63 assert (flash.input_per_mtok, flash.cached_input_per_mtok, flash.output_per_mtok) == (0.75, 0.075, 3.75)64 assert (flash.batch_input_per_mtok, flash.batch_output_per_mtok) == (0.375, 1.875)65 assert flash.features["scheduled_input_per_mtok"] == 1.5 and flash.features["promotional_until"] == "December 31, 2026"66 assert flash.features["priority_input_per_mtok"] == 1.35 and flash.features["free_tier"] is True67 assert flash.provider.identifiers["registry_provider"] == "google-gemini-api" and flash.model.name == "Gemini 3.8 Flash"68 pro = by_id["gemini-2.5-pro"]69 assert (pro.input_per_mtok, pro.cached_input_per_mtok, pro.output_per_mtok) == (1.25, 0.125, 10.0)70 assert pro.features["long_context_input_per_mtok"] == 2.5 and pro.features["long_context_output_per_mtok"] == 15.071 assert pro.features["cache_storage_per_mtok_hour"] == 4.572 lite = by_id["gemini-2.5-flash-lite"]73 assert lite.input_per_mtok == 0.10 and lite.features["audio_input_per_mtok"] == 0.30 # "(text / image / video)" is a modality list, not a unit74 assert by_id["gemini-2.5-flash-image"].per_image == 0.03975 assert by_id["lyria-3.5"].per_request == 0.0876 assert "720p" in str(by_id["veo-3.1-generate-preview"].features["per_second"])77 assert by_id["gemini-embedding-2"].input_per_mtok == 0.20 and by_id["gemini-embedding-2"].features["image_input_per_mtok"] == 0.45787980async def test_changelog(connector):81 target = Target(url=f"{DOCS}/changelog", doc_type="listing", key="changelog")82 facts = await extract_from_fixture(connector, target, fixture_path("google", "changelog.html"))83 events = facts.events84 assert len(events) >= 80 and all(e.effective_at is not None and e.dedupe_key for e in events)85 assert events[0].summary == "Gemini API: Lyria 3.5 in public preview" and events[0].effective_at.date().isoformat() == "2026-09-03"86 ga = next(e for e in events if "Gemini 3.8 Flash generally available" in e.summary)87 assert ga.category == "release" and "gemini-3.8-flash" in ga.meta["models"]88 assert len({e.dedupe_key for e in events}) == len(events)899091async def test_feeds(connector):92 facts = await extract_from_fixture(connector, Target(url=DEEPMIND_RSS, doc_type="feed", key="deepmind_feed"), fixture_path("google", "deepmind.rss.xml"),93 content_type="application/rss+xml")94 assert len(facts.events) == 100 and facts.events[0].entity.name == "Google DeepMind"95 assert any(e.category == "release" and "WeatherNext 3" in e.summary for e in facts.events)96 facts = await extract_from_fixture(connector, Target(url=GOOGLE_AI_RSS, doc_type="feed", key="google_ai_feed"), fixture_path("google", "google-ai-blog.rss.xml"),97 content_type="application/rss+xml")98 assert len(facts.events) == 20 and facts.events[0].entity.name == "Google"99 assert facts.events[0].effective_at.date().isoformat() == "2026-09-10"100