Skip to content

Commit d3749c4

Browse files
test(integration): add comprehensive API integration tests
- Add CustomWebApplicationFactory with mocked services - Add OrdersController tests (17 tests for stuck orders endpoints) - Add AlertsController tests (9 tests for test alert endpoint) - Add HealthCheck tests (3 tests for health endpoint) - Add general API behavior tests (6 tests for routing, content type) - Configure xunit to disable parallel execution for test isolation - Total: 35 integration tests, all passing BD-702 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 4ca76e9 commit d3749c4

File tree

7 files changed

+905
-0
lines changed

7 files changed

+905
-0
lines changed
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
using System.Net;
2+
using System.Net.Http.Json;
3+
using System.Text;
4+
using System.Text.Json;
5+
using FluentAssertions;
6+
using Moq;
7+
using OrderMonitor.Api.Controllers;
8+
using OrderMonitor.Core.Interfaces;
9+
10+
namespace OrderMonitor.IntegrationTests;
11+
12+
/// <summary>
13+
/// Integration tests for AlertsController endpoints.
14+
/// Tests HTTP request/response handling for alert functionality.
15+
/// </summary>
16+
public class AlertsControllerIntegrationTests : IClassFixture<CustomWebApplicationFactory>
17+
{
18+
private readonly CustomWebApplicationFactory _factory;
19+
20+
public AlertsControllerIntegrationTests(CustomWebApplicationFactory factory)
21+
{
22+
_factory = factory;
23+
}
24+
25+
#region POST /api/alerts/test Tests
26+
27+
[Fact]
28+
public async Task SendTestAlert_WithValidEmail_ReturnsOk()
29+
{
30+
// Arrange
31+
_factory.SetupDefaultMocks();
32+
var request = new TestAlertRequest { Email = "test@example.com" };
33+
var content = new StringContent(
34+
JsonSerializer.Serialize(request),
35+
Encoding.UTF8,
36+
"application/json");
37+
using var client = _factory.CreateClient();
38+
39+
// Act
40+
var response = await client.PostAsync("/api/alerts/test", content);
41+
42+
// Assert
43+
response.StatusCode.Should().Be(HttpStatusCode.OK);
44+
45+
var result = await response.Content.ReadFromJsonAsync<TestAlertResponse>();
46+
result.Should().NotBeNull();
47+
result!.Success.Should().BeTrue();
48+
result.Message.Should().Contain("test@example.com");
49+
}
50+
51+
[Fact]
52+
public async Task SendTestAlert_CallsAlertService()
53+
{
54+
// Arrange
55+
_factory.SetupDefaultMocks();
56+
var request = new TestAlertRequest { Email = "verify@example.com" };
57+
var content = new StringContent(
58+
JsonSerializer.Serialize(request),
59+
Encoding.UTF8,
60+
"application/json");
61+
using var client = _factory.CreateClient();
62+
63+
// Act
64+
await client.PostAsync("/api/alerts/test", content);
65+
66+
// Assert
67+
_factory.MockAlertService.Verify(
68+
s => s.SendTestAlertAsync("verify@example.com", It.IsAny<CancellationToken>()),
69+
Times.Once);
70+
}
71+
72+
[Fact]
73+
public async Task SendTestAlert_WithEmptyEmail_ReturnsBadRequest()
74+
{
75+
// Arrange
76+
_factory.SetupDefaultMocks();
77+
var request = new TestAlertRequest { Email = "" };
78+
var content = new StringContent(
79+
JsonSerializer.Serialize(request),
80+
Encoding.UTF8,
81+
"application/json");
82+
using var client = _factory.CreateClient();
83+
84+
// Act
85+
var response = await client.PostAsync("/api/alerts/test", content);
86+
87+
// Assert
88+
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
89+
90+
// Verify the service was NOT called (validation should prevent it)
91+
_factory.MockAlertService.Verify(
92+
s => s.SendTestAlertAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()),
93+
Times.Never);
94+
}
95+
96+
[Fact]
97+
public async Task SendTestAlert_WithNullEmail_ReturnsBadRequest()
98+
{
99+
// Arrange
100+
_factory.SetupDefaultMocks();
101+
var content = new StringContent(
102+
"{}",
103+
Encoding.UTF8,
104+
"application/json");
105+
using var client = _factory.CreateClient();
106+
107+
// Act
108+
var response = await client.PostAsync("/api/alerts/test", content);
109+
110+
// Assert
111+
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
112+
}
113+
114+
[Fact]
115+
public async Task SendTestAlert_WithWhitespaceEmail_ReturnsBadRequest()
116+
{
117+
// Arrange
118+
_factory.SetupDefaultMocks();
119+
var request = new TestAlertRequest { Email = " " };
120+
var content = new StringContent(
121+
JsonSerializer.Serialize(request),
122+
Encoding.UTF8,
123+
"application/json");
124+
using var client = _factory.CreateClient();
125+
126+
// Act
127+
var response = await client.PostAsync("/api/alerts/test", content);
128+
129+
// Assert
130+
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
131+
}
132+
133+
[Fact]
134+
public async Task SendTestAlert_WhenSmtpPasswordNotConfigured_Returns500WithMessage()
135+
{
136+
// Arrange
137+
_factory.SetupDefaultMocks();
138+
_factory.MockAlertService
139+
.Setup(s => s.SendTestAlertAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
140+
.ThrowsAsync(new InvalidOperationException("SMTP password not configured"));
141+
142+
var request = new TestAlertRequest { Email = "test@example.com" };
143+
var content = new StringContent(
144+
JsonSerializer.Serialize(request),
145+
Encoding.UTF8,
146+
"application/json");
147+
using var client = _factory.CreateClient();
148+
149+
// Act
150+
var response = await client.PostAsync("/api/alerts/test", content);
151+
152+
// Assert
153+
response.StatusCode.Should().Be(HttpStatusCode.InternalServerError);
154+
155+
var responseBody = await response.Content.ReadAsStringAsync();
156+
responseBody.Should().Contain("SMTP password not configured");
157+
}
158+
159+
[Fact]
160+
public async Task SendTestAlert_WhenSmtpFails_Returns500()
161+
{
162+
// Arrange
163+
_factory.SetupDefaultMocks();
164+
_factory.MockAlertService
165+
.Setup(s => s.SendTestAlertAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
166+
.ThrowsAsync(new Exception("SMTP server unreachable"));
167+
168+
var request = new TestAlertRequest { Email = "test@example.com" };
169+
var content = new StringContent(
170+
JsonSerializer.Serialize(request),
171+
Encoding.UTF8,
172+
"application/json");
173+
using var client = _factory.CreateClient();
174+
175+
// Act
176+
var response = await client.PostAsync("/api/alerts/test", content);
177+
178+
// Assert
179+
response.StatusCode.Should().Be(HttpStatusCode.InternalServerError);
180+
}
181+
182+
[Fact]
183+
public async Task SendTestAlert_ReturnsTimestamp()
184+
{
185+
// Arrange
186+
_factory.SetupDefaultMocks();
187+
var request = new TestAlertRequest { Email = "test@example.com" };
188+
var content = new StringContent(
189+
JsonSerializer.Serialize(request),
190+
Encoding.UTF8,
191+
"application/json");
192+
var beforeRequest = DateTime.UtcNow;
193+
using var client = _factory.CreateClient();
194+
195+
// Act
196+
var response = await client.PostAsync("/api/alerts/test", content);
197+
198+
// Assert
199+
response.StatusCode.Should().Be(HttpStatusCode.OK);
200+
201+
var result = await response.Content.ReadFromJsonAsync<TestAlertResponse>();
202+
result.Should().NotBeNull();
203+
result!.SentAt.Should().BeOnOrAfter(beforeRequest);
204+
result.SentAt.Should().BeOnOrBefore(DateTime.UtcNow.AddSeconds(1));
205+
}
206+
207+
[Fact]
208+
public async Task SendTestAlert_ReturnsJsonContentType()
209+
{
210+
// Arrange
211+
_factory.SetupDefaultMocks();
212+
var request = new TestAlertRequest { Email = "test@example.com" };
213+
var content = new StringContent(
214+
JsonSerializer.Serialize(request),
215+
Encoding.UTF8,
216+
"application/json");
217+
using var client = _factory.CreateClient();
218+
219+
// Act
220+
var response = await client.PostAsync("/api/alerts/test", content);
221+
222+
// Assert
223+
response.Content.Headers.ContentType!.MediaType.Should().Be("application/json");
224+
}
225+
226+
#endregion
227+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
using System.Net;
2+
using FluentAssertions;
3+
4+
namespace OrderMonitor.IntegrationTests;
5+
6+
/// <summary>
7+
/// Integration tests for general API behavior.
8+
/// Tests routing, error handling, and infrastructure endpoints.
9+
/// </summary>
10+
public class ApiGeneralIntegrationTests : IClassFixture<CustomWebApplicationFactory>
11+
{
12+
private readonly CustomWebApplicationFactory _factory;
13+
14+
public ApiGeneralIntegrationTests(CustomWebApplicationFactory factory)
15+
{
16+
_factory = factory;
17+
}
18+
19+
#region Routing Tests
20+
21+
[Fact]
22+
public async Task NonExistentEndpoint_Returns404()
23+
{
24+
// Arrange
25+
_factory.SetupDefaultMocks();
26+
using var client = _factory.CreateClient();
27+
28+
// Act
29+
var response = await client.GetAsync("/api/nonexistent");
30+
31+
// Assert
32+
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
33+
}
34+
35+
[Fact]
36+
public async Task WrongHttpMethod_Returns405()
37+
{
38+
// Arrange
39+
_factory.SetupDefaultMocks();
40+
using var client = _factory.CreateClient();
41+
42+
// Act - GET is not supported for /api/alerts/test
43+
var response = await client.GetAsync("/api/alerts/test");
44+
45+
// Assert
46+
response.StatusCode.Should().Be(HttpStatusCode.MethodNotAllowed);
47+
}
48+
49+
[Theory]
50+
[InlineData("/api/orders/stuck")]
51+
[InlineData("/api/orders/stuck/summary")]
52+
[InlineData("/api/orders/CO12345/status-history")]
53+
public async Task GetEndpoints_AcceptGetMethod(string endpoint)
54+
{
55+
// Arrange
56+
_factory.SetupDefaultMocks();
57+
using var client = _factory.CreateClient();
58+
59+
// Act
60+
var response = await client.GetAsync(endpoint);
61+
62+
// Assert
63+
response.StatusCode.Should().NotBe(HttpStatusCode.MethodNotAllowed);
64+
}
65+
66+
#endregion
67+
68+
#region Content Negotiation Tests
69+
70+
[Fact]
71+
public async Task ApiEndpoints_ReturnJsonByDefault()
72+
{
73+
// Arrange
74+
_factory.SetupDefaultMocks();
75+
using var client = _factory.CreateClient();
76+
77+
// Act
78+
var response = await client.GetAsync("/api/orders/stuck");
79+
80+
// Assert
81+
response.Content.Headers.ContentType!.MediaType.Should().Be("application/json");
82+
}
83+
84+
[Fact]
85+
public async Task ApiEndpoints_ReturnUtf8Encoding()
86+
{
87+
// Arrange
88+
_factory.SetupDefaultMocks();
89+
using var client = _factory.CreateClient();
90+
91+
// Act
92+
var response = await client.GetAsync("/api/orders/stuck");
93+
94+
// Assert
95+
response.Content.Headers.ContentType!.CharSet.Should().Be("utf-8");
96+
}
97+
98+
#endregion
99+
100+
#region Base URL Tests
101+
102+
[Fact]
103+
public async Task RootUrl_IsAccessible()
104+
{
105+
// Arrange
106+
_factory.SetupDefaultMocks();
107+
using var client = _factory.CreateClient();
108+
109+
// Act
110+
var response = await client.GetAsync("/");
111+
112+
// Assert - Root URL might return 404 or redirect, but should not error
113+
response.StatusCode.Should().NotBe(HttpStatusCode.InternalServerError);
114+
}
115+
116+
#endregion
117+
}

0 commit comments

Comments
 (0)