GoogleGemini
Google Gemini LLM Integration for AI-powered analysis and chat capabilities. This integration provides access to Google Gemini's large language models for: - AI-powered chat conversations - Text analysis and generation - Natural language processing tasks Supports both Google AI Studio (API key) and Google Cloud Vertex AI (service account) authentication. Supported models include Gemini 2.0 Flash, Gemini 1.5 Pro, and various preview models.
Cloud Services · Google Gemini
Details
| ID | GoogleGemini |
|---|---|
| Provider | |
| Category | Cloud Services |
| From Version | 6.10.0 |
| Docker Image | demisto/google-api-py3:1.0.0.10182333 |
| Supported Modules | XSIAM Agentix |
README
Google Gemini Integration
This integration provides access to Google Gemini’s large language models for AI-powered analysis and chat capabilities in Cortex XSOAR or XSIAM. Supports both Google AI Studio (API key) and Google Cloud Vertex AI (service account) authentication.
Configure GoogleGemini in Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for Google Gemini.
- Click Add instance to create and configure a new integration instance.
Configure GoogleGemini in Cortex XSIAM
- Go to Marketplace
- Search for GoogleGemini
- Add ContentPack
- Search for GoogleGemini in Data Source and Integrations
- Create new instance
Instance Configuration Parameters
| Parameter | Description | Required |
|---|---|---|
| Authentication Type | Choose between “AI Studio API Key” or “Vertex AI Service Account” | True |
| Server URL | For AI Studio: https://generativelanguage.googleapis.com. For Vertex AI: https://aiplatform.googleapis.com (auto-detected if unchanged). |
True |
| API Key | Google AI Studio API key. Required when using AI Studio. | False |
| Service Account Key (JSON) | Service Account Key JSON for Vertex AI authentication. Required when using Vertex AI. | False |
| Project ID | Google Cloud Project ID. Required when using Vertex AI. | False |
| Location | Google Cloud location for Vertex AI (e.g., global, us-central1). Defaults to global. |
False |
| Default Model | Select a Gemini model from the dropdown | True |
| Max tokens | Maximum number of tokens in the response (default: 1024) | True |
| Temperature | Controls randomness in responses (0.0-2.0) | False |
| Top P | Nucleus sampling parameter (0.0-1.0) | False |
| Top K | Top-k sampling parameter | False |
| Trust any certificate (not secure) | Whether to ignore SSL certificate verification | False |
| Use system proxy settings | Whether to use system proxy configuration | False |
Supported Models
The integration supports various Gemini models including:
Stable Models:
- gemini-2.5-pro
- gemini-2.5-flash
Deprecated (Legacy Only) Models — operational until June 1, 2026:
- gemini-2.0-flash
- gemini-2.0-flash-lite
Preview Models:
- gemini-3.1-pro-preview
- gemini-3.1-flash-preview
- gemini-3.1-flash-lite
Note: You can also use the freetext model field to specify newer models not in the dropdown list.
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
google-gemini-send-message
Send a prompt to Google Gemini and receive an AI-generated response.
Base Command
google-gemini-send-message
Input
| Argument Name | Description | Required |
|---|---|---|
| prompt | The prompt or question to send to the AI model | Required |
| model | Override the instance default model for this specific request | Optional |
| history | Conversation history in JSON format for maintaining context across multiple interactions | Optional |
| save_conversation | Whether to automatically save and retrieve conversation history (default: false) | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| GoogleGemini.Chat.Prompt | String | The original prompt sent to the model |
| GoogleGemini.Chat.Response | String | The AI model’s response |
| GoogleGemini.Chat.Model | String | The model used for generation |
| GoogleGemini.Chat.Temperature | Number | The temperature parameter used for response generation |
| GoogleGemini.Chat.History | Array | Complete conversation history (when save_conversation=true) |
| GoogleGemini.Chat.ConversationId | String | A unique identifier, used to identify the chat session |
Command Examples
!google-gemini-send-message prompt="What is artificial intelligence?"
!google-gemini-send-message prompt="Analyze this suspicious email for potential threats" model="gemini-2.5-pro"
!google-gemini-send-message prompt="Continue our previous discussion" history='[{"role": "user", "parts": [{"text": "Hello"}]}, {"role": "model", "parts": [{"text": "Hi there! How can I help you?"}]}]'
!google-gemini-send-message prompt="What are the next investigation steps?" save_conversation=true
Conversation History Management
When save_conversation=true, the integration:
- Automatically retrieves existing conversation history from context
- Uses the last exchange (user + model response) to provide context for the current request
- Saves the complete updated conversation history to
GoogleGemini.Chat.History - Allows analysts to maintain conversation continuity without manually managing JSON history
Human Readable Output
The command returns the AI model’s response as human-readable output in the War Room.
Setup Instructions
AI Studio (API Key)
- Obtain API Key: Visit Google AI Studio to create an API key.
- Configure Integration: Add a new GoogleGemini integration instance, set Authentication Type to AI Studio API Key, and enter your API key.
- Test Connection: Use the Test button to verify connectivity.
- Start Using: Execute the
google-gemini-send-messagecommand for AI interactions.
Vertex AI (Service Account)
- Create a Service Account: In the Google Cloud Console, go to IAM & Admin > Service Accounts and create a service account with the Vertex AI User role.
- Generate a JSON Key: On the service account page, create a new JSON key and download it.
- Configure Integration: Add a new GoogleGemini integration instance, set Authentication Type to Vertex AI Service Account, and paste the full JSON key contents into the Service Account Key field.
- Set Project ID: Enter your Google Cloud Project ID.
- Set Location: Enter the location (default:
global). Useus-central1,europe-west4, etc. for regional endpoints. - Test Connection: Use the Test button to verify connectivity.
Troubleshooting and Tips
- Ensure your API key has access to the Generative Language API.
- Verify your Cortex XSOAR or XSIAM instance can access the configured endpoint.
- Check that the specified model is available in your region.
- Review usage quotas and rate limits for your API key or project.
- The integration attempts to use models not included in the official list and issues a warning.
- Ensure the service account has the
roles/aiplatform.userrole and the Vertex AI API is enabled in your project. - For AI Studio, use the server URL
https://generativelanguage.googleapis.com. For Vertex AI, the URL auto-switches tohttps://aiplatform.googleapis.comby default.
Configuration parameters
auth_type— Authentication Type (required)url— Server URL (required)api_key—service_account_key—project_id— Project IDlocation— Locationmodel— Default Model (required)max_tokens— Max tokens (required)temperature— Temperaturetop_p— Top Ptop_k— Top Kinsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (1)
-
google-gemini-send-messageSend a chat message to the Gemini AI model.
"""Unit tests for GoogleGemini module""" import json import pytest import GoogleGemini import demistomock as demisto from CommonServerPython import DemistoException, CommandResults def util_load_json(path): with open(path, encoding="utf-8") as f: return json.loads(f.read()) MOCK_SERVICE_ACCOUNT_JSON = json.dumps( { "type": "service_account", "project_id": "test-project", "private_key_id": "key123", "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIBogIBAAJBALRiM\n-----END RSA PRIVATE KEY-----\n", "client_email": "test@test-project.iam.gserviceaccount.com", "client_id": "123456789", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", } ) @pytest.fixture def client_fixture(mocker): """ Fixture for the Client object (AI Studio auth). Mocks the BaseClient._http_request method. """ client = GoogleGemini.Client( base_url="https://generativelanguage.googleapis.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_AI_STUDIO, api_key="test_api_key", model="gemini-2.0-flash", max_tokens=1024, temperature=0.7, top_p=None, top_k=None, ) mocker.patch.object(client, "_http_request") return client @pytest.fixture def vertex_client_fixture(mocker): """ Fixture for the Client object (Vertex AI auth). Mocks the google-auth credentials. """ mock_credentials = mocker.MagicMock() mock_credentials.valid = True mock_credentials.token = "mock_access_token" mocker.patch( "GoogleGemini.service_account.Credentials.from_service_account_info", return_value=mock_credentials, ) client = GoogleGemini.Client( base_url="https://aiplatform.googleapis.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_VERTEX_AI, service_account_json=MOCK_SERVICE_ACCOUNT_JSON, project_id="test-project", location="global", model="gemini-2.0-flash", max_tokens=1024, temperature=0.7, top_p=None, top_k=None, ) mocker.patch.object(client, "_http_request") return client # Mock responses MOCK_SUCCESSFUL_CHAT_RESPONSE = { "candidates": [{"content": {"parts": [{"text": "Hello! This is a test response from Gemini."}]}}] } MOCK_ERROR_RESPONSE = {"error": {"code": 400, "message": "Invalid request parameters", "status": "INVALID_ARGUMENT"}} MOCK_EMPTY_RESPONSE = {"candidates": []} MOCK_NO_TEXT_RESPONSE = {"candidates": [{"content": {"parts": []}}]} def test_client_init(): """Test Client initialization with all parameters (AI Studio)""" client = GoogleGemini.Client( base_url="https://test.com", verify=False, proxy=True, auth_type=GoogleGemini.AUTH_TYPE_AI_STUDIO, api_key="test_key", model="gemini-2.5-pro", max_tokens=2048, temperature=0.8, top_p=0.9, top_k=40, ) assert client.api_key == "test_key" assert client.model == "gemini-2.5-pro" assert client.max_tokens == 2048 assert client.temperature == 0.8 assert client.top_p == 0.9 assert client.top_k == 40 assert client.auth_type == GoogleGemini.AUTH_TYPE_AI_STUDIO assert client._headers["x-goog-api-key"] == "test_key" assert client._headers["Content-Type"] == "application/json" assert client._headers["Accept"] == "application/json" def test_client_init_default_model(): """Test Client initialization with default model and parameters""" client = GoogleGemini.Client( base_url="https://test.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_AI_STUDIO, api_key="test_key", ) assert client.model == "gemini-2.5-flash" assert client.max_tokens == 1024 assert client.temperature is None assert client.top_p is None assert client.top_k is None def test_client_init_vertex_ai(mocker): """Test Client initialization with Vertex AI authentication""" mock_credentials = mocker.MagicMock() mocker.patch( "GoogleGemini.service_account.Credentials.from_service_account_info", return_value=mock_credentials, ) client = GoogleGemini.Client( base_url="https://aiplatform.googleapis.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_VERTEX_AI, service_account_json=MOCK_SERVICE_ACCOUNT_JSON, project_id="test-project", location="us-central1", model="gemini-2.0-flash", max_tokens=2048, ) assert client.auth_type == GoogleGemini.AUTH_TYPE_VERTEX_AI assert client.project_id == "test-project" assert client.location == "us-central1" assert client.service_account_info["client_email"] == "test@test-project.iam.gserviceaccount.com" assert client._credentials == mock_credentials assert "x-goog-api-key" not in client._headers assert client._headers["Content-Type"] == "application/json" def test_send_chat_message_success(client_fixture): """Test successful chat message sending""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE result = client_fixture.send_chat_message(prompt="Hello, how are you?", model="gemini-2.0-flash") assert result == MOCK_SUCCESSFUL_CHAT_RESPONSE client_fixture._http_request.assert_called_once_with( method="POST", url_suffix="/v1beta/models/gemini-2.0-flash:generateContent", json_data={ "contents": [{"role": "user", "parts": [{"text": "Hello, how are you?"}]}], "generationConfig": {"maxOutputTokens": 1024, "temperature": 0.7}, }, headers=None, ) def test_send_chat_message_with_history(client_fixture): """Test chat message sending with conversation history""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE history = [ {"role": "user", "parts": [{"text": "What is the capital of France?"}]}, {"role": "model", "parts": [{"text": "The capital of France is Paris."}]}, ] result = client_fixture.send_chat_message(prompt="What about Italy?", history=history) expected_contents = history + [{"role": "user", "parts": [{"text": "What about Italy?"}]}] assert result == MOCK_SUCCESSFUL_CHAT_RESPONSE client_fixture._http_request.assert_called_once() call_args = client_fixture._http_request.call_args[1]["json_data"] assert call_args["contents"] == expected_contents def test_send_chat_message_default_model(client_fixture): """Test chat message sending with default model""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE client_fixture.send_chat_message("Test prompt") client_fixture._http_request.assert_called_once() url_suffix = client_fixture._http_request.call_args[1]["url_suffix"] assert f"/v1beta/models/{client_fixture.model}:generateContent" in url_suffix def test_test_module_success(client_fixture): """Test test_module function with successful response""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE result = GoogleGemini.test_module(client_fixture) assert result == "ok" client_fixture._http_request.assert_called_once() def test_test_module_exception(client_fixture, mocker): """Test test_module function with exception""" client_fixture._http_request.side_effect = DemistoException("Connection failed") mock_return_error = mocker.patch.object(GoogleGemini, "return_error") GoogleGemini.test_module(client_fixture) mock_return_error.assert_called_once_with("An unexpected error occurred during connectivity test: Connection failed") def test_test_module_api_error(client_fixture, mocker): """Test test_module function with API error response""" client_fixture._http_request.side_effect = DemistoException(message="{'message': 'Invalid API key'}") mock_return_error = mocker.patch.object(GoogleGemini, "return_error") GoogleGemini.test_module(client_fixture) mock_return_error.assert_called_once_with( "An unexpected error occurred during connectivity test: {'message': 'Invalid API key'}" ) def test_google_gemini_send_message_command_success(client_fixture): """Test google_gemini_send_message_command with successful response""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE args = {"prompt": "What is AI?", "model": "gemini-2.0-flash"} result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert isinstance(result, CommandResults) assert result.outputs_prefix == "GoogleGemini.Chat" assert result.outputs_key_field == "prompt" assert result.outputs is not None assert isinstance(result.outputs, dict) assert result.outputs["Prompt"] == "What is AI?" assert result.outputs["Response"] == "Hello! This is a test response from Gemini." assert result.outputs["Model"] == "gemini-2.0-flash" assert result.readable_output == "Hello! This is a test response from Gemini." def test_google_gemini_send_message_command_missing_prompt(client_fixture): """Test google_gemini_send_message_command with missing prompt""" args = {"model": "gemini-2.0-flash"} with pytest.raises(ValueError) as e: GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert "The 'prompt' argument is required." in str(e.value) def test_google_gemini_send_message_command_unsupported_model(client_fixture): """Test google_gemini_send_message_command with unsupported model""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE args = {"prompt": "Test prompt", "model": "unsupported-model"} # Should not raise an error, but issue a warning and continue result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert isinstance(result, CommandResults) assert result.outputs_key_field == "prompt" assert result.outputs is not None assert isinstance(result.outputs, dict) assert result.outputs["Prompt"] == "Test prompt" assert result.outputs["Model"] == "unsupported-model" def test_google_gemini_send_message_command_with_history_string(client_fixture): """Test google_gemini_send_message_command with history as JSON string""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE history = [ {"role": "user", "parts": [{"text": "Previous question"}]}, {"role": "model", "parts": [{"text": "Previous answer"}]}, ] args = {"prompt": "Follow-up question", "history": json.dumps(history)} result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert isinstance(result, CommandResults) assert result.outputs_key_field == "prompt" assert result.outputs is not None assert isinstance(result.outputs, dict) assert result.outputs["Prompt"] == "Follow-up question" def test_google_gemini_send_message_command_with_history_list(client_fixture): """Test google_gemini_send_message_command with history as list""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE history = [{"role": "user", "parts": [{"text": "Previous question"}]}] args = {"prompt": "Follow-up question", "history": history} result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert isinstance(result, CommandResults) assert result.outputs_key_field == "prompt" assert result.outputs is not None assert isinstance(result.outputs, dict) assert result.outputs["Prompt"] == "Follow-up question" def test_google_gemini_send_message_command_invalid_history(client_fixture): """Test google_gemini_send_message_command with invalid history JSON""" args = {"prompt": "Test prompt", "history": "invalid json"} with pytest.raises(ValueError) as e: GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert "History must be valid JSON array of conversation objects." in str(e.value) def test_google_gemini_send_message_command_api_error(client_fixture): """Test google_gemini_send_message_command with API error""" # client_fixture._http_request.return_value = MOCK_ERROR_RESPONSE client_fixture._http_request.side_effect = DemistoException(message=str(MOCK_ERROR_RESPONSE)) args = {"prompt": "Test prompt"} with pytest.raises(Exception) as e: GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert "Invalid request parameters" in str(e.value) def test_google_gemini_send_message_command_no_response_content(client_fixture): """Test google_gemini_send_message_command with empty response""" client_fixture._http_request.return_value = MOCK_EMPTY_RESPONSE args = {"prompt": "Test prompt"} result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert result.outputs_key_field == "prompt" assert isinstance(result.outputs, dict) assert result.outputs["Response"] == "No response generated." assert result.readable_output == "No response generated." def test_google_gemini_send_message_command_no_text_in_response(client_fixture): """Test google_gemini_send_message_command with response that has no text""" client_fixture._http_request.return_value = MOCK_NO_TEXT_RESPONSE args = {"prompt": "Test prompt"} result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert result.outputs_key_field == "prompt" assert isinstance(result.outputs, dict) assert result.outputs["Response"] == "No response generated." assert result.readable_output == "No response generated." def test_google_gemini_send_message_command_default_values(client_fixture): """Test google_gemini_send_message_command with default parameter values""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE args = {"prompt": "Test prompt"} result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert result.outputs_key_field == "prompt" assert isinstance(result.outputs, dict) assert result.outputs["Model"] == client_fixture.model # Check that instance default values were used in the API call call_args = client_fixture._http_request.call_args[1]["json_data"] assert call_args["generationConfig"]["maxOutputTokens"] == 1024 assert call_args["generationConfig"]["temperature"] == 0.7 def test_google_gemini_send_message_command_save_conversation_false(client_fixture, mocker): """Test google_gemini_send_message_command with save_conversation=false""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE prompt = "Test prompt" args = {"prompt": prompt, "save_conversation": "false"} result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert isinstance(result, CommandResults) assert isinstance(result.outputs, dict) assert result.outputs["Prompt"] == prompt assert "Response" in result.outputs assert "History" not in result.outputs def test_google_gemini_send_message_command_save_conversation_true(client_fixture, mocker): """Test google_gemini_send_message_command with save_conversation=true""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE # Mock demisto.context() to return empty context mocker.patch.object(GoogleGemini.demisto, "context", return_value={}) args = {"prompt": "Test prompt", "save_conversation": "true"} result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert isinstance(result, CommandResults) assert isinstance(result.outputs, dict) assert "Response" in result.outputs assert "History" in result.outputs assert result.outputs_key_field == "ConversationId" assert isinstance(result.outputs["History"], list) assert len(result.outputs["History"]) == 2 # user prompt + model response assert result.outputs["History"][0]["role"] == "user" assert result.outputs["History"][1]["role"] == "model" def test_google_gemini_send_message_command_save_conversation_with_existing_history_dict(client_fixture, mocker): """Test save_conversation with existing history in context as dict""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE existing_history = [ {"role": "user", "parts": [{"text": "Previous question"}]}, {"role": "model", "parts": [{"text": "Previous answer"}]}, ] mocked_context = {"GoogleGemini": {"Chat": {"History": existing_history, "ConversationId": "123abc"}}} mocker.patch.object(GoogleGemini.demisto, "context", return_value=mocked_context) args = {"prompt": "Follow-up question", "save_conversation": "true"} result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert isinstance(result.outputs, dict) assert "History" in result.outputs # Should include previous history + new exchange = 4 items total assert len(result.outputs["History"]) == 4 def test_google_gemini_send_message_command_save_conversation_with_existing_history_list(client_fixture, mocker): """Test save_conversation with existing history in context as list""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE existing_history = [ {"role": "user", "parts": [{"text": "Previous question"}]}, {"role": "model", "parts": [{"text": "Previous answer"}]}, ] mocked_context = {"GoogleGemini": {"Chat": {"History": existing_history, "ConversationId": "123abc"}}} mocker.patch.object(GoogleGemini.demisto, "context", return_value=mocked_context) args = {"prompt": "Follow-up question", "save_conversation": "true"} result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert isinstance(result.outputs, dict) assert "History" in result.outputs # Should include previous history + new exchange = 4 items total assert len(result.outputs["History"]) == 4 def test_google_gemini_send_message_command_save_conversation_single_existing_item(client_fixture, mocker): """Test save_conversation with single item in existing history""" client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE existing_history = [{"role": "user", "parts": [{"text": "Previous question"}]}] mocked_context = {"GoogleGemini": {"Chat": {"History": existing_history, "ConversationId": "123abc"}}} mocker.patch.object(GoogleGemini.demisto, "context", return_value=mocked_context) args = {"prompt": "Follow-up question", "save_conversation": "true"} result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert isinstance(result.outputs, dict) assert "History" in result.outputs # Should include previous single item + new exchange = 3 items total assert len(result.outputs["History"]) == 3 def test_google_gemini_send_message_command_save_conversation_no_response_generated(client_fixture, mocker): """Test save_conversation when no response is generated""" client_fixture._http_request.return_value = MOCK_EMPTY_RESPONSE mocker.patch.object(GoogleGemini.demisto, "context", return_value={}) args = {"prompt": "Test prompt", "save_conversation": "true"} result = GoogleGemini.google_gemini_send_message_command(client_fixture, args) assert isinstance(result.outputs, dict) assert "History" in result.outputs # Should only include user prompt, no model response since content was "No response generated." assert len(result.outputs["History"]) == 1 assert result.outputs["History"][0]["role"] == "user" @pytest.fixture def demisto_mocker_fixture(mocker): """Mocks demisto related objects used in main function of the integration.""" mocker.patch.object(GoogleGemini, "demisto", demisto) mocker.patch.object( demisto, "params", return_value={ "auth_type": GoogleGemini.AUTH_TYPE_AI_STUDIO, "url": "https://generativelanguage.googleapis.com", "api_key": {"password": "test_api_key"}, # Fixed: API key as dict with password "model": ["gemini-2.0-flash"], "model-freetext": "", "max_tokens": "1024", "temperature": "0.7", "top_p": "", "top_k": "", "insecure": False, "proxy": False, }, ) mocker.patch.object(demisto, "args", return_value={}) mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(GoogleGemini, "return_results") mocker.patch.object(GoogleGemini, "return_error") def test_main_test_module(demisto_mocker_fixture, mocker): """Test main function routing to test-module""" mock_test_module_func = mocker.patch.object(GoogleGemini, "test_module", return_value="ok") GoogleGemini.main() mock_test_module_func.assert_called_once() GoogleGemini.return_results.assert_called_once_with("ok") def test_main_google_gemini_send_message(demisto_mocker_fixture, mocker): """Test main function routing to google-gemini-send-message""" demisto.command.return_value = "google-gemini-send-message" demisto.args.return_value = {"prompt": "Test prompt"} mock_command_result = CommandResults(outputs={"test": "result"}) mock_command_func = mocker.patch.object(GoogleGemini, "google_gemini_send_message_command", return_value=mock_command_result) GoogleGemini.main() mock_command_func.assert_called_once() GoogleGemini.return_results.assert_called_once_with(mock_command_result) def test_main_not_implemented_command(demisto_mocker_fixture, mocker): """Test main function with not implemented command""" demisto.command.return_value = "unknown-command" GoogleGemini.main() GoogleGemini.return_error.assert_called_once() args, _ = GoogleGemini.return_error.call_args assert "Command unknown-command is not implemented" in args[0] assert "Failed to execute unknown-command command" in args[0] def test_main_missing_api_key(mocker): """Test main function when API key is missing""" mocker.patch.object(GoogleGemini, "demisto", demisto) mocker.patch.object( demisto, "params", return_value={ "auth_type": GoogleGemini.AUTH_TYPE_AI_STUDIO, "url": "https://generativelanguage.googleapis.com", "model": "gemini-2.0-flash", "model-freetext": "", "max_tokens": "1024", "temperature": "", "top_p": "", "top_k": "", "api_key": {"password": ""}, # Empty API key }, ) mocker.patch.object(demisto, "command", return_value="test-module") mock_return_error = mocker.patch.object(GoogleGemini, "return_error") GoogleGemini.main() mock_return_error.assert_called_once_with("API key is not configured. Please configure it in the instance settings.") def test_main_exception_handling(demisto_mocker_fixture, mocker): """Test main function exception handling""" demisto.command.return_value = "google-gemini-send-message" demisto.args.return_value = {"prompt": "Test prompt"} # Mock an exception in the command mocker.patch.object(GoogleGemini, "google_gemini_send_message_command", side_effect=Exception("Test error")) GoogleGemini.main() GoogleGemini.return_error.assert_called_once() args, _ = GoogleGemini.return_error.call_args assert "Failed to execute google-gemini-send-message command" in args[0] assert "Test error" in args[0] def test_main_model_freetext_override(mocker): """Test main function when multi values selected fr model""" mocker.patch.object(GoogleGemini, "demisto", demisto) mock_return_error = mocker.patch.object(GoogleGemini, "return_error") mocker.patch.object( demisto, "params", return_value={ "auth_type": GoogleGemini.AUTH_TYPE_AI_STUDIO, "url": "https://generativelanguage.googleapis.com", "api_key": {"password": "test_api_key"}, "model": ["gemini-2.0-flash", "test"], "model-freetext": "gemini-2.5-pro", "max_tokens": "2048", "temperature": "0.8", "top_p": "0.9", "top_k": "40", "insecure": False, "proxy": False, }, ) GoogleGemini.main() mock_return_error.assert_called_once() def test_supported_models_list(): """Test that SUPPORTED_MODELS contains expected models""" assert "gemini-2.5-pro" in GoogleGemini.SUPPORTED_MODELS assert "gemini-2.5-flash" in GoogleGemini.SUPPORTED_MODELS assert "gemini-2.0-flash" in GoogleGemini.SUPPORTED_MODELS assert "gemini-2.0-flash-lite" in GoogleGemini.SUPPORTED_MODELS assert "gemini-3.1-pro-preview" in GoogleGemini.SUPPORTED_MODELS assert "gemini-3.1-flash-preview" in GoogleGemini.SUPPORTED_MODELS assert "gemini-3.1-flash-lite" in GoogleGemini.SUPPORTED_MODELS assert len(GoogleGemini.SUPPORTED_MODELS) == 7 def test_send_chat_message_with_instance_parameters(): """Test that send_chat_message uses instance parameters correctly""" client = GoogleGemini.Client( base_url="https://test.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_AI_STUDIO, api_key="test_key", model="gemini-2.5-pro", max_tokens=2048, temperature=0.8, top_p=0.9, top_k=40, ) # Mock the HTTP request import unittest.mock with unittest.mock.patch.object(client, "_http_request") as mock_request: mock_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE client.send_chat_message("Test prompt") # Verify the generation config includes all instance parameters call_args = mock_request.call_args[1]["json_data"] generation_config = call_args["generationConfig"] assert generation_config["maxOutputTokens"] == 2048 assert generation_config["temperature"] == 0.8 assert generation_config["topP"] == 0.9 assert generation_config["topK"] == 40 def test_send_chat_message_with_optional_parameters_none(): """Test that send_chat_message only includes configured parameters""" client = GoogleGemini.Client( base_url="https://test.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_AI_STUDIO, api_key="test_key", model="gemini-2.5-pro", max_tokens=1024, temperature=None, top_p=None, top_k=None, ) # Mock the HTTP request import unittest.mock with unittest.mock.patch.object(client, "_http_request") as mock_request: mock_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE client.send_chat_message("Test prompt") # Verify the generation config only includes maxOutputTokens call_args = mock_request.call_args[1]["json_data"] generation_config = call_args["generationConfig"] assert generation_config["maxOutputTokens"] == 1024 assert "temperature" not in generation_config assert "topP" not in generation_config assert "topK" not in generation_config def test_malformed_response_no_candidates(): """Test handling of malformed API response with no candidates key""" client = GoogleGemini.Client( base_url="https://test.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_AI_STUDIO, api_key="test_key", ) malformed_response = {"usage": {"promptTokens": 5, "totalTokens": 10}} import unittest.mock with unittest.mock.patch.object(client, "_http_request") as mock_request: mock_request.return_value = malformed_response result = GoogleGemini.google_gemini_send_message_command(client, {"prompt": "test"}) assert isinstance(result.outputs, dict) assert result.outputs["Response"] == "No response generated." def test_malformed_response_candidates_not_list(): """Test handling of malformed API response where candidates is not a list""" client = GoogleGemini.Client( base_url="https://test.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_AI_STUDIO, api_key="test_key", ) malformed_response = {"candidates": "not a list"} import unittest.mock with unittest.mock.patch.object(client, "_http_request") as mock_request: mock_request.return_value = malformed_response result = GoogleGemini.google_gemini_send_message_command(client, {"prompt": "test"}) assert isinstance(result.outputs, dict) assert result.outputs["Response"] == "No response generated." def test_malformed_response_missing_content(): """Test handling of malformed API response with missing content""" client = GoogleGemini.Client( base_url="https://test.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_AI_STUDIO, api_key="test_key", ) malformed_response = {"candidates": [{"finishReason": "STOP"}]} import unittest.mock with unittest.mock.patch.object(client, "_http_request") as mock_request: mock_request.return_value = malformed_response result = GoogleGemini.google_gemini_send_message_command(client, {"prompt": "test"}) assert isinstance(result.outputs, dict) assert result.outputs["Response"] == "No response generated." def test_malformed_response_missing_parts(): """Test handling of malformed API response with missing parts""" client = GoogleGemini.Client( base_url="https://test.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_AI_STUDIO, api_key="test_key", ) malformed_response = {"candidates": [{"content": {"role": "model"}}]} import unittest.mock with unittest.mock.patch.object(client, "_http_request") as mock_request: mock_request.return_value = malformed_response result = GoogleGemini.google_gemini_send_message_command(client, {"prompt": "test"}) assert isinstance(result.outputs, dict) assert result.outputs["Response"] == "No response generated." def test_malformed_response_parts_not_list(): """Test handling of malformed API response where parts is not a list""" client = GoogleGemini.Client( base_url="https://test.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_AI_STUDIO, api_key="test_key", ) malformed_response = {"candidates": [{"content": {"parts": "not a list"}}]} import unittest.mock with unittest.mock.patch.object(client, "_http_request") as mock_request: mock_request.return_value = malformed_response result = GoogleGemini.google_gemini_send_message_command(client, {"prompt": "test"}) assert isinstance(result.outputs, dict) assert result.outputs["Response"] == "No response generated." def test_malformed_response_missing_text(): """Test handling of malformed API response with missing text in parts""" client = GoogleGemini.Client( base_url="https://test.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_AI_STUDIO, api_key="test_key", ) malformed_response = {"candidates": [{"content": {"parts": [{"image": "base64data"}]}}]} import unittest.mock with unittest.mock.patch.object(client, "_http_request") as mock_request: mock_request.return_value = malformed_response result = GoogleGemini.google_gemini_send_message_command(client, {"prompt": "test"}) assert isinstance(result.outputs, dict) assert result.outputs["Response"] == "No response generated." # --- Vertex AI Tests --- def test_vertex_ai_url_suffix(mocker): """Test that Vertex AI URL suffix is constructed correctly""" mocker.patch( "GoogleGemini.service_account.Credentials.from_service_account_info", return_value=mocker.MagicMock(), ) client = GoogleGemini.Client( base_url="https://aiplatform.googleapis.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_VERTEX_AI, service_account_json=MOCK_SERVICE_ACCOUNT_JSON, project_id="my-project", location="us-central1", ) url_suffix = client._get_url_suffix("gemini-2.0-flash") assert url_suffix == ( "/v1/projects/my-project/locations/us-central1" "/publishers/google/models/gemini-2.0-flash:generateContent" ) def test_ai_studio_url_suffix(): """Test that AI Studio URL suffix is constructed correctly""" client = GoogleGemini.Client( base_url="https://generativelanguage.googleapis.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_AI_STUDIO, api_key="test_key", ) url_suffix = client._get_url_suffix("gemini-2.0-flash") assert url_suffix == "/v1beta/models/gemini-2.0-flash:generateContent" def test_vertex_ai_request_headers(vertex_client_fixture): """Test that Vertex AI uses Bearer token headers""" headers = vertex_client_fixture._get_request_headers() assert headers is not None assert headers["Authorization"] == "Bearer mock_access_token" assert headers["Content-Type"] == "application/json" def test_ai_studio_request_headers(client_fixture): """Test that AI Studio returns None headers (uses default self._headers)""" headers = client_fixture._get_request_headers() assert headers is None def test_vertex_ai_send_message(vertex_client_fixture): """Test send_chat_message with Vertex AI auth""" vertex_client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE result = vertex_client_fixture.send_chat_message(prompt="Hello", model="gemini-2.0-flash") assert result == MOCK_SUCCESSFUL_CHAT_RESPONSE vertex_client_fixture._http_request.assert_called_once() call_kwargs = vertex_client_fixture._http_request.call_args[1] assert ( "/v1/projects/test-project/locations/global" "/publishers/google/models/gemini-2.0-flash:generateContent" ) in call_kwargs["url_suffix"] assert call_kwargs["headers"]["Authorization"] == "Bearer mock_access_token" def test_vertex_ai_send_message_command(vertex_client_fixture): """Test google_gemini_send_message_command with Vertex AI client""" vertex_client_fixture._http_request.return_value = MOCK_SUCCESSFUL_CHAT_RESPONSE args = {"prompt": "What is AI?", "model": "gemini-2.0-flash"} result = GoogleGemini.google_gemini_send_message_command(vertex_client_fixture, args) assert isinstance(result, CommandResults) assert result.outputs_prefix == "GoogleGemini.Chat" assert result.outputs is not None assert isinstance(result.outputs, dict) assert result.outputs["Prompt"] == "What is AI?" assert result.outputs["Response"] == "Hello! This is a test response from Gemini." def test_get_access_token_valid(mocker): """Test that valid cached credentials return token without refresh""" mock_credentials = mocker.MagicMock() mock_credentials.valid = True mock_credentials.token = "valid_token" mocker.patch( "GoogleGemini.service_account.Credentials.from_service_account_info", return_value=mock_credentials, ) client = GoogleGemini.Client( base_url="https://aiplatform.googleapis.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_VERTEX_AI, service_account_json=MOCK_SERVICE_ACCOUNT_JSON, project_id="test-project", location="global", ) token = client._get_access_token() assert token == "valid_token" mock_credentials.refresh.assert_not_called() def test_get_access_token_expired(mocker): """Test that expired credentials trigger a refresh""" mock_credentials = mocker.MagicMock() mock_credentials.valid = False mock_credentials.token = "refreshed_token" mocker.patch( "GoogleGemini.service_account.Credentials.from_service_account_info", return_value=mock_credentials, ) client = GoogleGemini.Client( base_url="https://aiplatform.googleapis.com", verify=True, proxy=False, auth_type=GoogleGemini.AUTH_TYPE_VERTEX_AI, service_account_json=MOCK_SERVICE_ACCOUNT_JSON, project_id="test-project", location="global", ) token = client._get_access_token() assert token == "refreshed_token" mock_credentials.refresh.assert_called_once() def test_main_vertex_ai_missing_service_account(mocker): """Test main function when service account key is missing for Vertex AI""" mocker.patch.object(GoogleGemini, "demisto", demisto) mocker.patch.object( demisto, "params", return_value={ "auth_type": GoogleGemini.AUTH_TYPE_VERTEX_AI, "url": "https://aiplatform.googleapis.com", "model": ["gemini-2.0-flash"], "max_tokens": "1024", "temperature": "", "top_p": "", "top_k": "", "service_account_key": {"password": ""}, "project_id": "test-project", "location": "global", }, ) mocker.patch.object(demisto, "command", return_value="test-module") mock_return_error = mocker.patch.object(GoogleGemini, "return_error") GoogleGemini.main() mock_return_error.assert_called_once_with("Service Account Key JSON is required for Vertex AI authentication.") def test_main_vertex_ai_missing_project_id(mocker): """Test main function when project ID is missing for Vertex AI""" mocker.patch.object(GoogleGemini, "demisto", demisto) mocker.patch.object( demisto, "params", return_value={ "auth_type": GoogleGemini.AUTH_TYPE_VERTEX_AI, "url": "https://aiplatform.googleapis.com", "model": ["gemini-2.0-flash"], "max_tokens": "1024", "temperature": "", "top_p": "", "top_k": "", "service_account_key": {"password": MOCK_SERVICE_ACCOUNT_JSON}, "project_id": "", "location": "global", }, ) mocker.patch.object(demisto, "command", return_value="test-module") mock_return_error = mocker.patch.object(GoogleGemini, "return_error") GoogleGemini.main() mock_return_error.assert_called_once_with("Project ID is required for Vertex AI authentication.") def test_main_vertex_ai_auto_switch_url(mocker): """Test that Server URL auto-switches from AI Studio default to Vertex AI""" mocker.patch.object(GoogleGemini, "demisto", demisto) mocker.patch( "GoogleGemini.service_account.Credentials.from_service_account_info", return_value=mocker.MagicMock(), ) mocker.patch.object( demisto, "params", return_value={ "auth_type": GoogleGemini.AUTH_TYPE_VERTEX_AI, "url": "https://generativelanguage.googleapis.com", # AI Studio default "model": ["gemini-2.0-flash"], "max_tokens": "1024", "temperature": "", "top_p": "", "top_k": "", "service_account_key": {"password": MOCK_SERVICE_ACCOUNT_JSON}, "project_id": "test-project", "location": "global", "insecure": False, "proxy": False, }, ) mocker.patch.object(demisto, "command", return_value="test-module") mocker.patch.object(demisto, "args", return_value={}) mock_test_module = mocker.patch.object(GoogleGemini, "test_module", return_value="ok") mocker.patch.object(GoogleGemini, "return_results") GoogleGemini.main() mock_test_module.assert_called_once() client = mock_test_module.call_args[0][0] assert client._base_url == "https://aiplatform.googleapis.com"