Skip to content

Commit c1b7b2a

Browse files
committed
refactor: Adopt C# 12 collection expressions, discard operator, and NUnit multiple assertions.
1 parent 32b8af1 commit c1b7b2a

18 files changed

Lines changed: 112 additions & 113 deletions

src/ApiService/BookStore.ApiService.Analyzers/Analyzers/UseDateTimeOffsetUtcNowAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ static void AnalyzeMemberAccess(SyntaxNodeAnalysisContext context)
3636

3737
// Check if this is accessing Now or UtcNow
3838
var memberName = memberAccess.Name.Identifier.Text;
39-
if (memberName != "Now" && memberName != "UtcNow")
39+
if (memberName is not "Now" and not "UtcNow")
4040
{
4141
return;
4242
}

src/ApiService/BookStore.ApiService.Analyzers/DiagnosticIds.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public static class DiagnosticIds
1111
public const string EventMustBeRecord = "BS1001";
1212
public const string EventMustBeImmutable = "BS1002";
1313
public const string EventMustBeInEventsNamespace = "BS1003";
14-
14+
1515
// Best Practices (BS1xxx continued)
1616
public const string UseCreateVersion7 = "BS1006";
1717
public const string UseDateTimeOffsetUtcNow = "BS1007";

src/ApiService/BookStore.ApiService.Tests/ConfigurationValidationTests.cs

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ public async Task PaginationOptions_DefaultPageSizeNegative_FailsValidation()
4141
var results = ValidateModel(options);
4242

4343
// Assert
44-
_ = await Assert.That(results).Count().IsEqualTo(1);
45-
_ = await Assert.That(results[0].ErrorMessage).Contains("DefaultPageSize must be between 1 and 1000");
44+
using var _ = Assert.Multiple();
45+
await Assert.That(results).Count().IsEqualTo(1);
46+
await Assert.That(results[0].ErrorMessage).Contains("DefaultPageSize must be between 1 and 1000");
4647
}
4748

4849
[Test]
@@ -60,8 +61,9 @@ public async Task PaginationOptions_DefaultPageSizeZero_FailsValidation()
6061
var results = ValidateModel(options);
6162

6263
// Assert
63-
_ = await Assert.That(results).Count().IsEqualTo(1);
64-
_ = await Assert.That(results[0].ErrorMessage).Contains("DefaultPageSize must be between 1 and 1000");
64+
using var scope = Assert.Multiple();
65+
await Assert.That(results).Count().IsEqualTo(1);
66+
await Assert.That(results[0].ErrorMessage).Contains("DefaultPageSize must be between 1 and 1000");
6567
}
6668

6769
[Test]
@@ -79,8 +81,9 @@ public async Task PaginationOptions_DefaultPageSizeTooLarge_FailsValidation()
7981
var results = ValidateModel(options);
8082

8183
// Assert
82-
_ = await Assert.That(results).Count().IsGreaterThanOrEqualTo(1);
83-
_ = await Assert.That(results.Any(r => r.ErrorMessage!.Contains("DefaultPageSize must be between 1 and 1000"))).IsTrue();
84+
using var _ = Assert.Multiple();
85+
await Assert.That(results).Count().IsGreaterThanOrEqualTo(1);
86+
await Assert.That(results.Any(r => r.ErrorMessage!.Contains("DefaultPageSize must be between 1 and 1000"))).IsTrue();
8487
}
8588

8689
[Test]
@@ -98,8 +101,9 @@ public async Task PaginationOptions_MaxPageSizeNegative_FailsValidation()
98101
var results = ValidateModel(options);
99102

100103
// Assert
101-
_ = await Assert.That(results).Count().IsEqualTo(1);
102-
_ = await Assert.That(results[0].ErrorMessage).Contains("MaxPageSize must be between 1 and 1000");
104+
using var scope = Assert.Multiple();
105+
await Assert.That(results).Count().IsEqualTo(1);
106+
await Assert.That(results[0].ErrorMessage).Contains("MaxPageSize must be between 1 and 1000");
103107
}
104108

105109
[Test]
@@ -117,8 +121,9 @@ public async Task PaginationOptions_MaxPageSizeZero_FailsValidation()
117121
var results = ValidateModel(options);
118122

119123
// Assert
120-
_ = await Assert.That(results).Count().IsEqualTo(1);
121-
_ = await Assert.That(results[0].ErrorMessage).Contains("MaxPageSize must be between 1 and 1000");
124+
using var scope = Assert.Multiple();
125+
await Assert.That(results).Count().IsEqualTo(1);
126+
await Assert.That(results[0].ErrorMessage).Contains("MaxPageSize must be between 1 and 1000");
122127
}
123128

124129
[Test]
@@ -136,8 +141,9 @@ public async Task PaginationOptions_DefaultPageSizeGreaterThanMaxPageSize_FailsV
136141
var results = ValidateModel(options);
137142

138143
// Assert
139-
_ = await Assert.That(results).Count().IsEqualTo(1);
140-
_ = await Assert.That(results[0].ErrorMessage).Contains("DefaultPageSize (100) cannot be greater than MaxPageSize (50)");
144+
using var _ = Assert.Multiple();
145+
await Assert.That(results).Count().IsEqualTo(1);
146+
await Assert.That(results[0].ErrorMessage).Contains("DefaultPageSize (100) cannot be greater than MaxPageSize (50)");
141147
}
142148

143149
[Test]
@@ -155,7 +161,7 @@ public async Task LocalizationOptions_ValidConfiguration_PassesValidation()
155161
var results = ValidateModel(options);
156162

157163
// Assert
158-
_ = await Assert.That(results).IsEmpty();
164+
await Assert.That(results).IsEmpty();
159165
}
160166

161167
[Test]
@@ -173,8 +179,9 @@ public async Task LocalizationOptions_DefaultCultureEmpty_FailsValidation()
173179
var results = ValidateModel(options);
174180

175181
// Assert
176-
_ = await Assert.That(results).IsNotEmpty();
177-
_ = await Assert.That(results.Any(r => r.ErrorMessage!.Contains("DefaultCulture"))).IsTrue();
182+
using var _ = Assert.Multiple();
183+
await Assert.That(results).IsNotEmpty();
184+
await Assert.That(results.Any(r => r.ErrorMessage!.Contains("DefaultCulture"))).IsTrue();
178185
}
179186

180187
[Test]
@@ -192,8 +199,9 @@ public async Task LocalizationOptions_SupportedCulturesEmpty_FailsValidation()
192199
var results = ValidateModel(options);
193200

194201
// Assert
195-
_ = await Assert.That(results).IsNotEmpty();
196-
_ = await Assert.That(results.Any(r => r.ErrorMessage!.Contains("At least one supported culture"))).IsTrue();
202+
using var _ = Assert.Multiple();
203+
await Assert.That(results).IsNotEmpty();
204+
await Assert.That(results.Any(r => r.ErrorMessage!.Contains("At least one supported culture"))).IsTrue();
197205
}
198206

199207
[Test]
@@ -211,8 +219,9 @@ public async Task LocalizationOptions_DefaultCultureNotInSupportedCultures_Fails
211219
var results = ValidateModel(options);
212220

213221
// Assert
214-
_ = await Assert.That(results).Count().IsEqualTo(1);
215-
_ = await Assert.That(results[0].ErrorMessage).Contains("DefaultCulture 'en-US' must be included in SupportedCultures");
222+
using var _ = Assert.Multiple();
223+
await Assert.That(results).Count().IsEqualTo(1);
224+
await Assert.That(results[0].ErrorMessage).Contains("DefaultCulture 'en-US' must be included in SupportedCultures");
216225
}
217226

218227
[Test]
@@ -230,7 +239,7 @@ public async Task LocalizationOptions_DefaultCultureInSupportedCulturesCaseInsen
230239
var results = ValidateModel(options);
231240

232241
// Assert
233-
_ = await Assert.That(results).IsEmpty();
242+
await Assert.That(results).IsEmpty();
234243
}
235244

236245
/// <summary>

src/ApiService/BookStore.ApiService.Tests/CultureCacheTests.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ public async Task IsValidCultureCode_CaseInsensitive_ReturnsTrue()
8080
var result2 = Infrastructure.CultureCache.IsValidCultureCode("PT");
8181

8282
// Assert
83-
_ = await Assert.That(result1).IsTrue();
84-
_ = await Assert.That(result2).IsTrue();
83+
using var _ = Assert.Multiple();
84+
await Assert.That(result1).IsTrue();
85+
await Assert.That(result2).IsTrue();
8586
}
8687

8788
[Test]
@@ -142,9 +143,10 @@ public async Task GetInvalidCodes_SomeInvalid_ReturnsInvalidOnes()
142143
var invalidCodes = Infrastructure.CultureCache.GetInvalidCodes(codes);
143144

144145
// Assert
145-
_ = await Assert.That(invalidCodes).Count().IsEqualTo(2);
146-
_ = await Assert.That(invalidCodes).Contains("xx-XX");
147-
_ = await Assert.That(invalidCodes).Contains("invalid");
146+
using var _ = Assert.Multiple();
147+
await Assert.That(invalidCodes).Count().IsEqualTo(2);
148+
await Assert.That(invalidCodes).Contains("xx-XX");
149+
await Assert.That(invalidCodes).Contains("invalid");
148150
}
149151

150152
[Test]

src/ApiService/BookStore.ApiService/Aggregates/CategoryAggregate.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ void Apply(CategoryAdded @event)
1616
IsDeleted = false;
1717
}
1818

19-
void Apply(CategoryUpdated @event)
20-
{
21-
Translations = @event.Translations ?? [];
22-
}
19+
void Apply(CategoryUpdated @event) => Translations = @event.Translations ?? [];
2320

2421
void Apply(CategorySoftDeleted _) => IsDeleted = true;
2522

src/ApiService/BookStore.ApiService/Endpoints/Admin/AdminBookEndpoints.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,20 @@ static async Task<IResult> UploadCover(
150150
{
151151
// Validate file
152152
if (file.Length == 0)
153+
{
153154
return Results.BadRequest("No file uploaded");
155+
}
154156

155157
if (file.Length > 5 * 1024 * 1024) // 5MB limit
158+
{
156159
return Results.BadRequest("File too large (max 5MB)");
160+
}
157161

158162
var allowedTypes = new[] { "image/jpeg", "image/png", "image/webp" };
159163
if (!allowedTypes.Contains(file.ContentType))
164+
{
160165
return Results.BadRequest("Invalid file type (only JPEG, PNG, WebP allowed)");
166+
}
161167

162168
var etag = context.Request.Headers["If-Match"].FirstOrDefault();
163169

src/ApiService/BookStore.ApiService/Endpoints/BookEndpoints.cs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,18 @@ public static RouteGroupBuilder MapBookEndpoints(this RouteGroupBuilder group)
8787
book.PublisherId.HasValue && publishers.TryGetValue(book.PublisherId.Value, out var pub)
8888
? new Models.PublisherDto(pub.Id, pub.Name)
8989
: null,
90-
book.AuthorIds
90+
[.. book.AuthorIds
9191
.Select(id => authors.TryGetValue(id, out var author)
9292
? new Models.AuthorDto(author.Id, author.Name, author.Biography)
9393
: null)
9494
.Where(a => a != null)
95-
.Cast<Models.AuthorDto>()
96-
.ToList(),
97-
book.CategoryIds
95+
.Cast<Models.AuthorDto>()],
96+
[.. book.CategoryIds
9897
.Select(id => categories.TryGetValue(id, out var cat)
9998
? LocalizeCategory(cat, language)
10099
: null)
101100
.Where(c => c != null)
102-
.Cast<Models.CategoryDto>()
103-
.ToList()
101+
.Cast<Models.CategoryDto>()]
104102
)).ToList();
105103

106104
return TypedResults.Ok(new PagedListDto<Models.BookDto>(
@@ -110,7 +108,6 @@ public static RouteGroupBuilder MapBookEndpoints(this RouteGroupBuilder group)
110108
pagedList.TotalItemCount));
111109
}
112110

113-
114111
static async Task<IResult> GetBook(
115112
Guid id,
116113
[FromServices] IQuerySession session,
@@ -164,20 +161,18 @@ static async Task<IResult> GetBook(
164161
book.PublisherId.HasValue && publishers.TryGetValue(book.PublisherId.Value, out var pub)
165162
? new Models.PublisherDto(pub.Id, pub.Name)
166163
: null,
167-
book.AuthorIds
164+
[.. book.AuthorIds
168165
.Select(id => authors.TryGetValue(id, out var author)
169166
? new Models.AuthorDto(author.Id, author.Name, author.Biography)
170167
: null)
171168
.Where(a => a != null)
172-
.Cast<Models.AuthorDto>()
173-
.ToList(),
174-
book.CategoryIds
169+
.Cast<Models.AuthorDto>()],
170+
[.. book.CategoryIds
175171
.Select(catId => categories.TryGetValue(catId, out var cat)
176172
? LocalizeCategory(cat, language)
177173
: null)
178174
.Where(c => c != null)
179-
.Cast<Models.CategoryDto>()
180-
.ToList());
175+
.Cast<Models.CategoryDto>()]);
181176

182177
return TypedResults.Ok(bookDto);
183178
}
@@ -196,7 +191,7 @@ static Models.CategoryDto LocalizeCategory(CategoryProjection category, string l
196191
{
197192
var culture = new System.Globalization.CultureInfo(language);
198193
var twoLetterCode = culture.TwoLetterISOLanguageName;
199-
194+
200195
if (category.Translations.TryGetValue(twoLetterCode, out var twoLetterLocalized))
201196
{
202197
return new Models.CategoryDto(category.Id, twoLetterLocalized.Name);

src/ApiService/BookStore.ApiService/Infrastructure/CultureValidator.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ public static class CultureValidator
1616
/// - Two-letter ISO 639-1 codes (e.g., "en", "pt", "fr") - no hyphen, 2 letters
1717
/// - Three-letter ISO 639-3 codes (e.g., "fil" for Filipino) - no hyphen, 3 letters
1818
/// </remarks>
19-
public static bool IsValidCultureCode(string languageCode) =>
20-
CultureCache.IsValidCultureCode(languageCode);
19+
public static bool IsValidCultureCode(string languageCode)
20+
=> CultureCache.IsValidCultureCode(languageCode);
2121

2222
/// <summary>
2323
/// Validates a dictionary of translations, ensuring all keys are valid culture codes or ISO language codes
@@ -30,7 +30,7 @@ public static bool ValidateTranslations<T>(
3030
out List<string> invalidCodes)
3131
{
3232
var invalid = CultureCache.GetInvalidCodes(translations.Keys);
33-
invalidCodes = invalid.ToList();
33+
invalidCodes = [.. invalid];
3434
return invalidCodes.Count == 0;
3535
}
3636
}

src/ApiService/BookStore.ApiService/Infrastructure/DatabaseSeeder.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Dictionary<string, Guid> SeedPublishers(IDocumentSession session)
4646
foreach (var (key, (id, name, website)) in publishers)
4747
{
4848
var @event = PublisherAggregate.Create(id, name);
49-
session.Events.StartStream<PublisherAggregate>(id, @event);
49+
_ = session.Events.StartStream<PublisherAggregate>(id, @event);
5050
result[key] = id;
5151
}
5252

@@ -72,7 +72,7 @@ Dictionary<string, Guid> SeedAuthors(IDocumentSession session)
7272
foreach (var (key, (id, name, bio)) in authors)
7373
{
7474
var @event = AuthorAggregate.Create(id, name, bio);
75-
session.Events.StartStream<AuthorAggregate>(id, @event);
75+
_ = session.Events.StartStream<AuthorAggregate>(id, @event);
7676
result[key] = id;
7777
}
7878

@@ -99,9 +99,9 @@ Dictionary<string, Guid> SeedCategories(IDocumentSession session)
9999
var translations = names.ToDictionary(
100100
kvp => kvp.Key,
101101
kvp => new CategoryTranslation(kvp.Value, null));
102-
102+
103103
var @event = CategoryAggregate.Create(id, translations);
104-
session.Events.StartStream<CategoryAggregate>(id, @event);
104+
_ = session.Events.StartStream<CategoryAggregate>(id, @event);
105105
result[key] = id;
106106
}
107107

@@ -208,11 +208,11 @@ void SeedBooks(
208208
book.Description,
209209
book.PublicationDate,
210210
publisherIds[book.Publisher],
211-
book.Authors.Select(a => authorIds[a]).ToList(),
212-
book.Categories.Select(c => categoryIds[c]).ToList()
211+
[.. book.Authors.Select(a => authorIds[a])],
212+
[.. book.Categories.Select(c => categoryIds[c])]
213213
);
214214

215-
session.Events.StartStream<BookAggregate>(bookId, @event);
215+
_ = session.Events.StartStream<BookAggregate>(bookId, @event);
216216
}
217217
}
218218
}

0 commit comments

Comments
 (0)