"""Smoke tests for the Gemini Interactions API + Environments API (API Atlas). Cheap (always run, < $0.002 on gemini-3.5-flash-lite): create → steps/usage shape, stream event order, previous_interaction_id chaining, GET, DELETE (+404 after), store=false has no id, list endpoint is 404, function tool via generation_config.tool_choice → requires_action + function_result follow-up, top-level tool_choice rejected (400), environments create/get/delete. Expensive (RUN_EXPENSIVE_TESTS=true): Deep Research background create + immediate cancel (bills nothing measurable but touches an agent). Reference run: 2026-09-18 (tmp-live/gemini-tools/h*.json, env*.json). """ from __future__ import annotations import json import pytest MODEL = "gemini-3.5-flash-lite" FN = [{"type": "function", "name": "get_weather", "description": "Get the weather for a city.", "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}] def _create(gemini, body, note=""): st, r, _ = gemini("POST", "/v1beta/interactions", body, note=f"test_interactions {note}") return st, r def _delete(gemini, *ids): for i in ids: if i: gemini("DELETE", f"/v1beta/interactions/{i}", note="test_interactions cleanup") def test_create_get_delete_lifecycle(gemini): st, r = _create(gemini, {"model": MODEL, "input": "Reply with OK.", "generation_config": {"max_output_tokens": 16}}, "create") assert st == 200 and r["object"] == "interaction" and r["status"] == "completed" assert {"id", "model", "steps", "usage", "created", "updated", "service_tier"} <= set(r) types = [s["type"] for s in r["steps"]] assert "model_output" in types and r["usage"]["total_tokens"] > 0 st, g, _ = gemini("GET", f"/v1beta/interactions/{r['id']}", note="test_interactions get") assert st == 200 and g["id"] == r["id"] st, _, _ = gemini("DELETE", f"/v1beta/interactions/{r['id']}", note="test_interactions delete") assert st == 200 st, g2, _ = gemini("GET", f"/v1beta/interactions/{r['id']}", note="test_interactions get after delete") assert st == 404 def test_stream_events_and_chaining(gemini): st, r = _create(gemini, {"model": MODEL, "input": "Reply with OK.", "generation_config": {"max_output_tokens": 16}}, "chain base") assert st == 200 st, lines, _ = gemini("POST", "/v1beta/interactions", {"model": MODEL, "input": "Reply with OK.", "stream": True, "previous_interaction_id": r["id"], "generation_config": {"max_output_tokens": 16}}, stream=True, note="test_interactions stream") assert st == 200 events = [json.loads(ln[5:]) for ln in lines if ln.startswith("data:") and ln[5:].strip() != "[DONE]"] types = [e["event_type"] for e in events] assert types[0] == "interaction.created" and types[-1] == "interaction.completed" assert {"step.start", "step.delta", "step.stop"} <= set(types) last = events[-1]["interaction"] assert last["status"] == "completed" and last["id"] _delete(gemini, r["id"], last["id"]) def test_store_false_and_list_404(gemini): st, r = _create(gemini, {"model": MODEL, "input": "Reply with OK.", "store": False, "generation_config": {"max_output_tokens": 8}}, "store=false") assert st == 200 and "id" not in r and r["status"] == "completed" st, _, _ = gemini("GET", "/v1beta/interactions", note="test_interactions list probe") assert st == 404 def test_function_tool_round_trip(gemini): st, bad = _create(gemini, {"model": MODEL, "input": "Weather in Paris?", "tools": FN, "tool_choice": {"allowed_tools": {"mode": "any"}}}, "tool_choice top-level") assert st == 400 and "tool_choice" in json.dumps(bad) st, r = _create(gemini, {"model": MODEL, "input": "Weather in Paris?", "tools": FN, "generation_config": {"tool_choice": {"allowed_tools": {"mode": "any", "tools": ["get_weather"]}}, "max_output_tokens": 64}}, "function tool") assert st == 200 and r["status"] == "requires_action" fc = next(s for s in r["steps"] if s["type"] == "function_call") assert fc["name"] == "get_weather" and fc["id"].startswith("call_") and "city" in fc["arguments"] st, r2 = _create(gemini, {"model": MODEL, "previous_interaction_id": r["id"], "tools": FN, "input": [{"type": "function_result", "name": "get_weather", "call_id": fc["id"], "result": [{"type": "text", "text": "{\"temperature_c\": 18}"}]}], "generation_config": {"max_output_tokens": 32}}, "function_result") assert st == 200 and r2["status"] == "completed" and any(s["type"] == "model_output" for s in r2["steps"]) _delete(gemini, r["id"], r2["id"]) def test_environments_lifecycle(gemini): st, r, _ = gemini("POST", "/v1beta/environments", {}, note="test_interactions env create") assert st == 200 and r["id"] st, g, _ = gemini("GET", f"/v1beta/environments/{r['id']}", note="test_interactions env get") assert st == 200 and g["status"] == "active" and "storage" in g st, lst, _ = gemini("GET", "/v1beta/environments", note="test_interactions env list") assert st == 200 st, _, _ = gemini("DELETE", f"/v1beta/environments/{r['id']}", note="test_interactions env delete") assert st == 200 @pytest.mark.run_expensive_tests def test_deep_research_background_cancel(gemini): st, r = _create(gemini, {"agent": "deep-research-preview-04-2026", "input": "Reply with OK. Do not research anything.", "background": True}, "deep research") assert st == 200 and r["status"] in ("queued", "in_progress") and r["agent"].startswith("deep-research") st, c, _ = gemini("POST", f"/v1beta/interactions/{r['id']}/cancel", note="test_interactions cancel") assert st == 200 and c["status"] == "cancelled" _delete(gemini, r["id"])