-
-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathApiTests.cs
274 lines (208 loc) · 10.2 KB
/
ApiTests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// Copyright (c) Martin Costello, 2021. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Testing;
using TodoApp.Models;
namespace TodoApp;
[Collection<TodoAppCollection>]
public class ApiTests
{
public ApiTests(TodoAppFixture fixture, ITestOutputHelper outputHelper)
{
Fixture = fixture;
// Route output from the fixture's logs to xunit's output
OutputHelper = outputHelper;
Fixture.SetOutputHelper(OutputHelper);
}
private TodoAppFixture Fixture { get; }
private ITestOutputHelper OutputHelper { get; }
[Fact]
public async Task Can_Manage_Todo_Items_With_Api()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
var client = await CreateAuthenticatedClientAsync();
// Act - Get all the items
var items = await client.GetFromJsonAsync<TodoListViewModel>("/api/items", cancellationToken);
// Assert - There should be no items
items.ShouldNotBeNull();
items.Items.ShouldNotBeNull();
var beforeCount = items.Items.Count;
// Arrange
var text = "Buy eggs";
var newItem = new CreateTodoItemModel { Text = text };
// Act - Add a new item
using var createdResponse = await client.PostAsJsonAsync("/api/items", newItem, cancellationToken);
// Assert - An item was created
createdResponse.StatusCode.ShouldBe(HttpStatusCode.Created);
createdResponse.Headers.Location.ShouldNotBeNull();
using var createdJson = await createdResponse.Content.ReadFromJsonAsync<JsonDocument>(cancellationToken);
// Arrange - Get the new item's URL and Id
var itemUri = createdResponse.Headers.Location;
var itemId = createdJson!.RootElement.GetProperty("id").GetString();
// Act - Get the item
var item = await client.GetFromJsonAsync<TodoItemModel>(itemUri, cancellationToken);
// Assert - Verify the item was created correctly
item.ShouldNotBeNull();
item.Id.ShouldBe(itemId);
item.IsCompleted.ShouldBeFalse();
item.LastUpdated.ShouldNotBeNull();
item.Text.ShouldBe(text);
// Act - Mark the item as being completed
using var completedResponse = await client.PostAsJsonAsync(itemUri + "/complete", new { }, cancellationToken);
// Assert - The item was completed
completedResponse.StatusCode.ShouldBe(HttpStatusCode.NoContent);
item = await client.GetFromJsonAsync<TodoItemModel>(itemUri, cancellationToken);
item.ShouldNotBeNull();
item.Id.ShouldBe(itemId);
item.Text.ShouldBe(text);
item.IsCompleted.ShouldBeTrue();
// Act - Get all the items
items = await client.GetFromJsonAsync<TodoListViewModel>("/api/items",cancellationToken);
// Assert - The item was completed
items.ShouldNotBeNull();
items.Items.ShouldNotBeNull();
items.Items.Count.ShouldBe(beforeCount + 1);
item = items.Items.Last();
item.ShouldNotBeNull();
item.Id.ShouldBe(itemId);
item.Text.ShouldBe(text);
item.IsCompleted.ShouldBeTrue();
item.LastUpdated.ShouldNotBeNull();
// Act - Delete the item
using var deletedResponse = await client.DeleteAsync(itemUri, cancellationToken);
// Assert - The item no longer exists
deletedResponse.StatusCode.ShouldBe(HttpStatusCode.NoContent);
items = await client.GetFromJsonAsync<TodoListViewModel>("/api/items", cancellationToken);
items.ShouldNotBeNull();
items.Items.ShouldNotBeNull();
items.Items.Count.ShouldBe(beforeCount);
items.Items.ShouldNotContain(x => x.Id == itemId);
// Act
using var getResponse = await client.GetAsync(itemUri, cancellationToken);
// Assert
getResponse.StatusCode.ShouldBe(HttpStatusCode.NotFound);
var problem = await getResponse.Content.ReadFromJsonAsync<ProblemDetails>(cancellationToken);
problem.ShouldNotBeNull();
problem.Status.ShouldBe(StatusCodes.Status404NotFound);
problem.Title.ShouldBe("Not Found");
problem.Detail.ShouldBe("Item not found.");
problem.Type.ShouldBe("https://tools.ietf.org/html/rfc9110#section-15.5.5");
problem.Instance.ShouldBeNull();
}
[Fact]
public async Task Cannot_Create_Todo_Item_With_No_Text()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
var client = await CreateAuthenticatedClientAsync();
var item = new CreateTodoItemModel { Text = string.Empty };
// Act
var response = await client.PostAsJsonAsync("/api/items", item, cancellationToken);
// Assert
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>(cancellationToken);
problem.ShouldNotBeNull();
problem.Status.ShouldBe(StatusCodes.Status400BadRequest);
problem.Title.ShouldBe("Bad Request");
problem.Detail.ShouldBe("No item text specified.");
problem.Type.ShouldBe("https://tools.ietf.org/html/rfc9110#section-15.5.1");
problem.Instance.ShouldBeNull();
}
[Fact]
public async Task Cannot_Complete_Todo_Item_Multiple_Times()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
var client = await CreateAuthenticatedClientAsync();
var item = new CreateTodoItemModel { Text = "Something" };
using var createdResponse = await client.PostAsJsonAsync("/api/items", item, cancellationToken);
createdResponse.StatusCode.ShouldBe(HttpStatusCode.Created);
createdResponse.Headers.Location.ShouldNotBeNull();
var itemUri = createdResponse.Headers.Location;
using var completedResponse = await client.PostAsJsonAsync(itemUri + "/complete", new { }, cancellationToken);
completedResponse.StatusCode.ShouldBe(HttpStatusCode.NoContent);
// Act
using var response = await client.PostAsJsonAsync(itemUri + "/complete", new { }, cancellationToken);
// Assert
response.StatusCode.ShouldBe(HttpStatusCode.BadRequest);
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>(cancellationToken);
problem.ShouldNotBeNull();
problem.Status.ShouldBe(StatusCodes.Status400BadRequest);
problem.Title.ShouldBe("Bad Request");
problem.Detail.ShouldBe("Item already completed.");
problem.Type.ShouldBe("https://tools.ietf.org/html/rfc9110#section-15.5.1");
problem.Instance.ShouldBeNull();
}
[Fact]
public async Task Cannot_Complete_Deleted_Todo_Item()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
var client = await CreateAuthenticatedClientAsync();
var item = new CreateTodoItemModel { Text = "Something" };
using var createdResponse = await client.PostAsJsonAsync("/api/items", item, cancellationToken);
createdResponse.StatusCode.ShouldBe(HttpStatusCode.Created);
createdResponse.Headers.Location.ShouldNotBeNull();
var itemUri = createdResponse.Headers.Location;
using var deletedResponse = await client.DeleteAsync(itemUri, cancellationToken);
deletedResponse.StatusCode.ShouldBe(HttpStatusCode.NoContent);
// Act
using var response = await client.PostAsJsonAsync(itemUri + "/complete", new { }, cancellationToken);
// Assert
response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>(cancellationToken);
problem.ShouldNotBeNull();
problem.Status.ShouldBe(StatusCodes.Status404NotFound);
problem.Title.ShouldBe("Not Found");
problem.Detail.ShouldBe("Item not found.");
problem.Type.ShouldBe("https://tools.ietf.org/html/rfc9110#section-15.5.5");
problem.Instance.ShouldBeNull();
}
[Fact]
public async Task Cannot_Delete_Todo_Item_Multiple_Times()
{
// Arrange
var cancellationToken = TestContext.Current.CancellationToken;
var client = await CreateAuthenticatedClientAsync();
var item = new CreateTodoItemModel { Text = "Something" };
using var createdResponse = await client.PostAsJsonAsync("/api/items", item, cancellationToken);
createdResponse.StatusCode.ShouldBe(HttpStatusCode.Created);
createdResponse.Headers.Location.ShouldNotBeNull();
var itemUri = createdResponse.Headers.Location;
using var deletedResponse = await client.DeleteAsync(itemUri, cancellationToken);
deletedResponse.StatusCode.ShouldBe(HttpStatusCode.NoContent);
// Act
using var response = await client.DeleteAsync(itemUri, cancellationToken);
// Assert
response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>(cancellationToken);
problem.ShouldNotBeNull();
problem.Status.ShouldBe(StatusCodes.Status404NotFound);
problem.Title.ShouldBe("Not Found");
problem.Detail.ShouldBe("Item not found.");
problem.Type.ShouldBe("https://tools.ietf.org/html/rfc9110#section-15.5.5");
problem.Instance.ShouldBeNull();
}
private async Task<HttpClient> CreateAuthenticatedClientAsync()
{
var options = new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = true,
BaseAddress = Fixture.ClientOptions.BaseAddress,
HandleCookies = true
};
var client = Fixture.CreateClient(options);
var parameters = Array.Empty<KeyValuePair<string?, string?>>();
using var content = new FormUrlEncodedContent(parameters);
// Go through the sign-in flow, which will set
// the authentication cookie on the HttpClient.
using var response = await client.PostAsync("/sign-in", content);
response.IsSuccessStatusCode.ShouldBeTrue();
return client;
}
}