1 line
30 KiB
JSON
1 line
30 KiB
JSON
{"repo": "artcava/xposter", "files": {"tests/Models/DeepSeekOptionsTests.cs": {"8734f8443e5e4733245a61002bbf6882d1bb82fa": {"t": 1785746205.0735204, "n": 1071, "h": "76d48c1a701eba83", "c": "using XPoster.Models;\n\nnamespace XPoster.Tests.Models;\n\n/// <summary>\n/// Covers default values and shape of <see cref=\"DeepSeekOptions\"/>.\n/// Also acts as a regression test for issue #126: ApiVersion must not exist.\n/// </summary>\npublic class DeepSeekOptionsTests\n{\n [Fact]\n public void DeepSeekOptions_Defaults_AreCorrect()\n {\n var options = new DeepSeekOptions();\n\n Assert.Equal(\"https://api.deepseek.com\", options.Endpoint);\n Assert.Equal(\"deepseek-chat\", options.TextModelName);\n Assert.Equal(string.Empty, options.ApiKey);\n }\n\n /// <summary>\n /// Regression test for issue #126: ApiVersion was copied from AzureFoundryOptions\n /// but is not needed by the DeepSeek API and must not exist on this class.\n /// </summary>\n [Fact]\n public void DeepSeekOptions_DoesNotExpose_ApiVersionProperty()\n {\n var property = typeof(DeepSeekOptions)\n .GetProperty(\"ApiVersion\", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);\n\n Assert.Null(property);\n }\n}\n"}, "9f2aac816153e06d650dc898d53528abf484cf3e": {"t": 1785760596.4877608, "n": 1071, "h": "76d48c1a701eba83", "c": "using XPoster.Models;\n\nnamespace XPoster.Tests.Models;\n\n/// <summary>\n/// Covers default values and shape of <see cref=\"DeepSeekOptions\"/>.\n/// Also acts as a regression test for issue #126: ApiVersion must not exist.\n/// </summary>\npublic class DeepSeekOptionsTests\n{\n [Fact]\n public void DeepSeekOptions_Defaults_AreCorrect()\n {\n var options = new DeepSeekOptions();\n\n Assert.Equal(\"https://api.deepseek.com\", options.Endpoint);\n Assert.Equal(\"deepseek-chat\", options.TextModelName);\n Assert.Equal(string.Empty, options.ApiKey);\n }\n\n /// <summary>\n /// Regression test for issue #126: ApiVersion was copied from AzureFoundryOptions\n /// but is not needed by the DeepSeek API and must not exist on this class.\n /// </summary>\n [Fact]\n public void DeepSeekOptions_DoesNotExpose_ApiVersionProperty()\n {\n var property = typeof(DeepSeekOptions)\n .GetProperty(\"ApiVersion\", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);\n\n Assert.Null(property);\n }\n}\n"}, "d988630a4f05ea07315d04ce4b19e4f98ba7fca3": {"t": 1785760598.8266518, "n": 2043, "h": "3a24d98604aa6395", "c": "using XPoster.Models;\n\nnamespace XPoster.Tests.Models;\n\n/// <summary>\n/// Covers default values and shape of <see cref=\"DeepSeekOptions\"/>.\n/// Also acts as a regression test for issue #126: ApiVersion must not exist.\n/// </summary>\npublic class DeepSeekOptionsTests\n{\n [Fact]\n public void DeepSeekOptions_Defaults_AreCorrect()\n {\n var options = new DeepSeekOptions();\n\n Assert.Equal(\"https://api.deepseek.com\", options.Endpoint);\n Assert.Equal(\"deepseek-chat\", options.DeploymentName);\n Assert.Equal(string.Empty, options.ApiKey);\n Assert.Equal(0.5, options.SummaryTemperature);\n Assert.Equal(5, options.SummaryMaxTokensPerChar);\n Assert.Equal(50, options.SummarySafetyMarginChars);\n Assert.Equal(60, options.ImagePromptMaxTokens);\n Assert.Equal(0.7, options.ImagePromptTemperature);\n }\n\n [Fact]\n public void DeepSeekOptions_SummarySystemPromptTemplate_ContainsMaxCharsPlaceholder()\n {\n var options = new DeepSeekOptions();\n Assert.Contains(\"{MaxChars}\", options.SummarySystemPromptTemplate);\n }\n\n [Fact]\n public void DeepSeekOptions_SummaryUserPromptTemplate_ContainsTextPlaceholder()\n {\n var options = new DeepSeekOptions();\n Assert.Contains(\"{Text}\", options.SummaryUserPromptTemplate);\n }\n\n [Fact]\n public void DeepSeekOptions_ImagePromptUserTemplate_ContainsSummaryPlaceholder()\n {\n var options = new DeepSeekOptions();\n Assert.Contains(\"{Summary}\", options.ImagePromptUserTemplate);\n }\n\n /// <summary>\n /// Regression test for issue #126: ApiVersion was copied from AzureFoundryOptions\n /// but is not needed by the DeepSeek API and must not exist on this class.\n /// </summary>\n [Fact]\n public void DeepSeekOptions_DoesNotExpose_ApiVersionProperty()\n {\n var property = typeof(DeepSeekOptions)\n .GetProperty(\"ApiVersion\", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);\n\n Assert.Null(property);\n }\n}\n"}}, "tests/Integration/AiClientsResiliencePipelineTests.cs": {"8734f8443e5e4733245a61002bbf6882d1bb82fa": {"t": 1785746205.689518, "n": 2909, "h": "e51ee0eed6fc08e0", "c": "using System.Net;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace XPoster.Tests.Integration;\n\n[Trait(\"Category\", \"Integration\")]\npublic sealed class AiClientsResiliencePipelineTests : PollyIntegrationTestBase\n{\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 30, 180)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01\", 30, 180)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 30, 180)]\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 60, 300)]\n public async Task Polly_AiClient_RetriesOn429_AndEventuallySucceeds(\n string clientName, string baseUrl, string path,\n int attemptTimeoutSeconds, int totalRequestTimeoutSeconds)\n {\n var responseBody = \"{\\\"choices\\\":[{\\\"message\\\":{\\\"content\\\":\\\"ok\\\"}}]}\";\n var handler = BuildSequenceHandler(\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.OK, responseBody));\n\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: attemptTimeoutSeconds,\n totalRequestTimeoutSeconds: totalRequestTimeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n\n var response = await client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\"));\n\n Assert.Equal(HttpStatusCode.OK, response.StatusCode);\n }\n\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 180)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4\", 180)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 180)]\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 300)]\n public async Task Polly_AiClient_AttemptTimeout_CancelsSlowRequest(\n string clientName, string baseUrl, string path,\n int totalRequestTimeoutSeconds)\n {\n var handler = BuildDelayedHandler(delayMs: 5_000);\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: 1,\n totalRequestTimeoutSeconds: totalRequestTimeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n client.Timeout = TimeSpan.FromSeconds(totalRequestTimeoutSeconds);\n\n await Assert.ThrowsAnyAsync<Exception>(() =>\n client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\")));\n }\n}\n"}, "ba952a0c10d866252e4ea0e2577ce0f59b42c8e8": {"t": 1785760598.9154234, "n": 2909, "h": "e51ee0eed6fc08e0", "c": "using System.Net;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace XPoster.Tests.Integration;\n\n[Trait(\"Category\", \"Integration\")]\npublic sealed class AiClientsResiliencePipelineTests : PollyIntegrationTestBase\n{\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 30, 180)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01\", 30, 180)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 30, 180)]\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 60, 300)]\n public async Task Polly_AiClient_RetriesOn429_AndEventuallySucceeds(\n string clientName, string baseUrl, string path,\n int attemptTimeoutSeconds, int totalRequestTimeoutSeconds)\n {\n var responseBody = \"{\\\"choices\\\":[{\\\"message\\\":{\\\"content\\\":\\\"ok\\\"}}]}\";\n var handler = BuildSequenceHandler(\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.OK, responseBody));\n\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: attemptTimeoutSeconds,\n totalRequestTimeoutSeconds: totalRequestTimeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n\n var response = await client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\"));\n\n Assert.Equal(HttpStatusCode.OK, response.StatusCode);\n }\n\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 180)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4\", 180)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 180)]\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 300)]\n public async Task Polly_AiClient_AttemptTimeout_CancelsSlowRequest(\n string clientName, string baseUrl, string path,\n int totalRequestTimeoutSeconds)\n {\n var handler = BuildDelayedHandler(delayMs: 5_000);\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: 1,\n totalRequestTimeoutSeconds: totalRequestTimeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n client.Timeout = TimeSpan.FromSeconds(totalRequestTimeoutSeconds);\n\n await Assert.ThrowsAnyAsync<Exception>(() =>\n client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\")));\n }\n}\n"}, "678a1f96545b76f20b77adab58fe7b9023899ecb": {"t": 1785760603.7592509, "n": 3182, "h": "b7f8ac893a1cd014", "c": "using System.Net;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace XPoster.Tests.Integration;\n\n[Trait(\"Category\", \"Integration\")]\npublic sealed class AiClientsResiliencePipelineTests : PollyIntegrationTestBase\n{\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 30, 180)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01\", 30, 180)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 30, 180)]\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 60, 300)]\n public async Task Polly_AiClient_RetriesOn429_AndEventuallySucceeds(\n string clientName, string baseUrl, string path,\n int attemptTimeoutSeconds, int totalRequestTimeoutSeconds)\n {\n var responseBody = \"{\\\"choices\\\":[{\\\"message\\\":{\\\"content\\\":\\\"ok\\\"}}]}\";\n var handler = BuildSequenceHandler(\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.OK, responseBody));\n\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: attemptTimeoutSeconds,\n totalRequestTimeoutSeconds: totalRequestTimeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n\n var response = await client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\"));\n\n Assert.Equal(HttpStatusCode.OK, response.StatusCode);\n }\n\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 180)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4\", 180)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 180)]\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 300)]\n public async Task Polly_AiClient_AttemptTimeout_CancelsSlowRequest(\n string clientName, string baseUrl, string path,\n int totalRequestTimeoutSeconds)\n {\n var handler = BuildDelayedHandler(delayMs: 5_000);\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: 1,\n totalRequestTimeoutSeconds: totalRequestTimeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n client.Timeout = TimeSpan.FromSeconds(totalRequestTimeoutSeconds);\n\n await Assert.ThrowsAnyAsync<Exception>(() =>\n client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\")));\n }\n}\n"}, "b7f94b0bc4a16e6208eacb4dd12e4854e0470fe0": {"t": 1785760604.9108214, "n": 3195, "h": "4d64e20c6a0694ab", "c": "using System.Net;\nusing Microsoft.Extensions.DependencyInjection;\nusing Xunit;\n\nnamespace XPoster.Tests.Integration;\n\n[Trait(\"Category\", \"Integration\")]\npublic sealed class AiClientsResiliencePipelineTests : PollyIntegrationTestBase\n{\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 30, 180)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01\", 30, 180)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 30, 180)]\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 60, 300)]\n public async Task Polly_AiClient_RetriesOn429_AndEventuallySucceeds(\n string clientName, string baseUrl, string path,\n int attemptTimeoutSeconds, int totalRequestTimeoutSeconds)\n {\n var responseBody = \"{\\\"choices\\\":[{\\\"message\\\":{\\\"content\\\":\\\"ok\\\"}}]}\";\n var handler = BuildSequenceHandler(\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.OK, responseBody));\n\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: attemptTimeoutSeconds,\n totalRequestTimeoutSeconds: totalRequestTimeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n\n var response = await client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\"));\n\n Assert.Equal(HttpStatusCode.OK, response.StatusCode);\n }\n\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 180)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4\", 180)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 180)]\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 300)]\n public async Task Polly_AiClient_AttemptTimeout_CancelsSlowRequest(\n string clientName, string baseUrl, string path,\n int totalRequestTimeoutSeconds)\n {\n var handler = BuildDelayedHandler(delayMs: 5_000);\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: 1,\n totalRequestTimeoutSeconds: totalRequestTimeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n client.Timeout = TimeSpan.FromSeconds(totalRequestTimeoutSeconds);\n\n await Assert.ThrowsAnyAsync<Exception>(() =>\n client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\")));\n }\n}\n"}, "be72addb294f8bbb041da23730a5d367e60772f6": {"t": 1785760608.5397758, "n": 3806, "h": "136a5532e3e861e5", "c": "using System.Net;\nusing Microsoft.Extensions.DependencyInjection;\nusing Xunit;\n\nnamespace XPoster.Tests.Integration;\n\n/// <summary>\n/// Integration tests verifying that the Polly standard resilience pipeline is correctly\n/// wired for the AI-provider named HTTP clients: <c>OpenAI</c>, <c>AzureFoundry</c>,\n/// <c>DeepSeek</c>, and <c>FalAi</c>.\n/// </summary>\npublic sealed class AiClientsResiliencePipelineTests : PollyIntegrationTestBase\n{\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 30, 180)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01\", 30, 180)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 30, 180)]\n // FalAi uses a 60-second attempt timeout; TotalRequestTimeout must exceed it.\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 60, 300)]\n public async Task Polly_AiClient_RetriesOn429_AndEventuallySucceeds(\n string clientName, string baseUrl, string path,\n int attemptTimeoutSeconds, int totalRequestTimeoutSeconds)\n {\n var responseBody = \"{\\\"choices\\\":[{\\\"message\\\":{\\\"content\\\":\\\"ok\\\"}}]}\";\n var handler = BuildSequenceHandler(\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.OK, responseBody));\n\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: attemptTimeoutSeconds,\n totalRequestTimeoutSeconds: totalRequestTimeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n\n var response = await client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\"));\n\n Assert.Equal(HttpStatusCode.OK, response.StatusCode);\n }\n\n [Theory]\n // attemptTimeoutSeconds is overridden to 1 s inside the test body so the test\n // completes quickly; the InlineData value is intentionally unused — removed to\n // avoid xUnit1026. totalRequestTimeoutSeconds must remain > 1.\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 180)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4\", 180)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 180)]\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 300)]\n public async Task Polly_AiClient_AttemptTimeout_CancelsSlowRequest(\n string clientName, string baseUrl, string path,\n int totalRequestTimeoutSeconds)\n {\n // Override attemptTimeoutSeconds to 1 s so the test does not actually wait.\n var handler = BuildDelayedHandler(delayMs: 5_000);\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: 1,\n totalRequestTimeoutSeconds: totalRequestTimeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n client.Timeout = TimeSpan.FromSeconds(totalRequestTimeoutSeconds);\n\n await Assert.ThrowsAnyAsync<Exception>(() =>\n client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\")));\n }\n}\n"}, "74ba7aa9575c8bc659f2dccb9e7f14c962a92427": {"t": 1785760609.7435935, "n": 4227, "h": "7fa3d82450def75a", "c": "using System.Net;\nusing Microsoft.Extensions.DependencyInjection;\nusing Xunit;\n\nnamespace XPoster.Tests.Integration;\n\n/// <summary>\n/// Integration tests verifying that the Polly standard resilience pipeline is correctly\n/// wired for the AI-provider named HTTP clients: <c>OpenAI</c>, <c>AzureFoundry</c>,\n/// <c>DeepSeek</c>, and <c>FalAi</c>.\n///\n/// Each test builds a real <see cref=\"IServiceProvider\" /> with the same\n/// <c>AddStandardResilienceHandler</c> configuration as <c>Program.cs</c> and verifies\n/// that Polly retries on 429 and ultimately returns the final response to the caller.\n/// </summary>\npublic sealed class AiClientsResiliencePipelineTests : PollyIntegrationTestBase\n{\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 30, 180)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01\", 30, 180)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 30, 180)]\n // FalAi uses a 60-second attempt timeout to accommodate image generation latency;\n // TotalRequestTimeout must exceed AttemptTimeout, so we use 300 s.\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 60, 300)]\n public async Task Polly_AiClient_RetriesOn429_AndEventuallySucceeds(\n string clientName, string baseUrl, string path,\n int attemptTimeoutSeconds, int totalRequestTimeoutSeconds)\n {\n // Arrange: two 429s followed by a 200 with minimal JSON\n var responseBody = \"{\\\"choices\\\":[{\\\"message\\\":{\\\"content\\\":\\\"ok\\\"}}]}\";\n var handler = BuildSequenceHandler(\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.OK, responseBody));\n\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: attemptTimeoutSeconds,\n totalRequestTimeoutSeconds: totalRequestTimeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n\n // Act\n var response = await client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\"));\n\n // Assert\n Assert.Equal(HttpStatusCode.OK, response.StatusCode);\n }\n\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 30, 180)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4\", 30, 180)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 30, 180)]\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 60, 300)]\n public async Task Polly_AiClient_AttemptTimeout_CancelsSlowRequest(\n string clientName, string baseUrl, string path,\n int attemptTimeoutSeconds, int totalRequestTimeoutSeconds)\n {\n // Arrange: handler always takes longer than the 1-second test timeout.\n // We override attemptTimeoutSeconds to 1 so the test completes quickly,\n // but keep totalRequestTimeoutSeconds proportional (must be > 1).\n var handler = BuildDelayedHandler(delayMs: 5_000);\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: 1,\n totalRequestTimeoutSeconds: totalRequestTimeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n client.Timeout = TimeSpan.FromSeconds(totalRequestTimeoutSeconds);\n\n // Act & Assert\n await Assert.ThrowsAnyAsync<Exception>(() =>\n client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\")));\n }\n}\n"}, "dc37bc9d1af825c8ae09a93825256b072e4cba67": {"t": 1785760611.2619174, "n": 3535, "h": "6865df7f3ed0b93d", "c": "using System.Net;\nusing Microsoft.Extensions.DependencyInjection;\nusing Xunit;\n\nnamespace XPoster.Tests.Integration;\n\n/// <summary>\n/// Integration tests verifying that the Polly standard resilience pipeline is correctly\n/// wired for the AI-provider named HTTP clients: <c>OpenAI</c>, <c>AzureFoundry</c>,\n/// <c>DeepSeek</c>, and <c>FalAi</c>.\n///\n/// Each test builds a real <see cref=\"IServiceProvider\" /> with the same\n/// <c>AddStandardResilienceHandler</c> configuration as <c>Program.cs</c> and verifies\n/// that Polly retries on 429 and ultimately returns the final response to the caller.\n/// </summary>\npublic sealed class AiClientsResiliencePipelineTests : PollyIntegrationTestBase\n{\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\")]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4/chat/completions?api-version=2024-02-01\")]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\")]\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\")]\n public async Task Polly_AiClient_RetriesOn429_AndEventuallySucceeds(\n string clientName, string baseUrl, string path)\n {\n // Arrange: two 429s followed by a 200 with minimal JSON\n var responseBody = \"{\\\"choices\\\":[{\\\"message\\\":{\\\"content\\\":\\\"ok\\\"}}]}\";\n var handler = BuildSequenceHandler(\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.TooManyRequests, \"{}\"),\n (HttpStatusCode.OK, responseBody));\n\n var timeoutSeconds = clientName == \"FalAi\" ? 60 : 30;\n var provider = BuildProviderWithHandler(clientName, handler, attemptTimeoutSeconds: timeoutSeconds);\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n\n // Act\n var response = await client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\"));\n\n // Assert\n Assert.Equal(HttpStatusCode.OK, response.StatusCode);\n }\n\n [Theory]\n [InlineData(\"OpenAI\", \"https://api.openai.com\", \"/v1/chat/completions\", 30)]\n [InlineData(\"AzureFoundry\", \"https://xposter.openai.azure.com\", \"/openai/deployments/gpt-4\", 30)]\n [InlineData(\"DeepSeek\", \"https://api.deepseek.com\", \"/v1/chat/completions\", 30)]\n [InlineData(\"FalAi\", \"https://fal.run\", \"/fal-ai/flux/dev\", 60)]\n public async Task Polly_AiClient_AttemptTimeout_CancelsSlowRequest(\n string clientName, string baseUrl, string path, int configuredTimeoutSeconds)\n {\n // Arrange: handler always takes longer than the timeout\n var handler = BuildDelayedHandler(delayMs: 5_000);\n var provider = BuildProviderWithHandler(\n clientName,\n handler,\n attemptTimeoutSeconds: 1 /* use 1 s so the test does not actually wait */ );\n var client = provider.GetRequiredService<IHttpClientFactory>().CreateClient(clientName);\n client.BaseAddress = new Uri(baseUrl);\n client.Timeout = TimeSpan.FromSeconds(configuredTimeoutSeconds + 30);\n\n // Act & Assert\n await Assert.ThrowsAnyAsync<Exception>(() =>\n client.PostAsync(\n path,\n new StringContent(\"{}\", System.Text.Encoding.UTF8, \"application/json\")));\n }\n}\n"}}}} |