Skip to content

Commit 8f30e89

Browse files
committed
refactor: Update solution file structure and integrate HybridCache into API service unit tests.
1 parent 824e6cc commit 8f30e89

13 files changed

Lines changed: 98 additions & 111 deletions

BookStore.slnx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
<Project Path="src/BookStore.ServiceDefaults/BookStore.ServiceDefaults.csproj" />
1616
</Folder>
1717
<Folder Name="/tests/">
18-
<Folder Name="/ApiService/">
19-
<Project Path="tests/BookStore.ApiService.Analyzers.UnitTests/BookStore.ApiService.Analyzers.UnitTests.csproj" />
20-
<Project Path="tests/BookStore.ApiService.UnitTests/BookStore.ApiService.UnitTests.csproj" />
21-
</Folder>
18+
<Project Path="tests/BookStore.ApiService.Analyzers.UnitTests/BookStore.ApiService.Analyzers.UnitTests.csproj" />
19+
<Project Path="tests/BookStore.ApiService.UnitTests/BookStore.ApiService.UnitTests.csproj" />
2220
<Project Path="tests/BookStore.Shared.UnitTests/BookStore.Shared.UnitTests.csproj" />
2321
<Project Path="tests/BookStore.Web.Tests/BookStore.Web.Tests.csproj" />
2422
<Project Path="tests/BookStore.AppHost.Tests/BookStore.AppHost.Tests.csproj" />

src/BookStore.ApiService/appsettings.Development.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"DefaultCulture": "en",
3939
"SupportedCultures": [
4040
"pt",
41+
"pt-PT",
4142
"en",
4243
"fr",
4344
"de",

tests/BookStore.ApiService.UnitTests/Handlers/AuthorHandlerTests.cs

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using BookStore.Shared.Models;
77
using Marten;
88
using Microsoft.AspNetCore.Http;
9+
using Microsoft.Extensions.Caching.Hybrid;
910
using Microsoft.Extensions.Logging;
1011
using Microsoft.Extensions.Options;
1112
using NSubstitute;
@@ -21,10 +22,7 @@ public async Task CreateAuthorHandler_ShouldStartStreamWithAuthorAddedEvent()
2122
// Arrange
2223
var command = new CreateAuthor(
2324
"Robert C. Martin",
24-
new Dictionary<string, AuthorTranslationDto>
25-
{
26-
["en"] = new AuthorTranslationDto("Uncle Bob")
27-
}
25+
new Dictionary<string, AuthorTranslationDto> { ["en"] = new AuthorTranslationDto("Uncle Bob") }
2826
);
2927

3028
var session = Substitute.For<IDocumentSession>();
@@ -37,7 +35,8 @@ public async Task CreateAuthorHandler_ShouldStartStreamWithAuthorAddedEvent()
3735
});
3836

3937
// Act
40-
var result = await AuthorHandlers.Handle(command, session, localizationOptions, Substitute.For<ILogger<CreateAuthor>>());
38+
var result = await AuthorHandlers.Handle(command, session, localizationOptions, Substitute.For<HybridCache>(),
39+
Substitute.For<ILogger>());
4140

4241
// Assert
4342
_ = await Assert.That(result).IsNotNull();
@@ -57,10 +56,7 @@ public async Task CreateAuthorHandler_WithInvalidCulture_ShouldReturnBadRequest(
5756
// Arrange
5857
var command = new CreateAuthor(
5958
"Robert C. Martin",
60-
new Dictionary<string, AuthorTranslationDto>
61-
{
62-
[invalidCulture] = new AuthorTranslationDto("Uncle Bob")
63-
}
59+
new Dictionary<string, AuthorTranslationDto> { [invalidCulture] = new AuthorTranslationDto("Uncle Bob") }
6460
);
6561

6662
var session = Substitute.For<IDocumentSession>();
@@ -71,7 +67,8 @@ public async Task CreateAuthorHandler_WithInvalidCulture_ShouldReturnBadRequest(
7167
});
7268

7369
// Act
74-
var result = await AuthorHandlers.Handle(command, session, localizationOptions, Substitute.For<ILogger<CreateAuthor>>());
70+
var result = await AuthorHandlers.Handle(command, session, localizationOptions, Substitute.For<HybridCache>(),
71+
Substitute.For<ILogger>());
7572

7673
// Assert
7774
// Assert
@@ -88,14 +85,9 @@ public async Task UpdateAuthorHandler_ShouldAppendAuthorUpdatedEvent()
8885
var command = new UpdateAuthor(
8986
Guid.CreateVersion7(),
9087
"Robert C. Martin Updated",
91-
new Dictionary<string, AuthorTranslationDto>
92-
{
93-
["en"] = new AuthorTranslationDto("Uncle Bob Updated")
94-
}
88+
new Dictionary<string, AuthorTranslationDto> { ["en"] = new AuthorTranslationDto("Uncle Bob Updated") }
9589
)
96-
{
97-
ETag = "test-etag"
98-
};
90+
{ ETag = "test-etag" };
9991

10092
var session = Substitute.For<IDocumentSession>();
10193
var httpContext = new DefaultHttpContext();
@@ -116,7 +108,8 @@ public async Task UpdateAuthorHandler_ShouldAppendAuthorUpdatedEvent()
116108
_ = session.Events.AggregateStreamAsync<AuthorAggregate>(command.Id).Returns(existingAggregate);
117109

118110
// Act
119-
var result = await AuthorHandlers.Handle(command, session, httpContextAccessor, localizationOptions, Substitute.For<ILogger<UpdateAuthor>>());
111+
var result = await AuthorHandlers.Handle(command, session, httpContextAccessor, localizationOptions,
112+
Substitute.For<HybridCache>(), Substitute.For<ILogger>());
120113

121114
// Assert
122115
_ = await Assert.That(result).IsTypeOf<Microsoft.AspNetCore.Http.HttpResults.NoContent>();
@@ -147,7 +140,8 @@ public async Task SoftDeleteAuthorHandler_ShouldAppendAuthorSoftDeletedEvent()
147140
_ = session.Events.AggregateStreamAsync<AuthorAggregate>(id).Returns(existingAggregate);
148141

149142
// Act
150-
var result = await AuthorHandlers.Handle(command, session, httpContextAccessor, Substitute.For<ILogger<SoftDeleteAuthor>>());
143+
var result = await AuthorHandlers.Handle(command, session, httpContextAccessor, Substitute.For<HybridCache>(),
144+
Substitute.For<ILogger>());
151145

152146
// Assert
153147
_ = await Assert.That(result).IsTypeOf<Microsoft.AspNetCore.Http.HttpResults.NoContent>();
@@ -177,7 +171,8 @@ public async Task RestoreAuthorHandler_ShouldAppendAuthorRestoredEvent()
177171
_ = session.Events.AggregateStreamAsync<AuthorAggregate>(id).Returns(existingAggregate);
178172

179173
// Act
180-
var result = await AuthorHandlers.Handle(command, session, httpContextAccessor, Substitute.For<ILogger<RestoreAuthor>>());
174+
var result = await AuthorHandlers.Handle(command, session, httpContextAccessor, Substitute.For<HybridCache>(),
175+
Substitute.For<ILogger>());
181176

182177
// Assert
183178
_ = await Assert.That(result).IsTypeOf<Microsoft.AspNetCore.Http.HttpResults.NoContent>();
@@ -197,7 +192,8 @@ static AuthorAggregate CreateAuthorAggregate(Guid id, string name, bool isDelete
197192
typeof(AuthorAggregate).GetProperty(nameof(AuthorAggregate.Id))!.SetValue(aggregate, id);
198193
typeof(AuthorAggregate).GetProperty(nameof(AuthorAggregate.Name))!.SetValue(aggregate, name);
199194
typeof(AuthorAggregate).GetProperty(nameof(AuthorAggregate.Deleted))!.SetValue(aggregate, isDeleted);
200-
typeof(AuthorAggregate).GetProperty(nameof(AuthorAggregate.Translations))!.SetValue(aggregate, new Dictionary<string, AuthorTranslation> { ["en"] = new("Bio") });
195+
typeof(AuthorAggregate).GetProperty(nameof(AuthorAggregate.Translations))!.SetValue(aggregate,
196+
new Dictionary<string, AuthorTranslation> { ["en"] = new("Bio") });
201197

202198
return aggregate;
203199
}

tests/BookStore.ApiService.UnitTests/Handlers/BookHandlerTests.cs

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
using BookStore.ApiService.Infrastructure;
88
using BookStore.ApiService.Infrastructure.Logging;
99
using BookStore.Shared.Models;
10+
using JasperFx.Events;
1011
using Marten;
1112
using Marten.Events;
12-
using JasperFx.Events;
1313
using Microsoft.AspNetCore.Http;
1414
using Microsoft.AspNetCore.Http.HttpResults;
15+
using Microsoft.Extensions.Caching.Hybrid;
1516
using Microsoft.Extensions.Logging;
1617
using Microsoft.Extensions.Options;
1718
using NSubstitute;
@@ -25,18 +26,10 @@ namespace BookStore.ApiService.UnitTests.Handlers;
2526
public class BookHandlerTests
2627
{
2728
static IOptions<LocalizationOptions> CreateLocalizationOptions()
28-
=> Options.Create(new LocalizationOptions
29-
{
30-
DefaultCulture = "en",
31-
SupportedCultures = ["en"]
32-
});
29+
=> Options.Create(new LocalizationOptions { DefaultCulture = "en", SupportedCultures = ["en"] });
3330

3431
static IOptions<CurrencyOptions> CreateCurrencyOptions()
35-
=> Options.Create(new CurrencyOptions
36-
{
37-
DefaultCurrency = "USD",
38-
SupportedCurrencies = ["USD", "EUR"]
39-
});
32+
=> Options.Create(new CurrencyOptions { DefaultCurrency = "USD", SupportedCurrencies = ["USD", "EUR"] });
4033

4134
[Test]
4235
[Category("Unit")]
@@ -62,7 +55,8 @@ public async Task CreateBookHandler_ShouldStartStreamWithBookAddedEvent()
6255
_ = session.CorrelationId.Returns("test-correlation-id");
6356

6457
// Act
65-
var result = BookHandlers.Handle(command, session, CreateLocalizationOptions(), CreateCurrencyOptions(), Substitute.For<ILogger<CreateBook>>());
58+
var result = await BookHandlers.Handle(command, session, CreateLocalizationOptions(), CreateCurrencyOptions(),
59+
Substitute.For<HybridCache>(), Substitute.For<ILogger>());
6660

6761
// Assert
6862
_ = await Assert.That(result).IsNotNull();
@@ -79,7 +73,8 @@ public async Task CreateBookHandler_ShouldStartStreamWithBookAddedEvent()
7973
[Arguments("invalid", "en", 10)]
8074
[Arguments("en", "invalid", 10)]
8175
[Arguments("en", "en", 5001)]
82-
public async Task CreateBookHandler_WithInvalidHandlerValidation_ShouldReturnBadRequest(string language, string culture, int descLength)
76+
public async Task CreateBookHandler_WithInvalidHandlerValidation_ShouldReturnBadRequest(string language,
77+
string culture, int descLength)
8378
{
8479
// Arrange
8580
var description = new string('a', descLength);
@@ -88,10 +83,7 @@ public async Task CreateBookHandler_WithInvalidHandlerValidation_ShouldReturnBad
8883
"Clean Code",
8984
"978-0132350884",
9085
language,
91-
new Dictionary<string, BookTranslationDto>
92-
{
93-
[culture] = new BookTranslationDto(description)
94-
},
86+
new Dictionary<string, BookTranslationDto> { [culture] = new BookTranslationDto(description) },
9587
new PartialDate(2008, 8, 1),
9688
Guid.CreateVersion7(),
9789
[Guid.CreateVersion7()],
@@ -102,7 +94,8 @@ public async Task CreateBookHandler_WithInvalidHandlerValidation_ShouldReturnBad
10294
var session = Substitute.For<IDocumentSession>();
10395

10496
// Act
105-
var result = BookHandlers.Handle(command, session, CreateLocalizationOptions(), CreateCurrencyOptions(), Substitute.For<ILogger<CreateBook>>());
97+
var result = await BookHandlers.Handle(command, session, CreateLocalizationOptions(), CreateCurrencyOptions(),
98+
Substitute.For<HybridCache>(), Substitute.For<ILogger>());
10699

107100
// Assert
108101
_ = await Assert.That(result).IsAssignableTo<IStatusCodeHttpResult>();
@@ -124,10 +117,7 @@ public async Task CreateBookHandler_WithInvalidDomainValidation_ShouldReturnBadR
124117
title,
125118
isbn,
126119
"en",
127-
new Dictionary<string, BookTranslationDto>
128-
{
129-
["en"] = new BookTranslationDto("Description")
130-
},
120+
new Dictionary<string, BookTranslationDto> { ["en"] = new BookTranslationDto("Description") },
131121
new PartialDate(2008, 8, 1),
132122
Guid.CreateVersion7(),
133123
[Guid.CreateVersion7()],
@@ -138,7 +128,8 @@ public async Task CreateBookHandler_WithInvalidDomainValidation_ShouldReturnBadR
138128
var session = Substitute.For<IDocumentSession>();
139129

140130
// Act
141-
var result = BookHandlers.Handle(command, session, CreateLocalizationOptions(), CreateCurrencyOptions(), Substitute.For<ILogger<CreateBook>>());
131+
var result = await BookHandlers.Handle(command, session, CreateLocalizationOptions(), CreateCurrencyOptions(),
132+
Substitute.For<HybridCache>(), Substitute.For<ILogger>());
142133

143134
// Assert
144135
_ = await Assert.That(result).IsAssignableTo<IStatusCodeHttpResult>();
@@ -166,7 +157,8 @@ public async Task CreateBookHandler_WithMissingDefaultPrice_ShouldReturnBadReque
166157
var session = Substitute.For<IDocumentSession>();
167158

168159
// Act
169-
var result = BookHandlers.Handle(command, session, CreateLocalizationOptions(), CreateCurrencyOptions(), Substitute.For<ILogger<CreateBook>>());
160+
var result = await BookHandlers.Handle(command, session, CreateLocalizationOptions(), CreateCurrencyOptions(),
161+
Substitute.For<HybridCache>(), Substitute.For<ILogger>());
170162

171163
// Assert
172164
_ = await Assert.That(result).IsAssignableTo<IStatusCodeHttpResult>();
@@ -200,11 +192,13 @@ public async Task UpdateBookHandler_WithMissingBook_ShouldReturnNotFound()
200192
.Returns(Task.FromResult<Marten.Events.StreamState?>(null));
201193

202194
// Act
203-
var result = await BookHandlers.Handle(command, session, contextAccessor, CreateLocalizationOptions(), CreateCurrencyOptions(), Substitute.For<ILogger<UpdateBook>>());
195+
var result = await BookHandlers.Handle(command, session, contextAccessor, CreateLocalizationOptions(),
196+
CreateCurrencyOptions(), Substitute.For<HybridCache>(), Substitute.For<ILogger>());
204197

205198
// Assert
206199
_ = await Assert.That(result).IsTypeOf<Microsoft.AspNetCore.Http.HttpResults.NotFound>();
207200
}
201+
208202
[Test]
209203
[Category("Unit")]
210204
public async Task ScheduleSale_ShouldAppendEvent()
@@ -281,11 +275,12 @@ public async Task CancelSale_ShouldAppendEvent()
281275
var session = Substitute.For<IDocumentSession>();
282276

283277
_ = session.Events.FetchStreamStateAsync(bookId).Returns(new Marten.Events.StreamState { Version = 1 });
284-
278+
285279
// SaleHandlers.Handle for CancelSale fetches stream and projects manually
286280
var events = new List<IEvent>
287281
{
288-
new JasperFx.Events.Event<BookSaleScheduled>(new BookSaleScheduled(bookId, new BookSale(10m, saleStart, saleStart.AddDays(1))))
282+
new JasperFx.Events.Event<BookSaleScheduled>(new BookSaleScheduled(bookId,
283+
new BookSale(10m, saleStart, saleStart.AddDays(1))))
289284
};
290285
_ = session.Events.FetchStreamAsync(bookId).Returns(events);
291286

@@ -302,7 +297,8 @@ public async Task CancelSale_ShouldAppendEvent()
302297

303298
static void SetPrivateProperty<T>(T obj, string propertyName, object value)
304299
{
305-
var property = typeof(T).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
300+
var property = typeof(T).GetProperty(propertyName,
301+
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
306302
property?.SetValue(obj, value);
307303
}
308304
}

tests/BookStore.ApiService.UnitTests/Handlers/CategoryHandlerTests.cs

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using BookStore.Shared.Models;
77
using Marten;
88
using Microsoft.AspNetCore.Http;
9+
using Microsoft.Extensions.Caching.Hybrid;
910
using Microsoft.Extensions.Logging;
1011
using Microsoft.Extensions.Options;
1112
using NSubstitute;
@@ -20,17 +21,15 @@ public async Task CreateCategoryHandler_ShouldStartStreamWithCategoryAddedEvent(
2021
{
2122
// Arrange
2223
var command = new CreateCategory(
23-
new Dictionary<string, CategoryTranslationDto>
24-
{
25-
["en"] = new CategoryTranslationDto("Technology")
26-
}
24+
new Dictionary<string, CategoryTranslationDto> { ["en"] = new CategoryTranslationDto("Technology") }
2725
);
2826

2927
var session = Substitute.For<IDocumentSession>();
3028
_ = session.CorrelationId.Returns("test-correlation-id");
3129

3230
// Act
33-
var result = await CategoryHandlers.Handle(command, session, Substitute.For<ILogger<CreateCategory>>());
31+
var result = await CategoryHandlers.Handle(command, session, Substitute.For<HybridCache>(),
32+
Substitute.For<ILogger<CreateCategory>>());
3433

3534
// Assert
3635
_ = await Assert.That(result).IsNotNull();
@@ -52,17 +51,15 @@ public async Task CreateCategoryHandler_WithInvalidData_ShouldReturnBadRequest(s
5251
var name = new string('a', nameLength);
5352

5453
var command = new CreateCategory(
55-
new Dictionary<string, CategoryTranslationDto>
56-
{
57-
[culture] = new CategoryTranslationDto(name)
58-
}
54+
new Dictionary<string, CategoryTranslationDto> { [culture] = new CategoryTranslationDto(name) }
5955
);
6056

6157
var session = Substitute.For<IDocumentSession>();
6258

6359
// Act
6460
// Act
65-
var result = await CategoryHandlers.Handle(command, session, Substitute.For<ILogger<CreateCategory>>());
61+
var result = await CategoryHandlers.Handle(command, session, Substitute.For<HybridCache>(),
62+
Substitute.For<ILogger<CreateCategory>>());
6663

6764
// Assert
6865
_ = await Assert.That(result).IsAssignableTo<Microsoft.AspNetCore.Http.IStatusCodeHttpResult>();
@@ -77,14 +74,9 @@ public async Task UpdateCategoryHandler_ShouldAppendCategoryUpdatedEvent()
7774
// Arrange
7875
var command = new UpdateCategory(
7976
Guid.CreateVersion7(),
80-
new Dictionary<string, CategoryTranslationDto>
81-
{
82-
["en"] = new CategoryTranslationDto("Technology Updated")
83-
}
77+
new Dictionary<string, CategoryTranslationDto> { ["en"] = new CategoryTranslationDto("Technology Updated") }
8478
)
85-
{
86-
ETag = "test-etag"
87-
};
79+
{ ETag = "test-etag" };
8880

8981
var session = Substitute.For<IDocumentSession>();
9082
var httpContext = new DefaultHttpContext();
@@ -100,7 +92,8 @@ public async Task UpdateCategoryHandler_ShouldAppendCategoryUpdatedEvent()
10092

10193
// Act
10294
// Act
103-
var result = await CategoryHandlers.Handle(command, session, httpContextAccessor, Substitute.For<ILogger<UpdateCategory>>());
95+
var result = await CategoryHandlers.Handle(command, session, httpContextAccessor, Substitute.For<HybridCache>(),
96+
Substitute.For<ILogger<UpdateCategory>>());
10497

10598
// Assert
10699
_ = await Assert.That(result).IsTypeOf<Microsoft.AspNetCore.Http.HttpResults.NoContent>();
@@ -131,7 +124,8 @@ public async Task SoftDeleteCategoryHandler_ShouldAppendCategorySoftDeletedEvent
131124
_ = session.Events.AggregateStreamAsync<CategoryAggregate>(id).Returns(existingAggregate);
132125

133126
// Act
134-
var result = await CategoryHandlers.Handle(command, session, httpContextAccessor, Substitute.For<ILogger<SoftDeleteCategory>>());
127+
var result = await CategoryHandlers.Handle(command, session, httpContextAccessor, Substitute.For<HybridCache>(),
128+
Substitute.For<ILogger<SoftDeleteCategory>>());
135129

136130
// Assert
137131
_ = await Assert.That(result).IsTypeOf<Microsoft.AspNetCore.Http.HttpResults.NoContent>();
@@ -161,7 +155,8 @@ public async Task RestoreCategoryHandler_ShouldAppendCategoryRestoredEvent()
161155
_ = session.Events.AggregateStreamAsync<CategoryAggregate>(id).Returns(existingAggregate);
162156

163157
// Act
164-
var result = await CategoryHandlers.Handle(command, session, httpContextAccessor, Substitute.For<ILogger<RestoreCategory>>());
158+
var result = await CategoryHandlers.Handle(command, session, httpContextAccessor, Substitute.For<HybridCache>(),
159+
Substitute.For<ILogger<RestoreCategory>>());
165160

166161
// Assert
167162
_ = await Assert.That(result).IsTypeOf<Microsoft.AspNetCore.Http.HttpResults.NoContent>();
@@ -176,10 +171,8 @@ static CategoryAggregate CreateCategoryAggregate(Guid id, string defaultName, bo
176171

177172
typeof(CategoryAggregate).GetProperty(nameof(CategoryAggregate.Id))!.SetValue(aggregate, id);
178173
typeof(CategoryAggregate).GetProperty(nameof(CategoryAggregate.Deleted))!.SetValue(aggregate, isDeleted);
179-
typeof(CategoryAggregate).GetProperty(nameof(CategoryAggregate.Translations))!.SetValue(aggregate, new Dictionary<string, CategoryTranslation>
180-
{
181-
["en"] = new(defaultName)
182-
});
174+
typeof(CategoryAggregate).GetProperty(nameof(CategoryAggregate.Translations))!.SetValue(aggregate,
175+
new Dictionary<string, CategoryTranslation> { ["en"] = new(defaultName) });
183176

184177
return aggregate;
185178
}

0 commit comments

Comments
 (0)