Skip to content

Commit 167f594

Browse files
Add TodoApp tests
Add tests for Todo app before refactoring to use generated Kiota code.
1 parent 5c84c39 commit 167f594

3 files changed

Lines changed: 239 additions & 4 deletions

File tree

test/Swashbuckle.AspNetCore.IntegrationTests/CodeGenerationTests.cs

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
#if NET10_0_OR_GREATER
22

3+
using System.Net;
4+
using System.Net.Http.Json;
35
using System.Reflection;
46
using System.Text.Json;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.AspNetCore.Mvc;
59
using Microsoft.OpenApi;
10+
using TodoApp.Models;
11+
using Xunit;
612

713
namespace Swashbuckle.AspNetCore.IntegrationTests;
814

@@ -77,6 +83,236 @@ public async Task OpenApiDocument_Generates_Valid_Client_Code_From_Snapshot(Clie
7783
await generator.CompileAsync(project.Path);
7884
}
7985

86+
[Fact]
87+
public async Task Can_Manage_Todo_Items_With_Api()
88+
{
89+
// Arrange
90+
var cancellationToken = TestContext.Current.CancellationToken;
91+
using var client = SwaggerIntegrationTests.GetHttpClientForTestApplication(typeof(TodoApp.Program));
92+
93+
// Act - Get all the items
94+
var items = await client.GetFromJsonAsync<TodoListViewModel>("/api/items", cancellationToken);
95+
96+
// Assert - There should be no items
97+
Assert.NotNull(items);
98+
Assert.NotNull(items.Items);
99+
Assert.Empty(items.Items);
100+
101+
var beforeCount = items.Items.Count;
102+
103+
// Arrange
104+
var text = "Buy eggs";
105+
var newItem = new CreateTodoItemModel { Text = text };
106+
107+
// Act - Add a new item
108+
using var createdResponse = await client.PostAsJsonAsync("/api/items", newItem, cancellationToken);
109+
110+
// Assert - An item was created
111+
Assert.Equal(HttpStatusCode.Created, createdResponse.StatusCode);
112+
Assert.NotNull(createdResponse.Headers.Location);
113+
114+
using var createdJson = await createdResponse.Content.ReadFromJsonAsync<JsonDocument>(cancellationToken);
115+
116+
// Arrange - Get the new item's URL and Id
117+
var itemUri = createdResponse.Headers.Location;
118+
var itemId = createdJson!.RootElement.GetProperty("id").GetString();
119+
120+
// Act - Get the item
121+
var item = await client.GetFromJsonAsync<TodoItemModel>(itemUri, cancellationToken);
122+
123+
// Assert - Verify the item was created correctly
124+
Assert.NotNull(item);
125+
Assert.Equal(itemId, item.Id);
126+
Assert.Null(item.CompletedAt);
127+
Assert.NotEqual(default, item.CreatedAt);
128+
Assert.Equal(item.CreatedAt, item.LastUpdated);
129+
Assert.Equal(text, item.Text);
130+
131+
// Act - Mark the item as being completed
132+
using var completedResponse = await client.PostAsJsonAsync(itemUri + "/complete", new { }, cancellationToken);
133+
134+
// Assert - The item was completed
135+
Assert.Equal(HttpStatusCode.NoContent, completedResponse.StatusCode);
136+
137+
item = await client.GetFromJsonAsync<TodoItemModel>(itemUri, cancellationToken);
138+
139+
Assert.NotNull(item);
140+
Assert.Equal(itemId, item.Id);
141+
Assert.Equal(text, item.Text);
142+
Assert.NotNull(item.CompletedAt);
143+
Assert.Equal(item.CompletedAt.Value, item.LastUpdated);
144+
Assert.True(item.CompletedAt.Value > item.CreatedAt);
145+
146+
// Act - Get all the items
147+
items = await client.GetFromJsonAsync<TodoListViewModel>("/api/items", cancellationToken);
148+
149+
// Assert - The item was completed
150+
Assert.NotNull(items);
151+
Assert.NotNull(items.Items);
152+
Assert.Equal(beforeCount + 1, items.Items.Count);
153+
Assert.Contains(items.Items, (x) => x.Id == itemId);
154+
155+
item = items.Items.Last();
156+
157+
Assert.NotNull(item);
158+
Assert.Equal(itemId, item.Id);
159+
Assert.Equal(text, item.Text);
160+
Assert.NotNull(item.CompletedAt);
161+
Assert.Equal(item.CompletedAt.Value, item.LastUpdated);
162+
Assert.True(item.CompletedAt.Value > item.CreatedAt);
163+
164+
// Act - Delete the item
165+
using var deletedResponse = await client.DeleteAsync(itemUri, cancellationToken);
166+
167+
// Assert - The item no longer exists
168+
Assert.Equal(HttpStatusCode.NoContent, deletedResponse.StatusCode);
169+
170+
items = await client.GetFromJsonAsync<TodoListViewModel>("/api/items", cancellationToken);
171+
172+
Assert.NotNull(items);
173+
Assert.NotNull(items.Items);
174+
Assert.Equal(beforeCount, items.Items.Count);
175+
Assert.DoesNotContain(items.Items, (x) => x.Id == itemId);
176+
177+
// Act
178+
using var getResponse = await client.GetAsync(itemUri, cancellationToken);
179+
180+
// Assert
181+
Assert.Equal(HttpStatusCode.NotFound, getResponse.StatusCode);
182+
183+
var problem = await getResponse.Content.ReadFromJsonAsync<ProblemDetails>(cancellationToken);
184+
185+
Assert.NotNull(problem);
186+
Assert.Equal(StatusCodes.Status404NotFound, problem.Status);
187+
Assert.Equal("Not Found", problem.Title);
188+
Assert.Equal("Item not found.", problem.Detail);
189+
Assert.Equal("https://tools.ietf.org/html/rfc9110#section-15.5.5", problem.Type);
190+
Assert.Null(problem.Instance);
191+
}
192+
193+
[Fact]
194+
public async Task Cannot_Create_Todo_Item_With_No_Text()
195+
{
196+
// Arrange
197+
var cancellationToken = TestContext.Current.CancellationToken;
198+
using var client = SwaggerIntegrationTests.GetHttpClientForTestApplication(typeof(TodoApp.Program));
199+
var item = new CreateTodoItemModel { Text = string.Empty };
200+
201+
// Act
202+
var response = await client.PostAsJsonAsync("/api/items", item, cancellationToken);
203+
204+
// Assert
205+
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
206+
207+
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>(cancellationToken);
208+
209+
Assert.NotNull(problem);
210+
Assert.Equal(StatusCodes.Status400BadRequest, problem.Status);
211+
Assert.Equal("Bad Request", problem.Title);
212+
Assert.Equal("No item text specified.", problem.Detail);
213+
Assert.Equal("https://tools.ietf.org/html/rfc9110#section-15.5.1", problem.Type);
214+
Assert.Null(problem.Instance);
215+
}
216+
217+
[Fact]
218+
public async Task Cannot_Complete_Todo_Item_Multiple_Times()
219+
{
220+
// Arrange
221+
var cancellationToken = TestContext.Current.CancellationToken;
222+
using var client = SwaggerIntegrationTests.GetHttpClientForTestApplication(typeof(TodoApp.Program));
223+
var item = new CreateTodoItemModel { Text = "Something" };
224+
225+
using var createdResponse = await client.PostAsJsonAsync("/api/items", item, cancellationToken);
226+
Assert.Equal(HttpStatusCode.Created, createdResponse.StatusCode);
227+
Assert.NotNull(createdResponse.Headers.Location);
228+
229+
var itemUri = createdResponse.Headers.Location;
230+
231+
using var completedResponse = await client.PostAsJsonAsync(itemUri + "/complete", new { }, cancellationToken);
232+
Assert.Equal(HttpStatusCode.NoContent, completedResponse.StatusCode);
233+
234+
// Act
235+
using var response = await client.PostAsJsonAsync(itemUri + "/complete", new { }, cancellationToken);
236+
237+
// Assert
238+
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
239+
240+
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>(cancellationToken);
241+
242+
Assert.NotNull(problem);
243+
Assert.Equal(StatusCodes.Status400BadRequest, problem.Status);
244+
Assert.Equal("Bad Request", problem.Title);
245+
Assert.Equal("Item already completed.", problem.Detail);
246+
Assert.Equal("https://tools.ietf.org/html/rfc9110#section-15.5.1", problem.Type);
247+
Assert.Null(problem.Instance);
248+
}
249+
250+
[Fact]
251+
public async Task Cannot_Complete_Deleted_Todo_Item()
252+
{
253+
// Arrange
254+
var cancellationToken = TestContext.Current.CancellationToken;
255+
using var client = SwaggerIntegrationTests.GetHttpClientForTestApplication(typeof(TodoApp.Program));
256+
var item = new CreateTodoItemModel { Text = "Something" };
257+
258+
using var createdResponse = await client.PostAsJsonAsync("/api/items", item, cancellationToken);
259+
Assert.Equal(HttpStatusCode.Created, createdResponse.StatusCode);
260+
Assert.NotNull(createdResponse.Headers.Location);
261+
262+
var itemUri = createdResponse.Headers.Location;
263+
264+
using var deletedResponse = await client.DeleteAsync(itemUri, cancellationToken);
265+
Assert.Equal(HttpStatusCode.NoContent, deletedResponse.StatusCode);
266+
267+
// Act
268+
using var response = await client.PostAsJsonAsync(itemUri + "/complete", new { }, cancellationToken);
269+
270+
// Assert
271+
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
272+
273+
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>(cancellationToken);
274+
275+
Assert.NotNull(problem);
276+
Assert.Equal(StatusCodes.Status404NotFound, problem.Status);
277+
Assert.Equal("Not Found", problem.Title);
278+
Assert.Equal("Item not found.", problem.Detail);
279+
Assert.Equal("https://tools.ietf.org/html/rfc9110#section-15.5.5", problem.Type);
280+
Assert.Null(problem.Instance);
281+
}
282+
283+
[Fact]
284+
public async Task Cannot_Delete_Todo_Item_Multiple_Times()
285+
{
286+
// Arrange
287+
var cancellationToken = TestContext.Current.CancellationToken;
288+
using var client = SwaggerIntegrationTests.GetHttpClientForTestApplication(typeof(TodoApp.Program));
289+
var item = new CreateTodoItemModel { Text = "Something" };
290+
291+
using var createdResponse = await client.PostAsJsonAsync("/api/items", item, cancellationToken);
292+
Assert.Equal(HttpStatusCode.Created, createdResponse.StatusCode);
293+
Assert.NotNull(createdResponse.Headers.Location);
294+
295+
var itemUri = createdResponse.Headers.Location;
296+
297+
using var deletedResponse = await client.DeleteAsync(itemUri, cancellationToken);
298+
Assert.Equal(HttpStatusCode.NoContent, deletedResponse.StatusCode);
299+
300+
// Act
301+
using var response = await client.DeleteAsync(itemUri, cancellationToken);
302+
303+
// Assert
304+
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
305+
306+
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>(cancellationToken);
307+
308+
Assert.NotNull(problem);
309+
Assert.Equal(StatusCodes.Status404NotFound, problem.Status);
310+
Assert.Equal("Not Found", problem.Title);
311+
Assert.Equal("Item not found.", problem.Detail);
312+
Assert.Equal("https://tools.ietf.org/html/rfc9110#section-15.5.5", problem.Type);
313+
Assert.Null(problem.Instance);
314+
}
315+
80316
private static string GetProjectRoot() =>
81317
typeof(CodeGenerationTests).Assembly
82318
.GetCustomAttributes<AssemblyMetadataAttribute>()

test/WebSites/TodoApp/Models/TodoItemModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@ public class TodoItemModel
2828
/// <summary>
2929
/// Gets or sets the date and time the Todo item was last updated.
3030
/// </summary>
31-
public string LastUpdated { get; set; } = default!;
31+
public DateTimeOffset LastUpdated { get; set; } = default!;
3232
}

test/WebSites/TodoApp/TodoService.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System.Globalization;
2-
using TodoApp.Data;
1+
using TodoApp.Data;
32
using TodoApp.Models;
43

54
namespace TodoApp;
@@ -65,7 +64,7 @@ private static TodoItemModel MapItem(TodoItem item)
6564
Id = item.Id.ToString(),
6665
CompletedAt = item.CompletedAt,
6766
CreatedAt = item.CreatedAt,
68-
LastUpdated = (item.CompletedAt ?? item.CreatedAt).ToString("u", CultureInfo.InvariantCulture),
67+
LastUpdated = item.CompletedAt ?? item.CreatedAt,
6968
Text = item.Text,
7069
};
7170
}

0 commit comments

Comments
 (0)