diff --git a/apps/cli/src/test/kotlin/dev/diff2test/android/cli/D2tConfigTest.kt b/apps/cli/src/test/kotlin/dev/diff2test/android/cli/D2tConfigTest.kt index d742ea8..62bdede 100644 --- a/apps/cli/src/test/kotlin/dev/diff2test/android/cli/D2tConfigTest.kt +++ b/apps/cli/src/test/kotlin/dev/diff2test/android/cli/D2tConfigTest.kt @@ -163,6 +163,27 @@ class D2tConfigTest { assertContains(report, "Supported now: yes") } + @Test + fun `defaults gemini config to native generate content protocol`() { + val configFile = Files.createTempFile("d2t-gemini-config", ".toml") + Files.writeString( + configFile, + """ + [ai] + provider = "gemini" + """.trimIndent(), + ) + val loadResult = loadConfig(configFile) + val resolved = resolveAiConfiguration(loadResult, mapOf("GEMINI_API_KEY" to "sk-gem")) + + assertEquals(AiProvider.GEMINI, resolved?.provider) + assertEquals(AiProtocol.GEMINI_GENERATE_CONTENT, resolved?.protocol) + assertEquals("GEMINI_API_KEY", resolved?.apiKeyEnv) + assertEquals("gemini-2.5-pro", resolved?.model) + assertEquals("https://generativelanguage.googleapis.com/v1beta", resolved?.baseUrl) + assertTrue(resolved?.supportedByGenerator == true) + } + @Test fun `template does not include actual secrets`() { val template = defaultConfigTemplate() diff --git a/docs/providers/gemini.md b/docs/providers/gemini.md new file mode 100644 index 0000000..0220dfb --- /dev/null +++ b/docs/providers/gemini.md @@ -0,0 +1,37 @@ +# Gemini Provider + +`d2t` supports the native Gemini GenerateContent API. + +## Config + +```toml +[ai] +enabled = true +provider = "gemini" +protocol = "gemini-generate-content" +api_key_env = "GEMINI_API_KEY" +model = "gemini-2.5-pro" +base_url = "https://generativelanguage.googleapis.com/v1beta" +connect_timeout_seconds = 30 +request_timeout_seconds = 300 +``` + +## Environment + +```bash +export GEMINI_API_KEY="..." +``` + +## Verify + +```bash +d2t doctor +d2t auto --ai +``` + +## Notes + +- `d2t` calls `POST /v1beta/models/{model}:generateContent` +- authentication is sent with `x-goog-api-key` +- the request asks Gemini for JSON output with the generated test schema +- use `--strict-ai` if you want the command to fail instead of falling back when AI generation is rejected diff --git a/modules/test-generator/src/test/kotlin/dev/diff2test/android/testgenerator/OpenAiResponsesTestGeneratorTest.kt b/modules/test-generator/src/test/kotlin/dev/diff2test/android/testgenerator/OpenAiResponsesTestGeneratorTest.kt index fe5fbc7..f60d9b8 100644 --- a/modules/test-generator/src/test/kotlin/dev/diff2test/android/testgenerator/OpenAiResponsesTestGeneratorTest.kt +++ b/modules/test-generator/src/test/kotlin/dev/diff2test/android/testgenerator/OpenAiResponsesTestGeneratorTest.kt @@ -224,6 +224,30 @@ class OpenAiResponsesTestGeneratorTest { assertEquals(listOf("gemini"), payload.warnings) } + @Test + fun `gemini generator sends native header to generate content endpoint`() { + val capture = RequestCapture() + val generator = GeminiGenerateContentTestGenerator( + config = GeminiGenerateContentConfig( + apiKey = "sk-gem", + model = "gemini-2.5-pro", + baseUrl = "https://generativelanguage.googleapis.com/v1beta", + ), + httpClient = capturingHttpClient(capture, 200, geminiResponseBody()), + ) + + val bundle = generator.generate(plan(), context(), analysis()) + + assertContains(bundle.files.single().content, "class SignUpViewModelGeneratedTest") + val request = assertNotNull(capture.request) + assertEquals( + "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent", + request.uri().toString(), + ) + assertEquals("sk-gem", request.headers().firstValue("x-goog-api-key").orElse(null)) + assertEquals("application/json", request.headers().firstValue("Accept").orElse(null)) + } + @Test fun `normalizes junit test imports to kotlin test`() { val content = """ @@ -677,6 +701,22 @@ class OpenAiResponsesTestGeneratorTest { } } + private fun geminiResponseBody(): String = """ + { + "candidates": [ + { + "content": { + "parts": [ + { + "text": "{\"content\":\"package com.example.auth\\n\\nclass SignUpViewModelGeneratedTest\",\"warnings\":[]}" + } + ] + } + } + ] + } + """.trimIndent() + private fun anthropicResponseBody(): String = """ { "content": [