Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ namespace Hikkaba.Infrastructure.Repositories.Telemetry;

public static class RepositoriesTelemetry
{
public static readonly ActivitySource BoardSource = new("Hikkaba.Infrastructure.Repositories.Board");
public static readonly ActivitySource CategorySource = new("Hikkaba.Infrastructure.Repositories.Category");
public static readonly ActivitySource ThreadSource = new("Hikkaba.Infrastructure.Repositories.Thread");
public static readonly ActivitySource PostSource = new("Hikkaba.Infrastructure.Repositories.Post");
Expand Down
137 changes: 123 additions & 14 deletions Hikkaba.Tests.Integration/Builders/TestDataBuilder.ApplicationUser.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,145 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Hikkaba.Data.Entities;
using Hikkaba.Shared.Constants;
using Microsoft.AspNetCore.Identity;

namespace Hikkaba.Tests.Integration.Builders;

internal sealed partial class TestDataBuilder
{
private ApplicationUser? _admin;
private readonly List<ApplicationUser> _users = [];
private readonly List<(ApplicationUser User, ApplicationRole Role)> _pendingUserRoleAssignments = [];

public ApplicationUser Admin => _admin ?? throw new InvalidOperationException("Admin not created. Call WithDefaultAdmin() first.");
/// <summary>
/// Returns the default admin user (created via WithUser(Defaults.AdministratorUserName, isAdmin: true)).
/// </summary>
public ApplicationUser Admin => GetUser(Defaults.AdministratorUserName);

public TestDataBuilder WithDefaultAdmin()
/// <summary>
/// Returns the last created user.
/// </summary>
public ApplicationUser LastUser =>
_users.LastOrDefault()
?? throw new InvalidOperationException("User not created. Call WithUser() first.");

/// <summary>
/// Creates a user with the specified parameters.
/// </summary>
/// <param name="userName">The username for the user.</param>
/// <param name="email">The email address. If null, defaults to "{userName}@example.com".</param>
/// <param name="isDeleted">Whether the user is marked as deleted.</param>
/// <param name="emailConfirmed">Whether the email is confirmed.</param>
/// <param name="lastLoginAt">The last login timestamp.</param>
/// <param name="lockoutEnabled">Whether lockout is enabled for the user.</param>
/// <param name="lockoutEnd">The lockout end date.</param>
/// <param name="isAdmin">Whether to assign the administrator role to the user.</param>
/// <param name="isModerator">Whether to assign the moderator role to the user.</param>
public TestDataBuilder WithUser(
string userName,
string? email = null,
bool isDeleted = false,
bool emailConfirmed = true,
DateTime? lastLoginAt = null,
bool lockoutEnabled = false,
DateTimeOffset? lockoutEnd = null,
bool isAdmin = false,
bool isModerator = false)
{
_admin = new ApplicationUser
var emailValue = email ?? $"{userName}@example.com";
var user = new ApplicationUser
{
UserName = "admin",
NormalizedUserName = "ADMIN",
Email = "[email protected]",
NormalizedEmail = "[email protected]",
EmailConfirmed = true,
SecurityStamp = "896e8014-c237-41f5-a925-dabf640ee4c4",
ConcurrencyStamp = "43035b63-359d-4c23-8812-29bbc5affbf2",
UserName = userName,
NormalizedUserName = userName.ToUpperInvariant(),
Email = emailValue,
NormalizedEmail = emailValue.ToUpperInvariant(),
EmailConfirmed = emailConfirmed,
IsDeleted = isDeleted,
LastLoginAt = lastLoginAt,
LockoutEnabled = lockoutEnabled,
LockoutEnd = lockoutEnd,
SecurityStamp = _guidGenerator.GenerateSeededGuid().ToString(),
ConcurrencyStamp = _guidGenerator.GenerateSeededGuid().ToString(),
CreatedAt = TimeProvider.GetUtcNow().UtcDateTime,
};
_dbContext.Users.Add(_admin);
_users.Add(user);
_dbContext.Users.Add(user);

if (isAdmin)
{
WithAdministratorRole();
WithUserRole(userName, Defaults.AdministratorRoleName);
}

if (isModerator)
{
WithModeratorRole();
WithUserRole(userName, Defaults.ModeratorRoleName);
}

return this;
}

/// <summary>
/// Gets a user by username.
/// </summary>
public ApplicationUser GetUser(string userName)
{
return _users.Find(u => u.UserName == userName)
?? throw new InvalidOperationException($"User with username '{userName}' not found.");
}

/// <summary>
/// Assigns a role to a user. The role assignment is deferred until SaveAsync is called.
/// If the assignment already exists, does nothing.
/// </summary>
public TestDataBuilder WithUserRole(string userName, string roleName)
{
var user = GetUser(userName);
var role = GetRole(roleName);

// Check if assignment already exists
if (_pendingUserRoleAssignments.Exists(x => x.User == user && x.Role == role))
{
return this;
}

_pendingUserRoleAssignments.Add((user, role));
return this;
}

/// <summary>
/// Applies pending user role assignments after users and roles have been saved.
/// </summary>
private async Task ApplyPendingUserRoleAssignmentsAsync(CancellationToken cancellationToken)
{
if (_pendingUserRoleAssignments.Count == 0)
{
return;
}

foreach (var (user, role) in _pendingUserRoleAssignments)
{
_dbContext.UserRoles.Add(new IdentityUserRole<int>
{
UserId = user.Id,
RoleId = role.Id,
});
}

await _dbContext.SaveChangesAsync(cancellationToken);
_pendingUserRoleAssignments.Clear();
}

private void EnsureAdminExists()
{
if (_admin == null)
var adminExists = _users.Exists(u => u.UserName == Defaults.AdministratorUserName);
if (!adminExists)
{
throw new InvalidOperationException("Admin must be created first. Call WithDefaultAdmin().");
throw new InvalidOperationException("Admin must be created first. Call WithUser(\"admin\", isAdmin: true).");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public TestDataBuilder WithNotice(string text)
Post = _lastPost!,
Text = text,
CreatedAt = TimeProvider.GetUtcNow().UtcDateTime,
CreatedBy = _admin!,
CreatedBy = Admin,
};
_dbContext.Notices.Add(notice);
_lastPost!.Notices.Add(notice);
Expand Down
1 change: 0 additions & 1 deletion Hikkaba.Tests.Integration/Builders/TestDataBuilder.Ban.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ internal sealed partial class TestDataBuilder
private readonly List<Ban> _bans = [];
private Ban? _lastBan;

public IReadOnlyList<Ban> Bans => _bans;
public Ban LastBan => _lastBan ?? throw new InvalidOperationException("No ban created yet.");
public int LastBanId => _lastBan?.Id ?? throw new InvalidOperationException("No ban created yet.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ internal sealed partial class TestDataBuilder
{
private readonly List<Category> _categories = [];

public IReadOnlyList<Category> Categories => _categories;

/// <summary>
/// Returns the last created category.
/// </summary>
Expand Down
54 changes: 5 additions & 49 deletions Hikkaba.Tests.Integration/Builders/TestDataBuilder.Moderator.cs
Original file line number Diff line number Diff line change
@@ -1,62 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Hikkaba.Data.Entities;

namespace Hikkaba.Tests.Integration.Builders;

internal sealed partial class TestDataBuilder
{
private readonly List<ApplicationUser> _moderators = [];

public IReadOnlyList<ApplicationUser> Moderators => _moderators;

/// <summary>
/// Returns the last created moderator.
/// </summary>
public ApplicationUser LastModerator =>
_moderators.LastOrDefault()
?? throw new InvalidOperationException("Moderator not created. Call WithModerator() first.");

/// <summary>
/// Creates a moderator user with the specified username.
/// </summary>
public TestDataBuilder WithModerator(string userName)
{
var moderator = new ApplicationUser
{
UserName = userName,
NormalizedUserName = userName.ToUpperInvariant(),
Email = $"{userName}@example.com",
NormalizedEmail = $"{userName.ToUpperInvariant()}@EXAMPLE.COM",
EmailConfirmed = true,
SecurityStamp = _guidGenerator.GenerateSeededGuid().ToString(),
ConcurrencyStamp = _guidGenerator.GenerateSeededGuid().ToString(),
CreatedAt = TimeProvider.GetUtcNow().UtcDateTime,
};
_moderators.Add(moderator);
_dbContext.Users.Add(moderator);
return this;
}

/// <summary>
/// Gets a moderator by username.
/// </summary>
public ApplicationUser GetModerator(string userName)
{
return _moderators.Find(m => m.UserName == userName)
?? throw new InvalidOperationException($"Moderator with username '{userName}' not found.");
}

/// <summary>
/// Assigns a moderator to a category.
/// Assigns a user as moderator to a category.
/// </summary>
/// <param name="categoryAlias">The alias of the category.</param>
/// <param name="moderatorUserName">The username of the moderator.</param>
/// <param name="moderatorUserName">The username of the user to assign as moderator.</param>
public TestDataBuilder WithCategoryModerator(string categoryAlias, string moderatorUserName)
{
var category = GetCategory(categoryAlias);
var moderator = GetModerator(moderatorUserName);
var moderator = GetUser(moderatorUserName);

var categoryToModerator = new CategoryToModerator
{
Expand All @@ -68,10 +24,10 @@ public TestDataBuilder WithCategoryModerator(string categoryAlias, string modera
}

/// <summary>
/// Assigns multiple moderators to a category.
/// Assigns multiple users as moderators to a category.
/// </summary>
/// <param name="categoryAlias">The alias of the category.</param>
/// <param name="moderatorUserNames">The usernames of the moderators.</param>
/// <param name="moderatorUserNames">The usernames of the users to assign as moderators.</param>
public TestDataBuilder WithCategoryModerators(string categoryAlias, params string[] moderatorUserNames)
{
foreach (var userName in moderatorUserNames)
Expand Down
7 changes: 6 additions & 1 deletion Hikkaba.Tests.Integration/Builders/TestDataBuilder.Post.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@ internal sealed partial class TestDataBuilder
private readonly List<Post> _posts = [];
private Post? _lastPost;

public IReadOnlyList<Post> Posts => _posts;
public Post LastPost => _lastPost ?? throw new InvalidOperationException("No post created yet.");
public long LastPostId => _lastPost?.Id ?? throw new InvalidOperationException("No post created yet.");

public Post GetPost(string messageText)
{
return _posts.Find(p => p.MessageText == messageText)
?? throw new InvalidOperationException($"Post with message '{messageText}' not found");
}

/// <summary>
/// Creates a post in the last created thread.
/// </summary>
Expand Down
66 changes: 66 additions & 0 deletions Hikkaba.Tests.Integration/Builders/TestDataBuilder.Role.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Hikkaba.Data.Entities;
using Hikkaba.Shared.Constants;

namespace Hikkaba.Tests.Integration.Builders;

internal sealed partial class TestDataBuilder
{
private readonly List<ApplicationRole> _roles = [];

/// <summary>
/// Returns the last created role.
/// </summary>
public ApplicationRole LastRole =>
_roles.LastOrDefault()
?? throw new InvalidOperationException("Role not created. Call WithRole() first.");

/// <summary>
/// Creates a role with the specified name. If the role already exists, does nothing.
/// </summary>
public TestDataBuilder WithRole(string roleName)
{
// Check if role already exists
if (_roles.Exists(r => r.Name == roleName))
{
return this;
}

var role = new ApplicationRole
{
Name = roleName,
NormalizedName = roleName.ToUpperInvariant(),
ConcurrencyStamp = _guidGenerator.GenerateSeededGuid().ToString(),
};
_roles.Add(role);
_dbContext.Roles.Add(role);
return this;
}

/// <summary>
/// Creates the default administrator role.
/// </summary>
public TestDataBuilder WithAdministratorRole()
{
return WithRole(Defaults.AdministratorRoleName);
}

/// <summary>
/// Creates the default moderator role.
/// </summary>
public TestDataBuilder WithModeratorRole()
{
return WithRole(Defaults.ModeratorRoleName);
}

/// <summary>
/// Gets a role by name.
/// </summary>
public ApplicationRole GetRole(string roleName)
{
return _roles.Find(r => r.Name == roleName)
?? throw new InvalidOperationException($"Role with name '{roleName}' not found.");
}
}
Loading