Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
37 changes: 37 additions & 0 deletions docs/providers/gemini.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """
Expand Down Expand Up @@ -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": [
Expand Down
Loading