Skip to content

Add unit tests for Gemini client #45

@benodiwal

Description

@benodiwal

Description

The Gemini AI provider client (src/providers/gemini/client.cpp) lacks unit tests. Adding tests would improve reliability and catch regressions.

Files

  • Existing code: src/providers/gemini/client.cpp, src/include/gemini_client.h
  • Test to create: tests/unit/test_gemini_client.cpp

Task

Create unit tests covering:

  1. Request building (build_request_body):

    • Generates correct JSON structure with user prompt
    • System prompt is included in systemInstruction field
    • Temperature and max_tokens are set in generationConfig
    • Handles optional fields correctly
  2. Response parsing (parse_response):

    • Valid 200 response extracts text from candidates[0].content.parts[0].text
    • Non-200 status codes return error response
    • Handles missing fields gracefully (missing candidates, content, parts, text)
    • Extracts error message from error response JSON
  3. Error handling:

    • Invalid API key error (401)
    • Rate limit error (429)
    • Malformed response JSON
    • Missing required fields in response

Example Test Structure

#include <gtest/gtest.h>
#include <nlohmann/json.hpp>
#include "gemini_client.h"

class GeminiClientTest : public ::testing::Test {
protected:
    void SetUp() override {
        client_ = std::make_unique<gemini::GeminiClient>("test-api-key");
    }
    
    std::unique_ptr<gemini::GeminiClient> client_;
};

TEST_F(GeminiClientTest, BuildRequestBodyIncludesUserPrompt) {
    gemini::GeminiRequest request;
    request.model = "gemini-2.0-flash";
    request.user_prompt = "Generate a query";
    request.system_prompt = "";
    
    std::string body = client_->build_request_body(request);
    auto json = nlohmann::json::parse(body);
    
    ASSERT_TRUE(json.contains("contents"));
    ASSERT_TRUE(json["contents"].is_array());
    EXPECT_EQ(json["contents"][0]["parts"][0]["text"], "Generate a query");
}

TEST_F(GeminiClientTest, BuildRequestBodyIncludesSystemPrompt) {
    gemini::GeminiRequest request;
    request.model = "gemini-2.0-flash";
    request.user_prompt = "Generate a query";
    request.system_prompt = "You are a SQL expert";
    
    std::string body = client_->build_request_body(request);
    auto json = nlohmann::json::parse(body);
    
    ASSERT_TRUE(json.contains("systemInstruction"));
    EXPECT_EQ(json["systemInstruction"]["parts"][0]["text"], "You are a SQL expert");
}

TEST_F(GeminiClientTest, BuildRequestBodyIncludesGenerationConfig) {
    gemini::GeminiRequest request;
    request.model = "gemini-2.0-flash";
    request.user_prompt = "Test";
    request.temperature = 0.7;
    request.max_tokens = 1000;
    
    std::string body = client_->build_request_body(request);
    auto json = nlohmann::json::parse(body);
    
    ASSERT_TRUE(json.contains("generationConfig"));
    EXPECT_DOUBLE_EQ(json["generationConfig"]["temperature"], 0.7);
    EXPECT_EQ(json["generationConfig"]["maxOutputTokens"], 1000);
}

TEST_F(GeminiClientTest, ParseResponseExtractsContent) {
    std::string response_body = R"({
        "candidates": [{
            "content": {
                "parts": [{"text": "SELECT * FROM users;"}]
            }
        }]
    })";
    
    auto result = client_->parse_response(response_body, 200);
    
    EXPECT_TRUE(result.success);
    EXPECT_EQ(result.text, "SELECT * FROM users;");
}

TEST_F(GeminiClientTest, ParseResponseHandlesHttpError) {
    std::string error_body = R"({
        "error": {
            "code": 401,
            "message": "Invalid API key"
        }
    })";
    
    auto result = client_->parse_response(error_body, 401);
    
    EXPECT_FALSE(result.success);
    EXPECT_TRUE(result.error_message.find("Invalid API key") != std::string::npos);
}

TEST_F(GeminiClientTest, ParseResponseHandlesMissingCandidates) {
    std::string response_body = R"({"usageMetadata": {}})";
    
    auto result = client_->parse_response(response_body, 200);
    
    EXPECT_FALSE(result.success);
    EXPECT_TRUE(result.error_message.find("Invalid response format") != std::string::npos);
}

TEST_F(GeminiClientTest, ParseResponseHandlesMalformedJson) {
    std::string response_body = "not valid json {{{";
    
    auto result = client_->parse_response(response_body, 200);
    
    EXPECT_FALSE(result.success);
    EXPECT_TRUE(result.error_message.find("parse error") != std::string::npos);
}

Acceptance Criteria

  • Tests cover build_request_body() with all optional fields
  • Tests cover parse_response() for success and error cases
  • Tests handle malformed JSON gracefully
  • Tests follow existing patterns in tests/unit/
  • All tests pass with ctest

Skills Required

  • C++
  • Google Test framework
  • JSON handling

Helpful Resources

Difficulty

🟡 Medium

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions