import pytest from aiatlas.connectors.labs.google import DEEPMIND_RSS, DOCS, GOOGLE_AI_RSS, GoogleConnector from aiatlas.sdk.facts import Target from tests.conftest import claims_of, entity_names, extract_from_fixture, fixture_path @pytest.fixture def connector() -> GoogleConnector: return GoogleConnector() async def test_models_overview(connector): target = Target(url=f"{DOCS}/models", doc_type="model_docs", key="models") facts = await extract_from_fixture(connector, target, fixture_path("google", "models.html")) models = entity_names(facts, "model") assert len(models) >= 40 assert {"Gemini 3.8 Flash", "Gemini 2.5 Pro", "Nano Banana 2", "Veo 3.1", "Gemini Embedding 2"} <= models flash = claims_of(facts, "Gemini 3.8 Flash") assert flash["api_model_id"] == "gemini-3.8-flash" and flash["status"] == "active" and flash["family"] == "Gemini 3.8" # versioned family (ontology) flash_ref = next(e for e in facts.entities if e.name == "Gemini 3.8 Flash") assert flash_ref.family.entity_type == "model_family" and flash_ref.family.name == "Gemini 3.8" and flash_ref.family.organization.name == "Google" assert claims_of(facts, "Gemini 3.1 Pro")["status"] == "preview" assert claims_of(facts, "Gemini 2.0 Flash")["status"] == "retired" ref = next(e for e in facts.entities if e.name == "Nano Banana 2") assert ref.identifiers == {"gemini_model_id": "gemini-3.1-flash-image", "google_model_id": "gemini-3.1-flash-image"} # both schemes pages = [t for t in facts.targets if t.doc_type == "model_page"] assert len(pages) >= 40 and pages[0].url == f"{DOCS}/models/gemini-3.8-flash" # several endpoints under one display name: identity = first endpoint, the others are a claim (never aliases) transcribe = next(e for e in facts.entities if e.name == "Gemini 3.5 Transcribe") assert transcribe.identifiers["gemini_model_id"] == "gemini-3.5-transcribe" and "gemini-3.5-transcribe-live" not in transcribe.aliases assert claims_of(facts, "Gemini 3.5 Transcribe")["api_aliases"] == ["gemini-3.5-transcribe-live"] assert claims_of(facts, "Gemini 3.5 Transcribe")["api_alias"] == "gemini-3.5-transcribe-live" assert len([e for e in facts.entities if e.name == "Gemini Embedding 2"]) == 1 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")} assert omni == {"Gemini Omni Flash": "gemini-omni-flash"} # the GA endpoint row folds into the card's api_aliases assert claims_of(facts, "Gemini Omni Flash")["api_aliases"] == ["gemini-omni-1.1-flash"] assert sum(1 for c in facts.claims if c.property == "api_aliases" and c.entity.name == "Gemini Omni Flash") == 1 names = [e.name.lower() for e in facts.entities if e.entity_type == "model"] assert len(names) == len(set(names)) # every model ref has a distinct name inside the document props = [(c.entity.name, c.property) for c in facts.claims if c.property in ("api_model_id", "context_length")] assert len(props) == len(set(props)) async def test_model_pages(connector): 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"}) facts = await extract_from_fixture(connector, target, fixture_path("google", "model-gemini-3.8-flash.html")) m = claims_of(facts, "Gemini 3.8 Flash") assert m["context_length"] == 1_048_576 and m["max_output_tokens"] == 65_536 assert m["modalities_input"] == ["audio", "document", "image", "text", "video"] and m["modalities_output"] == ["text"] # pdf → document (ontology) assert m["tool_calling"] is True and m["structured_output"] is True and m["reasoning"] is True and m["vision"] is True and m["audio"] is True assert {"function_calling", "structured_output", "reasoning"} <= set(m["capabilities"]) and "image_generation" not in m["capabilities"] assert "Function calling" in m["capabilities_raw"] and "Image generation" not in m["capabilities_raw"] # Google's labels kept as raw assert m["latest_update"] == "2026-09" and m["versions"] == {"stable": ["gemini-3.8-flash"]} 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"}) facts = await extract_from_fixture(connector, target, fixture_path("google", "model-gemini-2.5-pro.html")) pro = claims_of(facts, "Gemini 2.5 Pro") assert pro["knowledge_cutoff"] == "2025-01" and pro["latest_update"] == "2025-06" and pro["context_length"] == 1_048_576 async def test_pricing(connector): target = Target(url=f"{DOCS}/pricing", doc_type="pricing", key="pricing") facts = await extract_from_fixture(connector, target, fixture_path("google", "pricing.html")) by_id = {p.provider_model_id: p for p in facts.prices} assert len(by_id) >= 25 flash = by_id["gemini-3.8-flash"] assert (flash.input_per_mtok, flash.cached_input_per_mtok, flash.output_per_mtok) == (0.75, 0.075, 3.75) assert (flash.batch_input_per_mtok, flash.batch_output_per_mtok) == (0.375, 1.875) assert flash.features["scheduled_input_per_mtok"] == 1.5 and flash.features["promotional_until"] == "December 31, 2026" assert flash.features["priority_input_per_mtok"] == 1.35 and flash.features["free_tier"] is True assert flash.provider.identifiers["registry_provider"] == "google-gemini-api" and flash.model.name == "Gemini 3.8 Flash" pro = by_id["gemini-2.5-pro"] assert (pro.input_per_mtok, pro.cached_input_per_mtok, pro.output_per_mtok) == (1.25, 0.125, 10.0) assert pro.features["long_context_input_per_mtok"] == 2.5 and pro.features["long_context_output_per_mtok"] == 15.0 assert pro.features["cache_storage_per_mtok_hour"] == 4.5 lite = by_id["gemini-2.5-flash-lite"] 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 unit assert by_id["gemini-2.5-flash-image"].per_image == 0.039 assert by_id["lyria-3.5"].per_request == 0.08 assert "720p" in str(by_id["veo-3.1-generate-preview"].features["per_second"]) assert by_id["gemini-embedding-2"].input_per_mtok == 0.20 and by_id["gemini-embedding-2"].features["image_input_per_mtok"] == 0.45 async def test_changelog(connector): target = Target(url=f"{DOCS}/changelog", doc_type="listing", key="changelog") facts = await extract_from_fixture(connector, target, fixture_path("google", "changelog.html")) events = facts.events assert len(events) >= 80 and all(e.effective_at is not None and e.dedupe_key for e in events) assert events[0].summary == "Gemini API: Lyria 3.5 in public preview" and events[0].effective_at.date().isoformat() == "2026-09-03" ga = next(e for e in events if "Gemini 3.8 Flash generally available" in e.summary) assert ga.category == "release" and "gemini-3.8-flash" in ga.meta["models"] assert len({e.dedupe_key for e in events}) == len(events) async def test_feeds(connector): facts = await extract_from_fixture(connector, Target(url=DEEPMIND_RSS, doc_type="feed", key="deepmind_feed"), fixture_path("google", "deepmind.rss.xml"), content_type="application/rss+xml") assert len(facts.events) == 100 and facts.events[0].entity.name == "Google DeepMind" assert any(e.category == "release" and "WeatherNext 3" in e.summary for e in facts.events) 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"), content_type="application/rss+xml") assert len(facts.events) == 20 and facts.events[0].entity.name == "Google" assert facts.events[0].effective_at.date().isoformat() == "2026-09-10"