"""Smoke tests for POST /v1beta/models/{model}:generateContent (gemini-3.5-flash-lite, maxOutputTokens <= 32). Verified live 2026-09-18. Each test < $0.0001. Paced for the free tier (see _core_helpers.call). """ from __future__ import annotations from tests.gemini._core_helpers import call, gc, text_of, user def test_minimal_shape(gemini, models): st, body, hdrs = call(gemini, "POST", gc(models["gemini"]), {"contents": user("Reply with OK."), "generationConfig": {"maxOutputTokens": 16}}, note="test_generate_content minimal") assert st == 200, body c = body["candidates"][0] assert c["finishReason"] == "STOP" and c["index"] == 0 and c["content"]["role"] == "model" assert "OK" in text_of(body) assert c["content"]["parts"][-1].get("thoughtSignature"), "Gemini 3 attaches a thoughtSignature to the last part" u = body["usageMetadata"] assert {"promptTokenCount", "candidatesTokenCount", "totalTokenCount", "promptTokensDetails", "serviceTier"} <= u.keys() assert u["serviceTier"] == "standard" and u["promptTokensDetails"][0]["modality"] == "TEXT" assert body["modelVersion"] == models["gemini"] and body["responseId"] def test_system_instruction_and_multiturn(gemini, models): body = {"systemInstruction": {"parts": [{"text": "Answer with one word."}]}, "contents": [{"role": "user", "parts": [{"text": "My name is Atlas."}]}, {"role": "model", "parts": [{"text": "Noted."}]}, {"role": "user", "parts": [{"text": "What is my name?"}]}], "generationConfig": {"maxOutputTokens": 16, "temperature": 1.0, "topP": 0.95, "topK": 64, "seed": 7, "stopSequences": ["STOP"]}} st, resp, _ = call(gemini, "POST", gc(models["gemini"]), body, note="test_generate_content system+multiturn") assert st == 200, resp assert "atlas" in text_of(resp).lower() def test_snake_case_aliases_accepted(gemini, models): st, resp, _ = call(gemini, "POST", gc(models["gemini"]), {"system_instruction": {"parts": [{"text": "Answer with OK."}]}, "contents": user("Hi"), "generation_config": {"max_output_tokens": 8}}, note="test_generate_content snake_case") assert st == 200, resp def test_model_turn_last_rejected(gemini, models): st, resp, _ = call(gemini, "POST", gc(models["gemini"]), {"contents": [{"role": "model", "parts": [{"text": "hi"}]}], "generationConfig": {"maxOutputTokens": 8}}, note="test_generate_content model-last") assert st == 400 and resp["error"]["status"] == "INVALID_ARGUMENT" assert "model turn" in resp["error"]["message"] def test_validation_errors_verbatim(gemini, models): st, resp, _ = call(gemini, "POST", gc(models["gemini"]), {"contents": user("x"), "generationConfig": {"maxOutputTokens": 8, "temperature": 2.5}}, note="test_generate_content temperature range") assert st == 400 and "temperature must be in the range [0.0, 2.0]" in resp["error"]["message"] st, resp, _ = call(gemini, "POST", gc(models["gemini"]), {"contents": user("x"), "generationConfig": {"maxOutputTokens": 8, "bogusField": 1}}, note="test_generate_content unknown field") assert st == 400 and 'Unknown name "bogusField"' in resp["error"]["message"] assert resp["error"]["details"][0]["@type"].endswith("google.rpc.BadRequest") def test_unsupported_features_on_gemini3(gemini, models): st, resp, _ = call(gemini, "POST", gc(models["gemini"]), {"contents": user("Reply with OK."), "generationConfig": {"maxOutputTokens": 8, "candidateCount": 2}}, note="test_generate_content candidateCount 2") assert st == 400 and "Multiple candidates is not enabled" in resp["error"]["message"] st, resp, _ = call(gemini, "POST", gc(models["gemini"]), {"contents": user("Reply with OK."), "generationConfig": {"maxOutputTokens": 8, "responseLogprobs": True, "logprobs": 2}}, note="test_generate_content logprobs") assert st == 400 and "Logprobs is not enabled" in resp["error"]["message"] def test_max_tokens_finish_reason(gemini, models): st, resp, _ = call(gemini, "POST", gc(models["gemini"]), {"contents": user("Write a 300 word essay about rivers."), "generationConfig": {"maxOutputTokens": 8}}, note="test_generate_content MAX_TOKENS") assert st == 200 and resp["candidates"][0]["finishReason"] == "MAX_TOKENS" def test_service_tier_echo(gemini, models): st, resp, _ = call(gemini, "POST", gc(models["gemini"]), {"contents": user("Reply with OK."), "serviceTier": "standard", "labels": {"env": "atlas-test"}, "generationConfig": {"maxOutputTokens": 8}}, note="test_generate_content serviceTier") assert st == 200 and resp["usageMetadata"]["serviceTier"] == "standard" def test_safety_ratings_present_when_settings_sent(gemini, models): cats = ["HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_DANGEROUS_CONTENT"] st, resp, _ = call(gemini, "POST", gc(models["gemini"]), {"contents": user("Reply with OK."), "safetySettings": [{"category": c, "threshold": "BLOCK_LOW_AND_ABOVE"} for c in cats], "generationConfig": {"maxOutputTokens": 8}}, note="test_generate_content safety") assert st == 200, resp ratings = resp["candidates"][0]["safetyRatings"] assert {r["category"] for r in ratings} == set(cats) and all(r["probability"] in ("NEGLIGIBLE", "LOW", "MEDIUM", "HIGH") for r in ratings) st, resp, _ = call(gemini, "POST", gc(models["gemini"]), {"contents": user("x"), "safetySettings": [{"category": "HARM_CATEGORY_TOXICITY", "threshold": "BLOCK_NONE"}], "generationConfig": {"maxOutputTokens": 8}}, note="test_generate_content PaLM category") assert st == 400 and "element predicate failed" in resp["error"]["message"] def test_unknown_model_404(gemini): st, resp, _ = call(gemini, "POST", gc("gemini-99-ultra"), {"contents": user("x")}, note="test_generate_content 404") assert st == 404 and resp["error"]["status"] == "NOT_FOUND" and "ListModels" in resp["error"]["message"]