diff --git a/src/GenerativeAI.Microsoft/GenerativeAIImageGenerator.cs b/src/GenerativeAI.Microsoft/GenerativeAIImageGenerator.cs
new file mode 100644
index 0000000..ed93ce5
--- /dev/null
+++ b/src/GenerativeAI.Microsoft/GenerativeAIImageGenerator.cs
@@ -0,0 +1,131 @@
+#pragma warning disable MEAI001
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using GenerativeAI;
+using GenerativeAI.Core;
+using GenerativeAI.Types;
+using GenerativeAI.Microsoft.Extensions;
+using Microsoft.Extensions.AI;
+
+namespace GenerativeAI.Microsoft;
+
+///
+/// Implements Microsoft.Extensions.AI.IImageGenerator using the Google_GenerativeAI SDK by
+/// creating a GenerateContentRequest that requests image modality and forwarding it to
+/// .
+///
+public sealed class GenerativeAIImageGenerator : IImageGenerator
+{
+ ///
+ /// Underlying GenerativeModel instance.
+ ///
+ public GenerativeModel model { get; }
+
+ ///
+ /// Creates a new instance using an API key and optional model name.
+ ///
+ public GenerativeAIImageGenerator(string apiKey, string modelName = GoogleAIModels.Gemini2FlashPreviewImageGeneration)
+ {
+ model = new GenerativeModel(apiKey, modelName);
+ }
+
+ ///
+ /// Creates a new instance using a platform adapter and optional model name.
+ ///
+ public GenerativeAIImageGenerator(IPlatformAdapter adapter, string modelName = GoogleAIModels.Gemini2FlashPreviewImageGeneration)
+ {
+ model = new GenerativeModel(adapter, modelName);
+ }
+
+ ///
+ public void Dispose()
+ {
+ }
+
+ ///
+ public object? GetService(Type serviceType, object? serviceKey = null)
+ {
+ if (serviceKey == null && serviceType?.IsInstanceOfType(this) == true)
+ return this;
+ return null;
+ }
+
+ ///
+ public async Task GenerateAsync(ImageGenerationRequest request,
+ ImageGenerationOptions? options = null, CancellationToken cancellationToken = default)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(request);
+#else
+ if (request == null) throw new ArgumentNullException(nameof(request));
+#endif
+
+ var genRequest = ToGenerateContentRequest(request, options);
+ var resp = await model.GenerateContentAsync(genRequest, cancellationToken).ConfigureAwait(false);
+ return ToImageGenerationResponse(resp);
+ }
+
+ // Convert the Microsoft request/options into a model-specific GenerateContentRequest
+ private GenerateContentRequest ToGenerateContentRequest(ImageGenerationRequest request, ImageGenerationOptions? options)
+ {
+ List parts = [];
+ // Add prompt text (if any)
+ if (!string.IsNullOrEmpty(request.Prompt))
+ {
+ parts.Add(new(request.Prompt!));
+ }
+
+ // If original images provided (image edit scenario), add them as parts
+ if (request.OriginalImages != null)
+ {
+ foreach (var aiContent in request.OriginalImages)
+ {
+ parts.Add(aiContent.ToPart()!);
+ }
+ }
+
+ // Configure generation to request images
+ GenerationConfig generationConfig = options?.RawRepresentationFactory?.Invoke(this) as GenerationConfig ?? new();
+ generationConfig.CandidateCount = options?.Count ?? 1;
+
+ // We must request both text and image modalities to get images back
+ generationConfig.ResponseModalities = new List { Modality.TEXT, Modality.IMAGE };
+
+ if (options != null)
+ {
+ if (!string.IsNullOrEmpty(options.MediaType))
+ generationConfig.ResponseMimeType = options.MediaType;
+
+ // Map requested image size (basic heuristic)
+ if (options.ImageSize.HasValue)
+ {
+ var sz = options.ImageSize.Value;
+ if (sz.Width >= 1024 || sz.Height >= 1024)
+ generationConfig.MediaResolution = MediaResolution.MEDIA_RESOLUTION_HIGH;
+ else if (sz.Width >= 512 || sz.Height >= 512)
+ generationConfig.MediaResolution = MediaResolution.MEDIA_RESOLUTION_MEDIUM;
+ else
+ generationConfig.MediaResolution = MediaResolution.MEDIA_RESOLUTION_LOW;
+ }
+ }
+
+ return new GenerateContentRequest()
+ {
+ GenerationConfig = generationConfig,
+ Contents = [new() { Parts = parts }]
+ };
+ }
+
+
+ // Convert the model response to ImageGenerationResponse
+ private static ImageGenerationResponse ToImageGenerationResponse(GenerateContentResponse? resp)
+ {
+ var aiContents = resp?.Candidates?.FirstOrDefault()?.Content?.Parts.ToAiContents();
+ return new ImageGenerationResponse(aiContents) { RawRepresentation = resp };
+ }
+}
+#pragma warning restore MEAI001
diff --git a/src/GenerativeAI.Microsoft/GenerativeAIImagenGenerator.cs b/src/GenerativeAI.Microsoft/GenerativeAIImagenGenerator.cs
new file mode 100644
index 0000000..d260a78
--- /dev/null
+++ b/src/GenerativeAI.Microsoft/GenerativeAIImagenGenerator.cs
@@ -0,0 +1,163 @@
+#pragma warning disable MEAI001
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using GenerativeAI;
+using GenerativeAI.Core;
+using GenerativeAI.Types;
+using GenerativeAI.Clients;
+using GenerativeAI.Microsoft.Extensions;
+using Microsoft.Extensions.AI;
+
+namespace GenerativeAI.Microsoft;
+
+///
+/// Implements Microsoft.Extensions.AI.IImageGenerator by creating an ImagenModel via GenAI.CreateImageModel
+/// and calling .
+///
+public sealed class GenerativeAIImagenGenerator : IImageGenerator
+{
+ ///
+ /// Underlying ImagenModel instance created from the provided GenAI factory.
+ ///
+ public ImagenModel model { get; }
+
+ ///
+ /// Creates a new instance using an API key and optional model name.
+ ///
+ public GenerativeAIImagenGenerator(string apiKey, string modelName = GoogleAIModels.Imagen.Imagen3Generate002):
+ this(new GoogleAi(apiKey), modelName)
+ { }
+
+
+ ///
+ /// Creates a new instance using an existing factory and optional model name.
+ ///
+ public GenerativeAIImagenGenerator(GenAI genai, string modelName = GoogleAIModels.Imagen.Imagen3Generate002)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(genai);
+#else
+ if (genai == null) throw new ArgumentNullException(nameof(genai));
+#endif
+ model = genai.CreateImageModel(modelName);
+ }
+
+ ///
+ public void Dispose()
+ { }
+
+ ///
+ public object? GetService(Type serviceType, object? serviceKey = null)
+ {
+ if (serviceKey == null && serviceType?.IsInstanceOfType(this) == true)
+ return this;
+ return null;
+ }
+
+ ///
+ public async Task GenerateAsync(ImageGenerationRequest request,
+ ImageGenerationOptions? options = null, CancellationToken cancellationToken = default)
+ {
+#if NET6_0_OR_GREATER
+ ArgumentNullException.ThrowIfNull(request);
+#else
+ if (request == null) throw new ArgumentNullException(nameof(request));
+#endif
+
+ var imgRequest = ToGenerateImageRequest(request, options);
+ var resp = await model.GenerateImagesAsync(imgRequest, cancellationToken).ConfigureAwait(false);
+ return ToImageGenerationResponse(resp);
+ }
+
+ // Convert Microsoft ImageGenerationRequest + options to a GenerateImageRequest
+ private GenerateImageRequest ToGenerateImageRequest(ImageGenerationRequest request, ImageGenerationOptions? options)
+ {
+ var imgRequest = new GenerateImageRequest();
+ var instances = new List();
+
+ if (request.OriginalImages != null && request.OriginalImages.Any())
+ {
+ instances.AddRange(request.OriginalImages.Select(content => new ImageGenerationInstance
+ {
+ Prompt = request.Prompt,
+ Image = ConvertAiContentToImageSource(content)
+ }));
+ }
+ else
+ {
+ instances.Add(new ImageGenerationInstance { Prompt = request.Prompt });
+ }
+
+ ImageGenerationParameters parameters = options?.RawRepresentationFactory?.Invoke(this) as ImageGenerationParameters ?? new();
+ parameters.SampleCount = options?.Count ?? 1;
+
+ if (options != null)
+ {
+ if (!string.IsNullOrEmpty(options.MediaType))
+ {
+ parameters.OutputOptions = new OutputOptions { MimeType = options.MediaType };
+ }
+
+ if (options.ImageSize.HasValue)
+ {
+ var sz = options.ImageSize.Value;
+ parameters.AspectRatio = $"{sz.Width}:{sz.Height}";
+
+ }
+ }
+
+ return new GenerateImageRequest
+ {
+ Instances = instances,
+ Parameters = parameters
+ };
+ }
+
+ // Convert model response to Microsoft ImageGenerationResponse
+ private static ImageGenerationResponse ToImageGenerationResponse(GenerateImageResponse? resp)
+ {
+ var contents = new List();
+ if (resp?.Predictions != null)
+ {
+ foreach (var pred in resp.Predictions)
+ {
+ if (!string.IsNullOrEmpty(pred.BytesBase64Encoded))
+ {
+ var data = Convert.FromBase64String(pred.BytesBase64Encoded);
+ contents.Add(new DataContent(data, pred.MimeType ?? "image/png"));
+ }
+ }
+ }
+
+ return new ImageGenerationResponse(contents) { RawRepresentation = resp };
+ }
+
+ private static ImageSource? ConvertAiContentToImageSource(AIContent content)
+ {
+ if (content == null) return null;
+
+ if (content is DataContent dc)
+ {
+ return new ImageSource { BytesBase64Encoded = Convert.ToBase64String(dc.Data.ToArray()) };
+ }
+
+ if (content is UriContent uc)
+ {
+ var uriVal = uc.Uri?.ToString();
+
+ // Only treat known GCS URIs as storage references for Imagen API.
+ if (uriVal?.StartsWith("gs://", StringComparison.OrdinalIgnoreCase) == true ||
+ uriVal?.IndexOf("storage.googleapis.com", StringComparison.OrdinalIgnoreCase) >= 0)
+ {
+ return new ImageSource { GcsUri = uriVal };
+ }
+ }
+
+ return null;
+ }
+}
+#pragma warning restore MEAI001
diff --git a/src/GenerativeAI.Microsoft/README.md b/src/GenerativeAI.Microsoft/README.md
index 5fcae61..d364bc1 100644
--- a/src/GenerativeAI.Microsoft/README.md
+++ b/src/GenerativeAI.Microsoft/README.md
@@ -85,6 +85,66 @@ public class MyChatService
}
```
+### 4. Using IImageClient
+
+Image client can also be used from a service as above. Here's a sample that shows it's capabilities.
+
+```C#
+using System.Diagnostics;
+using GenerativeAI.Microsoft;
+using Microsoft.Extensions.AI;
+
+#pragma warning disable MEAI001
+// ImageGen creates high quality initial images
+IImageGenerator imageGenerator = new GenerativeAIImagenGenerator(
+ Environment.GetEnvironmentVariable("GOOGLE_API_KEY"),
+ "imagen-4.0-fast-generate-001");
+
+var response = await imageGenerator.GenerateImagesAsync("A clown fish with orange and black-bordered white stripes.");
+var img1 = GetImageContent(response);
+SaveImage(img1, "i1.png");
+ShowImage("i1.png");
+
+response = await imageGenerator.GenerateImagesAsync("A blue tang fish, blue and black with yellow tipped fin and tail.");
+var img2 = GetImageContent(response);
+SaveImage(img2, "i2.png");
+ShowImage("i2.png");
+
+// Imagen cannot edit, but we can use the gemini model for that.
+IImageGenerator imageGeneratorEdit = new GenerativeAIImageGenerator(
+ Environment.GetEnvironmentVariable("GOOGLE_API_KEY"),
+ "gemini-2.5-flash-image-preview");
+var request = new ImageGenerationRequest()
+{
+ Prompt = "Combine the two images into a single scene.",
+ OriginalImages = new[] { img1, img2 }
+};
+response = await imageGeneratorEdit.GenerateAsync(request);
+var scene = GetImageContent(response);
+SaveImage(scene, "scene.png");
+ShowImage("scene.png");
+
+response = await imageGeneratorEdit.EditImageAsync(scene, "Change the setting to a fish tank.");
+var edit = GetImageContent(response);
+SaveImage(edit, "edit.png");
+ShowImage("edit.png");
+
+DataContent GetImageContent(ImageGenerationResponse response) =>
+ response.Contents.OfType().Single();
+
+void SaveImage(DataContent content, string fileName) =>
+ File.WriteAllBytes(fileName, content.Data.Span);
+
+void ShowImage(string fileName)
+{
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = fileName,
+ UseShellExecute = true
+ });
+}
+```
+
## Dependencies
- [Google_GenerativeAI](https://github.com/Google_GenerativeAI) (Unofficial C# Google Generative AI SDK)
diff --git a/tests/GenerativeAI.Microsoft.Tests/Microsoft_ImageGenerator_Tests.cs b/tests/GenerativeAI.Microsoft.Tests/Microsoft_ImageGenerator_Tests.cs
new file mode 100644
index 0000000..36d68ad
--- /dev/null
+++ b/tests/GenerativeAI.Microsoft.Tests/Microsoft_ImageGenerator_Tests.cs
@@ -0,0 +1,256 @@
+#pragma warning disable MEAI001
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using GenerativeAI.Core;
+using GenerativeAI.Microsoft;
+using GenerativeAI.Microsoft.Extensions;
+using GenerativeAI.Tests;
+using GenerativeAI.Tests.Base;
+using Microsoft.Extensions.AI;
+using Shouldly;
+using Xunit;
+
+namespace GenerativeAI.Tests.Microsoft;
+
+#pragma warning disable MEAI001
+
+///
+/// Tests for .
+/// Demonstrates a style similar to other Generative AI tests,
+/// using xUnit and Shouldly for assertions.
+///
+[TestCaseOrderer(
+ typeof(TestPriorityAttribute))]
+public class Microsoft_ImageGenerator_Tests : TestBase
+{
+ private const string DefaultTestModelName = GoogleAIModels.DefaultGeminiModel;
+
+ public Microsoft_ImageGenerator_Tests(ITestOutputHelper helper) : base(helper)
+ {
+ }
+
+ #region Helper Methods
+
+ ///
+ /// Creates a minimal mock or fake platform adapter for testing.
+ /// This can be replaced by a more feature-complete mock if needed.
+ ///
+ private IPlatformAdapter CreateTestPlatformAdapter()
+ {
+ return new GoogleAIPlatformAdapter("test_api_key");
+ }
+
+ #endregion
+
+ #region Constructor Tests
+
+ [Fact, TestPriority(1)]
+ public void ShouldCreateWithBasicConstructor()
+ {
+ // Arrange
+ var adapter = CreateTestPlatformAdapter();
+
+ // Act
+ var generator = new GenerativeAIImageGenerator(adapter);
+
+ // Assert
+ generator.ShouldNotBeNull();
+ generator.model.ShouldNotBeNull();
+ generator.model.Model.ShouldBe(DefaultTestModelName);
+ Console.WriteLine("GenerativeAIImageGenerator created successfully with the basic constructor.");
+ }
+
+ [Fact, TestPriority(2)]
+ public void ShouldCreateWithCustomModelName()
+ {
+ // Arrange
+ var adapter = CreateTestPlatformAdapter();
+ var customModel = "my-custom-model";
+
+ // Act
+ var generator = new GenerativeAIImageGenerator(adapter, customModel);
+
+ // Assert
+ generator.ShouldNotBeNull();
+ generator.model.ShouldNotBeNull();
+ generator.model.Model.ShouldBe(customModel);
+ Console.WriteLine($"GenerativeAIImageGenerator created with custom model: {customModel}");
+ }
+
+ [Fact, TestPriority(3)]
+ public void ShouldCreateWithApiKeyConstructor()
+ {
+ // Arrange
+ const string testApiKey = "test_api_key";
+
+ // Act
+ var generator = new GenerativeAIImageGenerator(testApiKey);
+
+ // Assert
+ generator.ShouldNotBeNull();
+ generator.model.ShouldNotBeNull();
+ generator.model.Model.ShouldBe(DefaultTestModelName);
+ Console.WriteLine("GenerativeAIImageGenerator created successfully with API key constructor.");
+ }
+
+ [Fact, TestPriority(4)]
+ public void ShouldCreateWithApiKeyAndCustomModel()
+ {
+ // Arrange
+ const string testApiKey = "test_api_key";
+ const string customModel = "custom-gemini-model";
+
+ // Act
+ var generator = new GenerativeAIImageGenerator(testApiKey, customModel);
+
+ // Assert
+ generator.ShouldNotBeNull();
+ generator.model.ShouldNotBeNull();
+ generator.model.Model.ShouldBe(customModel);
+ Console.WriteLine($"GenerativeAIImageGenerator created with API key and custom model: {customModel}");
+ }
+
+ #endregion
+
+ #region GenerateAsync Tests
+
+ [Fact, TestPriority(5)]
+ public async Task ShouldThrowArgumentNullExceptionWhenRequestIsNull()
+ {
+ // Arrange
+ var adapter = CreateTestPlatformAdapter();
+ var generator = new GenerativeAIImageGenerator(adapter);
+
+ // Act & Assert
+ await Should.ThrowAsync(async () =>
+ {
+ await generator.GenerateAsync(null!, cancellationToken: TestContext.Current.CancellationToken);
+ });
+ Console.WriteLine("GenerateAsync threw ArgumentNullException as expected when request was null.");
+ }
+
+ [Fact, TestPriority(6)]
+ public async Task ShouldReturnImageGenerationResponseOnValidInput()
+ {
+ Assert.SkipWhen(!IsGoogleApiKeySet, GoogleTestSkipMessage);
+
+ // Arrange
+ var adapter = GetTestGooglePlatform();
+ var generator = new GenerativeAIImageGenerator(adapter);
+
+ var request = new ImageGenerationRequest("Generate an image of a beautiful sunset over mountains");
+ var options = new ImageGenerationOptions
+ {
+ Count = 1,
+ MediaType = "image/png"
+ };
+
+ // Act
+ var result = await generator.GenerateAsync(request, options, cancellationToken: TestContext.Current.CancellationToken);
+
+ // Assert
+ result.ShouldNotBeNull();
+ Console.WriteLine("GenerateAsync returned a valid result.");
+
+ // Check if the result has content - either check the raw response or the constructed response
+ if (result.RawRepresentation != null)
+ {
+ Console.WriteLine("Raw representation is available.");
+ }
+ }
+
+ #endregion
+
+ #region GetService Tests
+
+ [Fact, TestPriority(7)]
+ public void ShouldReturnSelfFromGetServiceIfTypeMatches()
+ {
+ // Arrange
+ var adapter = CreateTestPlatformAdapter();
+ var generator = new GenerativeAIImageGenerator(adapter);
+
+ // Act
+ var service = generator.GetService(typeof(GenerativeAIImageGenerator));
+
+ // Assert
+ service.ShouldNotBeNull();
+ service.ShouldBeOfType();
+ service.ShouldBe(generator);
+ Console.WriteLine("GetService returned the correct instance when serviceType matches the generator type.");
+ }
+
+ [Fact, TestPriority(8)]
+ public void ShouldReturnSelfFromGetServiceForIImageGenerator()
+ {
+ // Arrange
+ var adapter = CreateTestPlatformAdapter();
+ var generator = new GenerativeAIImageGenerator(adapter);
+
+ // Act
+ var service = generator.GetService(typeof(IImageGenerator));
+
+ // Assert
+ service.ShouldNotBeNull();
+ service.ShouldBeOfType();
+ service.ShouldBe(generator);
+ Console.WriteLine("GetService returned the correct instance when serviceType is IImageGenerator.");
+ }
+
+ [Fact, TestPriority(9)]
+ public void ShouldReturnNullFromGetServiceIfTypeDoesNotMatch()
+ {
+ // Arrange
+ var adapter = CreateTestPlatformAdapter();
+ var generator = new GenerativeAIImageGenerator(adapter);
+
+ // Act
+ var service = generator.GetService(typeof(string));
+
+ // Assert
+ service.ShouldBeNull();
+ Console.WriteLine("GetService returned null when the requested serviceType did not match.");
+ }
+
+ [Fact, TestPriority(10)]
+ public void ShouldReturnNullFromGetServiceWithServiceKey()
+ {
+ // Arrange
+ var adapter = CreateTestPlatformAdapter();
+ var generator = new GenerativeAIImageGenerator(adapter);
+
+ // Act
+ var service = generator.GetService(typeof(GenerativeAIImageGenerator), "some_key");
+
+ // Assert
+ service.ShouldBeNull();
+ Console.WriteLine("GetService returned null when serviceKey was provided.");
+ }
+
+ #endregion
+
+ #region Dispose Tests
+
+ [Fact, TestPriority(11)]
+ public void ShouldDisposeWithoutException()
+ {
+ // Arrange
+ var adapter = CreateTestPlatformAdapter();
+ var generator = new GenerativeAIImageGenerator(adapter);
+
+ // Act & Assert
+ Should.NotThrow(() => generator.Dispose());
+ Console.WriteLine("Dispose completed without throwing an exception.");
+ }
+
+ #endregion
+
+ protected override IPlatformAdapter GetTestGooglePlatform()
+ {
+ Assert.SkipWhen(!IsGoogleApiKeySet, GoogleTestSkipMessage);
+ return new GoogleAIPlatformAdapter(EnvironmentVariables.GOOGLE_API_KEY);
+ }
+}
+#pragma warning restore MEAI001
diff --git a/tests/GenerativeAI.Microsoft.Tests/Microsoft_ImagenGenerator_Tests.cs b/tests/GenerativeAI.Microsoft.Tests/Microsoft_ImagenGenerator_Tests.cs
new file mode 100644
index 0000000..eecf7b4
--- /dev/null
+++ b/tests/GenerativeAI.Microsoft.Tests/Microsoft_ImagenGenerator_Tests.cs
@@ -0,0 +1,288 @@
+#pragma warning disable MEAI001
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using GenerativeAI;
+using GenerativeAI.Core;
+using GenerativeAI.Microsoft;
+using GenerativeAI.Microsoft.Extensions;
+using GenerativeAI.Tests;
+using GenerativeAI.Tests.Base;
+using Microsoft.Extensions.AI;
+using Shouldly;
+using Xunit;
+
+namespace GenerativeAI.Tests.Microsoft;
+
+#pragma warning disable MEAI001
+
+///
+/// Tests for .
+/// Demonstrates a style similar to other Generative AI tests,
+/// using xUnit and Shouldly for assertions.
+///
+[TestCaseOrderer(
+ typeof(TestPriorityAttribute))]
+public class Microsoft_ImagenGenerator_Tests : TestBase
+{
+ private const string DefaultTestModelName = GoogleAIModels.Imagen.Imagen3FastGenerate001;
+
+ public Microsoft_ImagenGenerator_Tests(ITestOutputHelper helper) : base(helper)
+ {
+ }
+
+ #region Helper Methods
+
+ ///
+ /// Creates a minimal mock GoogleAi instance for testing.
+ ///
+ private GoogleAi CreateTestGoogleAi()
+ {
+ return new GoogleAi("test_api_key");
+ }
+
+ #endregion
+
+ #region Constructor Tests
+
+ [Fact, TestPriority(1)]
+ public void ShouldCreateWithApiKeyConstructor()
+ {
+ // Arrange
+ const string testApiKey = "test_api_key";
+
+ // Act
+ var generator = new GenerativeAIImagenGenerator(testApiKey);
+
+ // Assert
+ generator.ShouldNotBeNull();
+ generator.model.ShouldNotBeNull();
+ Console.WriteLine("GenerativeAIImagenGenerator created successfully with API key constructor.");
+ }
+
+ [Fact, TestPriority(2)]
+ public void ShouldCreateWithApiKeyAndCustomModel()
+ {
+ // Arrange
+ const string testApiKey = "test_api_key";
+ const string customModel = "custom-imagen-model";
+
+ // Act
+ var generator = new GenerativeAIImagenGenerator(testApiKey, customModel);
+
+ // Assert
+ generator.ShouldNotBeNull();
+ generator.model.ShouldNotBeNull();
+ Console.WriteLine($"GenerativeAIImagenGenerator created with API key and custom model: {customModel}");
+ }
+
+ [Fact, TestPriority(3)]
+ public void ShouldCreateWithGenAIConstructor()
+ {
+ // Arrange
+ var genAi = CreateTestGoogleAi();
+
+ // Act
+ var generator = new GenerativeAIImagenGenerator(genAi);
+
+ // Assert
+ generator.ShouldNotBeNull();
+ generator.model.ShouldNotBeNull();
+ Console.WriteLine("GenerativeAIImagenGenerator created successfully with GenAI constructor.");
+ }
+
+ [Fact, TestPriority(4)]
+ public void ShouldCreateWithGenAIAndCustomModel()
+ {
+ // Arrange
+ var genAi = CreateTestGoogleAi();
+ const string customModel = "custom-imagen-model";
+
+ // Act
+ var generator = new GenerativeAIImagenGenerator(genAi, customModel);
+
+ // Assert
+ generator.ShouldNotBeNull();
+ generator.model.ShouldNotBeNull();
+ Console.WriteLine($"GenerativeAIImagenGenerator created with GenAI and custom model: {customModel}");
+ }
+
+ [Fact, TestPriority(5)]
+ public void ShouldThrowArgumentNullExceptionWhenGenAIIsNull()
+ {
+ // Arrange & Act & Assert
+ Should.Throw(() =>
+ {
+ var generator = new GenerativeAIImagenGenerator((GenAI)null!);
+ });
+ Console.WriteLine("Constructor threw ArgumentNullException as expected when GenAI was null.");
+ }
+
+ #endregion
+
+ #region GenerateAsync Tests
+
+ [Fact, TestPriority(6)]
+ public async Task ShouldThrowArgumentNullExceptionWhenRequestIsNull()
+ {
+ // Arrange
+ const string testApiKey = "test_api_key";
+ var generator = new GenerativeAIImagenGenerator(testApiKey);
+
+ // Act & Assert
+ await Should.ThrowAsync(async () =>
+ {
+ await generator.GenerateAsync(null!, cancellationToken: TestContext.Current.CancellationToken);
+ });
+ Console.WriteLine("GenerateAsync threw ArgumentNullException as expected when request was null.");
+ }
+
+ [Fact, TestPriority(7)]
+ public async Task ShouldReturnImageGenerationResponseOnValidInput()
+ {
+ Assert.SkipWhen(!IsGoogleApiKeySet, GoogleTestSkipMessage);
+
+ // Arrange
+ var genAi = new GoogleAi(EnvironmentVariables.GOOGLE_API_KEY);
+ var generator = new GenerativeAIImagenGenerator(genAi);
+
+ var request = new ImageGenerationRequest("A beautiful landscape with mountains and a lake");
+ var options = new ImageGenerationOptions
+ {
+ Count = 1,
+ MediaType = "image/png"
+ };
+
+ // Act
+ var result = await generator.GenerateAsync(request, options, cancellationToken: TestContext.Current.CancellationToken);
+
+ // Assert
+ result.ShouldNotBeNull();
+ Console.WriteLine("GenerateAsync returned a valid result.");
+
+ // Check if the result has content - either check the raw response or the constructed response
+ if (result.RawRepresentation != null)
+ {
+ Console.WriteLine("Raw representation is available.");
+ }
+ }
+
+ [Fact, TestPriority(8)]
+ public async Task ShouldHandleImageGenerationWithoutOptions()
+ {
+ Assert.SkipWhen(!IsGoogleApiKeySet, GoogleTestSkipMessage);
+
+ // Arrange
+ var genAi = new GoogleAi(EnvironmentVariables.GOOGLE_API_KEY);
+ var generator = new GenerativeAIImagenGenerator(genAi);
+
+ var request = new ImageGenerationRequest("A simple drawing of a cat");
+
+ // Act
+ var result = await generator.GenerateAsync(request, null, cancellationToken: TestContext.Current.CancellationToken);
+
+ // Assert
+ result.ShouldNotBeNull();
+ Console.WriteLine("GenerateAsync without options returned a valid result.");
+
+ // Check if the result has content - either check the raw response or the constructed response
+ if (result.RawRepresentation != null)
+ {
+ Console.WriteLine("Raw representation is available.");
+ }
+ }
+
+ #endregion
+
+ #region GetService Tests
+
+ [Fact, TestPriority(9)]
+ public void ShouldReturnSelfFromGetServiceIfTypeMatches()
+ {
+ // Arrange
+ const string testApiKey = "test_api_key";
+ var generator = new GenerativeAIImagenGenerator(testApiKey);
+
+ // Act
+ var service = generator.GetService(typeof(GenerativeAIImagenGenerator));
+
+ // Assert
+ service.ShouldNotBeNull();
+ service.ShouldBeOfType();
+ service.ShouldBe(generator);
+ Console.WriteLine("GetService returned the correct instance when serviceType matches the generator type.");
+ }
+
+ [Fact, TestPriority(10)]
+ public void ShouldReturnSelfFromGetServiceForIImageGenerator()
+ {
+ // Arrange
+ const string testApiKey = "test_api_key";
+ var generator = new GenerativeAIImagenGenerator(testApiKey);
+
+ // Act
+ var service = generator.GetService(typeof(IImageGenerator));
+
+ // Assert
+ service.ShouldNotBeNull();
+ service.ShouldBeOfType();
+ service.ShouldBe(generator);
+ Console.WriteLine("GetService returned the correct instance when serviceType is IImageGenerator.");
+ }
+
+ [Fact, TestPriority(11)]
+ public void ShouldReturnNullFromGetServiceIfTypeDoesNotMatch()
+ {
+ // Arrange
+ const string testApiKey = "test_api_key";
+ var generator = new GenerativeAIImagenGenerator(testApiKey);
+
+ // Act
+ var service = generator.GetService(typeof(string));
+
+ // Assert
+ service.ShouldBeNull();
+ Console.WriteLine("GetService returned null when the requested serviceType did not match.");
+ }
+
+ [Fact, TestPriority(12)]
+ public void ShouldReturnNullFromGetServiceWithServiceKey()
+ {
+ // Arrange
+ const string testApiKey = "test_api_key";
+ var generator = new GenerativeAIImagenGenerator(testApiKey);
+
+ // Act
+ var service = generator.GetService(typeof(GenerativeAIImagenGenerator), "some_key");
+
+ // Assert
+ service.ShouldBeNull();
+ Console.WriteLine("GetService returned null when serviceKey was provided.");
+ }
+
+ #endregion
+
+ #region Dispose Tests
+
+ [Fact, TestPriority(13)]
+ public void ShouldDisposeWithoutException()
+ {
+ // Arrange
+ const string testApiKey = "test_api_key";
+ var generator = new GenerativeAIImagenGenerator(testApiKey);
+
+ // Act & Assert
+ Should.NotThrow(() => generator.Dispose());
+ Console.WriteLine("Dispose completed without throwing an exception.");
+ }
+
+ #endregion
+
+ protected override IPlatformAdapter GetTestGooglePlatform()
+ {
+ Assert.SkipWhen(!IsGoogleApiKeySet, GoogleTestSkipMessage);
+ return new GoogleAIPlatformAdapter(EnvironmentVariables.GOOGLE_API_KEY);
+ }
+}
+#pragma warning restore MEAI001