diff --git a/AdvancedTodoList.Application/AdvancedTodoList.Application.csproj b/AdvancedTodoList.Application/AdvancedTodoList.Application.csproj new file mode 100644 index 0000000..1e16afc --- /dev/null +++ b/AdvancedTodoList.Application/AdvancedTodoList.Application.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/AdvancedTodoList.Core/Dtos/ApplicationUserDtos.cs b/AdvancedTodoList.Application/Dtos/ApplicationUserDtos.cs similarity index 90% rename from AdvancedTodoList.Core/Dtos/ApplicationUserDtos.cs rename to AdvancedTodoList.Application/Dtos/ApplicationUserDtos.cs index d2cc9c9..fae6253 100644 --- a/AdvancedTodoList.Core/Dtos/ApplicationUserDtos.cs +++ b/AdvancedTodoList.Application/Dtos/ApplicationUserDtos.cs @@ -1,6 +1,6 @@ using AdvancedTodoList.Core.Models.Auth; -namespace AdvancedTodoList.Core.Dtos; +namespace AdvancedTodoList.Application.Dtos; /// /// Represents a minimal view DTO for diff --git a/AdvancedTodoList.Core/Dtos/AuthDtos.cs b/AdvancedTodoList.Application/Dtos/AuthDtos.cs similarity index 93% rename from AdvancedTodoList.Core/Dtos/AuthDtos.cs rename to AdvancedTodoList.Application/Dtos/AuthDtos.cs index 7938d05..1d175e7 100644 --- a/AdvancedTodoList.Core/Dtos/AuthDtos.cs +++ b/AdvancedTodoList.Application/Dtos/AuthDtos.cs @@ -1,4 +1,4 @@ -namespace AdvancedTodoList.Core.Dtos; +namespace AdvancedTodoList.Application.Dtos; /// /// DTO for log in. diff --git a/AdvancedTodoList.Core/Dtos/InvitationLinkDtos.cs b/AdvancedTodoList.Application/Dtos/InvitationLinkDtos.cs similarity index 75% rename from AdvancedTodoList.Core/Dtos/InvitationLinkDtos.cs rename to AdvancedTodoList.Application/Dtos/InvitationLinkDtos.cs index 5b2a4ac..b556146 100644 --- a/AdvancedTodoList.Core/Dtos/InvitationLinkDtos.cs +++ b/AdvancedTodoList.Application/Dtos/InvitationLinkDtos.cs @@ -1,4 +1,4 @@ -namespace AdvancedTodoList.Core.Dtos; +namespace AdvancedTodoList.Application.Dtos; /// /// Represents a DTO for invitation link view. diff --git a/AdvancedTodoList.Core/Dtos/PermissionsAggregate.cs b/AdvancedTodoList.Application/Dtos/PermissionsAggregate.cs similarity index 88% rename from AdvancedTodoList.Core/Dtos/PermissionsAggregate.cs rename to AdvancedTodoList.Application/Dtos/PermissionsAggregate.cs index f2e52ad..020249b 100644 --- a/AdvancedTodoList.Core/Dtos/PermissionsAggregate.cs +++ b/AdvancedTodoList.Application/Dtos/PermissionsAggregate.cs @@ -1,6 +1,6 @@ using AdvancedTodoList.Core.Models.TodoLists.Members; -namespace AdvancedTodoList.Core.Dtos; +namespace AdvancedTodoList.Application.Dtos; /// /// Represents an aggregate of a to-do list member with a role. diff --git a/AdvancedTodoList.Core/Dtos/TodoItemCategoryDtos.cs b/AdvancedTodoList.Application/Dtos/TodoItemCategoryDtos.cs similarity index 70% rename from AdvancedTodoList.Core/Dtos/TodoItemCategoryDtos.cs rename to AdvancedTodoList.Application/Dtos/TodoItemCategoryDtos.cs index 76f079d..efc60d6 100644 --- a/AdvancedTodoList.Core/Dtos/TodoItemCategoryDtos.cs +++ b/AdvancedTodoList.Application/Dtos/TodoItemCategoryDtos.cs @@ -1,4 +1,4 @@ -namespace AdvancedTodoList.Core.Dtos; +namespace AdvancedTodoList.Application.Dtos; public record TodoItemCategoryCreateDto(string Name); diff --git a/AdvancedTodoList.Core/Dtos/TodoListDtos.cs b/AdvancedTodoList.Application/Dtos/TodoListDtos.cs similarity index 90% rename from AdvancedTodoList.Core/Dtos/TodoListDtos.cs rename to AdvancedTodoList.Application/Dtos/TodoListDtos.cs index 6bcbb1a..6c5e62f 100644 --- a/AdvancedTodoList.Core/Dtos/TodoListDtos.cs +++ b/AdvancedTodoList.Application/Dtos/TodoListDtos.cs @@ -1,4 +1,4 @@ -namespace AdvancedTodoList.Core.Dtos; +namespace AdvancedTodoList.Application.Dtos; /// /// DTO for creating/editing a to-do list. diff --git a/AdvancedTodoList.Application/Dtos/TodoListItemDtos.cs b/AdvancedTodoList.Application/Dtos/TodoListItemDtos.cs new file mode 100644 index 0000000..d904e0e --- /dev/null +++ b/AdvancedTodoList.Application/Dtos/TodoListItemDtos.cs @@ -0,0 +1,37 @@ +using AdvancedTodoList.Core.Models.TodoLists; + +namespace AdvancedTodoList.Application.Dtos; + +/// +/// DTO for creating/editing a to-do list item. +/// +public record TodoItemCreateDto( + string Name, string Description, DateTime? DeadlineDate, + int Priority, int? CategoryId + ); + +/// +/// DTO for changing the state of a to-do list item. +/// +public record TodoItemUpdateStateDto(TodoItemState State); + +/// +/// DTO for a full view of a to-do list item. +/// +public record TodoItemGetByIdDto( + int Id, string TodoListId, string Name, + string Description, DateTime? DeadlineDate, + TodoItemState State, int Priority, + ApplicationUserPreviewDto Owner, + TodoItemCategoryViewDto? Category + ); + +/// +/// DTO for a partial view of a to-do list item. +/// +public record TodoItemPreviewDto( + int Id, string TodoListId, string Name, + DateTime? DeadlineDate, TodoItemState State, + int Priority, ApplicationUserPreviewDto Owner, + TodoItemCategoryViewDto? Category + ); \ No newline at end of file diff --git a/AdvancedTodoList.Core/Dtos/TodoListMembersDtos.cs b/AdvancedTodoList.Application/Dtos/TodoListMembersDtos.cs similarity index 92% rename from AdvancedTodoList.Core/Dtos/TodoListMembersDtos.cs rename to AdvancedTodoList.Application/Dtos/TodoListMembersDtos.cs index 4179c78..000dbe6 100644 --- a/AdvancedTodoList.Core/Dtos/TodoListMembersDtos.cs +++ b/AdvancedTodoList.Application/Dtos/TodoListMembersDtos.cs @@ -1,4 +1,4 @@ -namespace AdvancedTodoList.Core.Dtos; +namespace AdvancedTodoList.Application.Dtos; /// /// DTO for a minimal view of a to-do list member. diff --git a/AdvancedTodoList.Core/Dtos/TodoListRolesDtos.cs b/AdvancedTodoList.Application/Dtos/TodoListRolesDtos.cs similarity index 91% rename from AdvancedTodoList.Core/Dtos/TodoListRolesDtos.cs rename to AdvancedTodoList.Application/Dtos/TodoListRolesDtos.cs index e20c8e4..f6eca62 100644 --- a/AdvancedTodoList.Core/Dtos/TodoListRolesDtos.cs +++ b/AdvancedTodoList.Application/Dtos/TodoListRolesDtos.cs @@ -1,6 +1,6 @@ using AdvancedTodoList.Core.Models.TodoLists.Members; -namespace AdvancedTodoList.Core.Dtos; +namespace AdvancedTodoList.Application.Dtos; /// /// Represents a partial view for a role. diff --git a/AdvancedTodoList.Application/Mapping/MappingGlobalSettings.cs b/AdvancedTodoList.Application/Mapping/MappingGlobalSettings.cs new file mode 100644 index 0000000..5175ad8 --- /dev/null +++ b/AdvancedTodoList.Application/Mapping/MappingGlobalSettings.cs @@ -0,0 +1,30 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Models.TodoLists; +using AdvancedTodoList.Core.Models.TodoLists.Members; +using Mapster; + +namespace AdvancedTodoList.Application.Mapping; + +/// +/// Class that defines global mapping settings. +/// +public static class MappingGlobalSettings +{ + /// + /// Apply global mapping settings. + /// + public static void Apply() + { + // Ignore null IDs + TypeAdapterConfig.NewConfig() + .IgnoreIf((src, dest) => src.RoleId == null, dest => dest.Role!); + TypeAdapterConfig.NewConfig() + .IgnoreIf((src, dest) => src.CategoryId == null, dest => dest.Category!); + TypeAdapterConfig.NewConfig() + .IgnoreIf((src, dest) => src.CategoryId == null, dest => dest.Category!); + + // Convert null strings into empty strings and trim strings + TypeAdapterConfig.GlobalSettings.Default + .AddDestinationTransform((string? dest) => dest != null ? dest.Trim() : string.Empty); + } +} diff --git a/AdvancedTodoList.Application/Options/AccessTokenOptions.cs b/AdvancedTodoList.Application/Options/AccessTokenOptions.cs new file mode 100644 index 0000000..185ae54 --- /dev/null +++ b/AdvancedTodoList.Application/Options/AccessTokenOptions.cs @@ -0,0 +1,27 @@ +namespace AdvancedTodoList.Application.Options; + +/// +/// A class that contains access token options. +/// +public class AccessTokenOptions +{ + /// + /// Valid audience of tokens. + /// + public string ValidAudience { get; set; } = null!; + + /// + /// Valid issuer of tokens. + /// + public string ValidIssuer { get; set; } = null!; + + /// + /// Seconds before token expires. + /// + public int ExpirationSeconds { get; set; } + + /// + /// A secret key used for signing access tokens. + /// + public string SecretKey { get; set; } = null!; +} diff --git a/AdvancedTodoList.Application/Options/InvitationLinkOptions.cs b/AdvancedTodoList.Application/Options/InvitationLinkOptions.cs new file mode 100644 index 0000000..2430e7e --- /dev/null +++ b/AdvancedTodoList.Application/Options/InvitationLinkOptions.cs @@ -0,0 +1,17 @@ +namespace AdvancedTodoList.Application.Options; + +/// +/// A class that contains invitation link options. +/// +public class InvitationLinkOptions +{ + /// + /// Size of the refresh token in bytes. + /// + public int Size { get; set; } + + /// + /// Days before token expires. + /// + public int ExpirationDays { get; set; } +} diff --git a/AdvancedTodoList.Application/Options/RefreshTokenOptions.cs b/AdvancedTodoList.Application/Options/RefreshTokenOptions.cs new file mode 100644 index 0000000..1877057 --- /dev/null +++ b/AdvancedTodoList.Application/Options/RefreshTokenOptions.cs @@ -0,0 +1,17 @@ +namespace AdvancedTodoList.Application.Options; + +/// +/// A class that contains refresh token options. +/// +public class RefreshTokenOptions +{ + /// + /// Size of the refresh token in bytes. + /// + public int Size { get; set; } + + /// + /// Days before token expires. + /// + public int ExpirationDays { get; set; } +} diff --git a/AdvancedTodoList.Application/Services/Definitions/Auth/IAccessTokensService.cs b/AdvancedTodoList.Application/Services/Definitions/Auth/IAccessTokensService.cs new file mode 100644 index 0000000..bf8385a --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/Auth/IAccessTokensService.cs @@ -0,0 +1,28 @@ +using AdvancedTodoList.Core.Models.Auth; + +namespace AdvancedTodoList.Application.Services.Definitions.Auth; + +/// +/// An interface for a service that manages access tokens. +/// +public interface IAccessTokensService +{ + /// + /// Generates an access token for the user. + /// + /// User which will receive an access token. + /// + /// A string that represents an access token. + /// + string GenerateAccessToken(ApplicationUser user); + + /// + /// Validates an access token without checking expiration time and then returns + /// ID of the user stored in it asynchronously. + /// + /// A string that represents an access token. + /// + /// A user ID retrieved from the access token or , if validation failed. + /// + Task GetUserIdFromExpiredTokenAsync(string accessToken); +} diff --git a/AdvancedTodoList.Application/Services/Definitions/Auth/IAuthService.cs b/AdvancedTodoList.Application/Services/Definitions/Auth/IAuthService.cs new file mode 100644 index 0000000..eea50ea --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/Auth/IAuthService.cs @@ -0,0 +1,49 @@ +using AdvancedTodoList.Application.Dtos; + +namespace AdvancedTodoList.Application.Services.Definitions.Auth; + +/// +/// Interface for service that performs authentication operations. +/// +public interface IAuthService +{ + /// + /// Logs a user in asynchronously. + /// + /// Data required for logging in. + /// + /// Returns a task representing the asynchronous operation, containing a + /// response with access and refresh tokens or null if authorization fails. + /// + Task LogInAsync(LogInDto logInDto); + + /// + /// Registers a new user asynchronously. + /// + /// Data required for user registration. + /// + /// Returns a task representing the asynchronous operation, containing the registration result. + /// + Task RegisterAsync(RegisterDto registerDto); + + /// + /// Refreshes the access token asynchronously. + /// + /// Data required for token refresh. + /// + /// Returns a task representing the asynchronous operation, + /// containing a response with access and refresh tokens or null if authorization fails. + /// + Task RefreshAsync(RefreshDto refreshDto); + + /// + /// Logs a user out asynchronously by revoking a refresh token. + /// + /// ID of the caller. + /// Data required for logging out. + /// + /// Returns a task representing the asynchronous operation, + /// indicating the success or failure of the operation. + /// + Task LogOutAsync(string userId, LogOutDto logOutDto); +} diff --git a/AdvancedTodoList.Application/Services/Definitions/Auth/IPermissionsChecker.cs b/AdvancedTodoList.Application/Services/Definitions/Auth/IPermissionsChecker.cs new file mode 100644 index 0000000..8fb10bb --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/Auth/IPermissionsChecker.cs @@ -0,0 +1,68 @@ +using AdvancedTodoList.Core.Models; +using AdvancedTodoList.Core.Models.TodoLists.Members; + +namespace AdvancedTodoList.Application.Services.Definitions.Auth; + +/// +/// Interface for a service that checks user's permissions. +/// +public interface IPermissionsChecker +{ + /// + /// Asynchronously checks whether the user is a member of the to-do list with + /// specified ID. + /// + /// To-do list context. + /// + /// if user is a member of the list; otherwise . + /// + Task IsMemberOfListAsync(TodoListContext context); + + /// + /// Asynchronously checks whether the user is a member of the to-do list and + /// has a permission defined by the funciton . + /// + /// To-do list context. + /// Function that should return if user has required permission. + /// + /// if user is a member of the list and has required permission; + /// otherwise . + /// + Task HasPermissionAsync(TodoListContext context, Func permission); + + /// + /// Asynchronously checks whether the user can touch an entity. + /// + /// + /// This method firstly checks whether implements + /// interface and if yes, checks if the user is the owner of the entity and is a member of the to-do list; + /// otherwise the method checks if user has the permission defined by the function . + /// + /// Type of the entity. + /// Type of the unique identifier used by the entity. + /// To-do list context. + /// ID of the entity. + /// Function that should return if user has required permission. + /// + /// if user is either an owner of the entity and a member of a to-do list, + /// or he/she/they has permission defined by ; otherwise . + /// + Task CanTouchEntityAsync(TodoListContext context, TEntity entity, + Func permission) + where TEntity : class, IEntity + where TKey : IEquatable; + + /// + /// Asynchronously checks whether the user has a permission to change the role + /// with the priority of . + /// + /// To-do list context. + /// ID of the role. + /// Function that should return if user has required permission. + /// + /// if user has and highest role priority than + /// the ; otherwise . + /// + Task HasPermissionOverRoleAsync(TodoListContext context, int rolePriority, + Func permission); +} diff --git a/AdvancedTodoList.Application/Services/Definitions/Auth/IRefreshTokensService.cs b/AdvancedTodoList.Application/Services/Definitions/Auth/IRefreshTokensService.cs new file mode 100644 index 0000000..a43f2c7 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/Auth/IRefreshTokensService.cs @@ -0,0 +1,37 @@ +namespace AdvancedTodoList.Application.Services.Definitions.Auth; + +/// +/// An interface for a service that manages refresh tokens. +/// +public interface IRefreshTokensService +{ + /// + /// Generates a refresh token for the user and saves it asynchronously. + /// + /// ID of the user who will receive the token. + /// + /// A string that represents a refresh token or if user does not exist. + /// + Task GenerateAsync(string userId); + + /// + /// Revokes the refresh token of the user asynchronously + /// + /// ID of the user whose token is being revoked. + /// Value of the token to be revoked. + /// + /// on success; otherwise. + /// + Task RevokeAsync(string userId, string token); + + /// + /// Checks whether refresh token is valid asynchronously. + /// + /// ID of the user whose token is being validated. + /// Value of the token to be validated. + /// + /// if token is valid; + /// otherwise. + /// + Task ValidateAsync(string userId, string token); +} diff --git a/AdvancedTodoList.Application/Services/Definitions/Auth/RegisterResult.cs b/AdvancedTodoList.Application/Services/Definitions/Auth/RegisterResult.cs new file mode 100644 index 0000000..c9d9e7e --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/Auth/RegisterResult.cs @@ -0,0 +1,46 @@ +namespace AdvancedTodoList.Application.Services.Definitions.Auth; + +/// +/// Class that represents a result of register operation. +/// +public class RegisterResult +{ + /// + /// Flag which determines whether result reports succeess. + /// + public bool IsSuccess { get; private init; } + /// + /// Error messages. + /// + public IEnumerable Errors { get; private init; } + + private RegisterResult(bool isSuccess, IEnumerable errors) + { + IsSuccess = isSuccess; + Errors = errors; + } + + /// + /// Returns a result that reports success. + /// + /// + /// A result that reports success. + /// + public static RegisterResult Success() => new(true, []); + + /// + /// Returns a result that reports failure. + /// + /// Error messages. + /// + /// A result that reports failure. + /// + public static RegisterResult Failure(IEnumerable errors) => new(false, errors); +} + +/// +/// Record that represents a register error. +/// +/// Property that caused an error or '$' if error was not caused by a property. +/// An error message. +public record RegisterError(string Property, string Message); diff --git a/AdvancedTodoList.Application/Services/Definitions/IInvitationLinksService.cs b/AdvancedTodoList.Application/Services/Definitions/IInvitationLinksService.cs new file mode 100644 index 0000000..f335ac5 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/IInvitationLinksService.cs @@ -0,0 +1,54 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Pagination; + +namespace AdvancedTodoList.Application.Services.Definitions; + +/// +/// An interface for a service that manages invitation links. +/// +public interface IInvitationLinksService +{ + /// + /// Joins the caller to the to-do list by invitation list asynchronously. + /// + /// ID of the caller. + /// Invitation link to use. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + Task JoinAsync(string callerId, string invitationLinkValue); + + /// + /// Gets invitation links associated with the to-do list asynchronously. + /// + /// To-do list context of the operation. + /// Pagination parameters to use. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + Task>> GetInvitationLinksAsync(TodoListContext context, + PaginationParameters parameters); + + /// + /// Creates an invitation link associated to the to-do list asynchronously. + /// + /// To-do list context. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + Task> CreateAsync(TodoListContext context); + + /// + /// Deletes an invitation link associted to the to-do list asynchronously. + /// + /// To-do list context. + /// ID of the link. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + Task DeleteAsync(TodoListContext context, int linkId); +} diff --git a/AdvancedTodoList.Application/Services/Definitions/ITodoItemCategoriesService.cs b/AdvancedTodoList.Application/Services/Definitions/ITodoItemCategoriesService.cs new file mode 100644 index 0000000..55cae08 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/ITodoItemCategoriesService.cs @@ -0,0 +1,78 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Pagination; + +namespace AdvancedTodoList.Application.Services.Definitions; + +/// +/// Interface for a service that manages to-do list items categories. +/// +public interface ITodoItemCategoriesService +{ + /// + /// Asynchronously checks whether the category ID is valid for the given context. + /// + /// + /// ID is considered as valid. + /// + /// To-do list context. + /// ID of the category to validate. + /// + /// A task representing the asynchronous operation. + /// if ID is valid, otherwise. + /// + public Task IsCategoryValidForContext(TodoListContext context, int? categoryId); + + /// + /// Retrieves a page of to-do list items categories of the list with the specified ID. + /// + /// To-do list context. + /// Pagination parameters to use. + /// Optional name to filter categories by. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task>> GetCategoriesOfListAsync( + TodoListContext context, PaginationParameters paginationParameters, string? name = null); + + /// + /// Retrieves a to-do list item category by its ID asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to retrieve. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task> GetByIdAsync(TodoListContext context, int categoryId); + + /// + /// Creates a new to-do list item category asynchronously. + /// + /// To-do list context. + /// The DTO containing information for creating the to-do list item. + /// ID of the user who creates the to-do list item. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task> CreateAsync(TodoListContext context, TodoItemCategoryCreateDto dto); + + /// + /// Edits a to-do list item category asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to edit. + /// The DTO containing information for editing the to-do list item. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task EditAsync(TodoListContext context, int categoryId, TodoItemCategoryCreateDto dto); + + /// + /// Deletes a to-do list item category asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to delete. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task DeleteAsync(TodoListContext context, int categoryId); +} diff --git a/AdvancedTodoList.Application/Services/Definitions/ITodoItemsService.cs b/AdvancedTodoList.Application/Services/Definitions/ITodoItemsService.cs new file mode 100644 index 0000000..810d229 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/ITodoItemsService.cs @@ -0,0 +1,75 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Pagination; +using AdvancedTodoList.Core.Specifications.Filters; + +namespace AdvancedTodoList.Application.Services.Definitions; + +/// +/// Interface for a service that manages to-do list items. +/// +public interface ITodoItemsService +{ + /// + /// Retrieves a page of to-do list items of the list with the specified ID. + /// + /// To-do list context. + /// Pagination parameters to use. + /// Filter parameters to apply. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task>> GetItemsOfListAsync( + TodoListContext context, PaginationParameters paginationParameters, TodoItemsFilter filter); + + /// + /// Retrieves a to-do list item by its ID asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to retrieve. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task> GetByIdAsync(TodoListContext context, int itemId); + + /// + /// Creates a new to-do list item asynchronously. + /// + /// To-do list context. + /// The DTO containing information for creating the to-do list item. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task CreateAsync(TodoListContext context, TodoItemCreateDto dto); + + /// + /// Edits a to-do list item asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to edit. + /// The DTO containing information for editing the to-do list item. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task EditAsync(TodoListContext context, int itemId, TodoItemCreateDto dto); + + /// + /// Updates the state of a to-do list item asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to update the state. + /// The DTO which contains the state of the to-do item to set. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task UpdateStateAsync(TodoListContext context, int itemId, TodoItemUpdateStateDto stateDto); + + /// + /// Deletes a to-do list item asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to delete. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task DeleteAsync(TodoListContext context, int itemId); +} diff --git a/AdvancedTodoList.Application/Services/Definitions/ITodoListDependantEntitiesService.cs b/AdvancedTodoList.Application/Services/Definitions/ITodoListDependantEntitiesService.cs new file mode 100644 index 0000000..30a371e --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/ITodoListDependantEntitiesService.cs @@ -0,0 +1,94 @@ +using AdvancedTodoList.Core.Models; +using AdvancedTodoList.Core.Models.TodoLists; +using AdvancedTodoList.Core.Models.TodoLists.Members; +using AdvancedTodoList.Core.Pagination; +using AdvancedTodoList.Core.Specifications; + +namespace AdvancedTodoList.Application.Services.Definitions; + +/// +/// An interface that represents a service for perfoming CRUD operations +/// on to-do list dependant entities and mapping/unmapping DTOs. +/// +/// Type of the to-do list dependant entity. +/// Type of the unique identifier used by the entity. +public interface ITodoListDependantEntitiesService + where TEntity : class, IEntity, ITodoListDependant + where TKey : IEquatable +{ + /// + /// Retrieves a page of to-do list dependant entities mapped to . + /// + /// + /// This method checks if to-do list ID is valid, but doesn't filter by it. + /// Filtering should be done in . + /// + /// To-do list context. + /// Specification to apply to entities. + /// Pagination parameters to use. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation and the requested page on success. + /// + public Task>> GetPageAsync(TodoListContext context, + ISpecification specification, PaginationParameters paginationParameters); + + /// + /// Retrieves a to-do list dependant entity by its ID asynchronously and maps it to . + /// + /// To-do list context. + /// The ID of the entity to retrieve. + /// DTO to map entity to. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation and the requested dto on success. + /// + Task> GetByIdAsync(TodoListContext context, TKey entityId) where TDto : class; + + /// + /// Creates a new a to-do list dependant entity asynchronously from the DTO. + /// + /// + /// If implements the interface, then + /// this method will set the caller as an owner. + /// + /// To-do list context. + /// The DTO containing information for creating the entity. + /// Optional accessor for the permission required for the user to perform the action. + /// + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation and the created mapped to + /// on success. + /// + public Task> CreateAsync( + TodoListContext context, TInputDto dto, Func? permission = null) + where TOutputDto : class; + + /// + /// Edits a to-do list dependant entity asynchronously. + /// + /// To-do list context. + /// The ID of the entity to edit. + /// The DTO containing information for editing the entity. + /// Optional accessor for the permission required for the user to perform the action. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public Task UpdateAsync(TodoListContext context, TKey entityId, + TDto dto, Func? permission = null); + + /// + /// Deletes a to-do list dependant entity asynchronously. + /// + /// To-do list context. + /// The ID of the entity to delete. + /// Optional accessor for the permission required for the user to perform the action. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public Task DeleteAsync(TodoListContext context, + TKey entityId, Func? permission = null); +} diff --git a/AdvancedTodoList.Application/Services/Definitions/ITodoListMembersService.cs b/AdvancedTodoList.Application/Services/Definitions/ITodoListMembersService.cs new file mode 100644 index 0000000..5887d72 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/ITodoListMembersService.cs @@ -0,0 +1,55 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Pagination; +using AdvancedTodoList.Core.Specifications.Filters; + +namespace AdvancedTodoList.Application.Services.Definitions; + +/// +/// Interface for a service that manages to-do list members. +/// +public interface ITodoListMembersService +{ + /// + /// Gets a page with members of a to-do list asynchronously. + /// + /// To-do list context. + /// Pagination parameters to use. + /// Filter parameters to use. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + Task>> GetMembersAsync(TodoListContext context, + PaginationParameters paginationParameters, TodoListMembersFilter filter); + + /// + /// Adds a member to a to-do list asynchronously. + /// + /// To-do list context. + /// DTO that contains information needed for adding a member. Supossed to be valid. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + Task AddMemberAsync(TodoListContext context, TodoListMemberAddDto dto); + + /// + /// Updates a role of the member of a to-do list asynchronously. + /// + /// To-do list context. + /// ID of the member. + /// DTO that contains information needed for updating a role. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + Task UpdateMemberRoleAsync(TodoListContext context, + int memberId, TodoListMemberUpdateRoleDto dto); + + /// + /// Removes a member from a to-do list asynchronously. + /// + /// To-do list context. + /// ID of the member. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + Task RemoveMemberAsync(TodoListContext context, int memberId); +} diff --git a/AdvancedTodoList.Application/Services/Definitions/ITodoListRolesService.cs b/AdvancedTodoList.Application/Services/Definitions/ITodoListRolesService.cs new file mode 100644 index 0000000..81794e1 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/ITodoListRolesService.cs @@ -0,0 +1,63 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Pagination; + +namespace AdvancedTodoList.Application.Services.Definitions; + +/// +/// Interface for a service that manages to-do list role. +/// +public interface ITodoListRolesService +{ + /// + /// Retrieves a page of to-do list roles of the list with the specified ID. + /// + /// To-do list context. + /// Pagination parameters to use. + /// Optional name to filter categories by. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task>> GetRolesOfListAsync( + TodoListContext context, PaginationParameters paginationParameters, string? name = null); + + /// + /// Retrieves a to-do list role by its ID asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list role to retrieve. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task> GetByIdAsync(TodoListContext context, int roleId); + + /// + /// Creates a new to-do list role asynchronously. + /// + /// To-do list context. + /// The DTO containing information for creating the to-do list role. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task> CreateAsync(TodoListContext context, TodoListRoleCreateDto dto); + + /// + /// Edits a to-do list role asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list role to edit. + /// The DTO containing information for editing the to-do list role. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task EditAsync(TodoListContext context, int roleId, TodoListRoleCreateDto dto); + + /// + /// Deletes a to-do list role asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list role to delete. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task DeleteAsync(TodoListContext context, int roleId); +} diff --git a/AdvancedTodoList.Application/Services/Definitions/ITodoListsService.cs b/AdvancedTodoList.Application/Services/Definitions/ITodoListsService.cs new file mode 100644 index 0000000..bf0b941 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/ITodoListsService.cs @@ -0,0 +1,69 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Pagination; +using AdvancedTodoList.Core.Specifications.Filters; + +namespace AdvancedTodoList.Application.Services.Definitions; + +/// +/// Interface for a service that manages to-do lists. +/// +public interface ITodoListsService +{ + /// + /// Retrieves a page of to-do lists, with the requirement that the user + /// is a member of those lists. + /// + /// Id of the user + /// Pagination parameters to use. + /// Filter parameters to apply. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task> GetListsOfUserAsync(string userId, + PaginationParameters paginationParameters, TodoListsFilter filter); + + /// + /// Retrieves a to-do list by its ID asynchronously. + /// + /// To-do list context. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public Task> GetByIdAsync(TodoListContext context); + + /// + /// Creates a new to-do list asynchronously. + /// + /// + /// This method should also create an "Owner" role with all permissions and assign the caller to it. + /// + /// The DTO containing information for creating the to-do list. + /// ID of the user who creates the to-do list. + /// + /// A task representing the asynchronous operation. The task contains + /// a created model mapped to . + /// + public Task CreateAsync(TodoListCreateDto dto, string callerId); + + /// + /// Edits a to-do list asynchronously. + /// + /// To-do list context. + /// The DTO containing information for editing the to-do list. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public Task EditAsync(TodoListContext context, TodoListCreateDto dto); + + /// + /// Deletes a to-do list asynchronously. + /// + /// To-do list context. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public Task DeleteAsync(TodoListContext context); +} diff --git a/AdvancedTodoList.Application/Services/Definitions/JoinByInvitationLinkResult.cs b/AdvancedTodoList.Application/Services/Definitions/JoinByInvitationLinkResult.cs new file mode 100644 index 0000000..b0c9022 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/JoinByInvitationLinkResult.cs @@ -0,0 +1,42 @@ +using AdvancedTodoList.Application.Dtos; + +namespace AdvancedTodoList.Application.Services.Definitions; + +/// +/// Represents possible results of the join to-do list by invitatation link operation. +/// +public class JoinByInvitationLinkResult(JoinByInvitationLinkStatus status, TodoListMemberMinimalViewDto? dto = null) +{ + /// + /// Status of the operation. + /// + public JoinByInvitationLinkStatus Status { get; } = status; + + /// + /// Gets additional DTO of the member, can be . + /// + public TodoListMemberMinimalViewDto? Dto { get; } = dto; +} + +/// +/// Enum that represents possible result statuses of the join to-do list by invitatation link operation. +/// +public enum JoinByInvitationLinkStatus +{ + /// + /// Operation was successfull. + /// + Success, + /// + /// Invitation link was not found. + /// + NotFound, + /// + /// Invitation link is expired. + /// + Expired, + /// + /// User is already a member of the to-do list. + /// + UserIsAlreadyMember +} \ No newline at end of file diff --git a/AdvancedTodoList.Application/Services/Definitions/ServiceResponse.cs b/AdvancedTodoList.Application/Services/Definitions/ServiceResponse.cs new file mode 100644 index 0000000..05cc5dd --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/ServiceResponse.cs @@ -0,0 +1,39 @@ +namespace AdvancedTodoList.Application.Services.Definitions; + +/// +/// Represent a generic service response which indicates an operation status and +/// contains an additonal information. +/// +/// Type of the content saved in the response. +public class ServiceResponse(ServiceResponseStatus status, T? result = default) +{ + /// + /// Gets the status of operation. + /// + public ServiceResponseStatus Status { get; } = status; + + /// + /// Gets the result of the operation. + /// + public T? Result { get; } = result; +} + +/// +/// Represents a possible service response status. +/// +public enum ServiceResponseStatus +{ + /// + /// Status that indicates success. + /// + Success, + /// + /// Status that indicates that entity could not be found or the caller isn't + /// suppossed to know that entity exists. + /// + NotFound, + /// + /// Status that indicates that user has no permission to perform the operation. + /// + Forbidden +} diff --git a/AdvancedTodoList.Application/Services/Definitions/TodoItemsServiceResponse.cs b/AdvancedTodoList.Application/Services/Definitions/TodoItemsServiceResponse.cs new file mode 100644 index 0000000..51701bc --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/TodoItemsServiceResponse.cs @@ -0,0 +1,37 @@ +using AdvancedTodoList.Application.Dtos; + +namespace AdvancedTodoList.Application.Services.Definitions; + +public class TodoItemsServiceResponse(TodoItemsServiceStatus status, TodoItemGetByIdDto? result = null) +{ + /// + /// Status of the operation. + /// + public TodoItemsServiceStatus Status { get; set; } = status; + + /// + /// Result of the operation. + /// + public TodoItemGetByIdDto? Result { get; set; } = result; +} + +public enum TodoItemsServiceStatus +{ + /// + /// Status that indicates success. + /// + Success, + /// + /// Status that indicates that entity could not be found or the caller isn't + /// suppossed to know that entity exists. + /// + NotFound, + /// + /// Status that indicates that user has no permission to perform the operation. + /// + Forbidden, + /// + /// Status that indicates that user provided an invalid category ID. + /// + InvalidCategoryId +} \ No newline at end of file diff --git a/AdvancedTodoList.Core/Services/TodoListContext.cs b/AdvancedTodoList.Application/Services/Definitions/TodoListContext.cs similarity index 81% rename from AdvancedTodoList.Core/Services/TodoListContext.cs rename to AdvancedTodoList.Application/Services/Definitions/TodoListContext.cs index b7188ef..1657806 100644 --- a/AdvancedTodoList.Core/Services/TodoListContext.cs +++ b/AdvancedTodoList.Application/Services/Definitions/TodoListContext.cs @@ -1,4 +1,4 @@ -namespace AdvancedTodoList.Core.Services; +namespace AdvancedTodoList.Application.Services.Definitions; /// /// Represents a context of an operation related to the to-do list. diff --git a/AdvancedTodoList.Application/Services/Definitions/TodoListMembersServiceResponse.cs b/AdvancedTodoList.Application/Services/Definitions/TodoListMembersServiceResponse.cs new file mode 100644 index 0000000..f1a6a53 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Definitions/TodoListMembersServiceResponse.cs @@ -0,0 +1,47 @@ +using AdvancedTodoList.Application.Dtos; + +namespace AdvancedTodoList.Application.Services.Definitions; + +/// +/// Class that represents possible results of the method . +/// +public class AddTodoListMemberServiceResult( + TodoListMemberServiceResultStatus status, TodoListMemberMinimalViewDto? dto = null) +{ + /// + /// Status of the operation. + /// + public TodoListMemberServiceResultStatus Status { get; } = status; + + /// + /// Gets additional DTO of the member, can be . + /// + public TodoListMemberMinimalViewDto? Dto { get; } = dto; +} + +/// +/// Enum that represents possible result statuses of the to-do list members service. +/// +public enum TodoListMemberServiceResultStatus +{ + /// + /// Operation was successfull. + /// + Success, + /// + /// To-do list was not found. + /// + NotFound, + /// + /// User is already a member of the to-do list, returned only in the add member method. + /// + UserAlreadyAdded, + /// + /// Role either doesn't exist or it's invalid for the current to-do list. + /// + InvalidRoleId, + /// + /// User has no permission to perform the operation. + /// + Forbidden +} diff --git a/AdvancedTodoList.Application/Services/Implementations/Auth/AccessTokensService.cs b/AdvancedTodoList.Application/Services/Implementations/Auth/AccessTokensService.cs new file mode 100644 index 0000000..2c78cc2 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Implementations/Auth/AccessTokensService.cs @@ -0,0 +1,83 @@ +using AdvancedTodoList.Application.Options; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models.Auth; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; + +namespace AdvancedTodoList.Application.Services.Implementations.Auth; + +/// +/// A service that manages access tokens. +/// +public class AccessTokensService(IOptions options) : IAccessTokensService +{ + private readonly AccessTokenOptions _tokenOptions = options.Value; + + /// + /// Generates an access token for the user. + /// + /// User which will receive an access token. + /// + /// A string that represents an access token. + /// + public string GenerateAccessToken(ApplicationUser user) + { + SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_tokenOptions.SecretKey)); + + List claims = + [ + new(JwtRegisteredClaimNames.Sub, user.Id), + new(JwtRegisteredClaimNames.Email, user.Email!), + new(JwtRegisteredClaimNames.UniqueName, user.UserName!), + new(JwtRegisteredClaimNames.GivenName, user.FirstName), + new(JwtRegisteredClaimNames.FamilyName, user.LastName), + ]; + + JwtSecurityToken token = new( + issuer: _tokenOptions.ValidIssuer, + audience: _tokenOptions.ValidAudience, + expires: DateTime.UtcNow.AddSeconds(_tokenOptions.ExpirationSeconds), + claims: claims, + signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) + ); + string accessToken = new JwtSecurityTokenHandler().WriteToken(token); + + return accessToken; + } + + /// + /// Validates an access token without checking expiration time and then returns + /// ID of the user stored in it asynchronously. + /// + /// A string that represents an access token. + /// + /// A user ID retrieved from the access token or , if validation failed. + /// + public async Task GetUserIdFromExpiredTokenAsync(string accessToken) + { + // Validate the access token + string key = _tokenOptions.SecretKey; + JwtSecurityTokenHandler tokenHandler = new(); + TokenValidationParameters validationParameters = new() + { + ValidateLifetime = false, // Ignore expiration time + ValidIssuer = _tokenOptions.ValidIssuer, + ValidAudience = _tokenOptions.ValidAudience, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) + }; + var validationResult = await tokenHandler.ValidateTokenAsync( + accessToken, validationParameters); + // Return null if validation failed + if (!validationResult.IsValid) return null; + + // Get ID of the user + JwtSecurityToken jwtToken = (JwtSecurityToken)validationResult.SecurityToken; + var subClaim = jwtToken.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Sub); + if (subClaim == null) return null; + + return subClaim.Value; + } +} diff --git a/AdvancedTodoList.Application/Services/Implementations/Auth/AuthService.cs b/AdvancedTodoList.Application/Services/Implementations/Auth/AuthService.cs new file mode 100644 index 0000000..312e0f9 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Implementations/Auth/AuthService.cs @@ -0,0 +1,156 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models.Auth; +using Microsoft.AspNetCore.Identity; + +namespace AdvancedTodoList.Application.Services.Implementations.Auth; + +/// +/// Service that performs authentication operations. +/// +public class AuthService( + IAccessTokensService accessTokensService, + IRefreshTokensService refreshTokensService, + UserManager userManager) + : IAuthService +{ + private readonly IAccessTokensService _accessTokensService = accessTokensService; + private readonly IRefreshTokensService _refreshTokensService = refreshTokensService; + private readonly UserManager _userManager = userManager; + + /// + /// Logs a user in asynchronously. + /// + /// Data required for logging in. + /// + /// Returns a task representing the asynchronous operation, containing a + /// response with access and refresh tokens or null if authorization fails. + /// + public async Task LogInAsync(LogInDto logInDto) + { + // Try to find a user + ApplicationUser? user; + // Try to find by email + if (logInDto.UserNameOrEmail.Contains('@')) + { + user = await _userManager.FindByEmailAsync(logInDto.UserNameOrEmail); + } + // Try to find by username + else + { + user = await _userManager.FindByNameAsync(logInDto.UserNameOrEmail); + } + + // Invalid username/email - fail + if (user == null) return null; + + // Invalid password - fail + if (!await _userManager.CheckPasswordAsync(user, logInDto.Password)) + return null; + + // Generate a new refresh token for the user + string? refreshToken = await _refreshTokensService.GenerateAsync(user.Id); + if (refreshToken == null) return null; + + // Generate an access token + string accessToken = _accessTokensService.GenerateAccessToken(user); + + // Return both tokens + return new(accessToken, refreshToken); + } + + /// + /// Logs a user out asynchronously by revoking a refresh token. + /// + /// ID of the caller. + /// Data required for logging out. + /// + /// Returns a task representing the asynchronous operation, + /// indicating the success or failure of the operation. + /// + public async Task LogOutAsync(string userId, LogOutDto logOutDto) + { + // Revoke the token + return await _refreshTokensService.RevokeAsync(userId, logOutDto.RefreshToken); + } + + /// + /// Refreshes the access token asynchronously. + /// + /// Data required for token refresh. + /// + /// Returns a task representing the asynchronous operation, + /// containing a response with access and refresh tokens or null if authorization fails. + /// + public async Task RefreshAsync(RefreshDto refreshDto) + { + // Try to get a user ID + string? userId = await _accessTokensService + .GetUserIdFromExpiredTokenAsync(refreshDto.AccessToken); + if (userId == null) return null; + + // Validate the refresh token + if (!await _refreshTokensService.ValidateAsync(userId, refreshDto.RefreshToken)) + return null; + + // Find the user + ApplicationUser? user = await _userManager.FindByIdAsync(userId); + // User doesn't exist - return null + if (user == null) return null; + + // Generate access token + string accessToken = _accessTokensService.GenerateAccessToken(user); + + // Return tokens + return new LogInResponse(accessToken, refreshDto.RefreshToken); + } + + /// + /// Registers a new user asynchronously. + /// + /// Data required for user registration. + /// + /// Returns a task representing the asynchronous operation, containing the registration result. + /// + public async Task RegisterAsync(RegisterDto registerDto) + { + List errors = []; + // Check if email is available + if (await _userManager.FindByEmailAsync(registerDto.Email) != null) + errors.Add(new("Email", "Email is already taken.")); + // Check if username is available + if (await _userManager.FindByNameAsync(registerDto.UserName) != null) + errors.Add(new("UserName", "Username is already taken.")); + + if (errors.Count > 0) return RegisterResult.Failure(errors); + + // Try to register the user + ApplicationUser user = new() + { + FirstName = registerDto.FirstName, + LastName = registerDto.LastName, + Email = registerDto.Email, + UserName = registerDto.UserName + }; + var result = await _userManager.CreateAsync(user, registerDto.Password); + + // Return the result + return result.Succeeded ? + RegisterResult.Success() : + RegisterResult.Failure(IdentityErrorsToRegisterErrors(result.Errors)); + } + + private static IEnumerable IdentityErrorsToRegisterErrors(IEnumerable identityErrors) + { + foreach (var error in identityErrors) + { + // Determine the property which caused an error + string property = "$"; + if (error.Code.Contains("Password")) property = "Password"; + else if (error.Code.Contains("UserName")) property = "UserName"; + else if (error.Code.Contains("Email")) property = "Email"; + + yield return new(property, error.Description); + } + } +} diff --git a/AdvancedTodoList.Application/Services/Implementations/Auth/PermissionsChecker.cs b/AdvancedTodoList.Application/Services/Implementations/Auth/PermissionsChecker.cs new file mode 100644 index 0000000..56dde25 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Implementations/Auth/PermissionsChecker.cs @@ -0,0 +1,102 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models; +using AdvancedTodoList.Core.Models.TodoLists.Members; +using AdvancedTodoList.Core.Repositories; +using AdvancedTodoList.Core.Specifications; + +namespace AdvancedTodoList.Application.Services.Implementations.Auth; + +/// +/// Service that checks user's permissions. +/// +public class PermissionsChecker(ITodoListMembersRepository membersRepository) : IPermissionsChecker +{ + private readonly ITodoListMembersRepository _membersRepository = membersRepository; + + /// + /// Asynchronously checks whether the user is a member of the to-do list with + /// specified ID. + /// + /// To-do list context. + /// + /// if user is a member of the list; otherwise . + /// + public async Task IsMemberOfListAsync(TodoListContext context) + { + return await _membersRepository.FindAsync(context.TodoListId, context.CallerId) != null; + } + + /// + /// Asynchronously checks whether the user is a member of the to-do list and + /// has a permission defined by the funciton . + /// + /// To-do list context. + /// Function that should return if user has required permission. + /// + /// if user is a member of the list and has required permission; + /// otherwise . + /// + public async Task HasPermissionAsync(TodoListContext context, Func permission) + { + MemberPermissionsSpecification specification = new(context.TodoListId, context.CallerId); + var member = await _membersRepository.GetAggregateAsync(specification); + // User is not a member or has no role - return false + if (member == null || member.Role == null) return false; + + return permission(member.Role.Permissions); + } + + /// + /// Asynchronously checks whether the user can touch an entity. + /// + /// + /// This method firstly checks whether implements + /// interface and if yes, checks if the user is the owner of the entity and is a member of the to-do list; + /// otherwise the method checks if user has the permission defined by the function . + /// + /// Type of the entity. + /// Type of the unique identifier used by the entity. + /// To-do list context. + /// ID of the entity. + /// Function that should return if user has required permission. + /// + /// if user is either an owner of the entity and a member of a to-do list, + /// or he/she/they has permission defined by ; otherwise . + /// + Task IPermissionsChecker.CanTouchEntityAsync(TodoListContext context, TEntity entity, Func permission) + { + // If user owns entity only check if he/she/they is member + if (entity is IHasOwner ownedEntity && ownedEntity.OwnerId == context.CallerId) + { + return IsMemberOfListAsync(context); + } + // Otherwise check if user has permission + return HasPermissionAsync(context, permission); + } + + /// + /// Asynchronously checks whether the user has a permission to change the role + /// with the priority of . + /// + /// To-do list context. + /// ID of the role. + /// Function that should return if user has required permission. + /// + /// if user has and highest role priority than + /// the ; otherwise . + /// + public async Task HasPermissionOverRoleAsync(TodoListContext context, int rolePriority, Func permission) + { + MemberPermissionsSpecification specification = new(context.TodoListId, context.CallerId); + var member = await _membersRepository.GetAggregateAsync(specification); + + // User is not a member, has no role or permission - return false + if (member == null || member.Role == null || !permission(member.Role.Permissions)) + return false; + + // Check if user has a higher priority + return member.Role.Priority < rolePriority; + } +} diff --git a/AdvancedTodoList.Application/Services/Implementations/Auth/RefreshTokensService.cs b/AdvancedTodoList.Application/Services/Implementations/Auth/RefreshTokensService.cs new file mode 100644 index 0000000..071800d --- /dev/null +++ b/AdvancedTodoList.Application/Services/Implementations/Auth/RefreshTokensService.cs @@ -0,0 +1,86 @@ +using AdvancedTodoList.Application.Options; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models.Auth; +using AdvancedTodoList.Core.Repositories; +using Microsoft.Extensions.Options; +using System.Security.Cryptography; + +namespace AdvancedTodoList.Application.Services.Implementations.Auth; + +public class RefreshTokensService( + IUserRefreshTokensRepository repository, + IOptions options, + IEntityExistenceChecker existenceChecker) : IRefreshTokensService +{ + private readonly IUserRefreshTokensRepository _repository = repository; + private readonly RefreshTokenOptions _tokenOptions = options.Value; + private readonly IEntityExistenceChecker _existenceChecker = existenceChecker; + + /// + /// Generates a refresh token for the user and saves it asynchronously. + /// + /// ID of the user who will receive the token. + /// + /// A string that represents a refresh token or if user does not exist. + /// + public async Task GenerateAsync(string userId) + { + // Check if user exists + if (!await _existenceChecker.ExistsAsync(userId)) + return null; + + // Generate a token + using RandomNumberGenerator rng = RandomNumberGenerator.Create(); + byte[] refreshTokenBytes = new byte[_tokenOptions.Size]; + rng.GetBytes(refreshTokenBytes); + + // Set the expiration date and assign token to the user + int expirationDays = _tokenOptions.ExpirationDays; + UserRefreshToken tokenEntity = new() + { + Token = Convert.ToBase64String(refreshTokenBytes), + UserId = userId, + ValidTo = DateTime.UtcNow.AddDays(expirationDays) + }; + + // Save the token + await _repository.AddAsync(tokenEntity); + + // Return the token value + return tokenEntity.Token; + } + + /// + /// Revokes the refresh token of the user asynchronously + /// + /// ID of the user whose token is being revoked. + /// Value of the token to be revoked. + /// + /// on success; otherwise. + /// + public async Task RevokeAsync(string userId, string token) + { + var tokenEntity = await _repository.FindAsync(userId, token); + if (tokenEntity == null) return false; + + // Delete the token + await _repository.DeleteAsync(tokenEntity); + + return true; + } + + /// + /// Checks whether refresh token is valid asynchronously. + /// + /// ID of the user whose token is being validated. + /// Value of the token to be validated. + /// + /// if token is valid; + /// otherwise. + /// + public async Task ValidateAsync(string userId, string token) + { + var tokenEntity = await _repository.FindAsync(userId, token); + return tokenEntity != null && DateTime.UtcNow < tokenEntity.ValidTo; + } +} diff --git a/AdvancedTodoList.Application/Services/Implementations/InvitationLinksService.cs b/AdvancedTodoList.Application/Services/Implementations/InvitationLinksService.cs new file mode 100644 index 0000000..37d44e4 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Implementations/InvitationLinksService.cs @@ -0,0 +1,156 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Options; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models.TodoLists; +using AdvancedTodoList.Core.Models.TodoLists.Members; +using AdvancedTodoList.Core.Pagination; +using AdvancedTodoList.Core.Repositories; +using AdvancedTodoList.Core.Specifications; +using Mapster; +using Microsoft.Extensions.Options; +using System.Security.Cryptography; + +namespace AdvancedTodoList.Application.Services.Implementations; + +/// +/// A service that manages invitation links. +/// +public class InvitationLinksService( + IPermissionsChecker permissionsChecker, + IInvitationLinksRepository linksRepository, + ITodoListMembersRepository membersRepository, + IEntityExistenceChecker existenceChecker, + IOptions options + ) : IInvitationLinksService +{ + private readonly IPermissionsChecker _permissionsChecker = permissionsChecker; + private readonly IInvitationLinksRepository _linksRepository = linksRepository; + private readonly ITodoListMembersRepository _membersRepository = membersRepository; + private readonly IEntityExistenceChecker _existenceChecker = existenceChecker; + private readonly InvitationLinkOptions _options = options.Value; + + /// + /// Joins the caller to the to-do list by invitation list asynchronously. + /// + /// ID of the caller. + /// Invitation link to use. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public async Task JoinAsync(string callerId, string invitationLinkValue) + { + // Try to find a link + InvitationLink? invitationLink = await _linksRepository.FindAsync(invitationLinkValue); + if (invitationLink == null) return new(JoinByInvitationLinkStatus.NotFound); + + // Check if link is still valid + if (invitationLink.ValidTo < DateTime.UtcNow) return new(JoinByInvitationLinkStatus.Expired); + + // Check if user is not already a member + var member = await _membersRepository.FindAsync(invitationLink.TodoListId, callerId); + if (member != null) return new(JoinByInvitationLinkStatus.UserIsAlreadyMember); + + // Add a new member + TodoListMember newMember = new() + { + TodoListId = invitationLink.TodoListId, + UserId = callerId + }; + await _membersRepository.AddAsync(newMember); + var dto = newMember.Adapt(); + return new(JoinByInvitationLinkStatus.Success, dto); + } + + /// + /// Gets invitation links associated with the to-do list asynchronously. + /// + /// To-do list context of the operation. + /// Pagination parameters to use. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public async Task>> GetInvitationLinksAsync(TodoListContext context, + PaginationParameters parameters) + { + // Check if to-do list exists + if (!await _existenceChecker.ExistsAsync(context.TodoListId)) + return new(ServiceResponseStatus.NotFound); + + // Check if user has the permission to see links + if (!await _permissionsChecker.HasPermissionAsync( + context, x => x.ManageInvitationLinks || x.AddMembers)) + return new(ServiceResponseStatus.Forbidden); + + // Get the requested page + InvitationLinksSpecification specification = new(context.TodoListId); + var page = await _linksRepository + .GetPageAsync(parameters, specification); + // Return the page + return new(ServiceResponseStatus.Success, page); + } + + /// + /// Creates an invitation link associated to the to-do list asynchronously. + /// + /// To-do list context. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public async Task> CreateAsync(TodoListContext context) + { + // Check if to-do list exists + if (!await _existenceChecker.ExistsAsync(context.TodoListId)) + return new(ServiceResponseStatus.NotFound); + // Check if the user has the permission + if (!await _permissionsChecker.HasPermissionAsync(context, x => x.AddMembers)) + return new(ServiceResponseStatus.Forbidden); + + // Generate the link + using RandomNumberGenerator rng = RandomNumberGenerator.Create(); + byte[] valueBytes = new byte[_options.Size]; + rng.GetBytes(valueBytes); + + // Create the link + InvitationLink link = new() + { + TodoListId = context.TodoListId, + Value = Convert.ToBase64String(valueBytes), + ValidTo = DateTime.UtcNow.AddDays(_options.ExpirationDays) + }; + // Save it + await _linksRepository.AddAsync(link); + // Map it to DTO and return + var result = link.Adapt(); + return new(ServiceResponseStatus.Success, result); + } + + /// + /// Deletes an invitation link associted to the to-do list asynchronously. + /// + /// To-do list context. + /// ID of the link. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public async Task DeleteAsync(TodoListContext context, int linkId) + { + // Get the model of a link + var link = await _linksRepository.GetByIdAsync(linkId); + // Check if it's valid + if (link == null || link.TodoListId != context.TodoListId) + return ServiceResponseStatus.NotFound; + // Check if user has the permission + if (!await _permissionsChecker.HasPermissionAsync(context, x => x.ManageInvitationLinks)) + return ServiceResponseStatus.Forbidden; + + // Delete the link + await _linksRepository.DeleteAsync(link); + + return ServiceResponseStatus.Success; + } +} diff --git a/AdvancedTodoList.Application/Services/Implementations/TodoItemCategoriesService.cs b/AdvancedTodoList.Application/Services/Implementations/TodoItemCategoriesService.cs new file mode 100644 index 0000000..34bcd70 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Implementations/TodoItemCategoriesService.cs @@ -0,0 +1,111 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Core.Models.TodoLists; +using AdvancedTodoList.Core.Pagination; +using AdvancedTodoList.Core.Repositories; +using AdvancedTodoList.Core.Specifications.Todo; + +namespace AdvancedTodoList.Application.Services.Implementations; + +/// +/// A service that manages to-do list items categories. +/// +public class TodoItemCategoriesService( + ITodoListDependantEntitiesService helperService, + IRepository categoriesRepository + ) : ITodoItemCategoriesService +{ + private readonly ITodoListDependantEntitiesService _helperService = helperService; + private readonly IRepository _categoriesRepository = categoriesRepository; + + /// + /// Asynchronously checks whether the category ID is valid for the given context. + /// + /// + /// ID is considered as valid. + /// + /// To-do list context. + /// ID of the category to validate. + /// + /// A task representing the asynchronous operation. + /// if ID is valid, otherwise. + /// + public async Task IsCategoryValidForContext(TodoListContext context, int? categoryId) + { + // Return true, null category is allowed for any context + if (categoryId == null) return true; + + var category = await _categoriesRepository.GetByIdAsync(categoryId.Value); + return category != null && category.TodoListId == context.TodoListId; + } + + /// + /// Retrieves a page of to-do list items categories of the list with the specified ID. + /// + /// To-do list context. + /// Pagination parameters to use. + /// Optional name to filter categories by. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task>> GetCategoriesOfListAsync( + TodoListContext context, PaginationParameters paginationParameters, string? name = null) + { + TodoListDependantEntitiesSpecification specification = new(context.TodoListId, name); + return _helperService.GetPageAsync(context, specification, paginationParameters); + } + + /// + /// Retrieves a to-do list item category by its ID asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to retrieve. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task> GetByIdAsync(TodoListContext context, int categoryId) + { + return _helperService.GetByIdAsync(context, categoryId); + } + + /// + /// Creates a new to-do list item category asynchronously. + /// + /// To-do list context. + /// The DTO containing information for creating the to-do list item. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task> CreateAsync(TodoListContext context, TodoItemCategoryCreateDto dto) + { + return _helperService.CreateAsync(context, dto, x => x.EditCategories); + } + + /// + /// Edits a to-do list item category asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to edit. + /// The DTO containing information for editing the to-do list item. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task EditAsync(TodoListContext context, int categoryId, TodoItemCategoryCreateDto dto) + { + return _helperService.UpdateAsync(context, categoryId, dto, x => x.EditCategories); + } + + /// + /// Deletes a to-do list item category asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to delete. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + + public Task DeleteAsync(TodoListContext context, int categoryId) + { + return _helperService.DeleteAsync(context, categoryId, x => x.EditCategories); + } +} diff --git a/AdvancedTodoList.Application/Services/Implementations/TodoItemsService.cs b/AdvancedTodoList.Application/Services/Implementations/TodoItemsService.cs new file mode 100644 index 0000000..0a4098c --- /dev/null +++ b/AdvancedTodoList.Application/Services/Implementations/TodoItemsService.cs @@ -0,0 +1,144 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models.TodoLists; +using AdvancedTodoList.Core.Pagination; +using AdvancedTodoList.Core.Repositories; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.Core.Specifications.Todo; + +namespace AdvancedTodoList.Application.Services.Implementations; + +/// +/// A service that manages to-do lists items. +/// +public class TodoItemsService( + ITodoListDependantEntitiesService helperService, + IRepository repository, + ITodoItemCategoriesService categoriesService, + IPermissionsChecker permissionsChecker + ) : ITodoItemsService +{ + private readonly ITodoListDependantEntitiesService _helperService = helperService; + private readonly IRepository _repository = repository; + private readonly ITodoItemCategoriesService _categoriesService = categoriesService; + private readonly IPermissionsChecker _permissionsChecker = permissionsChecker; + + /// + /// Retrieves a page of to-do list items of the list with the specified ID. + /// + /// To-do list context. + /// Pagination parameters to use. + /// Filter parameters to apply. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task>> GetItemsOfListAsync(TodoListContext context, + PaginationParameters paginationParameters, TodoItemsFilter filter) + { + TodoItemsSpecification specification = new(context.TodoListId, filter); + return _helperService.GetPageAsync(context, specification, paginationParameters); + } + + /// + /// Retrieves a to-do list item by its ID asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to retrieve. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public async Task> GetByIdAsync(TodoListContext context, int itemId) + { + // Check is user is a member of the to-do list + if (!await _permissionsChecker.IsMemberOfListAsync(context)) + return new(ServiceResponseStatus.Forbidden); + + TodoItemAggregateSpecification specification = new(itemId); + // Get the aggregate + var dto = await _repository.GetAggregateAsync(specification); + // Check if it's valid + if (dto == null || dto.TodoListId != context.TodoListId) + return new(ServiceResponseStatus.NotFound); + + // Return requested DTO + return new(ServiceResponseStatus.Success, dto); + } + + /// + /// Creates a new to-do list item asynchronously. + /// + /// To-do list context. + /// The DTO containing information for creating the to-do list item. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public async Task CreateAsync(TodoListContext context, TodoItemCreateDto dto) + { + // Validate the category + if (!await _categoriesService.IsCategoryValidForContext(context, dto.CategoryId)) + return new(TodoItemsServiceStatus.InvalidCategoryId); + + var response = await _helperService.CreateAsync + (context, dto, x => x.AddItems); + + return new(ToTodoItemsServiceStatus(response.Status), response.Result); + } + + /// + /// Edits a to-do list item asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to edit. + /// The DTO containing information for editing the to-do list item. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public async Task EditAsync(TodoListContext context, int itemId, TodoItemCreateDto dto) + { + // Validate the category + if (!await _categoriesService.IsCategoryValidForContext(context, dto.CategoryId)) + return TodoItemsServiceStatus.InvalidCategoryId; + + var response = await _helperService.UpdateAsync(context, itemId, dto, x => x.EditItems); + return ToTodoItemsServiceStatus(response); + } + + /// + /// Updates the state of a to-do list item asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to update the state. + /// The DTO which contains the state of the to-do item to set. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task UpdateStateAsync(TodoListContext context, int itemId, TodoItemUpdateStateDto stateDto) + { + return _helperService.UpdateAsync(context, itemId, stateDto, x => x.SetItemsState); + } + + /// + /// Deletes a to-do list item asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list item to delete. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task DeleteAsync(TodoListContext context, int itemId) + { + return _helperService.DeleteAsync(context, itemId, x => x.DeleteItems); + } + + private static TodoItemsServiceStatus ToTodoItemsServiceStatus(ServiceResponseStatus status) + { + return status switch + { + ServiceResponseStatus.Success => TodoItemsServiceStatus.Success, + ServiceResponseStatus.NotFound => TodoItemsServiceStatus.NotFound, + ServiceResponseStatus.Forbidden => TodoItemsServiceStatus.Forbidden, + _ => throw new ArgumentException("Invalid service response", nameof(status)) + }; + } +} diff --git a/AdvancedTodoList.Application/Services/Implementations/TodoListDependantEntitiesService.cs b/AdvancedTodoList.Application/Services/Implementations/TodoListDependantEntitiesService.cs new file mode 100644 index 0000000..02251a9 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Implementations/TodoListDependantEntitiesService.cs @@ -0,0 +1,189 @@ +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models; +using AdvancedTodoList.Core.Models.TodoLists; +using AdvancedTodoList.Core.Models.TodoLists.Members; +using AdvancedTodoList.Core.Pagination; +using AdvancedTodoList.Core.Repositories; +using AdvancedTodoList.Core.Specifications; +using Mapster; + +namespace AdvancedTodoList.Application.Services.Implementations; + +/// +/// A service that performs CRUD operations +/// on to-do list dependant entities and maps/unmaps DTOs. +/// +/// Type of the to-do list dependant entity. +/// Type of the unique identifier used by the entity. +public sealed class TodoListDependantEntitiesService( + IRepository repository, + IEntityExistenceChecker existenceChecker, + IPermissionsChecker permissionsChecker) : ITodoListDependantEntitiesService + where TEntity : class, IEntity, ITodoListDependant + where TKey : IEquatable +{ + private readonly IRepository _repository = repository; + private readonly IEntityExistenceChecker _existenceChecker = existenceChecker; + private readonly IPermissionsChecker _permissionsChecker = permissionsChecker; + + /// + /// Retrieves a page of to-do list dependant entities mapped to . + /// + /// + /// This method checks if to-do list ID is valid, but doesn't filter by it. + /// Filtering should be done in . + /// + /// To-do list context. + /// Specification to apply to entities. + /// Pagination parameters to use. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation and the requested page on success. + /// + public async Task>> GetPageAsync(TodoListContext context, + ISpecification specification, PaginationParameters paginationParameters) + { + // Check if to-do list exists + if (!await _existenceChecker.ExistsAsync(context.TodoListId)) + return new(ServiceResponseStatus.NotFound); + + // Check if user is a member of the to-do list + if (!await _permissionsChecker.IsMemberOfListAsync(context)) + return new(ServiceResponseStatus.Forbidden); + + // Get the requested page + var page = await _repository.GetPageAsync(paginationParameters, specification); + // Return the page + return new(ServiceResponseStatus.Success, page); + } + + /// + /// Retrieves a to-do list dependant entity by its ID asynchronously and maps it to . + /// + /// To-do list context. + /// The ID of the entity to retrieve. + /// DTO to map entity to. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation and the requested dto on success. + /// + public async Task> GetByIdAsync(TodoListContext context, TKey entityId) + where TDto : class + { + // Check if user is a member of the to-do list + if (!await _permissionsChecker.IsMemberOfListAsync(context)) + return new(ServiceResponseStatus.Forbidden); + + // Get the model + var entity = await _repository.GetByIdAsync(entityId); + // Return null if model is null or has wrong to-do list ID + if (entity == null || entity.TodoListId != context.TodoListId) + return new(ServiceResponseStatus.NotFound); + + // Map it to DTO and return + var result = entity.Adapt(); + return new(ServiceResponseStatus.Success, result); + } + + /// + /// Creates a new a to-do list dependant entity asynchronously from the DTO. + /// + /// + /// If implements the interface, then + /// this method will set the caller as an owner. + /// + /// To-do list context. + /// The DTO containing information for creating the entity. + /// Optional accessor for the permission required for the user to perform the action. + /// + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation and the created mapped to + /// on success. + /// + public async Task> CreateAsync( + TodoListContext context, TInputDto dto, Func? permission = null) + where TOutputDto : class + { + // Check if to-do list exists + if (!await _existenceChecker.ExistsAsync(context.TodoListId)) + return new(ServiceResponseStatus.NotFound); + // Check if the user has the permission + if (permission != null && !await _permissionsChecker.HasPermissionAsync(context, permission)) + return new(ServiceResponseStatus.Forbidden); + + // Create the model + var entity = dto.Adapt(); + // Set the foreign key + entity.TodoListId = context.TodoListId; + // Set the owner + if (entity is IHasOwner hasOwnerEntity) + hasOwnerEntity.OwnerId = context.CallerId; + // Save it + await _repository.AddAsync(entity); + // Map it to DTO and return + var result = entity.Adapt(); + return new(ServiceResponseStatus.Success, result); + } + + /// + /// Edits a to-do list dependant entity asynchronously. + /// + /// To-do list context. + /// The ID of the entity to edit. + /// The DTO containing information for editing the entity. + /// Optional accessor for the permission required for the user to perform the action. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public async Task UpdateAsync( + TodoListContext context, TKey entityId, TDto dto, Func? permission = null) + { + // Get the model of a to-do list item + var entity = await _repository.GetByIdAsync(entityId); + // Check if it's valid + if (entity == null || entity.TodoListId != context.TodoListId) + return ServiceResponseStatus.NotFound; + + // Check if user has the permission + if (permission != null && !await _permissionsChecker.CanTouchEntityAsync(context, entity, permission)) + return ServiceResponseStatus.Forbidden; + + // Update the model + dto.Adapt(entity); + // Save changes + await _repository.UpdateAsync(entity); + + return ServiceResponseStatus.Success; + } + + /// + /// Deletes a to-do list dependant entity asynchronously. + /// + /// To-do list context. + /// The ID of the entity to delete. + /// Optional accessor for the permission required for the user to perform the action. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public async Task DeleteAsync( + TodoListContext context, TKey entityId, Func? permission = null) + { + // Get the model of a to-do list item + var todoItem = await _repository.GetByIdAsync(entityId); + // Check if it's valid + if (todoItem == null || todoItem.TodoListId != context.TodoListId) + return ServiceResponseStatus.NotFound; + // Check if user has the permission + if (permission != null && !await _permissionsChecker.CanTouchEntityAsync(context, todoItem, permission)) + return ServiceResponseStatus.Forbidden; + + // Delete the model + await _repository.DeleteAsync(todoItem); + + return ServiceResponseStatus.Success; + } +} diff --git a/AdvancedTodoList.Application/Services/Implementations/TodoListMembersService.cs b/AdvancedTodoList.Application/Services/Implementations/TodoListMembersService.cs new file mode 100644 index 0000000..dcebf99 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Implementations/TodoListMembersService.cs @@ -0,0 +1,146 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models.TodoLists.Members; +using AdvancedTodoList.Core.Pagination; +using AdvancedTodoList.Core.Repositories; +using AdvancedTodoList.Core.Specifications; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.Core.Specifications.Todo; +using Mapster; + +namespace AdvancedTodoList.Application.Services.Implementations; + +/// +/// A service that manages to-do list members. +/// +public class TodoListMembersService( + ITodoListDependantEntitiesService helperService, + ITodoListMembersRepository membersRepository, + IRepository rolesRepository, + IPermissionsChecker permissionsChecker) : + ITodoListMembersService +{ + private readonly ITodoListDependantEntitiesService _helperService = helperService; + private readonly ITodoListMembersRepository _membersRepository = membersRepository; + private readonly IRepository _rolesRepository = rolesRepository; + private readonly IPermissionsChecker _permissionsChecker = permissionsChecker; + + /// + /// Gets a page with members of a to-do list asynchronously. + /// + /// To-do list context. + /// Pagination parameters to use. + /// Filter parameters to use. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task>> GetMembersAsync(TodoListContext context, + PaginationParameters paginationParameters, TodoListMembersFilter filter) + { + TodoListMembersSpecification specification = new(context.TodoListId, filter); + return _helperService.GetPageAsync( + context, specification, paginationParameters + ); + } + + /// + /// Adds a member to a to-do list asynchronously. + /// + /// To-do list context. + /// DTO that contains information needed for adding a member. Supossed to be valid. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public async Task AddMemberAsync(TodoListContext context, TodoListMemberAddDto dto) + { + // Check if user has the permission + if (!await _permissionsChecker.HasPermissionAsync(context, x => x.AddMembers)) + return new(TodoListMemberServiceResultStatus.Forbidden); + + // Try to find already existing member + var member = await _membersRepository.FindAsync(context.TodoListId, dto.UserId); + // Return error if it exists + if (member != null) return new(TodoListMemberServiceResultStatus.UserAlreadyAdded); + + // Add member + var response = await _helperService + .CreateAsync(context, dto); + + return response.Status switch + { + ServiceResponseStatus.Success => new(TodoListMemberServiceResultStatus.Success, response.Result), + ServiceResponseStatus.NotFound => new(TodoListMemberServiceResultStatus.NotFound), + _ => throw new InvalidOperationException("Invalid to-do lists dependant entities (members) service response.") + }; + } + + /// + /// Updates a role of the member of a to-do list asynchronously. + /// + /// To-do list context. + /// ID of the member. + /// DTO that contains information needed for updating a role. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public async Task UpdateMemberRoleAsync(TodoListContext context, int memberId, TodoListMemberUpdateRoleDto dto) + { + // I think that this method does too much and has many test cases, probably needs refactoring + + // Check if the caller has the permission to assign roles + MemberPermissionsSpecification specification = new(context.TodoListId, context.CallerId); + var caller = await _membersRepository.GetAggregateAsync(specification); + if (caller == null || caller.Role == null || !caller.Role.Permissions.AssignRoles) + return TodoListMemberServiceResultStatus.Forbidden; + + // Check if user has the permission to assign the role + if (dto.RoleId != null) + { + var role = await _rolesRepository.GetByIdAsync(dto.RoleId.Value); + // Validate RoleId + if (role == null || role.TodoListId != context.TodoListId) + return TodoListMemberServiceResultStatus.InvalidRoleId; + // Check priority + if (role.Priority <= caller.Role.Priority) + return TodoListMemberServiceResultStatus.Forbidden; + } + + // Get the model of a member + var member = await _membersRepository.GetByIdAsync(memberId); + // Check if it's valid + if (member == null || member.TodoListId != context.TodoListId) + return TodoListMemberServiceResultStatus.NotFound; + + // Check if user has a permission to assign roles to the member + if (member.RoleId != null) + { + // Get the member's role + var role = await _rolesRepository.GetByIdAsync(member.RoleId.Value) ?? + throw new InvalidOperationException("Member's role is not found"); + + if (role.Priority <= caller.Role.Priority) + return TodoListMemberServiceResultStatus.Forbidden; + } + + // Update the model + dto.Adapt(member); + // Save changes + await _membersRepository.UpdateAsync(member); + + return TodoListMemberServiceResultStatus.Success; + } + + /// + /// Removes a member from a to-do list asynchronously. + /// + /// To-do list context. + /// ID of the member. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task RemoveMemberAsync(TodoListContext context, int memberId) + { + return _helperService.DeleteAsync(context, memberId, x => x.RemoveMembers); + } +} diff --git a/AdvancedTodoList.Application/Services/Implementations/TodoListRolesService.cs b/AdvancedTodoList.Application/Services/Implementations/TodoListRolesService.cs new file mode 100644 index 0000000..7ce4aed --- /dev/null +++ b/AdvancedTodoList.Application/Services/Implementations/TodoListRolesService.cs @@ -0,0 +1,132 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models.TodoLists.Members; +using AdvancedTodoList.Core.Pagination; +using AdvancedTodoList.Core.Repositories; +using AdvancedTodoList.Core.Specifications.Todo; +using Mapster; + +namespace AdvancedTodoList.Application.Services.Implementations; + +/// +/// A service that manages to-do lists roles. +/// +public class TodoListRolesService( + ITodoListDependantEntitiesService helperService, + IRepository rolesRepository, + IPermissionsChecker permissionsChecker + ) : ITodoListRolesService +{ + private readonly ITodoListDependantEntitiesService _helperService = helperService; + private readonly IRepository _rolesRepository = rolesRepository; + private readonly IPermissionsChecker _permissionsChecker = permissionsChecker; + + /// + /// Retrieves a page of to-do list roles of the list with the specified ID. + /// + /// To-do list context. + /// Pagination parameters to use. + /// Optional name to filter categories by. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task>> GetRolesOfListAsync( + TodoListContext context, PaginationParameters paginationParameters, string? name = null) + { + TodoListDependantEntitiesSpecification specification = new(context.TodoListId, name); + return _helperService.GetPageAsync(context, specification, paginationParameters); + } + + /// + /// Retrieves a to-do list role by its ID asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list role to retrieve. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task> GetByIdAsync(TodoListContext context, int roleId) + { + return _helperService.GetByIdAsync(context, roleId); + } + + /// + /// Creates a new to-do list role asynchronously. + /// + /// To-do list context. + /// The DTO containing information for creating the to-do list role. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// It's not practically possible for this implementation to have the result that indicates + /// 'NotFound' status. + /// + public async Task> CreateAsync(TodoListContext context, TodoListRoleCreateDto dto) + { + // Check if user has a permission to create a role with the given priority + if (!await _permissionsChecker.HasPermissionOverRoleAsync(context, dto.Priority, x => x.EditRoles)) + return new(ServiceResponseStatus.Forbidden); + + // Pass null as the third argument to not check permissions twice + return await _helperService.CreateAsync( + context, dto, null); + } + + /// + /// Edits a to-do list role asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list role to edit. + /// The DTO containing information for editing the to-do list role. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public async Task EditAsync(TodoListContext context, int roleId, TodoListRoleCreateDto dto) + { + // Get the model of a role + var role = await _rolesRepository.GetByIdAsync(roleId); + // Check if it's valid + if (role == null || role.TodoListId != context.TodoListId) + return ServiceResponseStatus.NotFound; + + // Check if user has a permission to change it + // Checking only the minimal priority is sufficient, because both + // values should be greater than caller's role priority. + int minPriority = Math.Min(role.Priority, dto.Priority); + if (!await _permissionsChecker.HasPermissionOverRoleAsync(context, minPriority, x => x.EditRoles)) + return ServiceResponseStatus.Forbidden; + + // Update the model + dto.Adapt(role); + // Save changes + await _rolesRepository.UpdateAsync(role); + + return ServiceResponseStatus.Success; + } + + /// + /// Deletes a to-do list role asynchronously. + /// + /// To-do list context. + /// The ID of the to-do list role to delete. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public async Task DeleteAsync(TodoListContext context, int roleId) + { + // Get the model of a role + var role = await _rolesRepository.GetByIdAsync(roleId); + // Check if it's valid + if (role == null || role.TodoListId != context.TodoListId) + return ServiceResponseStatus.NotFound; + + // Check if user has the permission to delete the role + if (!await _permissionsChecker.HasPermissionOverRoleAsync(context, role.Priority, x => x.EditRoles)) + return ServiceResponseStatus.Forbidden; + + // Delete the role + await _rolesRepository.DeleteAsync(role); + + return ServiceResponseStatus.Success; + } +} diff --git a/AdvancedTodoList.Application/Services/Implementations/TodoListsService.cs b/AdvancedTodoList.Application/Services/Implementations/TodoListsService.cs new file mode 100644 index 0000000..2fd8a53 --- /dev/null +++ b/AdvancedTodoList.Application/Services/Implementations/TodoListsService.cs @@ -0,0 +1,182 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models.TodoLists; +using AdvancedTodoList.Core.Models.TodoLists.Members; +using AdvancedTodoList.Core.Pagination; +using AdvancedTodoList.Core.Repositories; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.Core.Specifications.Todo; +using Mapster; + +namespace AdvancedTodoList.Application.Services.Implementations; + +/// +/// A service that manages to-do lists. +/// +public class TodoListsService( + IPermissionsChecker permissionsChecker, + IRepository todoListsRepository, + IRepository rolesRepository, + ITodoListMembersRepository membersRepository, + IUnitOfWork unitOfWork + ) : ITodoListsService +{ + private readonly IPermissionsChecker _permissionsChecker = permissionsChecker; + private readonly IRepository _todoListsRepository = todoListsRepository; + private readonly IRepository _rolesRepository = rolesRepository; + private readonly IRepository _membersRepository = membersRepository; + private readonly IUnitOfWork _unitOfWork = unitOfWork; + + /// + /// Retrieves a page of to-do lists, with the requirement that the user + /// is a member of those lists. + /// + /// Id of the user + /// Pagination parameters to use. + /// Filter parameters to apply. + /// + /// A task representing the asynchronous operation containing the result of operation. + /// + public Task> GetListsOfUserAsync(string userId, + PaginationParameters paginationParameters, TodoListsFilter filter) + { + TodoListsSpecification specification = new(userId, filter); + return _todoListsRepository.GetPageAsync( + paginationParameters, specification); + } + + /// + /// Retrieves a to-do list by its ID asynchronously. + /// + /// To-do list context. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public async Task> GetByIdAsync(TodoListContext context) + { + TodoListAggregateSpecification specification = new(context.TodoListId); + var result = await _todoListsRepository.GetAggregateAsync(specification); + // Check if to-do list exists + if (result == null) return new(ServiceResponseStatus.NotFound); + + // Check if the user has a permission to view the to-do list + if (!await _permissionsChecker.IsMemberOfListAsync(context)) + return new(ServiceResponseStatus.Forbidden); + + return new(ServiceResponseStatus.Success, result); + } + + /// + /// Creates a new to-do list asynchronously. + /// + /// + /// This method should also create an "Owner" role with all permissions and assign the caller to it. + /// + /// The DTO containing information for creating the to-do list. + /// ID of the user who creates the to-do list. + /// + /// A task representing the asynchronous operation. The task contains + /// a created model mapped to . + /// + public async Task CreateAsync(TodoListCreateDto dto, string callerId) + { + // Map DTO to the model + var todoList = dto.Adapt(); + // Set the owner + todoList.OwnerId = callerId; + + // Begin a transaction + await _unitOfWork.BeginTransactionAsync(); + + try + { + // Add the list to the database + await _todoListsRepository.AddAsync(todoList); + + // Create an "Owner" role + TodoListRole ownerRole = new() + { + Name = "Owner", + Priority = 0, + TodoListId = todoList.Id, + Permissions = RolePermissions.All + }; + await _rolesRepository.AddAsync(ownerRole); + + // Assign the caller to it + TodoListMember member = new() + { + UserId = callerId, + TodoListId = todoList.Id, + RoleId = ownerRole.Id + }; + await _membersRepository.AddAsync(member); + } + catch (Exception) + { + // Rollback in a case of error + await _unitOfWork.RollbackAsync(); + throw; + } + + // Commit changes + await _unitOfWork.CommitAsync(); + + // Return a DTO of created model + return todoList.Adapt(); + } + + /// + /// Edits a to-do list asynchronously. + /// + /// To-do list context. + /// The DTO containing information for editing the to-do list. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public async Task EditAsync(TodoListContext context, TodoListCreateDto dto) + { + // Get the model + var todoList = await _todoListsRepository.GetByIdAsync(context.TodoListId); + // Return NotFound if the model doesn't exist + if (todoList == null) return ServiceResponseStatus.NotFound; + // Check if the user has a permission to edit the list + if (!await _permissionsChecker.CanTouchEntityAsync( + context, todoList, x => x.EditItems)) + { + return ServiceResponseStatus.Forbidden; + } + + // Update the model + dto.Adapt(todoList); + await _todoListsRepository.UpdateAsync(todoList); + + return ServiceResponseStatus.Success; + } + + /// + /// Deletes a to-do list asynchronously. + /// + /// To-do list context. + /// + /// A task representing the asynchronous operation. The task contains + /// a result of the operation. + /// + public async Task DeleteAsync(TodoListContext context) + { + // Get the model + var todoList = await _todoListsRepository.GetByIdAsync(context.TodoListId); + // Return NotFound if the model doesn't exist + if (todoList == null) return ServiceResponseStatus.NotFound; + // Check if the user is an owner of the list + if (todoList.OwnerId != context.CallerId) return ServiceResponseStatus.Forbidden; + + // Delete the model + await _todoListsRepository.DeleteAsync(todoList); + + return ServiceResponseStatus.Success; + } +} diff --git a/AdvancedTodoList.Application/Validation/Auth/LogInDtoValidator.cs b/AdvancedTodoList.Application/Validation/Auth/LogInDtoValidator.cs new file mode 100644 index 0000000..f12a23b --- /dev/null +++ b/AdvancedTodoList.Application/Validation/Auth/LogInDtoValidator.cs @@ -0,0 +1,17 @@ +using AdvancedTodoList.Application.Dtos; +using FluentValidation; + +namespace AdvancedTodoList.Application.Validation.Auth; + +public class LogInDtoValidator : AbstractValidator +{ + public LogInDtoValidator() + { + RuleFor(x => x.UserNameOrEmail) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + RuleFor(x => x.Password) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } +} diff --git a/AdvancedTodoList.Application/Validation/Auth/LogOutDtoValidator.cs b/AdvancedTodoList.Application/Validation/Auth/LogOutDtoValidator.cs new file mode 100644 index 0000000..51da4f1 --- /dev/null +++ b/AdvancedTodoList.Application/Validation/Auth/LogOutDtoValidator.cs @@ -0,0 +1,14 @@ +using AdvancedTodoList.Application.Dtos; +using FluentValidation; + +namespace AdvancedTodoList.Application.Validation.Auth; + +public class LogOutDtoValidator : AbstractValidator +{ + public LogOutDtoValidator() + { + RuleFor(x => x.RefreshToken) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } +} diff --git a/AdvancedTodoList.Application/Validation/Auth/RefreshDtoValidator.cs b/AdvancedTodoList.Application/Validation/Auth/RefreshDtoValidator.cs new file mode 100644 index 0000000..b125aff --- /dev/null +++ b/AdvancedTodoList.Application/Validation/Auth/RefreshDtoValidator.cs @@ -0,0 +1,18 @@ +using AdvancedTodoList.Application.Dtos; +using FluentValidation; + +namespace AdvancedTodoList.Application.Validation.Auth; + +public class RefreshDtoValidator : AbstractValidator +{ + public RefreshDtoValidator() + { + RuleFor(x => x.AccessToken) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + + RuleFor(x => x.RefreshToken) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } +} diff --git a/AdvancedTodoList.Application/Validation/Auth/RegisterDtoValidator.cs b/AdvancedTodoList.Application/Validation/Auth/RegisterDtoValidator.cs new file mode 100644 index 0000000..23f8c38 --- /dev/null +++ b/AdvancedTodoList.Application/Validation/Auth/RegisterDtoValidator.cs @@ -0,0 +1,34 @@ +using AdvancedTodoList.Application.Dtos; +using FluentValidation; +using FluentValidation.Validators; + +namespace AdvancedTodoList.Application.Validation.Auth; + +public class RegisterDtoValidator : AbstractValidator +{ + public RegisterDtoValidator() + { + RuleFor(x => x.Email) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + RuleFor(x => x.Email) + .EmailAddress(EmailValidationMode.AspNetCoreCompatible) + .WithErrorCode(ValidationErrorCodes.InvalidEmail); + + RuleFor(x => x.UserName) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + + RuleFor(x => x.FirstName) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + + RuleFor(x => x.LastName) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + + RuleFor(x => x.Password) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } +} diff --git a/AdvancedTodoList.Application/Validation/PaginationParametersValidator.cs b/AdvancedTodoList.Application/Validation/PaginationParametersValidator.cs new file mode 100644 index 0000000..ad59aa8 --- /dev/null +++ b/AdvancedTodoList.Application/Validation/PaginationParametersValidator.cs @@ -0,0 +1,25 @@ +using AdvancedTodoList.Core.Pagination; +using FluentValidation; + +namespace AdvancedTodoList.Application.Validation; + +public class PaginationParametersValidator : AbstractValidator +{ + public PaginationParametersValidator() + { + RuleFor(x => x.Page) + .GreaterThan(0) + .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange) + .WithMessage("Page number must be a positive number."); + + RuleFor(x => x.PageSize) + .GreaterThanOrEqualTo(PaginationParameters.MinPageSize) + .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange) + .WithMessage($"Page size must be within the range of {PaginationParameters.MinPageSize}-{PaginationParameters.MaxPageSize}"); + + RuleFor(x => x.PageSize) + .LessThanOrEqualTo(PaginationParameters.MaxPageSize) + .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange) + .WithMessage($"Page size must be within the range of {PaginationParameters.MinPageSize}-{PaginationParameters.MaxPageSize}"); + } +} diff --git a/AdvancedTodoList.Application/Validation/TodoItemCategoryCreateDtoValidator.cs b/AdvancedTodoList.Application/Validation/TodoItemCategoryCreateDtoValidator.cs new file mode 100644 index 0000000..a41394d --- /dev/null +++ b/AdvancedTodoList.Application/Validation/TodoItemCategoryCreateDtoValidator.cs @@ -0,0 +1,21 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Models.TodoLists; +using FluentValidation; + +namespace AdvancedTodoList.Application.Validation; + +public class TodoItemCategoryCreateDtoValidator : AbstractValidator +{ + public TodoItemCategoryCreateDtoValidator() + { + // Name is required + RuleFor(x => x.Name) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + + // Name should not be too long + RuleFor(x => x.Name) + .MaximumLength(TodoItemCategory.NameMaxLength) + .WithErrorCode(ValidationErrorCodes.PropertyTooLong); + } +} diff --git a/AdvancedTodoList.Application/Validation/TodoItemCreateDtoValidator.cs b/AdvancedTodoList.Application/Validation/TodoItemCreateDtoValidator.cs new file mode 100644 index 0000000..83be7d7 --- /dev/null +++ b/AdvancedTodoList.Application/Validation/TodoItemCreateDtoValidator.cs @@ -0,0 +1,49 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Models.TodoLists; +using FluentValidation; + +namespace AdvancedTodoList.Application.Validation; + +/// +/// Validator class for +/// +public class TodoItemCreateDtoValidator : AbstractValidator +{ + public TodoItemCreateDtoValidator() + { + // Name is required + RuleFor(x => x.Name) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + + // Name should not be too long + RuleFor(x => x.Name) + .MaximumLength(TodoItem.NameMaxLength) + .WithErrorCode(ValidationErrorCodes.PropertyTooLong); + + // Description is not null + RuleFor(x => x.Description) + .NotNull() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + + // Description should not be too long + RuleFor(x => x.Description) + .MaximumLength(TodoItem.DescriptionMaxLength) + .WithErrorCode(ValidationErrorCodes.PropertyTooLong); + + // Deadline date should be after the current date + RuleFor(x => x.DeadlineDate) + .GreaterThan(DateTime.UtcNow) + .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange); + + // Priority must be in a specific range + RuleFor(x => x.Priority) + .GreaterThanOrEqualTo(TodoItem.MinPriority) + .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange) + .WithMessage($"Priority must be within the range from {TodoItem.MinPriority} to {TodoItem.MaxPriority}"); + RuleFor(x => x.Priority) + .LessThanOrEqualTo(TodoItem.MaxPriority) + .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange) + .WithMessage($"Priority must be within the range from {TodoItem.MinPriority} to {TodoItem.MaxPriority}"); + } +} diff --git a/AdvancedTodoList.Application/Validation/TodoItemUpdateStateDtoValidator.cs b/AdvancedTodoList.Application/Validation/TodoItemUpdateStateDtoValidator.cs new file mode 100644 index 0000000..f03d82c --- /dev/null +++ b/AdvancedTodoList.Application/Validation/TodoItemUpdateStateDtoValidator.cs @@ -0,0 +1,16 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Models.TodoLists; +using FluentValidation; + +namespace AdvancedTodoList.Application.Validation; + +public class TodoItemUpdateStateDtoValidator : AbstractValidator +{ + public TodoItemUpdateStateDtoValidator() + { + RuleFor(x => x.State) + .Must(s => s >= TodoItemState.Active && s <= TodoItemState.Skipped) + .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange) + .WithMessage(x => $"{(int)x.State} is invalid value for {{PropertyName}}"); + } +} diff --git a/AdvancedTodoList.Application/Validation/TodoListCreateDtoValidator.cs b/AdvancedTodoList.Application/Validation/TodoListCreateDtoValidator.cs new file mode 100644 index 0000000..9aeeccf --- /dev/null +++ b/AdvancedTodoList.Application/Validation/TodoListCreateDtoValidator.cs @@ -0,0 +1,31 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Models.TodoLists; +using FluentValidation; + +namespace AdvancedTodoList.Application.Validation; + +public class TodoListCreateDtoValidator : AbstractValidator +{ + public TodoListCreateDtoValidator() + { + // Name is required + RuleFor(x => x.Name) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + + // Name should not be too long + RuleFor(x => x.Name) + .MaximumLength(TodoList.NameMaxLength) + .WithErrorCode(ValidationErrorCodes.PropertyTooLong); + + // Description is not null + RuleFor(x => x.Description) + .NotNull() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + + // Description should not be too long + RuleFor(x => x.Description) + .MaximumLength(TodoList.DescriptionMaxLength) + .WithErrorCode(ValidationErrorCodes.PropertyTooLong); + } +} diff --git a/AdvancedTodoList.Application/Validation/TodoListMemberAddDtoValidator.cs b/AdvancedTodoList.Application/Validation/TodoListMemberAddDtoValidator.cs new file mode 100644 index 0000000..59be32e --- /dev/null +++ b/AdvancedTodoList.Application/Validation/TodoListMemberAddDtoValidator.cs @@ -0,0 +1,27 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Models.Auth; +using AdvancedTodoList.Core.Repositories; +using FluentValidation; + +namespace AdvancedTodoList.Application.Validation; + +/// +/// Validator class for +/// +public class TodoListMemberAddDtoValidator : AbstractValidator +{ + public TodoListMemberAddDtoValidator(IEntityExistenceChecker existenceChecker) + { + RuleFor(x => x.UserId) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + + RuleFor(x => x.UserId) + .MustAsync(async (userId, _) => + { + return await existenceChecker.ExistsAsync(userId); + }) + .WithErrorCode(ValidationErrorCodes.InvalidForeignKey) + .WithMessage("User not found."); + } +} diff --git a/AdvancedTodoList.Application/Validation/TodoListRoleCreateDtoValidator.cs b/AdvancedTodoList.Application/Validation/TodoListRoleCreateDtoValidator.cs new file mode 100644 index 0000000..bc8f284 --- /dev/null +++ b/AdvancedTodoList.Application/Validation/TodoListRoleCreateDtoValidator.cs @@ -0,0 +1,25 @@ +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Core.Models.TodoLists.Members; +using FluentValidation; + +namespace AdvancedTodoList.Application.Validation; + +/// +/// Validator class for +/// +public class TodoListRoleCreateDtoValidator : AbstractValidator +{ + public TodoListRoleCreateDtoValidator() + { + RuleFor(x => x.Name) + .NotEmpty() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + RuleFor(x => x.Name) + .MaximumLength(TodoListRole.NameMaxLength) + .WithErrorCode(ValidationErrorCodes.PropertyTooLong); + + RuleFor(x => x.Permissions) + .NotNull() + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } +} diff --git a/AdvancedTodoList.Application/Validation/ValidationErrorCodes.cs b/AdvancedTodoList.Application/Validation/ValidationErrorCodes.cs new file mode 100644 index 0000000..d2ba001 --- /dev/null +++ b/AdvancedTodoList.Application/Validation/ValidationErrorCodes.cs @@ -0,0 +1,28 @@ +namespace AdvancedTodoList.Application.Validation; + +/// +/// Class that contains validation error codes. +/// +public static class ValidationErrorCodes +{ + /// + /// Required property is null or empty. + /// + public const string PropertyRequired = "100"; + /// + /// Length of the property exceeds maximum possible value. + /// + public const string PropertyTooLong = "200"; + /// + /// Value of the property is out of range. + /// + public const string PropertyOutOfRange = "300"; + /// + /// Property is invalid foreign key. + /// + public const string InvalidForeignKey = "400"; + /// + /// Property is invalid email. + /// + public const string InvalidEmail = "500"; +} diff --git a/AdvancedTodoList.Core/AdvancedTodoList.Core.csproj b/AdvancedTodoList.Core/AdvancedTodoList.Core.csproj index 399f334..51845b4 100644 --- a/AdvancedTodoList.Core/AdvancedTodoList.Core.csproj +++ b/AdvancedTodoList.Core/AdvancedTodoList.Core.csproj @@ -7,9 +7,9 @@ - + - + diff --git a/AdvancedTodoList.Core/Dtos/TodoListItemDtos.cs b/AdvancedTodoList.Core/Dtos/TodoListItemDtos.cs deleted file mode 100644 index b163ae8..0000000 --- a/AdvancedTodoList.Core/Dtos/TodoListItemDtos.cs +++ /dev/null @@ -1,37 +0,0 @@ -using AdvancedTodoList.Core.Models.TodoLists; - -namespace AdvancedTodoList.Core.Dtos; - -/// -/// DTO for creating/editing a to-do list item. -/// -public record TodoItemCreateDto( - string Name, string Description, DateTime? DeadlineDate, - int Priority, int? CategoryId - ); - -/// -/// DTO for changing the state of a to-do list item. -/// -public record TodoItemUpdateStateDto(TodoItemState State); - -/// -/// DTO for a full view of a to-do list item. -/// -public record TodoItemGetByIdDto( - int Id, string TodoListId, string Name, - string Description, DateTime? DeadlineDate, - TodoItemState State, int Priority, - ApplicationUserPreviewDto Owner, - TodoItemCategoryViewDto? Category - ); - -/// -/// DTO for a partial view of a to-do list item. -/// -public record TodoItemPreviewDto( - int Id, string TodoListId, string Name, - DateTime? DeadlineDate, TodoItemState State, - int Priority, ApplicationUserPreviewDto Owner, - TodoItemCategoryViewDto? Category - ); \ No newline at end of file diff --git a/AdvancedTodoList.Core/Mapping/MappingGlobalSettings.cs b/AdvancedTodoList.Core/Mapping/MappingGlobalSettings.cs deleted file mode 100644 index f6cea61..0000000 --- a/AdvancedTodoList.Core/Mapping/MappingGlobalSettings.cs +++ /dev/null @@ -1,30 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Models.TodoLists.Members; -using Mapster; - -namespace AdvancedTodoList.Core.Mapping; - -/// -/// Class that defines global mapping settings. -/// -public static class MappingGlobalSettings -{ - /// - /// Apply global mapping settings. - /// - public static void Apply() - { - // Ignore null IDs - TypeAdapterConfig.NewConfig() - .IgnoreIf((src, dest) => src.RoleId == null, dest => dest.Role!); - TypeAdapterConfig.NewConfig() - .IgnoreIf((src, dest) => src.CategoryId == null, dest => dest.Category!); - TypeAdapterConfig.NewConfig() - .IgnoreIf((src, dest) => src.CategoryId == null, dest => dest.Category!); - - // Convert null strings into empty strings and trim strings - TypeAdapterConfig.GlobalSettings.Default - .AddDestinationTransform((string? dest) => dest != null ? dest.Trim() : string.Empty); - } -} diff --git a/AdvancedTodoList.Core/Models/Auth/ApplicationUser.cs b/AdvancedTodoList.Core/Models/Auth/ApplicationUser.cs index 11660e1..cc05b7c 100644 --- a/AdvancedTodoList.Core/Models/Auth/ApplicationUser.cs +++ b/AdvancedTodoList.Core/Models/Auth/ApplicationUser.cs @@ -5,13 +5,13 @@ namespace AdvancedTodoList.Core.Models.Auth; public class ApplicationUser : IdentityUser, IEntity { - [MaxLength(MaxNameLength)] - public required string FirstName { get; set; } - [MaxLength(MaxNameLength)] - public required string LastName { get; set; } + [MaxLength(MaxNameLength)] + public required string FirstName { get; set; } + [MaxLength(MaxNameLength)] + public required string LastName { get; set; } - /// - /// Maximum length for properties and . - /// - public const int MaxNameLength = 100; + /// + /// Maximum length for properties and . + /// + public const int MaxNameLength = 100; } diff --git a/AdvancedTodoList.Core/Models/Auth/UserRefreshToken.cs b/AdvancedTodoList.Core/Models/Auth/UserRefreshToken.cs index 8e9f067..df022f4 100644 --- a/AdvancedTodoList.Core/Models/Auth/UserRefreshToken.cs +++ b/AdvancedTodoList.Core/Models/Auth/UserRefreshToken.cs @@ -8,29 +8,29 @@ namespace AdvancedTodoList.Core.Models.Auth; /// public class UserRefreshToken : IEntity { - /// - /// The unique identifier. - /// - [Key] - public int Id { get; set; } + /// + /// The unique identifier. + /// + [Key] + public int Id { get; set; } - /// - /// A foreign key to the user who owns the token. - /// - [ForeignKey(nameof(User))] - public required string UserId { get; set; } + /// + /// A foreign key to the user who owns the token. + /// + [ForeignKey(nameof(User))] + public required string UserId { get; set; } - /// - /// A value of the token. - /// - public required string Token { get; set; } - /// - /// Date after which token becomes invalid. - /// - public required DateTime ValidTo { get; set; } + /// + /// A value of the token. + /// + public required string Token { get; set; } + /// + /// Date after which token becomes invalid. + /// + public required DateTime ValidTo { get; set; } - /// - /// A navigation property for a user which owns the token. - /// - public ApplicationUser User { get; set; } = null!; + /// + /// A navigation property for a user which owns the token. + /// + public ApplicationUser User { get; set; } = null!; } diff --git a/AdvancedTodoList.Core/Models/IEntity.cs b/AdvancedTodoList.Core/Models/IEntity.cs index 7a31f73..0ab02d6 100644 --- a/AdvancedTodoList.Core/Models/IEntity.cs +++ b/AdvancedTodoList.Core/Models/IEntity.cs @@ -6,5 +6,5 @@ /// Type of the entity ID. public interface IEntity where TId : IEquatable { - TId Id { get; } + TId Id { get; } } diff --git a/AdvancedTodoList.Core/Models/IHasName.cs b/AdvancedTodoList.Core/Models/IHasName.cs index 981c8b3..643d5f7 100644 --- a/AdvancedTodoList.Core/Models/IHasName.cs +++ b/AdvancedTodoList.Core/Models/IHasName.cs @@ -6,5 +6,5 @@ /// public interface IHasName { - public string Name { get; } + public string Name { get; } } diff --git a/AdvancedTodoList.Core/Models/IHasOwner.cs b/AdvancedTodoList.Core/Models/IHasOwner.cs index 1e03816..a0a5f0b 100644 --- a/AdvancedTodoList.Core/Models/IHasOwner.cs +++ b/AdvancedTodoList.Core/Models/IHasOwner.cs @@ -5,8 +5,8 @@ /// public interface IHasOwner { - /// - /// Foreign key referencing the user who created this entity. - /// - public string? OwnerId { get; set; } + /// + /// Foreign key referencing the user who created this entity. + /// + public string? OwnerId { get; set; } } diff --git a/AdvancedTodoList.Core/Models/TodoLists/ITodoListDependant.cs b/AdvancedTodoList.Core/Models/TodoLists/ITodoListDependant.cs index f9dd8db..79b4fff 100644 --- a/AdvancedTodoList.Core/Models/TodoLists/ITodoListDependant.cs +++ b/AdvancedTodoList.Core/Models/TodoLists/ITodoListDependant.cs @@ -5,8 +5,8 @@ /// public interface ITodoListDependant { - /// - /// A foreign key of a to-do list. - /// - string TodoListId { get; set; } + /// + /// A foreign key of a to-do list. + /// + string TodoListId { get; set; } } diff --git a/AdvancedTodoList.Core/Models/TodoLists/InvitationLink.cs b/AdvancedTodoList.Core/Models/TodoLists/InvitationLink.cs index b88dc02..bf6b909 100644 --- a/AdvancedTodoList.Core/Models/TodoLists/InvitationLink.cs +++ b/AdvancedTodoList.Core/Models/TodoLists/InvitationLink.cs @@ -8,29 +8,29 @@ namespace AdvancedTodoList.Core.Models.TodoLists; /// public class InvitationLink : IEntity { - /// - /// A unique identifier for the to-do list item. - /// - [Key] - public int Id { get; set; } + /// + /// A unique identifier for the to-do list item. + /// + [Key] + public int Id { get; set; } - /// - /// Foreign key of the to-do list where the link is active. - /// - [ForeignKey(nameof(TodoList))] - public required string TodoListId { get; set; } - /// - /// Navigation property to the to-do list associated with this link. - /// - public TodoList TodoList { get; set; } = null!; + /// + /// Foreign key of the to-do list where the link is active. + /// + [ForeignKey(nameof(TodoList))] + public required string TodoListId { get; set; } + /// + /// Navigation property to the to-do list associated with this link. + /// + public TodoList TodoList { get; set; } = null!; - /// - /// A unique string value representing the link. - /// - public required string Value { get; set; } + /// + /// A unique string value representing the link. + /// + public required string Value { get; set; } - /// - /// Date after which the link becomes invalid. - /// - public DateTime ValidTo { get; set; } + /// + /// Date after which the link becomes invalid. + /// + public DateTime ValidTo { get; set; } } diff --git a/AdvancedTodoList.Core/Models/TodoLists/Members/RolePermissions.cs b/AdvancedTodoList.Core/Models/TodoLists/Members/RolePermissions.cs index 100065e..59bfba2 100644 --- a/AdvancedTodoList.Core/Models/TodoLists/Members/RolePermissions.cs +++ b/AdvancedTodoList.Core/Models/TodoLists/Members/RolePermissions.cs @@ -14,20 +14,20 @@ /// A flag that determines whether user can edit/delete existing categories and add new categories. /// A flag that determines whether user can view/delete existing invitation links. public record struct RolePermissions( - bool SetItemsState = false, - bool AddItems = false, - bool EditItems = false, - bool DeleteItems = false, - bool AddMembers = false, - bool RemoveMembers = false, - bool AssignRoles = false, - bool EditRoles = false, - bool EditCategories = false, - bool ManageInvitationLinks = false - ) + bool SetItemsState = false, + bool AddItems = false, + bool EditItems = false, + bool DeleteItems = false, + bool AddMembers = false, + bool RemoveMembers = false, + bool AssignRoles = false, + bool EditRoles = false, + bool EditCategories = false, + bool ManageInvitationLinks = false + ) { - /// - /// Instance of a structure with all permissions. - /// - public static readonly RolePermissions All = new(true, true, true, true, true, true, true, true, true, true); + /// + /// Instance of a structure with all permissions. + /// + public static readonly RolePermissions All = new(true, true, true, true, true, true, true, true, true, true); } diff --git a/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListMember.cs b/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListMember.cs index ff84356..686bf37 100644 --- a/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListMember.cs +++ b/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListMember.cs @@ -9,39 +9,39 @@ namespace AdvancedTodoList.Core.Models.TodoLists.Members; /// public class TodoListMember : IEntity, ITodoListDependant { - /// - /// An unique identifier. - /// - [Key] - public int Id { get; set; } + /// + /// An unique identifier. + /// + [Key] + public int Id { get; set; } - /// - /// A foreign key of the user who is the member. - /// - [ForeignKey(nameof(User))] - public required string UserId { get; set; } - /// - /// A foreign key of the to-do list. - /// - [ForeignKey(nameof(TodoList))] - public required string TodoListId { get; set; } - /// - /// A foreign key of the role, if null user has no role - /// and has a read-only access. - /// - [ForeignKey(nameof(Role))] - public int? RoleId { get; set; } + /// + /// A foreign key of the user who is the member. + /// + [ForeignKey(nameof(User))] + public required string UserId { get; set; } + /// + /// A foreign key of the to-do list. + /// + [ForeignKey(nameof(TodoList))] + public required string TodoListId { get; set; } + /// + /// A foreign key of the role, if null user has no role + /// and has a read-only access. + /// + [ForeignKey(nameof(Role))] + public int? RoleId { get; set; } - /// - /// A navigation property to the user who is the member. - /// - public ApplicationUser User { get; set; } = null!; - /// - /// A navigation property to the to-do list. - /// - public TodoList TodoList { get; set; } = null!; - /// - /// A navigation property to the role. - /// - public TodoListRole? Role { get; set; } + /// + /// A navigation property to the user who is the member. + /// + public ApplicationUser User { get; set; } = null!; + /// + /// A navigation property to the to-do list. + /// + public TodoList TodoList { get; set; } = null!; + /// + /// A navigation property to the role. + /// + public TodoListRole? Role { get; set; } } diff --git a/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListRole.cs b/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListRole.cs index fd16697..faa9185 100644 --- a/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListRole.cs +++ b/AdvancedTodoList.Core/Models/TodoLists/Members/TodoListRole.cs @@ -8,42 +8,42 @@ namespace AdvancedTodoList.Core.Models.TodoLists.Members; /// public class TodoListRole : IEntity, ITodoListDependant, IHasName { - /// - /// A unique identifier. - /// - [Key] - public int Id { get; set; } - /// - /// Name of the role. - /// - [MaxLength(NameMaxLength)] - public required string Name { get; set; } - /// - /// Priority of the role. - /// - /// - /// The lower value, the higher role is. 0 is reserved for the 'Owner' role which is created - /// together with a to-do list. Roles with negative priorities should not be possible. - /// - public required int Priority { get; set; } + /// + /// A unique identifier. + /// + [Key] + public int Id { get; set; } + /// + /// Name of the role. + /// + [MaxLength(NameMaxLength)] + public required string Name { get; set; } + /// + /// Priority of the role. + /// + /// + /// The lower value, the higher role is. 0 is reserved for the 'Owner' role which is created + /// together with a to-do list. Roles with negative priorities should not be possible. + /// + public required int Priority { get; set; } - /// - /// A foreign key of the to-do list which has this role. - /// - [ForeignKey(nameof(TodoList))] - public required string TodoListId { get; set; } - /// - /// A navigation property to the to-do list which has this role. - /// - public TodoList TodoList { get; set; } = null!; + /// + /// A foreign key of the to-do list which has this role. + /// + [ForeignKey(nameof(TodoList))] + public required string TodoListId { get; set; } + /// + /// A navigation property to the to-do list which has this role. + /// + public TodoList TodoList { get; set; } = null!; - /// - /// Permissions which each member with this role has. - /// - public RolePermissions Permissions { get; set; } = new(); + /// + /// Permissions which each member with this role has. + /// + public RolePermissions Permissions { get; set; } = new(); - /// - /// Max length of the property. - /// - public const int NameMaxLength = 100; + /// + /// Max length of the property. + /// + public const int NameMaxLength = 100; } diff --git a/AdvancedTodoList.Core/Models/TodoLists/TodoItem.cs b/AdvancedTodoList.Core/Models/TodoLists/TodoItem.cs index 461cbaf..1c027b7 100644 --- a/AdvancedTodoList.Core/Models/TodoLists/TodoItem.cs +++ b/AdvancedTodoList.Core/Models/TodoLists/TodoItem.cs @@ -9,81 +9,81 @@ namespace AdvancedTodoList.Core.Models.TodoLists; /// public class TodoItem : IEntity, ITodoListDependant, IHasOwner { - /// - /// An unique identifier for the to-do list item. - /// - [Key] - public int Id { get; set; } - /// - /// Name (title) of the to-do item. - /// - [MaxLength(NameMaxLength)] - public required string Name { get; set; } - /// - /// Description of the to-do item. - /// - [MaxLength(DescriptionMaxLength)] - public required string Description { get; set; } - /// - /// Current state of the to-do item. - /// - public TodoItemState State { get; set; } - /// - /// Deadline date for the todo item. Can be null. - /// - public DateTime? DeadlineDate { get; set; } - /// - /// Priority of the item. 0 means the lowest, 10 means the highest. - /// - public int Priority { get; set; } = 0; + /// + /// An unique identifier for the to-do list item. + /// + [Key] + public int Id { get; set; } + /// + /// Name (title) of the to-do item. + /// + [MaxLength(NameMaxLength)] + public required string Name { get; set; } + /// + /// Description of the to-do item. + /// + [MaxLength(DescriptionMaxLength)] + public required string Description { get; set; } + /// + /// Current state of the to-do item. + /// + public TodoItemState State { get; set; } + /// + /// Deadline date for the todo item. Can be null. + /// + public DateTime? DeadlineDate { get; set; } + /// + /// Priority of the item. 0 means the lowest, 10 means the highest. + /// + public int Priority { get; set; } = 0; - /// - /// Foreign key referencing the associated category. - /// - [ForeignKey(nameof(Category))] - public int? CategoryId { get; set; } - /// - /// Navigation property to the category associated with this to-do item. - /// - public TodoItemCategory? Category { get; set; } + /// + /// Foreign key referencing the associated category. + /// + [ForeignKey(nameof(Category))] + public int? CategoryId { get; set; } + /// + /// Navigation property to the category associated with this to-do item. + /// + public TodoItemCategory? Category { get; set; } - /// - /// Foreign key referencing the associated to-do list. - /// - [ForeignKey(nameof(TodoList))] - public required string TodoListId { get; set; } - /// - /// Navigation property to the to-do list associated with this to-do item. - /// - public TodoList TodoList { get; set; } = null!; + /// + /// Foreign key referencing the associated to-do list. + /// + [ForeignKey(nameof(TodoList))] + public required string TodoListId { get; set; } + /// + /// Navigation property to the to-do list associated with this to-do item. + /// + public TodoList TodoList { get; set; } = null!; - /// - /// Foreign key referencing the user who created this item. - /// - [ForeignKey(nameof(Owner))] - public required string? OwnerId { get; set; } = null!; - /// - /// Navigation property to the user who created this item. - /// - public ApplicationUser? Owner { get; set; } + /// + /// Foreign key referencing the user who created this item. + /// + [ForeignKey(nameof(Owner))] + public required string? OwnerId { get; set; } = null!; + /// + /// Navigation property to the user who created this item. + /// + public ApplicationUser? Owner { get; set; } - /// - /// Maximum allowed length of . - /// - public const int NameMaxLength = 100; - /// - /// Maximum allowed length of . - /// - public const int DescriptionMaxLength = 10_000; + /// + /// Maximum allowed length of . + /// + public const int NameMaxLength = 100; + /// + /// Maximum allowed length of . + /// + public const int DescriptionMaxLength = 10_000; - /// - /// Minimum allowed value of - /// - public const int MinPriority = 0; - /// - /// Maximum allowed value of - /// - public const int MaxPriority = 10; + /// + /// Minimum allowed value of + /// + public const int MinPriority = 0; + /// + /// Maximum allowed value of + /// + public const int MaxPriority = 10; } /// @@ -91,16 +91,16 @@ public class TodoItem : IEntity, ITodoListDependant, IHasOwner /// public enum TodoItemState : byte { - /// - /// The task is active (default state). - /// - Active = 0, - /// - /// The task has been completed. - /// - Completed, - /// - /// The task has been skipped. - /// - Skipped + /// + /// The task is active (default state). + /// + Active = 0, + /// + /// The task has been completed. + /// + Completed, + /// + /// The task has been skipped. + /// + Skipped } diff --git a/AdvancedTodoList.Core/Models/TodoLists/TodoItemCategory.cs b/AdvancedTodoList.Core/Models/TodoLists/TodoItemCategory.cs index 0a10d0b..67ae24b 100644 --- a/AdvancedTodoList.Core/Models/TodoLists/TodoItemCategory.cs +++ b/AdvancedTodoList.Core/Models/TodoLists/TodoItemCategory.cs @@ -8,34 +8,34 @@ namespace AdvancedTodoList.Core.Models.TodoLists; /// public class TodoItemCategory : IEntity, ITodoListDependant, IHasName { - /// - /// An unique identifier for the category. - /// - [Key] - public int Id { get; set; } - /// - /// Name of the category. - /// - [MaxLength(NameMaxLength)] - public required string Name { get; set; } + /// + /// An unique identifier for the category. + /// + [Key] + public int Id { get; set; } + /// + /// Name of the category. + /// + [MaxLength(NameMaxLength)] + public required string Name { get; set; } - /// - /// Foreign key referencing the associated to-do list. - /// - [ForeignKey(nameof(TodoList))] - public required string TodoListId { get; set; } - /// - /// Navigation property to the to-do list associated with this to-do item. - /// - public TodoList TodoList { get; set; } = null!; + /// + /// Foreign key referencing the associated to-do list. + /// + [ForeignKey(nameof(TodoList))] + public required string TodoListId { get; set; } + /// + /// Navigation property to the to-do list associated with this to-do item. + /// + public TodoList TodoList { get; set; } = null!; - /// - /// Collection of to-do items associated with this category. - /// - public virtual ICollection TodoItems { get; set; } = null!; + /// + /// Collection of to-do items associated with this category. + /// + public virtual ICollection TodoItems { get; set; } = null!; - /// - /// Maximum allowed length of . - /// - public const int NameMaxLength = 50; + /// + /// Maximum allowed length of . + /// + public const int NameMaxLength = 50; } diff --git a/AdvancedTodoList.Core/Models/TodoLists/TodoList.cs b/AdvancedTodoList.Core/Models/TodoLists/TodoList.cs index bd093e5..0dfd0d2 100644 --- a/AdvancedTodoList.Core/Models/TodoLists/TodoList.cs +++ b/AdvancedTodoList.Core/Models/TodoLists/TodoList.cs @@ -10,50 +10,50 @@ namespace AdvancedTodoList.Core.Models.TodoLists; /// public class TodoList : IEntity, IHasOwner { - /// - /// An unique identifier for the to-do list. - /// - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public string Id { get; set; } = null!; + /// + /// An unique identifier for the to-do list. + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public string Id { get; set; } = null!; - /// - /// Name (title) of the to-do list. - /// - [MaxLength(NameMaxLength)] - public required string Name { get; set; } = null!; - /// - /// Description of the to-do list. - /// - [MaxLength(DescriptionMaxLength)] - public required string Description { get; set; } = null!; + /// + /// Name (title) of the to-do list. + /// + [MaxLength(NameMaxLength)] + public required string Name { get; set; } = null!; + /// + /// Description of the to-do list. + /// + [MaxLength(DescriptionMaxLength)] + public required string Description { get; set; } = null!; - /// - /// Foreign key referencing the user who created this to-do list. - /// - [ForeignKey(nameof(Owner))] - public required string? OwnerId { get; set; } = null!; - /// - /// Navigation property to the user who created this to-do list. - /// - public ApplicationUser? Owner { get; set; } + /// + /// Foreign key referencing the user who created this to-do list. + /// + [ForeignKey(nameof(Owner))] + public required string? OwnerId { get; set; } = null!; + /// + /// Navigation property to the user who created this to-do list. + /// + public ApplicationUser? Owner { get; set; } - /// - /// Maximum allowed length of . - /// - public const int NameMaxLength = 100; - /// - /// Maximum allowed length of . - /// - public const int DescriptionMaxLength = 25_000; + /// + /// Maximum allowed length of . + /// + public const int NameMaxLength = 100; + /// + /// Maximum allowed length of . + /// + public const int DescriptionMaxLength = 25_000; - /// - /// Collection of to-do items associated with this todo list. - /// - public virtual ICollection TodoItems { get; set; } = null!; + /// + /// Collection of to-do items associated with this todo list. + /// + public virtual ICollection TodoItems { get; set; } = null!; - /// - /// Collection of list's members. - /// - public virtual ICollection TodoListMembers { get; set; } = null!; + /// + /// Collection of list's members. + /// + public virtual ICollection TodoListMembers { get; set; } = null!; } diff --git a/AdvancedTodoList.Core/Options/AccessTokenOptions.cs b/AdvancedTodoList.Core/Options/AccessTokenOptions.cs deleted file mode 100644 index 5f34ae1..0000000 --- a/AdvancedTodoList.Core/Options/AccessTokenOptions.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace AdvancedTodoList.Core.Options; - -/// -/// A class that contains access token options. -/// -public class AccessTokenOptions -{ - /// - /// Valid audience of tokens. - /// - public string ValidAudience { get; set; } = null!; - - /// - /// Valid issuer of tokens. - /// - public string ValidIssuer { get; set; } = null!; - - /// - /// Seconds before token expires. - /// - public int ExpirationSeconds { get; set; } - - /// - /// A secret key used for signing access tokens. - /// - public string SecretKey { get; set; } = null!; -} diff --git a/AdvancedTodoList.Core/Options/InvitationLinkOptions.cs b/AdvancedTodoList.Core/Options/InvitationLinkOptions.cs deleted file mode 100644 index 8eae032..0000000 --- a/AdvancedTodoList.Core/Options/InvitationLinkOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace AdvancedTodoList.Core.Options; - -/// -/// A class that contains invitation link options. -/// -public class InvitationLinkOptions -{ - /// - /// Size of the refresh token in bytes. - /// - public int Size { get; set; } - - /// - /// Days before token expires. - /// - public int ExpirationDays { get; set; } -} diff --git a/AdvancedTodoList.Core/Options/RefreshTokenOptions.cs b/AdvancedTodoList.Core/Options/RefreshTokenOptions.cs deleted file mode 100644 index 60e6925..0000000 --- a/AdvancedTodoList.Core/Options/RefreshTokenOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace AdvancedTodoList.Core.Options; - -/// -/// A class that contains refresh token options. -/// -public class RefreshTokenOptions -{ - /// - /// Size of the refresh token in bytes. - /// - public int Size { get; set; } - - /// - /// Days before token expires. - /// - public int ExpirationDays { get; set; } -} diff --git a/AdvancedTodoList.Core/Pagination/Page.cs b/AdvancedTodoList.Core/Pagination/Page.cs index e937e08..e346790 100644 --- a/AdvancedTodoList.Core/Pagination/Page.cs +++ b/AdvancedTodoList.Core/Pagination/Page.cs @@ -10,20 +10,20 @@ /// The type of items contained in the page. public class Page(IEnumerable items, int pageNumber, int pageSize, int totalCount) { - /// - /// The collection of items in the current page. - /// - public IEnumerable Items { get; } = items; - /// - /// The page number. - /// - public int PageNumber { get; } = pageNumber; - /// - /// The size of the page (number of items per page). - /// - public int PageSize { get; } = pageSize; - /// - /// The total count of items across all pages. - /// - public int TotalCount { get; } = totalCount; + /// + /// The collection of items in the current page. + /// + public IEnumerable Items { get; } = items; + /// + /// The page number. + /// + public int PageNumber { get; } = pageNumber; + /// + /// The size of the page (number of items per page). + /// + public int PageSize { get; } = pageSize; + /// + /// The total count of items across all pages. + /// + public int TotalCount { get; } = totalCount; } diff --git a/AdvancedTodoList.Core/Pagination/PaginationParameters.cs b/AdvancedTodoList.Core/Pagination/PaginationParameters.cs index efc346b..766c667 100644 --- a/AdvancedTodoList.Core/Pagination/PaginationParameters.cs +++ b/AdvancedTodoList.Core/Pagination/PaginationParameters.cs @@ -7,12 +7,12 @@ /// The number of items per page. Default is 20. public record PaginationParameters(int Page = 1, int PageSize = 20) { - /// - /// Minimum size of the page. - /// - public const int MinPageSize = 1; - /// - /// Maximum size of the page. - /// - public const int MaxPageSize = 100; + /// + /// Minimum size of the page. + /// + public const int MinPageSize = 1; + /// + /// Maximum size of the page. + /// + public const int MaxPageSize = 100; } diff --git a/AdvancedTodoList.Core/Repositories/IEntityExistenceChecker.cs b/AdvancedTodoList.Core/Repositories/IEntityExistenceChecker.cs new file mode 100644 index 0000000..295d5f0 --- /dev/null +++ b/AdvancedTodoList.Core/Repositories/IEntityExistenceChecker.cs @@ -0,0 +1,25 @@ +using AdvancedTodoList.Core.Models; + +namespace AdvancedTodoList.Core.Repositories; + +/// +/// An interface for the service that checks whether an entity with an ID exists. +/// +public interface IEntityExistenceChecker +{ + /// + /// Asynchronously checks whether an entity of type with an ID + /// of type exists. + /// + /// Type of the entity. + /// Type which ID of the entity has. + /// ID of the entity which existence is checked. + /// + /// A task representing the asynchronous operation. The task result contains + /// if entity with the given ID exists; otherwise + /// . + /// + Task ExistsAsync(TId id) + where TEntity : class, IEntity + where TId : IEquatable; +} diff --git a/AdvancedTodoList.Core/Repositories/IInvitationLinksRepository.cs b/AdvancedTodoList.Core/Repositories/IInvitationLinksRepository.cs index 6d686b9..38d9c6d 100644 --- a/AdvancedTodoList.Core/Repositories/IInvitationLinksRepository.cs +++ b/AdvancedTodoList.Core/Repositories/IInvitationLinksRepository.cs @@ -4,13 +4,13 @@ namespace AdvancedTodoList.Core.Repositories; public interface IInvitationLinksRepository : IRepository { - /// - /// Finds an invintation link by its value asynchronously. - /// - /// Value of the link. - /// - /// A task representing asynchronous operation which contains requested link or - /// it was not found. - /// - Task FindAsync(string linkValue); + /// + /// Finds an invintation link by its value asynchronously. + /// + /// Value of the link. + /// + /// A task representing asynchronous operation which contains requested link or + /// it was not found. + /// + Task FindAsync(string linkValue); } diff --git a/AdvancedTodoList.Core/Repositories/IRepository.cs b/AdvancedTodoList.Core/Repositories/IRepository.cs index a1f06f9..e5c3436 100644 --- a/AdvancedTodoList.Core/Repositories/IRepository.cs +++ b/AdvancedTodoList.Core/Repositories/IRepository.cs @@ -10,57 +10,57 @@ namespace AdvancedTodoList.Core.Repositories; /// The type of entity. /// The type of entity's primary key. public interface IRepository - where TEntity : class, IEntity - where TKey : IEquatable + where TEntity : class, IEntity + where TKey : IEquatable { - /// - /// Asynchronously adds a new entity to the repository. - /// - /// The entity to add. - Task AddAsync(TEntity entity); + /// + /// Asynchronously adds a new entity to the repository. + /// + /// The entity to add. + Task AddAsync(TEntity entity); - /// - /// Asynchronously retrieves an entity by its primary key. - /// - /// The primary key of the entity to retrieve. - /// - /// A task that represents an asynchronous operation and the entity if found; - /// otherwise, . - /// - Task GetByIdAsync(TKey id); + /// + /// Asynchronously retrieves an entity by its primary key. + /// + /// The primary key of the entity to retrieve. + /// + /// A task that represents an asynchronous operation and the entity if found; + /// otherwise, . + /// + Task GetByIdAsync(TKey id); - /// - /// Asynchronously retrieves an aggregate by applying a specification. - /// - /// Type of the aggregate to retrieve. - /// Specification to apply. - /// - /// A task that represents an asynchronous operation and the aggregate if found; - /// otherwise, . - /// - Task GetAggregateAsync(ISpecification specification) where TDto : class; + /// + /// Asynchronously retrieves an aggregate by applying a specification. + /// + /// Type of the aggregate to retrieve. + /// Specification to apply. + /// + /// A task that represents an asynchronous operation and the aggregate if found; + /// otherwise, . + /// + Task GetAggregateAsync(ISpecification specification) where TDto : class; - /// - /// Gets a page with entities mapped to type asynchronously. - /// - /// Returned type of items on the page. - /// Pagination parameters. - /// Specification used for entities retrival. - /// - /// A task that represents an asynchronous operation and a page with entities mapped - /// to type. - /// - Task> GetPageAsync(PaginationParameters paginationParameters, ISpecification specification); + /// + /// Gets a page with entities mapped to type asynchronously. + /// + /// Returned type of items on the page. + /// Pagination parameters. + /// Specification used for entities retrival. + /// + /// A task that represents an asynchronous operation and a page with entities mapped + /// to type. + /// + Task> GetPageAsync(PaginationParameters paginationParameters, ISpecification specification); - /// - /// Asynchronously updates an existing entity in the repository. - /// - /// The entity to update. - Task UpdateAsync(TEntity entity); + /// + /// Asynchronously updates an existing entity in the repository. + /// + /// The entity to update. + Task UpdateAsync(TEntity entity); - /// - /// Asynchronously deletes an entity from the repository. - /// - /// The entity to delete. - Task DeleteAsync(TEntity entity); + /// + /// Asynchronously deletes an entity from the repository. + /// + /// The entity to delete. + Task DeleteAsync(TEntity entity); } diff --git a/AdvancedTodoList.Core/Repositories/ITodoListMembersRepository.cs b/AdvancedTodoList.Core/Repositories/ITodoListMembersRepository.cs index 32fa72d..791b4d8 100644 --- a/AdvancedTodoList.Core/Repositories/ITodoListMembersRepository.cs +++ b/AdvancedTodoList.Core/Repositories/ITodoListMembersRepository.cs @@ -7,13 +7,13 @@ namespace AdvancedTodoList.Core.Repositories; /// public interface ITodoListMembersRepository : IRepository { - /// - /// Finds a to-do list member by to-do list ID and user's ID asynchronously. - /// - /// ID of the user. - /// ID of the to-do list. - /// - /// Found to-do list member, or if it was not found. - /// - Task FindAsync(string todoListId, string userId); + /// + /// Finds a to-do list member by to-do list ID and user's ID asynchronously. + /// + /// ID of the user. + /// ID of the to-do list. + /// + /// Found to-do list member, or if it was not found. + /// + Task FindAsync(string todoListId, string userId); } diff --git a/AdvancedTodoList.Core/Repositories/IUnitOfWork.cs b/AdvancedTodoList.Core/Repositories/IUnitOfWork.cs index 177b096..d452e12 100644 --- a/AdvancedTodoList.Core/Repositories/IUnitOfWork.cs +++ b/AdvancedTodoList.Core/Repositories/IUnitOfWork.cs @@ -5,21 +5,21 @@ /// public interface IUnitOfWork { - /// - /// Begins a new transaction asynchronously. - /// - /// A task representing the asynchronous operation. - Task BeginTransactionAsync(); + /// + /// Begins a new transaction asynchronously. + /// + /// A task representing the asynchronous operation. + Task BeginTransactionAsync(); - /// - /// Commits the transaction asynchronously. - /// - /// A task representing the asynchronous operation. - Task CommitAsync(); + /// + /// Commits the transaction asynchronously. + /// + /// A task representing the asynchronous operation. + Task CommitAsync(); - /// - /// Rolls back the transaction asynchronously. - /// - /// A task representing the asynchronous operation. - Task RollbackAsync(); + /// + /// Rolls back the transaction asynchronously. + /// + /// A task representing the asynchronous operation. + Task RollbackAsync(); } \ No newline at end of file diff --git a/AdvancedTodoList.Core/Repositories/IUserRefreshTokensRepository.cs b/AdvancedTodoList.Core/Repositories/IUserRefreshTokensRepository.cs index 9bc813f..8320f1b 100644 --- a/AdvancedTodoList.Core/Repositories/IUserRefreshTokensRepository.cs +++ b/AdvancedTodoList.Core/Repositories/IUserRefreshTokensRepository.cs @@ -7,13 +7,13 @@ namespace AdvancedTodoList.Core.Repositories; /// public interface IUserRefreshTokensRepository : IRepository { - /// - /// Finds user's refresh token by user's ID and refresh token's value asynchronously. - /// - /// User's unique identifier - /// Value of the refresh token. - /// - /// Found user's refresh token, or if it was not found. - /// - Task FindAsync(string userId, string refreshToken); + /// + /// Finds user's refresh token by user's ID and refresh token's value asynchronously. + /// + /// User's unique identifier + /// Value of the refresh token. + /// + /// Found user's refresh token, or if it was not found. + /// + Task FindAsync(string userId, string refreshToken); } diff --git a/AdvancedTodoList.Core/Services/Auth/IAccessTokensService.cs b/AdvancedTodoList.Core/Services/Auth/IAccessTokensService.cs deleted file mode 100644 index 1d7c0c8..0000000 --- a/AdvancedTodoList.Core/Services/Auth/IAccessTokensService.cs +++ /dev/null @@ -1,28 +0,0 @@ -using AdvancedTodoList.Core.Models.Auth; - -namespace AdvancedTodoList.Core.Services.Auth; - -/// -/// An interface for a service that manages access tokens. -/// -public interface IAccessTokensService -{ - /// - /// Generates an access token for the user. - /// - /// User which will receive an access token. - /// - /// A string that represents an access token. - /// - string GenerateAccessToken(ApplicationUser user); - - /// - /// Validates an access token without checking expiration time and then returns - /// ID of the user stored in it asynchronously. - /// - /// A string that represents an access token. - /// - /// A user ID retrieved from the access token or , if validation failed. - /// - Task GetUserIdFromExpiredTokenAsync(string accessToken); -} diff --git a/AdvancedTodoList.Core/Services/Auth/IAuthService.cs b/AdvancedTodoList.Core/Services/Auth/IAuthService.cs deleted file mode 100644 index 0715f89..0000000 --- a/AdvancedTodoList.Core/Services/Auth/IAuthService.cs +++ /dev/null @@ -1,49 +0,0 @@ -using AdvancedTodoList.Core.Dtos; - -namespace AdvancedTodoList.Core.Services.Auth; - -/// -/// Interface for service that performs authentication operations. -/// -public interface IAuthService -{ - /// - /// Logs a user in asynchronously. - /// - /// Data required for logging in. - /// - /// Returns a task representing the asynchronous operation, containing a - /// response with access and refresh tokens or null if authorization fails. - /// - Task LogInAsync(LogInDto logInDto); - - /// - /// Registers a new user asynchronously. - /// - /// Data required for user registration. - /// - /// Returns a task representing the asynchronous operation, containing the registration result. - /// - Task RegisterAsync(RegisterDto registerDto); - - /// - /// Refreshes the access token asynchronously. - /// - /// Data required for token refresh. - /// - /// Returns a task representing the asynchronous operation, - /// containing a response with access and refresh tokens or null if authorization fails. - /// - Task RefreshAsync(RefreshDto refreshDto); - - /// - /// Logs a user out asynchronously by revoking a refresh token. - /// - /// ID of the caller. - /// Data required for logging out. - /// - /// Returns a task representing the asynchronous operation, - /// indicating the success or failure of the operation. - /// - Task LogOutAsync(string userId, LogOutDto logOutDto); -} diff --git a/AdvancedTodoList.Core/Services/Auth/IPermissionsChecker.cs b/AdvancedTodoList.Core/Services/Auth/IPermissionsChecker.cs deleted file mode 100644 index 153adaf..0000000 --- a/AdvancedTodoList.Core/Services/Auth/IPermissionsChecker.cs +++ /dev/null @@ -1,68 +0,0 @@ -using AdvancedTodoList.Core.Models; -using AdvancedTodoList.Core.Models.TodoLists.Members; - -namespace AdvancedTodoList.Core.Services.Auth; - -/// -/// Interface for a service that checks user's permissions. -/// -public interface IPermissionsChecker -{ - /// - /// Asynchronously checks whether the user is a member of the to-do list with - /// specified ID. - /// - /// To-do list context. - /// - /// if user is a member of the list; otherwise . - /// - Task IsMemberOfListAsync(TodoListContext context); - - /// - /// Asynchronously checks whether the user is a member of the to-do list and - /// has a permission defined by the funciton . - /// - /// To-do list context. - /// Function that should return if user has required permission. - /// - /// if user is a member of the list and has required permission; - /// otherwise . - /// - Task HasPermissionAsync(TodoListContext context, Func permission); - - /// - /// Asynchronously checks whether the user can touch an entity. - /// - /// - /// This method firstly checks whether implements - /// interface and if yes, checks if the user is the owner of the entity and is a member of the to-do list; - /// otherwise the method checks if user has the permission defined by the function . - /// - /// Type of the entity. - /// Type of the unique identifier used by the entity. - /// To-do list context. - /// ID of the entity. - /// Function that should return if user has required permission. - /// - /// if user is either an owner of the entity and a member of a to-do list, - /// or he/she/they has permission defined by ; otherwise . - /// - Task CanTouchEntityAsync(TodoListContext context, TEntity entity, - Func permission) - where TEntity : class, IEntity - where TKey : IEquatable; - - /// - /// Asynchronously checks whether the user has a permission to change the role - /// with the priority of . - /// - /// To-do list context. - /// ID of the role. - /// Function that should return if user has required permission. - /// - /// if user has and highest role priority than - /// the ; otherwise . - /// - Task HasPermissionOverRoleAsync(TodoListContext context, int rolePriority, - Func permission); -} diff --git a/AdvancedTodoList.Core/Services/Auth/IRefreshTokensService.cs b/AdvancedTodoList.Core/Services/Auth/IRefreshTokensService.cs deleted file mode 100644 index e1aaddb..0000000 --- a/AdvancedTodoList.Core/Services/Auth/IRefreshTokensService.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace AdvancedTodoList.Core.Services.Auth; - -/// -/// An interface for a service that manages refresh tokens. -/// -public interface IRefreshTokensService -{ - /// - /// Generates a refresh token for the user and saves it asynchronously. - /// - /// ID of the user who will receive the token. - /// - /// A string that represents a refresh token or if user does not exist. - /// - Task GenerateAsync(string userId); - - /// - /// Revokes the refresh token of the user asynchronously - /// - /// ID of the user whose token is being revoked. - /// Value of the token to be revoked. - /// - /// on success; otherwise. - /// - Task RevokeAsync(string userId, string token); - - /// - /// Checks whether refresh token is valid asynchronously. - /// - /// ID of the user whose token is being validated. - /// Value of the token to be validated. - /// - /// if token is valid; - /// otherwise. - /// - Task ValidateAsync(string userId, string token); -} diff --git a/AdvancedTodoList.Core/Services/Auth/RegisterResult.cs b/AdvancedTodoList.Core/Services/Auth/RegisterResult.cs deleted file mode 100644 index ccffe4e..0000000 --- a/AdvancedTodoList.Core/Services/Auth/RegisterResult.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace AdvancedTodoList.Core.Services.Auth; - -/// -/// Class that represents a result of register operation. -/// -public class RegisterResult -{ - /// - /// Flag which determines whether result reports succeess. - /// - public bool IsSuccess { get; private init; } - /// - /// Error messages. - /// - public IEnumerable Errors { get; private init; } - - private RegisterResult(bool isSuccess, IEnumerable errors) - { - IsSuccess = isSuccess; - Errors = errors; - } - - /// - /// Returns a result that reports success. - /// - /// - /// A result that reports success. - /// - public static RegisterResult Success() => new(true, Array.Empty()); - - /// - /// Returns a result that reports failure. - /// - /// Error messages. - /// - /// A result that reports failure. - /// - public static RegisterResult Failure(IEnumerable errors) => new(false, errors); -} - -/// -/// Record that represents a register error. -/// -/// Property that caused an error or '$' if error was not caused by a property. -/// An error message. -public record RegisterError(string Property, string Message); diff --git a/AdvancedTodoList.Core/Services/IEntityExistenceChecker.cs b/AdvancedTodoList.Core/Services/IEntityExistenceChecker.cs deleted file mode 100644 index 1893c06..0000000 --- a/AdvancedTodoList.Core/Services/IEntityExistenceChecker.cs +++ /dev/null @@ -1,25 +0,0 @@ -using AdvancedTodoList.Core.Models; - -namespace AdvancedTodoList.Core.Services; - -/// -/// An interface for the service that checks whether an entity with an ID exists. -/// -public interface IEntityExistenceChecker -{ - /// - /// Asynchronously checks whether an entity of type with an ID - /// of type exists. - /// - /// Type of the entity. - /// Type which ID of the entity has. - /// ID of the entity which existence is checked. - /// - /// A task representing the asynchronous operation. The task result contains - /// if entity with the given ID exists; otherwise - /// . - /// - Task ExistsAsync(TId id) - where TEntity : class, IEntity - where TId : IEquatable; -} diff --git a/AdvancedTodoList.Core/Services/IInvitationLinksService.cs b/AdvancedTodoList.Core/Services/IInvitationLinksService.cs deleted file mode 100644 index 9e9da10..0000000 --- a/AdvancedTodoList.Core/Services/IInvitationLinksService.cs +++ /dev/null @@ -1,54 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Pagination; - -namespace AdvancedTodoList.Core.Services; - -/// -/// An interface for a service that manages invitation links. -/// -public interface IInvitationLinksService -{ - /// - /// Joins the caller to the to-do list by invitation list asynchronously. - /// - /// ID of the caller. - /// Invitation link to use. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - Task JoinAsync(string callerId, string invitationLinkValue); - - /// - /// Gets invitation links associated with the to-do list asynchronously. - /// - /// To-do list context of the operation. - /// Pagination parameters to use. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - Task>> GetInvitationLinksAsync(TodoListContext context, - PaginationParameters parameters); - - /// - /// Creates an invitation link associated to the to-do list asynchronously. - /// - /// To-do list context. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - Task> CreateAsync(TodoListContext context); - - /// - /// Deletes an invitation link associted to the to-do list asynchronously. - /// - /// To-do list context. - /// ID of the link. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - Task DeleteAsync(TodoListContext context, int linkId); -} diff --git a/AdvancedTodoList.Core/Services/ITodoItemCategoriesService.cs b/AdvancedTodoList.Core/Services/ITodoItemCategoriesService.cs deleted file mode 100644 index 888fcfb..0000000 --- a/AdvancedTodoList.Core/Services/ITodoItemCategoriesService.cs +++ /dev/null @@ -1,78 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Pagination; - -namespace AdvancedTodoList.Core.Services; - -/// -/// Interface for a service that manages to-do list items categories. -/// -public interface ITodoItemCategoriesService -{ - /// - /// Asynchronously checks whether the category ID is valid for the given context. - /// - /// - /// ID is considered as valid. - /// - /// To-do list context. - /// ID of the category to validate. - /// - /// A task representing the asynchronous operation. - /// if ID is valid, otherwise. - /// - public Task IsCategoryValidForContext(TodoListContext context, int? categoryId); - - /// - /// Retrieves a page of to-do list items categories of the list with the specified ID. - /// - /// To-do list context. - /// Pagination parameters to use. - /// Optional name to filter categories by. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task>> GetCategoriesOfListAsync( - TodoListContext context, PaginationParameters paginationParameters, string? name = null); - - /// - /// Retrieves a to-do list item category by its ID asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to retrieve. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task> GetByIdAsync(TodoListContext context, int categoryId); - - /// - /// Creates a new to-do list item category asynchronously. - /// - /// To-do list context. - /// The DTO containing information for creating the to-do list item. - /// ID of the user who creates the to-do list item. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task> CreateAsync(TodoListContext context, TodoItemCategoryCreateDto dto); - - /// - /// Edits a to-do list item category asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to edit. - /// The DTO containing information for editing the to-do list item. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task EditAsync(TodoListContext context, int categoryId, TodoItemCategoryCreateDto dto); - - /// - /// Deletes a to-do list item category asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to delete. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task DeleteAsync(TodoListContext context, int categoryId); -} diff --git a/AdvancedTodoList.Core/Services/ITodoItemsService.cs b/AdvancedTodoList.Core/Services/ITodoItemsService.cs deleted file mode 100644 index f338721..0000000 --- a/AdvancedTodoList.Core/Services/ITodoItemsService.cs +++ /dev/null @@ -1,75 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Specifications; - -namespace AdvancedTodoList.Core.Services; - -/// -/// Interface for a service that manages to-do list items. -/// -public interface ITodoItemsService -{ - /// - /// Retrieves a page of to-do list items of the list with the specified ID. - /// - /// To-do list context. - /// Pagination parameters to use. - /// Filter parameters to apply. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task>> GetItemsOfListAsync( - TodoListContext context, PaginationParameters paginationParameters, TodoItemsFilter filter); - - /// - /// Retrieves a to-do list item by its ID asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to retrieve. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task> GetByIdAsync(TodoListContext context, int itemId); - - /// - /// Creates a new to-do list item asynchronously. - /// - /// To-do list context. - /// The DTO containing information for creating the to-do list item. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task CreateAsync(TodoListContext context, TodoItemCreateDto dto); - - /// - /// Edits a to-do list item asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to edit. - /// The DTO containing information for editing the to-do list item. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task EditAsync(TodoListContext context, int itemId, TodoItemCreateDto dto); - - /// - /// Updates the state of a to-do list item asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to update the state. - /// The DTO which contains the state of the to-do item to set. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task UpdateStateAsync(TodoListContext context, int itemId, TodoItemUpdateStateDto stateDto); - - /// - /// Deletes a to-do list item asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to delete. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task DeleteAsync(TodoListContext context, int itemId); -} diff --git a/AdvancedTodoList.Core/Services/ITodoListDependantEntitiesService.cs b/AdvancedTodoList.Core/Services/ITodoListDependantEntitiesService.cs deleted file mode 100644 index 2c33383..0000000 --- a/AdvancedTodoList.Core/Services/ITodoListDependantEntitiesService.cs +++ /dev/null @@ -1,94 +0,0 @@ -using AdvancedTodoList.Core.Models; -using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Specifications; - -namespace AdvancedTodoList.Core.Services; - -/// -/// An interface that represents a service for perfoming CRUD operations -/// on to-do list dependant entities and mapping/unmapping DTOs. -/// -/// Type of the to-do list dependant entity. -/// Type of the unique identifier used by the entity. -public interface ITodoListDependantEntitiesService - where TEntity : class, IEntity, ITodoListDependant - where TKey : IEquatable -{ - /// - /// Retrieves a page of to-do list dependant entities mapped to . - /// - /// - /// This method checks if to-do list ID is valid, but doesn't filter by it. - /// Filtering should be done in . - /// - /// To-do list context. - /// Specification to apply to entities. - /// Pagination parameters to use. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation and the requested page on success. - /// - public Task>> GetPageAsync(TodoListContext context, - ISpecification specification, PaginationParameters paginationParameters); - - /// - /// Retrieves a to-do list dependant entity by its ID asynchronously and maps it to . - /// - /// To-do list context. - /// The ID of the entity to retrieve. - /// DTO to map entity to. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation and the requested dto on success. - /// - Task> GetByIdAsync(TodoListContext context, TKey entityId) where TDto : class; - - /// - /// Creates a new a to-do list dependant entity asynchronously from the DTO. - /// - /// - /// If implements the interface, then - /// this method will set the caller as an owner. - /// - /// To-do list context. - /// The DTO containing information for creating the entity. - /// Optional accessor for the permission required for the user to perform the action. - /// - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation and the created mapped to - /// on success. - /// - public Task> CreateAsync( - TodoListContext context, TInputDto dto, Func? permission = null) - where TOutputDto : class; - - /// - /// Edits a to-do list dependant entity asynchronously. - /// - /// To-do list context. - /// The ID of the entity to edit. - /// The DTO containing information for editing the entity. - /// Optional accessor for the permission required for the user to perform the action. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public Task UpdateAsync(TodoListContext context, TKey entityId, - TDto dto, Func? permission = null); - - /// - /// Deletes a to-do list dependant entity asynchronously. - /// - /// To-do list context. - /// The ID of the entity to delete. - /// Optional accessor for the permission required for the user to perform the action. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public Task DeleteAsync(TodoListContext context, - TKey entityId, Func? permission = null); -} diff --git a/AdvancedTodoList.Core/Services/ITodoListMembersService.cs b/AdvancedTodoList.Core/Services/ITodoListMembersService.cs deleted file mode 100644 index 29c07a3..0000000 --- a/AdvancedTodoList.Core/Services/ITodoListMembersService.cs +++ /dev/null @@ -1,55 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Specifications; - -namespace AdvancedTodoList.Core.Services; - -/// -/// Interface for a service that manages to-do list members. -/// -public interface ITodoListMembersService -{ - /// - /// Gets a page with members of a to-do list asynchronously. - /// - /// To-do list context. - /// Pagination parameters to use. - /// Filter parameters to use. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - Task>> GetMembersAsync(TodoListContext context, - PaginationParameters paginationParameters, TodoListMembersFilter filter); - - /// - /// Adds a member to a to-do list asynchronously. - /// - /// To-do list context. - /// DTO that contains information needed for adding a member. Supossed to be valid. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - Task AddMemberAsync(TodoListContext context, TodoListMemberAddDto dto); - - /// - /// Updates a role of the member of a to-do list asynchronously. - /// - /// To-do list context. - /// ID of the member. - /// DTO that contains information needed for updating a role. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - Task UpdateMemberRoleAsync(TodoListContext context, - int memberId, TodoListMemberUpdateRoleDto dto); - - /// - /// Removes a member from a to-do list asynchronously. - /// - /// To-do list context. - /// ID of the member. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - Task RemoveMemberAsync(TodoListContext context, int memberId); -} diff --git a/AdvancedTodoList.Core/Services/ITodoListRolesService.cs b/AdvancedTodoList.Core/Services/ITodoListRolesService.cs deleted file mode 100644 index 88030be..0000000 --- a/AdvancedTodoList.Core/Services/ITodoListRolesService.cs +++ /dev/null @@ -1,63 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Pagination; - -namespace AdvancedTodoList.Core.Services; - -/// -/// Interface for a service that manages to-do list role. -/// -public interface ITodoListRolesService -{ - /// - /// Retrieves a page of to-do list roles of the list with the specified ID. - /// - /// To-do list context. - /// Pagination parameters to use. - /// Optional name to filter categories by. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task>> GetRolesOfListAsync( - TodoListContext context, PaginationParameters paginationParameters, string? name = null); - - /// - /// Retrieves a to-do list role by its ID asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list role to retrieve. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task> GetByIdAsync(TodoListContext context, int roleId); - - /// - /// Creates a new to-do list role asynchronously. - /// - /// To-do list context. - /// The DTO containing information for creating the to-do list role. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task> CreateAsync(TodoListContext context, TodoListRoleCreateDto dto); - - /// - /// Edits a to-do list role asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list role to edit. - /// The DTO containing information for editing the to-do list role. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task EditAsync(TodoListContext context, int roleId, TodoListRoleCreateDto dto); - - /// - /// Deletes a to-do list role asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list role to delete. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task DeleteAsync(TodoListContext context, int roleId); -} diff --git a/AdvancedTodoList.Core/Services/ITodoListsService.cs b/AdvancedTodoList.Core/Services/ITodoListsService.cs deleted file mode 100644 index ad3be55..0000000 --- a/AdvancedTodoList.Core/Services/ITodoListsService.cs +++ /dev/null @@ -1,69 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Specifications; - -namespace AdvancedTodoList.Core.Services; - -/// -/// Interface for a service that manages to-do lists. -/// -public interface ITodoListsService -{ - /// - /// Retrieves a page of to-do lists, with the requirement that the user - /// is a member of those lists. - /// - /// Id of the user - /// Pagination parameters to use. - /// Filter parameters to apply. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task> GetListsOfUserAsync(string userId, - PaginationParameters paginationParameters, TodoListsFilter filter); - - /// - /// Retrieves a to-do list by its ID asynchronously. - /// - /// To-do list context. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public Task> GetByIdAsync(TodoListContext context); - - /// - /// Creates a new to-do list asynchronously. - /// - /// - /// This method should also create an "Owner" role with all permissions and assign the caller to it. - /// - /// The DTO containing information for creating the to-do list. - /// ID of the user who creates the to-do list. - /// - /// A task representing the asynchronous operation. The task contains - /// a created model mapped to . - /// - public Task CreateAsync(TodoListCreateDto dto, string callerId); - - /// - /// Edits a to-do list asynchronously. - /// - /// To-do list context. - /// The DTO containing information for editing the to-do list. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public Task EditAsync(TodoListContext context, TodoListCreateDto dto); - - /// - /// Deletes a to-do list asynchronously. - /// - /// To-do list context. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public Task DeleteAsync(TodoListContext context); -} diff --git a/AdvancedTodoList.Core/Services/JoinByInvitationLinkResult.cs b/AdvancedTodoList.Core/Services/JoinByInvitationLinkResult.cs deleted file mode 100644 index 0da518a..0000000 --- a/AdvancedTodoList.Core/Services/JoinByInvitationLinkResult.cs +++ /dev/null @@ -1,42 +0,0 @@ -using AdvancedTodoList.Core.Dtos; - -namespace AdvancedTodoList.Core.Services; - -/// -/// Represents possible results of the join to-do list by invitatation link operation. -/// -public class JoinByInvitationLinkResult(JoinByInvitationLinkStatus status, TodoListMemberMinimalViewDto? dto = null) -{ - /// - /// Status of the operation. - /// - public JoinByInvitationLinkStatus Status { get; } = status; - - /// - /// Gets additional DTO of the member, can be . - /// - public TodoListMemberMinimalViewDto? Dto { get; } = dto; -} - -/// -/// Enum that represents possible result statuses of the join to-do list by invitatation link operation. -/// -public enum JoinByInvitationLinkStatus -{ - /// - /// Operation was successfull. - /// - Success, - /// - /// Invitation link was not found. - /// - NotFound, - /// - /// Invitation link is expired. - /// - Expired, - /// - /// User is already a member of the to-do list. - /// - UserIsAlreadyMember -} \ No newline at end of file diff --git a/AdvancedTodoList.Core/Services/ServiceResponse.cs b/AdvancedTodoList.Core/Services/ServiceResponse.cs deleted file mode 100644 index b180319..0000000 --- a/AdvancedTodoList.Core/Services/ServiceResponse.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace AdvancedTodoList.Core.Services; - -/// -/// Represent a generic service response which indicates an operation status and -/// contains an additonal information. -/// -/// Type of the content saved in the response. -public class ServiceResponse(ServiceResponseStatus status, T? result = default) -{ - /// - /// Gets the status of operation. - /// - public ServiceResponseStatus Status { get; } = status; - - /// - /// Gets the result of the operation. - /// - public T? Result { get; } = result; -} - -/// -/// Represents a possible service response status. -/// -public enum ServiceResponseStatus -{ - /// - /// Status that indicates success. - /// - Success, - /// - /// Status that indicates that entity could not be found or the caller isn't - /// suppossed to know that entity exists. - /// - NotFound, - /// - /// Status that indicates that user has no permission to perform the operation. - /// - Forbidden -} diff --git a/AdvancedTodoList.Core/Services/TodoItemsServiceResponse.cs b/AdvancedTodoList.Core/Services/TodoItemsServiceResponse.cs deleted file mode 100644 index c3371dc..0000000 --- a/AdvancedTodoList.Core/Services/TodoItemsServiceResponse.cs +++ /dev/null @@ -1,37 +0,0 @@ -using AdvancedTodoList.Core.Dtos; - -namespace AdvancedTodoList.Core.Services; - -public class TodoItemsServiceResponse(TodoItemsServiceStatus status, TodoItemGetByIdDto? result = null) -{ - /// - /// Status of the operation. - /// - public TodoItemsServiceStatus Status { get; set; } = status; - - /// - /// Result of the operation. - /// - public TodoItemGetByIdDto? Result { get; set; } = result; -} - -public enum TodoItemsServiceStatus -{ - /// - /// Status that indicates success. - /// - Success, - /// - /// Status that indicates that entity could not be found or the caller isn't - /// suppossed to know that entity exists. - /// - NotFound, - /// - /// Status that indicates that user has no permission to perform the operation. - /// - Forbidden, - /// - /// Status that indicates that user provided an invalid category ID. - /// - InvalidCategoryId -} \ No newline at end of file diff --git a/AdvancedTodoList.Core/Services/TodoListMembersServiceResponse.cs b/AdvancedTodoList.Core/Services/TodoListMembersServiceResponse.cs deleted file mode 100644 index 782c389..0000000 --- a/AdvancedTodoList.Core/Services/TodoListMembersServiceResponse.cs +++ /dev/null @@ -1,47 +0,0 @@ -using AdvancedTodoList.Core.Dtos; - -namespace AdvancedTodoList.Core.Services; - -/// -/// Class that represents possible results of the method . -/// -public class AddTodoListMemberServiceResult( - TodoListMemberServiceResultStatus status, TodoListMemberMinimalViewDto? dto = null) -{ - /// - /// Status of the operation. - /// - public TodoListMemberServiceResultStatus Status { get; } = status; - - /// - /// Gets additional DTO of the member, can be . - /// - public TodoListMemberMinimalViewDto? Dto { get; } = dto; -} - -/// -/// Enum that represents possible result statuses of the to-do list members service. -/// -public enum TodoListMemberServiceResultStatus -{ - /// - /// Operation was successfull. - /// - Success, - /// - /// To-do list was not found. - /// - NotFound, - /// - /// User is already a member of the to-do list, returned only in the add member method. - /// - UserAlreadyAdded, - /// - /// Role either doesn't exist or it's invalid for the current to-do list. - /// - InvalidRoleId, - /// - /// User has no permission to perform the operation. - /// - Forbidden -} diff --git a/AdvancedTodoList.Core/Specifications/TodoItemsFilter.cs b/AdvancedTodoList.Core/Specifications/Filters/TodoItemsFilter.cs similarity index 72% rename from AdvancedTodoList.Core/Specifications/TodoItemsFilter.cs rename to AdvancedTodoList.Core/Specifications/Filters/TodoItemsFilter.cs index 26d47f8..0a227ea 100644 --- a/AdvancedTodoList.Core/Specifications/TodoItemsFilter.cs +++ b/AdvancedTodoList.Core/Specifications/Filters/TodoItemsFilter.cs @@ -1,6 +1,6 @@ using AdvancedTodoList.Core.Models.TodoLists; -namespace AdvancedTodoList.Core.Specifications; +namespace AdvancedTodoList.Core.Specifications.Filters; /// /// Parameters for filtering to-do lists items. @@ -17,7 +17,7 @@ namespace AdvancedTodoList.Core.Specifications; /// Optional min deadline date to filter by. /// Optional max deadline date to filter by. public record TodoItemsFilter( - string? Name = null, string? OwnerId = null, - IEnumerable? State = null, IEnumerable? CategoryId = null, - int? MinPriority = null, int? MaxPriority = null, - DateTime? MinDeadlineDate = null, DateTime? MaxDeadlineDate = null); + string? Name = null, string? OwnerId = null, + IEnumerable? State = null, IEnumerable? CategoryId = null, + int? MinPriority = null, int? MaxPriority = null, + DateTime? MinDeadlineDate = null, DateTime? MaxDeadlineDate = null); diff --git a/AdvancedTodoList.Core/Specifications/TodoListMembersFilter.cs b/AdvancedTodoList.Core/Specifications/Filters/TodoListMembersFilter.cs similarity index 84% rename from AdvancedTodoList.Core/Specifications/TodoListMembersFilter.cs rename to AdvancedTodoList.Core/Specifications/Filters/TodoListMembersFilter.cs index b6739c9..55504b6 100644 --- a/AdvancedTodoList.Core/Specifications/TodoListMembersFilter.cs +++ b/AdvancedTodoList.Core/Specifications/Filters/TodoListMembersFilter.cs @@ -1,4 +1,4 @@ -namespace AdvancedTodoList.Core.Specifications; +namespace AdvancedTodoList.Core.Specifications.Filters; /// /// Parameters for filtering to-do lists members. @@ -14,4 +14,4 @@ /// Entries which have this substring in the '{FirstName} {LastName}' will be returned. /// public record TodoListMembersFilter(IEnumerable? RoleId = null, string? UserId = null, - string? UserName = null, string? FullName = null); + string? UserName = null, string? FullName = null); diff --git a/AdvancedTodoList.Core/Specifications/TodoListsFilter.cs b/AdvancedTodoList.Core/Specifications/Filters/TodoListsFilter.cs similarity index 81% rename from AdvancedTodoList.Core/Specifications/TodoListsFilter.cs rename to AdvancedTodoList.Core/Specifications/Filters/TodoListsFilter.cs index dbb5e7a..8581b42 100644 --- a/AdvancedTodoList.Core/Specifications/TodoListsFilter.cs +++ b/AdvancedTodoList.Core/Specifications/Filters/TodoListsFilter.cs @@ -1,4 +1,4 @@ -namespace AdvancedTodoList.Core.Specifications; +namespace AdvancedTodoList.Core.Specifications.Filters; /// /// Parameters for filtering to-do lists. diff --git a/AdvancedTodoList.Core/Specifications/GetByIdSpecification.cs b/AdvancedTodoList.Core/Specifications/GetByIdSpecification.cs new file mode 100644 index 0000000..9f23856 --- /dev/null +++ b/AdvancedTodoList.Core/Specifications/GetByIdSpecification.cs @@ -0,0 +1,38 @@ +using AdvancedTodoList.Core.Models; +using System.Linq.Expressions; + +namespace AdvancedTodoList.Core.Specifications; + +/// +/// Represents a specification that defines criteria for obtaining entities by ID. +/// +/// The type of entity. +/// The type of the entity unique identifier. +/// The unique identifier to filter by. +public class GetByIdSpecification(TKey id) : ISpecification + where TEntity : class, IEntity + where TKey : IEquatable +{ + /// + /// The unique identifier to filter by. + /// + public TKey Id { get; } = id; + + /// + /// Gets the criteria expression that defines the filtering conditions. + /// Filters by ID provided in the constructor when not overriden. + /// + public Expression> Criteria => x => x.Id.Equals(Id); + + /// + /// Gets the list of include expressions specifying related entities to be included in the query results. + /// Is empty when not overriden. + /// + public virtual List>> Includes => []; + + /// + /// Gets the list of include strings specifying related entities to be included in the query results. + /// Is empty when not overriden. + /// + public virtual List IncludeStrings => []; +} diff --git a/AdvancedTodoList.Core/Specifications/ISpecification.cs b/AdvancedTodoList.Core/Specifications/ISpecification.cs index ab48e38..cbec074 100644 --- a/AdvancedTodoList.Core/Specifications/ISpecification.cs +++ b/AdvancedTodoList.Core/Specifications/ISpecification.cs @@ -8,18 +8,18 @@ namespace AdvancedTodoList.Core.Specifications; /// The type of entity. public interface ISpecification { - /// - /// Gets the criteria expression that defines the filtering conditions. - /// - Expression> Criteria { get; } + /// + /// Gets the criteria expression that defines the filtering conditions. + /// + Expression> Criteria { get; } - /// - /// Gets the list of include expressions specifying related entities to be included in the query results. - /// - List>> Includes { get; } + /// + /// Gets the list of include expressions specifying related entities to be included in the query results. + /// + List>> Includes { get; } - /// - /// Gets the list of include strings specifying related entities to be included in the query results. - /// - List IncludeStrings { get; } + /// + /// Gets the list of include strings specifying related entities to be included in the query results. + /// + List IncludeStrings { get; } } diff --git a/AdvancedTodoList.Core/Specifications/InvitationLinksSpecification.cs b/AdvancedTodoList.Core/Specifications/InvitationLinksSpecification.cs new file mode 100644 index 0000000..8e09a01 --- /dev/null +++ b/AdvancedTodoList.Core/Specifications/InvitationLinksSpecification.cs @@ -0,0 +1,31 @@ +using AdvancedTodoList.Core.Models.TodoLists; +using System.Linq.Expressions; + +namespace AdvancedTodoList.Core.Specifications; + +/// +/// Represents a specification that defines criteria for filtering invitation links. +/// +/// ID of the list invitation links of which will be obtained. +public class InvitationLinksSpecification(string todoListId) : ISpecification +{ + /// + /// Gets the ID of the to-do list to filter entities by. + /// + public string? TodoListId { get; } = todoListId; + + /// + /// Gets the criteria expression that defines the filtering conditions. + /// + public virtual Expression> Criteria => x => x.TodoListId == TodoListId; + + /// + /// Gets the list of include expressions specifying related entities to be included in the query results. + /// + public virtual List>> Includes { get; init; } = []; + + /// + /// Gets the list of include strings specifying related entities to be included in the query results. + /// + public virtual List IncludeStrings { get; init; } = []; +} diff --git a/AdvancedTodoList.Core/Specifications/MemberPermissionsSpecification.cs b/AdvancedTodoList.Core/Specifications/MemberPermissionsSpecification.cs new file mode 100644 index 0000000..02f6164 --- /dev/null +++ b/AdvancedTodoList.Core/Specifications/MemberPermissionsSpecification.cs @@ -0,0 +1,41 @@ +using AdvancedTodoList.Core.Models.TodoLists.Members; +using System.Linq.Expressions; + +namespace AdvancedTodoList.Core.Specifications; + +/// +/// Represents a specification for obtaining an aggregate containg the to-do list member +/// and his/her/their role, used in . +/// +/// ID of the to-do list. +/// ID of the user. +public class MemberPermissionsSpecification(string todoListId, string userId) : ISpecification +{ + /// + /// Gets the to-do list ID. + /// + public string TodoListId { get; } = todoListId; + /// + /// Gets the user ID. + /// + public string UserId { get; } = userId; + + /// + /// Gets the criteria expression that defines the filtering conditions. + /// + public Expression> Criteria => + x => x.TodoListId == TodoListId && x.UserId == UserId; + + /// + /// Gets the list of include expressions specifying a to-do list role to be included in the query results. + /// + public List>> Includes => + [ + x => x.Role + ]; + + /// + /// Gets the list of include strings specifying related entities to be included in the query results. + /// + public List IncludeStrings { get; } = []; +} diff --git a/AdvancedTodoList.Infrastructure/Specifications/TodoItemAggregateSpecification.cs b/AdvancedTodoList.Core/Specifications/Todo/TodoItemAggregateSpecification.cs similarity index 56% rename from AdvancedTodoList.Infrastructure/Specifications/TodoItemAggregateSpecification.cs rename to AdvancedTodoList.Core/Specifications/Todo/TodoItemAggregateSpecification.cs index bcca4a5..1218c9b 100644 --- a/AdvancedTodoList.Infrastructure/Specifications/TodoItemAggregateSpecification.cs +++ b/AdvancedTodoList.Core/Specifications/Todo/TodoItemAggregateSpecification.cs @@ -1,7 +1,7 @@ using AdvancedTodoList.Core.Models.TodoLists; using System.Linq.Expressions; -namespace AdvancedTodoList.Infrastructure.Specifications; +namespace AdvancedTodoList.Core.Specifications.Todo; /// /// Represents a specification used for obtaining a to-do list item aggregate. @@ -9,12 +9,12 @@ namespace AdvancedTodoList.Infrastructure.Specifications; /// The unique identifier of the to-do list item to obtain. public class TodoItemAggregateSpecification(int id) : GetByIdSpecification(id) { - /// - /// Gets the list of include expressions specifying an owner. - /// - public override List>> Includes => - [ - x => x.Owner, - x => x.Category - ]; + /// + /// Gets the list of include expressions specifying an owner. + /// + public override List>> Includes => + [ + x => x.Owner, + x => x.Category + ]; } diff --git a/AdvancedTodoList.Core/Specifications/Todo/TodoItemsSpecification.cs b/AdvancedTodoList.Core/Specifications/Todo/TodoItemsSpecification.cs new file mode 100644 index 0000000..54b223b --- /dev/null +++ b/AdvancedTodoList.Core/Specifications/Todo/TodoItemsSpecification.cs @@ -0,0 +1,59 @@ +using AdvancedTodoList.Core.Models.TodoLists; +using AdvancedTodoList.Core.Specifications.Filters; +using System.Linq.Expressions; + +namespace AdvancedTodoList.Core.Specifications.Todo; + +/// +/// Represents a specification that defines criteria for filtering to-do list members +/// and adds includes for users and roles models. +/// +/// ID of the to-do list items of which will be obtained. +/// Filter parameters. +public class TodoItemsSpecification(string todoListId, TodoItemsFilter filter) : + ISpecification +{ + /// + /// Gets the ID of the to-do list to filter items by. + /// + public string? TodoListId { get; } = todoListId; + /// + /// Gets the filter parameters. + /// + public TodoItemsFilter Filter { get; } = filter; + + /// + /// Gets the criteria expression that defines the filtering conditions. + /// + public Expression> Criteria => x => + // Filter by to-do list + x.TodoListId == TodoListId && + // Filter by name + (Filter.Name == null || x.Name.Contains(Filter.Name)) && + // Filter by owner's ID + (Filter.OwnerId == null || x.OwnerId == Filter.OwnerId) && + // Filter by states + (Filter.State == null || !Filter.State.Any() || Filter.State.Contains(x.State)) && + // Filter by categories + (Filter.CategoryId == null || !Filter.CategoryId.Any() || Filter.CategoryId.Contains(x.CategoryId)) && + // Filter by priority + (Filter.MinPriority == null || x.Priority >= Filter.MinPriority) && + (Filter.MaxPriority == null || x.Priority <= Filter.MaxPriority) && + // Filter by deadline date + (Filter.MinDeadlineDate == null || x.DeadlineDate >= Filter.MinDeadlineDate) && + (Filter.MaxDeadlineDate == null || x.DeadlineDate <= Filter.MaxDeadlineDate); + + /// + /// Gets the list of include expressions specifying related entities to be included in the query results. + /// + public List>> Includes => + [ + x => x.Owner, + x => x.Category + ]; + + /// + /// Gets the list of include strings specifying related entities to be included in the query results. + /// + public List IncludeStrings => []; +} diff --git a/AdvancedTodoList.Infrastructure/Specifications/TodoListAggregateSpecification.cs b/AdvancedTodoList.Core/Specifications/Todo/TodoListAggregateSpecification.cs similarity index 58% rename from AdvancedTodoList.Infrastructure/Specifications/TodoListAggregateSpecification.cs rename to AdvancedTodoList.Core/Specifications/Todo/TodoListAggregateSpecification.cs index 5aab3e5..d639fe9 100644 --- a/AdvancedTodoList.Infrastructure/Specifications/TodoListAggregateSpecification.cs +++ b/AdvancedTodoList.Core/Specifications/Todo/TodoListAggregateSpecification.cs @@ -1,7 +1,7 @@ using AdvancedTodoList.Core.Models.TodoLists; using System.Linq.Expressions; -namespace AdvancedTodoList.Infrastructure.Specifications; +namespace AdvancedTodoList.Core.Specifications.Todo; /// /// Represents a specification used for obtaining a to-do list aggregate. @@ -9,11 +9,11 @@ namespace AdvancedTodoList.Infrastructure.Specifications; /// The unique identifier of the to-do list to obtain. public class TodoListAggregateSpecification(string id) : GetByIdSpecification(id) { - /// - /// Gets the list of include expressions specifying an owner. - /// - public override List>> Includes => - [ - x => x.Owner - ]; + /// + /// Gets the list of include expressions specifying an owner. + /// + public override List>> Includes => + [ + x => x.Owner + ]; } diff --git a/AdvancedTodoList.Core/Specifications/Todo/TodoListDependantEntitiesSpecification.cs b/AdvancedTodoList.Core/Specifications/Todo/TodoListDependantEntitiesSpecification.cs new file mode 100644 index 0000000..e02f5d7 --- /dev/null +++ b/AdvancedTodoList.Core/Specifications/Todo/TodoListDependantEntitiesSpecification.cs @@ -0,0 +1,45 @@ +using AdvancedTodoList.Core.Models; +using AdvancedTodoList.Core.Models.TodoLists; +using System.Linq.Expressions; + +namespace AdvancedTodoList.Core.Specifications.Todo; + +/// +/// Represents a specification that defines criteria for filtering any todo-list dependant entities with name. +/// +/// ID of the list items of which will be obtained. +/// +/// Optional name to filter by. +/// Entries which have this substring in the name will be returned. +/// +public class TodoListDependantEntitiesSpecification(string todoListId, string? name = null) : ISpecification + where TEntity : ITodoListDependant, IHasName +{ + /// + /// Gets the ID of the to-do list to filter entities by. + /// + public string? TodoListId { get; } = todoListId; + /// + /// Gets the name to filter entities, which implement by. + /// + public string? Name { get; } = name; + + /// + /// Gets the criteria expression that defines the filtering conditions. + /// Filters only by the to-do list ID and the name when not overriden. + /// + public virtual Expression> Criteria => x => + x.TodoListId == TodoListId && (Name == null || x.Name.Contains(Name)); + + /// + /// Gets the list of include expressions specifying related entities to be included in the query results. + /// Is empty when not overriden. + /// + public virtual List>> Includes { get; init; } = []; + + /// + /// Gets the list of include strings specifying related entities to be included in the query results. + /// Is empty when not overriden. + /// + public virtual List IncludeStrings { get; init; } = []; +} diff --git a/AdvancedTodoList.Core/Specifications/Todo/TodoListMembersSpecification.cs b/AdvancedTodoList.Core/Specifications/Todo/TodoListMembersSpecification.cs new file mode 100644 index 0000000..3ddde03 --- /dev/null +++ b/AdvancedTodoList.Core/Specifications/Todo/TodoListMembersSpecification.cs @@ -0,0 +1,51 @@ +using AdvancedTodoList.Core.Models.TodoLists.Members; +using AdvancedTodoList.Core.Specifications.Filters; +using System.Linq.Expressions; + +namespace AdvancedTodoList.Core.Specifications.Todo; + +/// +/// Represents a specification that defines criteria for filtering to-do list members +/// and adds includes for users and roles models. +/// +/// ID of the to-do list items of which will be obtained. +/// Filter parameters. +public class TodoListMembersSpecification(string todoListId, TodoListMembersFilter filter) : ISpecification +{ + /// + /// Gets the ID of the to-do list to filter entities by. + /// + public string? TodoListId { get; } = todoListId; + /// + /// Gets the filter parameters. + /// + public TodoListMembersFilter Filter { get; } = filter; + + /// + /// Gets the criteria expression that defines the filtering conditions. + /// + public Expression> Criteria => + x => x.TodoListId == TodoListId && + // Filter by roles + (Filter.RoleId == null || !Filter.RoleId.Any() || Filter.RoleId.Contains(x.RoleId)) && + // Filter by user ID + (Filter.UserId == null || x.UserId == Filter.UserId) && + // Filter by username + (Filter.UserName == null || x.User!.UserName!.Contains(Filter.UserName)) && + // Filter by full name + (Filter.FullName == null || (x.User!.FirstName + ' ' + x.User!.LastName).Contains(Filter.FullName)); + + /// + /// Gets the list of include expressions specifying related entities to be included in the query results. + /// + public List>> Includes => + [ + x => x.Role, + x => x.User + ]; + + /// + /// Gets the list of include strings specifying related entities to be included in the query results. + /// + public List IncludeStrings => []; +} diff --git a/AdvancedTodoList.Core/Specifications/Todo/TodoListsSpecification.cs b/AdvancedTodoList.Core/Specifications/Todo/TodoListsSpecification.cs new file mode 100644 index 0000000..7d7b71c --- /dev/null +++ b/AdvancedTodoList.Core/Specifications/Todo/TodoListsSpecification.cs @@ -0,0 +1,45 @@ +using AdvancedTodoList.Core.Models.TodoLists; +using AdvancedTodoList.Core.Specifications.Filters; +using System.Linq.Expressions; + +namespace AdvancedTodoList.Core.Specifications.Todo; + +/// +/// Represents a specification that defines criteria for filtering to-do lists, +/// with the requirement that the user is a member of those lists. +/// +/// ID of the user. +/// Filter parameters. +public class TodoListsSpecification(string userId, TodoListsFilter filter) : ISpecification +{ + /// + /// Gets the ID of the user, to get lists where this user is a member. + /// + public string UserId { get; } = userId; + /// + /// Gets the filter parameters. + /// + public TodoListsFilter Filter { get; } = filter; + + /// + /// Gets the criteria expression that defines the filtering conditions. + /// + public Expression> Criteria => x => + // User is a member requirement + x.TodoListMembers.Any(m => m.UserId == UserId) && + // Filter by name + (Filter.Name == null || x.Name.Contains(Filter.Name)); + + /// + /// Gets the list of include expressions specifying related entities to be included in the query results. + /// + public List>> Includes => + [ + x => x.TodoListMembers + ]; + + /// + /// Gets the list of include strings specifying related entities to be included in the query results. + /// + public List IncludeStrings => []; +} diff --git a/AdvancedTodoList.Core/Validation/Auth/LogInDtoValidator.cs b/AdvancedTodoList.Core/Validation/Auth/LogInDtoValidator.cs deleted file mode 100644 index 83c5f3f..0000000 --- a/AdvancedTodoList.Core/Validation/Auth/LogInDtoValidator.cs +++ /dev/null @@ -1,17 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using FluentValidation; - -namespace AdvancedTodoList.Core.Validation.Auth; - -public class LogInDtoValidator : AbstractValidator -{ - public LogInDtoValidator() - { - RuleFor(x => x.UserNameOrEmail) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - RuleFor(x => x.Password) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } -} diff --git a/AdvancedTodoList.Core/Validation/Auth/LogOutDtoValidator.cs b/AdvancedTodoList.Core/Validation/Auth/LogOutDtoValidator.cs deleted file mode 100644 index 834e5dc..0000000 --- a/AdvancedTodoList.Core/Validation/Auth/LogOutDtoValidator.cs +++ /dev/null @@ -1,14 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using FluentValidation; - -namespace AdvancedTodoList.Core.Validation.Auth; - -public class LogOutDtoValidator : AbstractValidator -{ - public LogOutDtoValidator() - { - RuleFor(x => x.RefreshToken) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } -} diff --git a/AdvancedTodoList.Core/Validation/Auth/RefreshDtoValidator.cs b/AdvancedTodoList.Core/Validation/Auth/RefreshDtoValidator.cs deleted file mode 100644 index 0e85bb9..0000000 --- a/AdvancedTodoList.Core/Validation/Auth/RefreshDtoValidator.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using FluentValidation; - -namespace AdvancedTodoList.Core.Validation.Auth; - -public class RefreshDtoValidator : AbstractValidator -{ - public RefreshDtoValidator() - { - RuleFor(x => x.AccessToken) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - - RuleFor(x => x.RefreshToken) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } -} diff --git a/AdvancedTodoList.Core/Validation/Auth/RegisterDtoValidator.cs b/AdvancedTodoList.Core/Validation/Auth/RegisterDtoValidator.cs deleted file mode 100644 index 1475026..0000000 --- a/AdvancedTodoList.Core/Validation/Auth/RegisterDtoValidator.cs +++ /dev/null @@ -1,34 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using FluentValidation; -using FluentValidation.Validators; - -namespace AdvancedTodoList.Core.Validation.Auth; - -public class RegisterDtoValidator : AbstractValidator -{ - public RegisterDtoValidator() - { - RuleFor(x => x.Email) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - RuleFor(x => x.Email) - .EmailAddress(EmailValidationMode.AspNetCoreCompatible) - .WithErrorCode(ValidationErrorCodes.InvalidEmail); - - RuleFor(x => x.UserName) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - - RuleFor(x => x.FirstName) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - - RuleFor(x => x.LastName) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - - RuleFor(x => x.Password) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } -} diff --git a/AdvancedTodoList.Core/Validation/PaginationParametersValidator.cs b/AdvancedTodoList.Core/Validation/PaginationParametersValidator.cs deleted file mode 100644 index 1a9450a..0000000 --- a/AdvancedTodoList.Core/Validation/PaginationParametersValidator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using AdvancedTodoList.Core.Pagination; -using FluentValidation; - -namespace AdvancedTodoList.Core.Validation; - -public class PaginationParametersValidator : AbstractValidator -{ - public PaginationParametersValidator() - { - RuleFor(x => x.Page) - .GreaterThan(0) - .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange) - .WithMessage("Page number must be a positive number."); - - RuleFor(x => x.PageSize) - .GreaterThanOrEqualTo(PaginationParameters.MinPageSize) - .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange) - .WithMessage($"Page size must be within the range of {PaginationParameters.MinPageSize}-{PaginationParameters.MaxPageSize}"); - - RuleFor(x => x.PageSize) - .LessThanOrEqualTo(PaginationParameters.MaxPageSize) - .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange) - .WithMessage($"Page size must be within the range of {PaginationParameters.MinPageSize}-{PaginationParameters.MaxPageSize}"); - } -} diff --git a/AdvancedTodoList.Core/Validation/TodoItemCategoryCreateDtoValidator.cs b/AdvancedTodoList.Core/Validation/TodoItemCategoryCreateDtoValidator.cs deleted file mode 100644 index a46c7da..0000000 --- a/AdvancedTodoList.Core/Validation/TodoItemCategoryCreateDtoValidator.cs +++ /dev/null @@ -1,21 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.TodoLists; -using FluentValidation; - -namespace AdvancedTodoList.Core.Validation; - -public class TodoItemCategoryCreateDtoValidator : AbstractValidator -{ - public TodoItemCategoryCreateDtoValidator() - { - // Name is required - RuleFor(x => x.Name) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - - // Name should not be too long - RuleFor(x => x.Name) - .MaximumLength(TodoItemCategory.NameMaxLength) - .WithErrorCode(ValidationErrorCodes.PropertyTooLong); - } -} diff --git a/AdvancedTodoList.Core/Validation/TodoItemCreateDtoValidator.cs b/AdvancedTodoList.Core/Validation/TodoItemCreateDtoValidator.cs deleted file mode 100644 index 3d3ebe0..0000000 --- a/AdvancedTodoList.Core/Validation/TodoItemCreateDtoValidator.cs +++ /dev/null @@ -1,49 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.TodoLists; -using FluentValidation; - -namespace AdvancedTodoList.Core.Validation; - -/// -/// Validator class for -/// -public class TodoItemCreateDtoValidator : AbstractValidator -{ - public TodoItemCreateDtoValidator() - { - // Name is required - RuleFor(x => x.Name) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - - // Name should not be too long - RuleFor(x => x.Name) - .MaximumLength(TodoItem.NameMaxLength) - .WithErrorCode(ValidationErrorCodes.PropertyTooLong); - - // Description is not null - RuleFor(x => x.Description) - .NotNull() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - - // Description should not be too long - RuleFor(x => x.Description) - .MaximumLength(TodoItem.DescriptionMaxLength) - .WithErrorCode(ValidationErrorCodes.PropertyTooLong); - - // Deadline date should be after the current date - RuleFor(x => x.DeadlineDate) - .GreaterThan(DateTime.UtcNow) - .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange); - - // Priority must be in a specific range - RuleFor(x => x.Priority) - .GreaterThanOrEqualTo(TodoItem.MinPriority) - .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange) - .WithMessage($"Priority must be within the range from {TodoItem.MinPriority} to {TodoItem.MaxPriority}"); - RuleFor(x => x.Priority) - .LessThanOrEqualTo(TodoItem.MaxPriority) - .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange) - .WithMessage($"Priority must be within the range from {TodoItem.MinPriority} to {TodoItem.MaxPriority}"); - } -} diff --git a/AdvancedTodoList.Core/Validation/TodoItemUpdateStateDtoValidator.cs b/AdvancedTodoList.Core/Validation/TodoItemUpdateStateDtoValidator.cs deleted file mode 100644 index a97368a..0000000 --- a/AdvancedTodoList.Core/Validation/TodoItemUpdateStateDtoValidator.cs +++ /dev/null @@ -1,16 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.TodoLists; -using FluentValidation; - -namespace AdvancedTodoList.Core.Validation; - -public class TodoItemUpdateStateDtoValidator : AbstractValidator -{ - public TodoItemUpdateStateDtoValidator() - { - RuleFor(x => x.State) - .Must(s => s >= TodoItemState.Active && s <= TodoItemState.Skipped) - .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange) - .WithMessage(x => $"{(int)x.State} is invalid value for {{PropertyName}}"); - } -} diff --git a/AdvancedTodoList.Core/Validation/TodoListCreateDtoValidator.cs b/AdvancedTodoList.Core/Validation/TodoListCreateDtoValidator.cs deleted file mode 100644 index 6b21acd..0000000 --- a/AdvancedTodoList.Core/Validation/TodoListCreateDtoValidator.cs +++ /dev/null @@ -1,31 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.TodoLists; -using FluentValidation; - -namespace AdvancedTodoList.Core.Validation; - -public class TodoListCreateDtoValidator : AbstractValidator -{ - public TodoListCreateDtoValidator() - { - // Name is required - RuleFor(x => x.Name) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - - // Name should not be too long - RuleFor(x => x.Name) - .MaximumLength(TodoList.NameMaxLength) - .WithErrorCode(ValidationErrorCodes.PropertyTooLong); - - // Description is not null - RuleFor(x => x.Description) - .NotNull() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - - // Description should not be too long - RuleFor(x => x.Description) - .MaximumLength(TodoList.DescriptionMaxLength) - .WithErrorCode(ValidationErrorCodes.PropertyTooLong); - } -} diff --git a/AdvancedTodoList.Core/Validation/TodoListMemberAddDtoValidator.cs b/AdvancedTodoList.Core/Validation/TodoListMemberAddDtoValidator.cs deleted file mode 100644 index 69bc9e5..0000000 --- a/AdvancedTodoList.Core/Validation/TodoListMemberAddDtoValidator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.Auth; -using AdvancedTodoList.Core.Services; -using FluentValidation; - -namespace AdvancedTodoList.Core.Validation; - -/// -/// Validator class for -/// -public class TodoListMemberAddDtoValidator : AbstractValidator -{ - public TodoListMemberAddDtoValidator(IEntityExistenceChecker existenceChecker) - { - RuleFor(x => x.UserId) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - - RuleFor(x => x.UserId) - .MustAsync(async (userId, _) => - { - return await existenceChecker.ExistsAsync(userId); - }) - .WithErrorCode(ValidationErrorCodes.InvalidForeignKey) - .WithMessage("User not found."); - } -} diff --git a/AdvancedTodoList.Core/Validation/TodoListRoleCreateDtoValidator.cs b/AdvancedTodoList.Core/Validation/TodoListRoleCreateDtoValidator.cs deleted file mode 100644 index e71e158..0000000 --- a/AdvancedTodoList.Core/Validation/TodoListRoleCreateDtoValidator.cs +++ /dev/null @@ -1,25 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.TodoLists.Members; -using FluentValidation; - -namespace AdvancedTodoList.Core.Validation; - -/// -/// Validator class for -/// -public class TodoListRoleCreateDtoValidator : AbstractValidator -{ - public TodoListRoleCreateDtoValidator() - { - RuleFor(x => x.Name) - .NotEmpty() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - RuleFor(x => x.Name) - .MaximumLength(TodoListRole.NameMaxLength) - .WithErrorCode(ValidationErrorCodes.PropertyTooLong); - - RuleFor(x => x.Permissions) - .NotNull() - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } -} diff --git a/AdvancedTodoList.Core/Validation/ValidationErrorCodes.cs b/AdvancedTodoList.Core/Validation/ValidationErrorCodes.cs deleted file mode 100644 index 52e5c05..0000000 --- a/AdvancedTodoList.Core/Validation/ValidationErrorCodes.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace AdvancedTodoList.Core.Validation; - -/// -/// Class that contains validation error codes. -/// -public static class ValidationErrorCodes -{ - /// - /// Required property is null or empty. - /// - public const string PropertyRequired = "100"; - /// - /// Length of the property exceeds maximum possible value. - /// - public const string PropertyTooLong = "200"; - /// - /// Value of the property is out of range. - /// - public const string PropertyOutOfRange = "300"; - /// - /// Property is invalid foreign key. - /// - public const string InvalidForeignKey = "400"; - /// - /// Property is invalid email. - /// - public const string InvalidEmail = "500"; -} diff --git a/AdvancedTodoList.Infrastructure/AdvancedTodoList.Infrastructure.csproj b/AdvancedTodoList.Infrastructure/AdvancedTodoList.Infrastructure.csproj index e8b71c0..ac79dc0 100644 --- a/AdvancedTodoList.Infrastructure/AdvancedTodoList.Infrastructure.csproj +++ b/AdvancedTodoList.Infrastructure/AdvancedTodoList.Infrastructure.csproj @@ -11,17 +11,18 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + diff --git a/AdvancedTodoList.Infrastructure/Data/ApplicationDbContext.cs b/AdvancedTodoList.Infrastructure/Data/ApplicationDbContext.cs index db4ede9..4d125c4 100644 --- a/AdvancedTodoList.Infrastructure/Data/ApplicationDbContext.cs +++ b/AdvancedTodoList.Infrastructure/Data/ApplicationDbContext.cs @@ -7,24 +7,24 @@ namespace AdvancedTodoList.Infrastructure.Data; public class ApplicationDbContext(DbContextOptions options) : - IdentityDbContext(options) + IdentityDbContext(options) { - public DbSet TodoLists { get; set; } - public DbSet TodoItems { get; set; } - public DbSet TodoItemCategories { get; set; } - public DbSet InvitationLinks { get; set; } - public DbSet TodoListsMembers { get; set; } - public DbSet TodoListRoles { get; set; } - public DbSet UserRefreshTokens { get; set; } + public DbSet TodoLists { get; set; } + public DbSet TodoItems { get; set; } + public DbSet TodoItemCategories { get; set; } + public DbSet InvitationLinks { get; set; } + public DbSet TodoListsMembers { get; set; } + public DbSet TodoListRoles { get; set; } + public DbSet UserRefreshTokens { get; set; } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - base.OnModelCreating(modelBuilder); + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); - modelBuilder.Entity() - .ComplexProperty(x => x.Permissions); - modelBuilder.Entity() - .HasIndex(x => new { x.UserId, x.TodoListId }) - .IsUnique(true); - } + modelBuilder.Entity() + .ComplexProperty(x => x.Permissions); + modelBuilder.Entity() + .HasIndex(x => new { x.UserId, x.TodoListId }) + .IsUnique(true); + } } diff --git a/AdvancedTodoList.Infrastructure/Extensions/QueryableSpecificationExtensions.cs b/AdvancedTodoList.Infrastructure/Extensions/QueryableSpecificationExtensions.cs new file mode 100644 index 0000000..9f2aae7 --- /dev/null +++ b/AdvancedTodoList.Infrastructure/Extensions/QueryableSpecificationExtensions.cs @@ -0,0 +1,23 @@ +using AdvancedTodoList.Core.Specifications; +using Microsoft.EntityFrameworkCore; + +namespace AdvancedTodoList.Infrastructure.Extensions; + +public static class QueryableSpecificationExtensions +{ + public static IQueryable ApplySpecification(this IQueryable queryable, ISpecification specification) + where T : class + { + // Include all expression-based includes + var queryableResultWithIncludes = specification.Includes + .Aggregate(queryable, (current, include) => current.Include(include)); + + // Include string-based include statements + var secondaryResult = specification.IncludeStrings + .Aggregate(queryableResultWithIncludes, + (current, include) => current.Include(include)); + + // Apply criteria and return the result + return secondaryResult.Where(specification.Criteria); + } +} diff --git a/AdvancedTodoList.Infrastructure/Migrations/20240206171516_InitialSetup.cs b/AdvancedTodoList.Infrastructure/Migrations/20240206171516_InitialSetup.cs index b25b781..49aee93 100644 --- a/AdvancedTodoList.Infrastructure/Migrations/20240206171516_InitialSetup.cs +++ b/AdvancedTodoList.Infrastructure/Migrations/20240206171516_InitialSetup.cs @@ -7,259 +7,259 @@ namespace AdvancedTodoList.Infrastructure.Migrations; /// public partial class InitialSetup : Migration { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "bit", nullable: false), - PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), - SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), - ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), - PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), - TwoFactorEnabled = table.Column(type: "bit", nullable: false), - LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), - LockoutEnabled = table.Column(type: "bit", nullable: false), - AccessFailedCount = table.Column(type: "int", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + UserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + Email = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "nvarchar(256)", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "bit", nullable: false), + PasswordHash = table.Column(type: "nvarchar(max)", nullable: true), + SecurityStamp = table.Column(type: "nvarchar(max)", nullable: true), + ConcurrencyStamp = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumber = table.Column(type: "nvarchar(max)", nullable: true), + PhoneNumberConfirmed = table.Column(type: "bit", nullable: false), + TwoFactorEnabled = table.Column(type: "bit", nullable: false), + LockoutEnd = table.Column(type: "datetimeoffset", nullable: true), + LockoutEnabled = table.Column(type: "bit", nullable: false), + AccessFailedCount = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); - migrationBuilder.CreateTable( - name: "TodoLists", - columns: table => new - { - Id = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(max)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_TodoLists", x => x.Id); - }); + migrationBuilder.CreateTable( + name: "TodoLists", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoLists", x => x.Id); + }); - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - RoleId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + RoleId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - ClaimType = table.Column(type: "nvarchar(max)", nullable: true), - ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + ClaimType = table.Column(type: "nvarchar(max)", nullable: true), + ClaimValue = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), - ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), - UserId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + ProviderKey = table.Column(type: "nvarchar(450)", nullable: false), + ProviderDisplayName = table.Column(type: "nvarchar(max)", nullable: true), + UserId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "nvarchar(450)", nullable: false), - LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), - Name = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "nvarchar(450)", nullable: false), + LoginProvider = table.Column(type: "nvarchar(450)", nullable: false), + Name = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateTable( - name: "TodoItems", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(type: "nvarchar(max)", nullable: false), - TodoListId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_TodoItems", x => x.Id); - table.ForeignKey( - name: "FK_TodoItems_TodoLists_TodoListId", - column: x => x.TodoListId, - principalTable: "TodoLists", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "TodoItems", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false), + TodoListId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoItems", x => x.Id); + table.ForeignKey( + name: "FK_TodoItems_TodoLists_TodoListId", + column: x => x.TodoListId, + principalTable: "TodoLists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true, - filter: "[NormalizedName] IS NOT NULL"); + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true, + filter: "[NormalizedName] IS NOT NULL"); - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true, - filter: "[NormalizedUserName] IS NOT NULL"); + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true, + filter: "[NormalizedUserName] IS NOT NULL"); - migrationBuilder.CreateIndex( - name: "IX_TodoItems_TodoListId", - table: "TodoItems", - column: "TodoListId"); - } + migrationBuilder.CreateIndex( + name: "IX_TodoItems_TodoListId", + table: "TodoItems", + column: "TodoListId"); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); - migrationBuilder.DropTable( - name: "AspNetUserClaims"); + migrationBuilder.DropTable( + name: "AspNetUserClaims"); - migrationBuilder.DropTable( - name: "AspNetUserLogins"); + migrationBuilder.DropTable( + name: "AspNetUserLogins"); - migrationBuilder.DropTable( - name: "AspNetUserRoles"); + migrationBuilder.DropTable( + name: "AspNetUserRoles"); - migrationBuilder.DropTable( - name: "AspNetUserTokens"); + migrationBuilder.DropTable( + name: "AspNetUserTokens"); - migrationBuilder.DropTable( - name: "TodoItems"); + migrationBuilder.DropTable( + name: "TodoItems"); - migrationBuilder.DropTable( - name: "AspNetRoles"); + migrationBuilder.DropTable( + name: "AspNetRoles"); - migrationBuilder.DropTable( - name: "AspNetUsers"); + migrationBuilder.DropTable( + name: "AspNetUsers"); - migrationBuilder.DropTable( - name: "TodoLists"); - } + migrationBuilder.DropTable( + name: "TodoLists"); + } } diff --git a/AdvancedTodoList.Infrastructure/Migrations/20240209192939_BasicTodoList.cs b/AdvancedTodoList.Infrastructure/Migrations/20240209192939_BasicTodoList.cs index 63a593d..fa563a0 100644 --- a/AdvancedTodoList.Infrastructure/Migrations/20240209192939_BasicTodoList.cs +++ b/AdvancedTodoList.Infrastructure/Migrations/20240209192939_BasicTodoList.cs @@ -7,92 +7,92 @@ namespace AdvancedTodoList.Infrastructure.Migrations; /// public partial class BasicTodoList : Migration { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AlterColumn( - name: "Name", - table: "TodoLists", - type: "nvarchar(100)", - maxLength: 100, - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(max)"); + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Name", + table: "TodoLists", + type: "nvarchar(100)", + maxLength: 100, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); - migrationBuilder.AddColumn( - name: "Description", - table: "TodoLists", - type: "nvarchar(max)", - maxLength: 25000, - nullable: false, - defaultValue: ""); + migrationBuilder.AddColumn( + name: "Description", + table: "TodoLists", + type: "nvarchar(max)", + maxLength: 25000, + nullable: false, + defaultValue: ""); - migrationBuilder.AlterColumn( - name: "Name", - table: "TodoItems", - type: "nvarchar(100)", - maxLength: 100, - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(max)"); + migrationBuilder.AlterColumn( + name: "Name", + table: "TodoItems", + type: "nvarchar(100)", + maxLength: 100, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); - migrationBuilder.AddColumn( - name: "DeadlineDate", - table: "TodoItems", - type: "datetime2", - nullable: true); + migrationBuilder.AddColumn( + name: "DeadlineDate", + table: "TodoItems", + type: "datetime2", + nullable: true); - migrationBuilder.AddColumn( - name: "Description", - table: "TodoItems", - type: "nvarchar(max)", - maxLength: 10000, - nullable: false, - defaultValue: ""); + migrationBuilder.AddColumn( + name: "Description", + table: "TodoItems", + type: "nvarchar(max)", + maxLength: 10000, + nullable: false, + defaultValue: ""); - migrationBuilder.AddColumn( - name: "State", - table: "TodoItems", - type: "tinyint", - nullable: false, - defaultValue: (byte)0); - } + migrationBuilder.AddColumn( + name: "State", + table: "TodoItems", + type: "tinyint", + nullable: false, + defaultValue: (byte)0); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Description", - table: "TodoLists"); + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Description", + table: "TodoLists"); - migrationBuilder.DropColumn( - name: "DeadlineDate", - table: "TodoItems"); + migrationBuilder.DropColumn( + name: "DeadlineDate", + table: "TodoItems"); - migrationBuilder.DropColumn( - name: "Description", - table: "TodoItems"); + migrationBuilder.DropColumn( + name: "Description", + table: "TodoItems"); - migrationBuilder.DropColumn( - name: "State", - table: "TodoItems"); + migrationBuilder.DropColumn( + name: "State", + table: "TodoItems"); - migrationBuilder.AlterColumn( - name: "Name", - table: "TodoLists", - type: "nvarchar(max)", - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(100)", - oldMaxLength: 100); + migrationBuilder.AlterColumn( + name: "Name", + table: "TodoLists", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(100)", + oldMaxLength: 100); - migrationBuilder.AlterColumn( - name: "Name", - table: "TodoItems", - type: "nvarchar(max)", - nullable: false, - oldClrType: typeof(string), - oldType: "nvarchar(100)", - oldMaxLength: 100); - } + migrationBuilder.AlterColumn( + name: "Name", + table: "TodoItems", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(100)", + oldMaxLength: 100); + } } diff --git a/AdvancedTodoList.Infrastructure/Migrations/20240216131707_AuthImplementation.cs b/AdvancedTodoList.Infrastructure/Migrations/20240216131707_AuthImplementation.cs index 4f08f00..caa307b 100644 --- a/AdvancedTodoList.Infrastructure/Migrations/20240216131707_AuthImplementation.cs +++ b/AdvancedTodoList.Infrastructure/Migrations/20240216131707_AuthImplementation.cs @@ -7,64 +7,64 @@ namespace AdvancedTodoList.Infrastructure.Migrations; /// public partial class AuthImplementation : Migration { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "FirstName", - table: "AspNetUsers", - type: "nvarchar(100)", - maxLength: 100, - nullable: false, - defaultValue: ""); + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "FirstName", + table: "AspNetUsers", + type: "nvarchar(100)", + maxLength: 100, + nullable: false, + defaultValue: ""); - migrationBuilder.AddColumn( - name: "LastName", - table: "AspNetUsers", - type: "nvarchar(100)", - maxLength: 100, - nullable: false, - defaultValue: ""); + migrationBuilder.AddColumn( + name: "LastName", + table: "AspNetUsers", + type: "nvarchar(100)", + maxLength: 100, + nullable: false, + defaultValue: ""); - migrationBuilder.CreateTable( - name: "UserRefreshTokens", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - Token = table.Column(type: "nvarchar(max)", nullable: false), - ValidTo = table.Column(type: "datetime2", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_UserRefreshTokens", x => x.Id); - table.ForeignKey( - name: "FK_UserRefreshTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "UserRefreshTokens", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + Token = table.Column(type: "nvarchar(max)", nullable: false), + ValidTo = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_UserRefreshTokens", x => x.Id); + table.ForeignKey( + name: "FK_UserRefreshTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateIndex( - name: "IX_UserRefreshTokens_UserId", - table: "UserRefreshTokens", - column: "UserId"); - } + migrationBuilder.CreateIndex( + name: "IX_UserRefreshTokens_UserId", + table: "UserRefreshTokens", + column: "UserId"); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "UserRefreshTokens"); + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserRefreshTokens"); - migrationBuilder.DropColumn( - name: "FirstName", - table: "AspNetUsers"); + migrationBuilder.DropColumn( + name: "FirstName", + table: "AspNetUsers"); - migrationBuilder.DropColumn( - name: "LastName", - table: "AspNetUsers"); - } + migrationBuilder.DropColumn( + name: "LastName", + table: "AspNetUsers"); + } } diff --git a/AdvancedTodoList.Infrastructure/Migrations/20240302174336_AddMembersAndRolesEntities.cs b/AdvancedTodoList.Infrastructure/Migrations/20240302174336_AddMembersAndRolesEntities.cs index 9b518d3..9bc91cd 100644 --- a/AdvancedTodoList.Infrastructure/Migrations/20240302174336_AddMembersAndRolesEntities.cs +++ b/AdvancedTodoList.Infrastructure/Migrations/20240302174336_AddMembersAndRolesEntities.cs @@ -7,97 +7,97 @@ namespace AdvancedTodoList.Infrastructure.Migrations; /// public partial class AddMembersAndRolesEntities : Migration { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "TodoListRoles", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), - TodoListId = table.Column(type: "nvarchar(450)", nullable: false), - Permissions_AddItems = table.Column(type: "bit", nullable: false), - Permissions_AddMembers = table.Column(type: "bit", nullable: false), - Permissions_AssignRoles = table.Column(type: "bit", nullable: false), - Permissions_DeleteItems = table.Column(type: "bit", nullable: false), - Permissions_EditItems = table.Column(type: "bit", nullable: false), - Permissions_EditRoles = table.Column(type: "bit", nullable: false), - Permissions_RemoveMembers = table.Column(type: "bit", nullable: false), - Permissions_SetItemsState = table.Column(type: "bit", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_TodoListRoles", x => x.Id); - table.ForeignKey( - name: "FK_TodoListRoles_TodoLists_TodoListId", - column: x => x.TodoListId, - principalTable: "TodoLists", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "TodoListRoles", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + TodoListId = table.Column(type: "nvarchar(450)", nullable: false), + Permissions_AddItems = table.Column(type: "bit", nullable: false), + Permissions_AddMembers = table.Column(type: "bit", nullable: false), + Permissions_AssignRoles = table.Column(type: "bit", nullable: false), + Permissions_DeleteItems = table.Column(type: "bit", nullable: false), + Permissions_EditItems = table.Column(type: "bit", nullable: false), + Permissions_EditRoles = table.Column(type: "bit", nullable: false), + Permissions_RemoveMembers = table.Column(type: "bit", nullable: false), + Permissions_SetItemsState = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoListRoles", x => x.Id); + table.ForeignKey( + name: "FK_TodoListRoles_TodoLists_TodoListId", + column: x => x.TodoListId, + principalTable: "TodoLists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateTable( - name: "TodoListsMembers", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - UserId = table.Column(type: "nvarchar(450)", nullable: false), - TodoListId = table.Column(type: "nvarchar(450)", nullable: false), - RoleId = table.Column(type: "int", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_TodoListsMembers", x => x.Id); - table.ForeignKey( - name: "FK_TodoListsMembers_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_TodoListsMembers_TodoListRoles_RoleId", - column: x => x.RoleId, - principalTable: "TodoListRoles", - principalColumn: "Id"); - table.ForeignKey( - name: "FK_TodoListsMembers_TodoLists_TodoListId", - column: x => x.TodoListId, - principalTable: "TodoLists", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "TodoListsMembers", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + UserId = table.Column(type: "nvarchar(450)", nullable: false), + TodoListId = table.Column(type: "nvarchar(450)", nullable: false), + RoleId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoListsMembers", x => x.Id); + table.ForeignKey( + name: "FK_TodoListsMembers_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_TodoListsMembers_TodoListRoles_RoleId", + column: x => x.RoleId, + principalTable: "TodoListRoles", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_TodoListsMembers_TodoLists_TodoListId", + column: x => x.TodoListId, + principalTable: "TodoLists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateIndex( - name: "IX_TodoListRoles_TodoListId", - table: "TodoListRoles", - column: "TodoListId"); + migrationBuilder.CreateIndex( + name: "IX_TodoListRoles_TodoListId", + table: "TodoListRoles", + column: "TodoListId"); - migrationBuilder.CreateIndex( - name: "IX_TodoListsMembers_RoleId", - table: "TodoListsMembers", - column: "RoleId"); + migrationBuilder.CreateIndex( + name: "IX_TodoListsMembers_RoleId", + table: "TodoListsMembers", + column: "RoleId"); - migrationBuilder.CreateIndex( - name: "IX_TodoListsMembers_TodoListId", - table: "TodoListsMembers", - column: "TodoListId"); + migrationBuilder.CreateIndex( + name: "IX_TodoListsMembers_TodoListId", + table: "TodoListsMembers", + column: "TodoListId"); - migrationBuilder.CreateIndex( - name: "IX_TodoListsMembers_UserId", - table: "TodoListsMembers", - column: "UserId"); - } + migrationBuilder.CreateIndex( + name: "IX_TodoListsMembers_UserId", + table: "TodoListsMembers", + column: "UserId"); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "TodoListsMembers"); + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TodoListsMembers"); - migrationBuilder.DropTable( - name: "TodoListRoles"); - } + migrationBuilder.DropTable( + name: "TodoListRoles"); + } } diff --git a/AdvancedTodoList.Infrastructure/Migrations/20240302175508_AddRolePriorityColumn.cs b/AdvancedTodoList.Infrastructure/Migrations/20240302175508_AddRolePriorityColumn.cs index fd909f4..9992fbe 100644 --- a/AdvancedTodoList.Infrastructure/Migrations/20240302175508_AddRolePriorityColumn.cs +++ b/AdvancedTodoList.Infrastructure/Migrations/20240302175508_AddRolePriorityColumn.cs @@ -7,22 +7,22 @@ namespace AdvancedTodoList.Infrastructure.Migrations; /// public partial class AddRolePriorityColumn : Migration { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Priority", - table: "TodoListRoles", - type: "int", - nullable: false, - defaultValue: 0); - } + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Priority", + table: "TodoListRoles", + type: "int", + nullable: false, + defaultValue: 0); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Priority", - table: "TodoListRoles"); - } + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Priority", + table: "TodoListRoles"); + } } diff --git a/AdvancedTodoList.Infrastructure/Migrations/20240302182741_AddListsAndItemsOwners.cs b/AdvancedTodoList.Infrastructure/Migrations/20240302182741_AddListsAndItemsOwners.cs index ab867a3..a3b11d4 100644 --- a/AdvancedTodoList.Infrastructure/Migrations/20240302182741_AddListsAndItemsOwners.cs +++ b/AdvancedTodoList.Infrastructure/Migrations/20240302182741_AddListsAndItemsOwners.cs @@ -7,71 +7,71 @@ namespace AdvancedTodoList.Infrastructure.Migrations; /// public partial class AddListsAndItemsOwners : Migration { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "OwnerId", - table: "TodoLists", - type: "nvarchar(450)", - nullable: true); + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "OwnerId", + table: "TodoLists", + type: "nvarchar(450)", + nullable: true); - migrationBuilder.AddColumn( - name: "OwnerId", - table: "TodoItems", - type: "nvarchar(450)", - nullable: true); + migrationBuilder.AddColumn( + name: "OwnerId", + table: "TodoItems", + type: "nvarchar(450)", + nullable: true); - migrationBuilder.CreateIndex( - name: "IX_TodoLists_OwnerId", - table: "TodoLists", - column: "OwnerId"); + migrationBuilder.CreateIndex( + name: "IX_TodoLists_OwnerId", + table: "TodoLists", + column: "OwnerId"); - migrationBuilder.CreateIndex( - name: "IX_TodoItems_OwnerId", - table: "TodoItems", - column: "OwnerId"); + migrationBuilder.CreateIndex( + name: "IX_TodoItems_OwnerId", + table: "TodoItems", + column: "OwnerId"); - migrationBuilder.AddForeignKey( - name: "FK_TodoItems_AspNetUsers_OwnerId", - table: "TodoItems", - column: "OwnerId", - principalTable: "AspNetUsers", - principalColumn: "Id"); + migrationBuilder.AddForeignKey( + name: "FK_TodoItems_AspNetUsers_OwnerId", + table: "TodoItems", + column: "OwnerId", + principalTable: "AspNetUsers", + principalColumn: "Id"); - migrationBuilder.AddForeignKey( - name: "FK_TodoLists_AspNetUsers_OwnerId", - table: "TodoLists", - column: "OwnerId", - principalTable: "AspNetUsers", - principalColumn: "Id"); - } + migrationBuilder.AddForeignKey( + name: "FK_TodoLists_AspNetUsers_OwnerId", + table: "TodoLists", + column: "OwnerId", + principalTable: "AspNetUsers", + principalColumn: "Id"); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_TodoItems_AspNetUsers_OwnerId", - table: "TodoItems"); + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_TodoItems_AspNetUsers_OwnerId", + table: "TodoItems"); - migrationBuilder.DropForeignKey( - name: "FK_TodoLists_AspNetUsers_OwnerId", - table: "TodoLists"); + migrationBuilder.DropForeignKey( + name: "FK_TodoLists_AspNetUsers_OwnerId", + table: "TodoLists"); - migrationBuilder.DropIndex( - name: "IX_TodoLists_OwnerId", - table: "TodoLists"); + migrationBuilder.DropIndex( + name: "IX_TodoLists_OwnerId", + table: "TodoLists"); - migrationBuilder.DropIndex( - name: "IX_TodoItems_OwnerId", - table: "TodoItems"); + migrationBuilder.DropIndex( + name: "IX_TodoItems_OwnerId", + table: "TodoItems"); - migrationBuilder.DropColumn( - name: "OwnerId", - table: "TodoLists"); + migrationBuilder.DropColumn( + name: "OwnerId", + table: "TodoLists"); - migrationBuilder.DropColumn( - name: "OwnerId", - table: "TodoItems"); - } + migrationBuilder.DropColumn( + name: "OwnerId", + table: "TodoItems"); + } } diff --git a/AdvancedTodoList.Infrastructure/Migrations/20240308203118_TodoListMemberUniqueConstraint.cs b/AdvancedTodoList.Infrastructure/Migrations/20240308203118_TodoListMemberUniqueConstraint.cs index 0fabcde..0a62f68 100644 --- a/AdvancedTodoList.Infrastructure/Migrations/20240308203118_TodoListMemberUniqueConstraint.cs +++ b/AdvancedTodoList.Infrastructure/Migrations/20240308203118_TodoListMemberUniqueConstraint.cs @@ -1,36 +1,43 @@ using Microsoft.EntityFrameworkCore.Migrations; #nullable disable +#pragma warning disable IDE0079 +#pragma warning disable CA1861 +#pragma warning disable IDE0300 namespace AdvancedTodoList.Infrastructure.Migrations; /// public partial class TodoListMemberUniqueConstraint : Migration { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_TodoListsMembers_UserId", - table: "TodoListsMembers"); + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_TodoListsMembers_UserId", + table: "TodoListsMembers"); - migrationBuilder.CreateIndex( - name: "IX_TodoListsMembers_UserId_TodoListId", - table: "TodoListsMembers", - columns: new[] { "UserId", "TodoListId" }, - unique: true); - } + migrationBuilder.CreateIndex( + name: "IX_TodoListsMembers_UserId_TodoListId", + table: "TodoListsMembers", + columns: new[] { "UserId", "TodoListId" }, + unique: true); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropIndex( - name: "IX_TodoListsMembers_UserId_TodoListId", - table: "TodoListsMembers"); + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_TodoListsMembers_UserId_TodoListId", + table: "TodoListsMembers"); - migrationBuilder.CreateIndex( - name: "IX_TodoListsMembers_UserId", - table: "TodoListsMembers", - column: "UserId"); - } + migrationBuilder.CreateIndex( + name: "IX_TodoListsMembers_UserId", + table: "TodoListsMembers", + column: "UserId"); + } } + +#pragma warning restore CA1861 +#pragma warning restore IDE0300 +#pragma warning restore IDE0079 \ No newline at end of file diff --git a/AdvancedTodoList.Infrastructure/Migrations/20240313163140_AddPrioritiesAndCategories.cs b/AdvancedTodoList.Infrastructure/Migrations/20240313163140_AddPrioritiesAndCategories.cs index 5458227..ab8a889 100644 --- a/AdvancedTodoList.Infrastructure/Migrations/20240313163140_AddPrioritiesAndCategories.cs +++ b/AdvancedTodoList.Infrastructure/Migrations/20240313163140_AddPrioritiesAndCategories.cs @@ -7,91 +7,91 @@ namespace AdvancedTodoList.Infrastructure.Migrations; /// public partial class AddPrioritiesAndCategories : Migration { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Permissions_EditCategories", - table: "TodoListRoles", - type: "bit", - nullable: false, - defaultValue: false); + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Permissions_EditCategories", + table: "TodoListRoles", + type: "bit", + nullable: false, + defaultValue: false); - migrationBuilder.AddColumn( - name: "CategoryId", - table: "TodoItems", - type: "int", - nullable: true); + migrationBuilder.AddColumn( + name: "CategoryId", + table: "TodoItems", + type: "int", + nullable: true); - migrationBuilder.AddColumn( - name: "Priority", - table: "TodoItems", - type: "int", - nullable: false, - defaultValue: 0); + migrationBuilder.AddColumn( + name: "Priority", + table: "TodoItems", + type: "int", + nullable: false, + defaultValue: 0); - migrationBuilder.CreateTable( - name: "TodoItemCategories", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), - TodoListId = table.Column(type: "nvarchar(450)", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_TodoItemCategories", x => x.Id); - table.ForeignKey( - name: "FK_TodoItemCategories_TodoLists_TodoListId", - column: x => x.TodoListId, - principalTable: "TodoLists", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "TodoItemCategories", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + TodoListId = table.Column(type: "nvarchar(450)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoItemCategories", x => x.Id); + table.ForeignKey( + name: "FK_TodoItemCategories_TodoLists_TodoListId", + column: x => x.TodoListId, + principalTable: "TodoLists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateIndex( - name: "IX_TodoItems_CategoryId", - table: "TodoItems", - column: "CategoryId"); + migrationBuilder.CreateIndex( + name: "IX_TodoItems_CategoryId", + table: "TodoItems", + column: "CategoryId"); - migrationBuilder.CreateIndex( - name: "IX_TodoItemCategories_TodoListId", - table: "TodoItemCategories", - column: "TodoListId"); + migrationBuilder.CreateIndex( + name: "IX_TodoItemCategories_TodoListId", + table: "TodoItemCategories", + column: "TodoListId"); - migrationBuilder.AddForeignKey( - name: "FK_TodoItems_TodoItemCategories_CategoryId", - table: "TodoItems", - column: "CategoryId", - principalTable: "TodoItemCategories", - principalColumn: "Id"); - } + migrationBuilder.AddForeignKey( + name: "FK_TodoItems_TodoItemCategories_CategoryId", + table: "TodoItems", + column: "CategoryId", + principalTable: "TodoItemCategories", + principalColumn: "Id"); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_TodoItems_TodoItemCategories_CategoryId", - table: "TodoItems"); + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_TodoItems_TodoItemCategories_CategoryId", + table: "TodoItems"); - migrationBuilder.DropTable( - name: "TodoItemCategories"); + migrationBuilder.DropTable( + name: "TodoItemCategories"); - migrationBuilder.DropIndex( - name: "IX_TodoItems_CategoryId", - table: "TodoItems"); + migrationBuilder.DropIndex( + name: "IX_TodoItems_CategoryId", + table: "TodoItems"); - migrationBuilder.DropColumn( - name: "Permissions_EditCategories", - table: "TodoListRoles"); + migrationBuilder.DropColumn( + name: "Permissions_EditCategories", + table: "TodoListRoles"); - migrationBuilder.DropColumn( - name: "CategoryId", - table: "TodoItems"); + migrationBuilder.DropColumn( + name: "CategoryId", + table: "TodoItems"); - migrationBuilder.DropColumn( - name: "Priority", - table: "TodoItems"); - } + migrationBuilder.DropColumn( + name: "Priority", + table: "TodoItems"); + } } diff --git a/AdvancedTodoList.Infrastructure/Migrations/20240330120758_AddInvitationLinks.cs b/AdvancedTodoList.Infrastructure/Migrations/20240330120758_AddInvitationLinks.cs index b2cd0f3..1be1018 100644 --- a/AdvancedTodoList.Infrastructure/Migrations/20240330120758_AddInvitationLinks.cs +++ b/AdvancedTodoList.Infrastructure/Migrations/20240330120758_AddInvitationLinks.cs @@ -7,51 +7,51 @@ namespace AdvancedTodoList.Infrastructure.Migrations; /// public partial class AddInvitationLinks : Migration { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.AddColumn( - name: "Permissions_ManageInvitationLinks", - table: "TodoListRoles", - type: "bit", - nullable: false, - defaultValue: false); + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Permissions_ManageInvitationLinks", + table: "TodoListRoles", + type: "bit", + nullable: false, + defaultValue: false); - migrationBuilder.CreateTable( - name: "InvitationLinks", - columns: table => new - { - Id = table.Column(type: "int", nullable: false) - .Annotation("SqlServer:Identity", "1, 1"), - TodoListId = table.Column(type: "nvarchar(450)", nullable: false), - Value = table.Column(type: "nvarchar(max)", nullable: false), - ValidTo = table.Column(type: "datetime2", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_InvitationLinks", x => x.Id); - table.ForeignKey( - name: "FK_InvitationLinks_TodoLists_TodoListId", - column: x => x.TodoListId, - principalTable: "TodoLists", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "InvitationLinks", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + TodoListId = table.Column(type: "nvarchar(450)", nullable: false), + Value = table.Column(type: "nvarchar(max)", nullable: false), + ValidTo = table.Column(type: "datetime2", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_InvitationLinks", x => x.Id); + table.ForeignKey( + name: "FK_InvitationLinks_TodoLists_TodoListId", + column: x => x.TodoListId, + principalTable: "TodoLists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateIndex( - name: "IX_InvitationLinks_TodoListId", - table: "InvitationLinks", - column: "TodoListId"); - } + migrationBuilder.CreateIndex( + name: "IX_InvitationLinks_TodoListId", + table: "InvitationLinks", + column: "TodoListId"); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "InvitationLinks"); + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "InvitationLinks"); - migrationBuilder.DropColumn( - name: "Permissions_ManageInvitationLinks", - table: "TodoListRoles"); - } + migrationBuilder.DropColumn( + name: "Permissions_ManageInvitationLinks", + table: "TodoListRoles"); + } } diff --git a/AdvancedTodoList.Infrastructure/Pagination/QueryablePaginationExtensions.cs b/AdvancedTodoList.Infrastructure/Pagination/QueryablePaginationExtensions.cs index 8f9a3c0..f23afb9 100644 --- a/AdvancedTodoList.Infrastructure/Pagination/QueryablePaginationExtensions.cs +++ b/AdvancedTodoList.Infrastructure/Pagination/QueryablePaginationExtensions.cs @@ -6,43 +6,43 @@ namespace AdvancedTodoList.Infrastructure.Pagination; public static class QueryablePaginationExtensions { - /// - /// Applies pagination to an . - /// - /// The type of elements in the queryable. - /// The source to apply pagination to. - /// object specifying pagination parameters. - /// An representing the paginated subset of the source queryable. - public static IQueryable ApplyPagination( - this IQueryable queryable, PaginationParameters properties) - { - return queryable - .Skip((properties.Page - 1) * properties.PageSize) - .Take(properties.PageSize); - } + /// + /// Applies pagination to an . + /// + /// The type of elements in the queryable. + /// The source to apply pagination to. + /// object specifying pagination parameters. + /// An representing the paginated subset of the source queryable. + public static IQueryable ApplyPagination( + this IQueryable queryable, PaginationParameters properties) + { + return queryable + .Skip((properties.Page - 1) * properties.PageSize) + .Take(properties.PageSize); + } - /// - /// Applies pagination and returns the results as a page of . - /// - /// The type of elements in the source queryable. - /// The type to which the results are projected. - /// The source to apply pagination to. - /// An optional object specifying pagination parameters. If , default parameters are used. - /// A page of items of type from the queryable. - public static async Task> ToPageAsync( - this IQueryable queryable, PaginationParameters? parameters = null) - { - // If properties is null, then use default - parameters ??= new(); + /// + /// Applies pagination and returns the results as a page of . + /// + /// The type of elements in the source queryable. + /// The type to which the results are projected. + /// The source to apply pagination to. + /// An optional object specifying pagination parameters. If , default parameters are used. + /// A page of items of type from the queryable. + public static async Task> ToPageAsync( + this IQueryable queryable, PaginationParameters? parameters = null) + { + // If properties is null, then use default + parameters ??= new(); - // Get items at the page - var items = await queryable - .ApplyPagination(parameters) - .ProjectToType() - .ToListAsync(); - // Count all items - int count = await queryable.CountAsync(); + // Get items at the page + var items = await queryable + .ApplyPagination(parameters) + .ProjectToType() + .ToListAsync(); + // Count all items + int count = await queryable.CountAsync(); - return new Page(items, parameters.Page, parameters.PageSize, count); - } + return new Page(items, parameters.Page, parameters.PageSize, count); + } } diff --git a/AdvancedTodoList.Infrastructure/Repositories/BaseRepository.cs b/AdvancedTodoList.Infrastructure/Repositories/BaseRepository.cs index a912e12..7babc1c 100644 --- a/AdvancedTodoList.Infrastructure/Repositories/BaseRepository.cs +++ b/AdvancedTodoList.Infrastructure/Repositories/BaseRepository.cs @@ -3,8 +3,8 @@ using AdvancedTodoList.Core.Repositories; using AdvancedTodoList.Core.Specifications; using AdvancedTodoList.Infrastructure.Data; +using AdvancedTodoList.Infrastructure.Extensions; using AdvancedTodoList.Infrastructure.Pagination; -using AdvancedTodoList.Infrastructure.Specifications; using Mapster; using Microsoft.EntityFrameworkCore; @@ -16,91 +16,91 @@ namespace AdvancedTodoList.Infrastructure.Repositories; /// The type of entity. /// The type of entity's primary key. public abstract class BaseRepository(ApplicationDbContext dbContext) - : IRepository - where TEntity : class, IEntity - where TKey : IEquatable + : IRepository + where TEntity : class, IEntity + where TKey : IEquatable { - protected readonly ApplicationDbContext DbContext = dbContext; + protected readonly ApplicationDbContext DbContext = dbContext; - /// - /// Asynchronously adds a new entity to the repository. - /// - /// The entity to add. - public async Task AddAsync(TEntity entity) - { - DbContext.Add(entity); - await DbContext.SaveChangesAsync(); - } + /// + /// Asynchronously adds a new entity to the repository. + /// + /// The entity to add. + public async Task AddAsync(TEntity entity) + { + DbContext.Add(entity); + await DbContext.SaveChangesAsync(); + } - /// - /// Asynchronously retrieves an entity by its primary key. - /// - /// The primary key of the entity to retrieve. - /// - /// A task that represents an asynchronous operation and the entity if found; - /// otherwise, . - /// - public async Task GetByIdAsync(TKey id) - { - return await DbContext.Set().FindAsync(id); - } + /// + /// Asynchronously retrieves an entity by its primary key. + /// + /// The primary key of the entity to retrieve. + /// + /// A task that represents an asynchronous operation and the entity if found; + /// otherwise, . + /// + public async Task GetByIdAsync(TKey id) + { + return await DbContext.Set().FindAsync(id); + } - /// - /// Asynchronously retrieves an aggregate by applying a specification. - /// - /// Type of the aggregate to retrieve. - /// Specification to apply. - /// - /// A task that represents an asynchronous operation and the aggregate if found; - /// otherwise, . - /// - public async Task GetAggregateAsync(ISpecification specification) - where TDto : class - { - var entity = await DbContext.Set() - .ApplySpecification(specification) - .FirstOrDefaultAsync(); - if (entity == null) - return null; + /// + /// Asynchronously retrieves an aggregate by applying a specification. + /// + /// Type of the aggregate to retrieve. + /// Specification to apply. + /// + /// A task that represents an asynchronous operation and the aggregate if found; + /// otherwise, . + /// + public async Task GetAggregateAsync(ISpecification specification) + where TDto : class + { + var entity = await DbContext.Set() + .ApplySpecification(specification) + .FirstOrDefaultAsync(); + if (entity == null) + return null; - return entity.Adapt(); - } + return entity.Adapt(); + } - /// - /// Gets a page with entities mapped to type asynchronously. - /// - /// Returned type of items on the page. - /// Pagination parameters. - /// Specification used for entities retrival. - /// - /// A task that represents an asynchronous operation and a page with entities mapped - /// to type. - /// - public async Task> GetPageAsync(PaginationParameters paginationParameters, ISpecification specification) - { - return await DbContext.Set() - .AsNoTracking() - .ApplySpecification(specification) - .ToPageAsync(paginationParameters); - } + /// + /// Gets a page with entities mapped to type asynchronously. + /// + /// Returned type of items on the page. + /// Pagination parameters. + /// Specification used for entities retrival. + /// + /// A task that represents an asynchronous operation and a page with entities mapped + /// to type. + /// + public async Task> GetPageAsync(PaginationParameters paginationParameters, ISpecification specification) + { + return await DbContext.Set() + .AsNoTracking() + .ApplySpecification(specification) + .ToPageAsync(paginationParameters); + } - /// - /// Asynchronously updates an existing entity in the repository. - /// - /// The entity to update. - public async Task UpdateAsync(TEntity entity) - { - DbContext.Entry(entity).State = EntityState.Modified; - await DbContext.SaveChangesAsync(); - } + /// + /// Asynchronously updates an existing entity in the repository. + /// + /// The entity to update. + public async Task UpdateAsync(TEntity entity) + { + DbContext.Entry(entity).State = EntityState.Modified; + await DbContext.SaveChangesAsync(); + } - /// - /// Asynchronously deletes an entity from the repository. - /// - /// The entity to delete. - public async Task DeleteAsync(TEntity entity) - { - DbContext.Entry(entity).State = EntityState.Deleted; - await DbContext.SaveChangesAsync(); - } + /// + /// Asynchronously deletes an entity from the repository. + /// + /// The entity to delete. + public async Task DeleteAsync(TEntity entity) + { + DbContext.Entry(entity).State = EntityState.Deleted; + await DbContext.SaveChangesAsync(); + } } diff --git a/AdvancedTodoList.Infrastructure/Repositories/EntityExistenceChecker.cs b/AdvancedTodoList.Infrastructure/Repositories/EntityExistenceChecker.cs new file mode 100644 index 0000000..e6ffd6e --- /dev/null +++ b/AdvancedTodoList.Infrastructure/Repositories/EntityExistenceChecker.cs @@ -0,0 +1,33 @@ +using AdvancedTodoList.Core.Models; +using AdvancedTodoList.Core.Repositories; +using AdvancedTodoList.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace AdvancedTodoList.Infrastructure.Repositories; + +/// +/// A service that checks whether an entity with an ID exists. +/// +public class EntityExistenceChecker(ApplicationDbContext dbContext) : IEntityExistenceChecker +{ + private readonly ApplicationDbContext _dbContext = dbContext; + + /// + /// Asynchronously checks whether an entity of type with an ID + /// of type exists. + /// + /// Type of the entity. + /// Type which ID of the entity has. + /// ID of the entity which existence is checked. + /// + /// A task representing the asynchronous operation. The task result contains + /// if entity with the given ID exists; otherwise + /// . + /// + public async Task ExistsAsync(TId id) + where TEntity : class, IEntity + where TId : IEquatable + { + return await _dbContext.Set().AnyAsync(x => x.Id.Equals(id)); + } +} diff --git a/AdvancedTodoList.Infrastructure/Repositories/InvitationLinksRepository.cs b/AdvancedTodoList.Infrastructure/Repositories/InvitationLinksRepository.cs index 7581f75..e0ed55b 100644 --- a/AdvancedTodoList.Infrastructure/Repositories/InvitationLinksRepository.cs +++ b/AdvancedTodoList.Infrastructure/Repositories/InvitationLinksRepository.cs @@ -9,19 +9,19 @@ namespace AdvancedTodoList.Infrastructure.Repositories; /// Represents a repository for CRUD operations on invitation links. /// public class InvitationLinksRepository(ApplicationDbContext dbContext) : - BaseRepository(dbContext), IInvitationLinksRepository + BaseRepository(dbContext), IInvitationLinksRepository { - /// - /// Finds an invintation link by its value asynchronously. - /// - /// Value of the link. - /// - /// A task representing asynchronous operation which contains requested link. - /// - public Task FindAsync(string linkValue) - { - return DbContext.InvitationLinks - .Where(x => x.Value == linkValue) - .FirstOrDefaultAsync(); - } + /// + /// Finds an invintation link by its value asynchronously. + /// + /// Value of the link. + /// + /// A task representing asynchronous operation which contains requested link. + /// + public Task FindAsync(string linkValue) + { + return DbContext.InvitationLinks + .Where(x => x.Value == linkValue) + .FirstOrDefaultAsync(); + } } diff --git a/AdvancedTodoList.Infrastructure/Repositories/TodoItemCategoriesRepository.cs b/AdvancedTodoList.Infrastructure/Repositories/TodoItemCategoriesRepository.cs index edd9cb0..c597a72 100644 --- a/AdvancedTodoList.Infrastructure/Repositories/TodoItemCategoriesRepository.cs +++ b/AdvancedTodoList.Infrastructure/Repositories/TodoItemCategoriesRepository.cs @@ -7,6 +7,6 @@ namespace AdvancedTodoList.Infrastructure.Repositories; /// Represents a repository for CRUD operations on to-do items categories. /// public class TodoItemCategoriesRepository(ApplicationDbContext dbContext) : - BaseRepository(dbContext) + BaseRepository(dbContext) { } diff --git a/AdvancedTodoList.Infrastructure/Repositories/TodoItemsRepository.cs b/AdvancedTodoList.Infrastructure/Repositories/TodoItemsRepository.cs index 1ac406e..02391fd 100644 --- a/AdvancedTodoList.Infrastructure/Repositories/TodoItemsRepository.cs +++ b/AdvancedTodoList.Infrastructure/Repositories/TodoItemsRepository.cs @@ -7,6 +7,6 @@ namespace AdvancedTodoList.Infrastructure.Repositories; /// Represents a repository for CRUD operations on to-do items. /// public class TodoItemsRepository(ApplicationDbContext dbContext) : - BaseRepository(dbContext) + BaseRepository(dbContext) { } diff --git a/AdvancedTodoList.Infrastructure/Repositories/TodoListMembersRepository.cs b/AdvancedTodoList.Infrastructure/Repositories/TodoListMembersRepository.cs index a487081..cd0bf4b 100644 --- a/AdvancedTodoList.Infrastructure/Repositories/TodoListMembersRepository.cs +++ b/AdvancedTodoList.Infrastructure/Repositories/TodoListMembersRepository.cs @@ -9,20 +9,20 @@ namespace AdvancedTodoList.Infrastructure.Repositories; /// Represents a repository for CRUD operations on to-do lists members. /// public class TodoListMembersRepository(ApplicationDbContext dbContext) : - BaseRepository(dbContext), ITodoListMembersRepository + BaseRepository(dbContext), ITodoListMembersRepository { - /// - /// Finds a to-do list member by to-do list ID and user's ID asynchronously. - /// - /// ID of the user. - /// ID of the to-do list. - /// - /// Found to-do list member, or if it was not found. - /// - public Task FindAsync(string todoListId, string userId) - { - return DbContext.TodoListsMembers - .Where(x => x.TodoListId == todoListId && x.UserId == userId) - .FirstOrDefaultAsync(); - } + /// + /// Finds a to-do list member by to-do list ID and user's ID asynchronously. + /// + /// ID of the user. + /// ID of the to-do list. + /// + /// Found to-do list member, or if it was not found. + /// + public Task FindAsync(string todoListId, string userId) + { + return DbContext.TodoListsMembers + .Where(x => x.TodoListId == todoListId && x.UserId == userId) + .FirstOrDefaultAsync(); + } } diff --git a/AdvancedTodoList.Infrastructure/Repositories/TodoListRepository.cs b/AdvancedTodoList.Infrastructure/Repositories/TodoListRepository.cs index f37095c..e182b8b 100644 --- a/AdvancedTodoList.Infrastructure/Repositories/TodoListRepository.cs +++ b/AdvancedTodoList.Infrastructure/Repositories/TodoListRepository.cs @@ -7,6 +7,6 @@ namespace AdvancedTodoList.Infrastructure.Repositories; /// Represents a repository for CRUD operations on to-do lists. /// public class TodoListRepository(ApplicationDbContext dbContext) : - BaseRepository(dbContext) + BaseRepository(dbContext) { } diff --git a/AdvancedTodoList.Infrastructure/Repositories/TodoListRolesRepository.cs b/AdvancedTodoList.Infrastructure/Repositories/TodoListRolesRepository.cs index 8eed428..a69d7ca 100644 --- a/AdvancedTodoList.Infrastructure/Repositories/TodoListRolesRepository.cs +++ b/AdvancedTodoList.Infrastructure/Repositories/TodoListRolesRepository.cs @@ -7,6 +7,6 @@ namespace AdvancedTodoList.Infrastructure.Repositories; /// Represents a repository for CRUD operations on to-do lists roles. /// public class TodoListRolesRepository(ApplicationDbContext dbContext) : - BaseRepository(dbContext) + BaseRepository(dbContext) { } diff --git a/AdvancedTodoList.Infrastructure/Repositories/UnitOfWork.cs b/AdvancedTodoList.Infrastructure/Repositories/UnitOfWork.cs index 1b634a1..23cec26 100644 --- a/AdvancedTodoList.Infrastructure/Repositories/UnitOfWork.cs +++ b/AdvancedTodoList.Infrastructure/Repositories/UnitOfWork.cs @@ -9,45 +9,45 @@ namespace AdvancedTodoList.Infrastructure.Repositories; /// public class UnitOfWork(ApplicationDbContext dbContext) : IUnitOfWork { - private readonly ApplicationDbContext _dbContext = dbContext; - private IDbContextTransaction? _transaction; - - /// - /// Begins a new transaction asynchronously. - /// - /// A task representing the asynchronous operation. - /// Transaction has already begun. - public async Task BeginTransactionAsync() - { - if (_transaction != null) - throw new InvalidOperationException("Multiple transactions are not supported."); - - _transaction = await _dbContext.Database.BeginTransactionAsync(); - } - - /// - /// Commits the transaction asynchronously. - /// - /// A task representing the asynchronous operation. - public async Task CommitAsync() - { - if (_transaction == null) - throw new InvalidOperationException("Transaction has not begun."); - - await _transaction.CommitAsync(); - _transaction = null; - } - - /// - /// Rolls back the transaction asynchronously. - /// - /// A task representing the asynchronous operation. - public async Task RollbackAsync() - { - if (_transaction == null) - throw new InvalidOperationException("Transaction has not begun."); - - await _transaction.RollbackAsync(); - _transaction = null; - } + private readonly ApplicationDbContext _dbContext = dbContext; + private IDbContextTransaction? _transaction; + + /// + /// Begins a new transaction asynchronously. + /// + /// A task representing the asynchronous operation. + /// Transaction has already begun. + public async Task BeginTransactionAsync() + { + if (_transaction != null) + throw new InvalidOperationException("Multiple transactions are not supported."); + + _transaction = await _dbContext.Database.BeginTransactionAsync(); + } + + /// + /// Commits the transaction asynchronously. + /// + /// A task representing the asynchronous operation. + public async Task CommitAsync() + { + if (_transaction == null) + throw new InvalidOperationException("Transaction has not begun."); + + await _transaction.CommitAsync(); + _transaction = null; + } + + /// + /// Rolls back the transaction asynchronously. + /// + /// A task representing the asynchronous operation. + public async Task RollbackAsync() + { + if (_transaction == null) + throw new InvalidOperationException("Transaction has not begun."); + + await _transaction.RollbackAsync(); + _transaction = null; + } } diff --git a/AdvancedTodoList.Infrastructure/Repositories/UserRefreshTokensRepository.cs b/AdvancedTodoList.Infrastructure/Repositories/UserRefreshTokensRepository.cs index f5069a1..81ae2aa 100644 --- a/AdvancedTodoList.Infrastructure/Repositories/UserRefreshTokensRepository.cs +++ b/AdvancedTodoList.Infrastructure/Repositories/UserRefreshTokensRepository.cs @@ -9,20 +9,20 @@ namespace AdvancedTodoList.Infrastructure.Repositories; /// Represents a repository for perfoming CRUD operations on users refresh tokens. /// public class UserRefreshTokensRepository(ApplicationDbContext dbContext) : - BaseRepository(dbContext), IUserRefreshTokensRepository + BaseRepository(dbContext), IUserRefreshTokensRepository { - /// - /// Finds user's refresh token by user's ID and refresh token's value asynchronously. - /// - /// User's unique identifier - /// Value of the refresh token. - /// - /// Found user's refresh token, or if it was not found. - /// - public async Task FindAsync(string userId, string refreshToken) - { - return await DbContext.UserRefreshTokens - .Where(x => x.UserId == userId && x.Token == refreshToken) - .FirstOrDefaultAsync(); - } + /// + /// Finds user's refresh token by user's ID and refresh token's value asynchronously. + /// + /// User's unique identifier + /// Value of the refresh token. + /// + /// Found user's refresh token, or if it was not found. + /// + public async Task FindAsync(string userId, string refreshToken) + { + return await DbContext.UserRefreshTokens + .Where(x => x.UserId == userId && x.Token == refreshToken) + .FirstOrDefaultAsync(); + } } diff --git a/AdvancedTodoList.Infrastructure/Services/Auth/AccessTokensService.cs b/AdvancedTodoList.Infrastructure/Services/Auth/AccessTokensService.cs deleted file mode 100644 index 4d1ecd5..0000000 --- a/AdvancedTodoList.Infrastructure/Services/Auth/AccessTokensService.cs +++ /dev/null @@ -1,83 +0,0 @@ -using AdvancedTodoList.Core.Models.Auth; -using AdvancedTodoList.Core.Options; -using AdvancedTodoList.Core.Services.Auth; -using Microsoft.Extensions.Options; -using Microsoft.IdentityModel.Tokens; -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using System.Text; - -namespace AdvancedTodoList.Infrastructure.Services.Auth; - -/// -/// A service that manages access tokens. -/// -public class AccessTokensService(IOptions options) : IAccessTokensService -{ - private readonly AccessTokenOptions _tokenOptions = options.Value; - - /// - /// Generates an access token for the user. - /// - /// User which will receive an access token. - /// - /// A string that represents an access token. - /// - public string GenerateAccessToken(ApplicationUser user) - { - SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_tokenOptions.SecretKey)); - - List claims = - [ - new(JwtRegisteredClaimNames.Sub, user.Id), - new(JwtRegisteredClaimNames.Email, user.Email!), - new(JwtRegisteredClaimNames.UniqueName, user.UserName!), - new(JwtRegisteredClaimNames.GivenName, user.FirstName), - new(JwtRegisteredClaimNames.FamilyName, user.LastName), - ]; - - JwtSecurityToken token = new( - issuer: _tokenOptions.ValidIssuer, - audience: _tokenOptions.ValidAudience, - expires: DateTime.UtcNow.AddSeconds(_tokenOptions.ExpirationSeconds), - claims: claims, - signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) - ); - string accessToken = new JwtSecurityTokenHandler().WriteToken(token); - - return accessToken; - } - - /// - /// Validates an access token without checking expiration time and then returns - /// ID of the user stored in it asynchronously. - /// - /// A string that represents an access token. - /// - /// A user ID retrieved from the access token or , if validation failed. - /// - public async Task GetUserIdFromExpiredTokenAsync(string accessToken) - { - // Validate the access token - string key = _tokenOptions.SecretKey; - JwtSecurityTokenHandler tokenHandler = new(); - TokenValidationParameters validationParameters = new() - { - ValidateLifetime = false, // Ignore expiration time - ValidIssuer = _tokenOptions.ValidIssuer, - ValidAudience = _tokenOptions.ValidAudience, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) - }; - var validationResult = await tokenHandler.ValidateTokenAsync( - accessToken, validationParameters); - // Return null if validation failed - if (!validationResult.IsValid) return null; - - // Get ID of the user - JwtSecurityToken jwtToken = (JwtSecurityToken)validationResult.SecurityToken; - var subClaim = jwtToken.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Sub); - if (subClaim == null) return null; - - return subClaim.Value; - } -} diff --git a/AdvancedTodoList.Infrastructure/Services/Auth/AuthService.cs b/AdvancedTodoList.Infrastructure/Services/Auth/AuthService.cs deleted file mode 100644 index 5ec68d6..0000000 --- a/AdvancedTodoList.Infrastructure/Services/Auth/AuthService.cs +++ /dev/null @@ -1,156 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.Auth; -using AdvancedTodoList.Core.Services.Auth; -using Microsoft.AspNetCore.Identity; - -namespace AdvancedTodoList.Infrastructure.Services.Auth; - -/// -/// Service that performs authentication operations. -/// -public class AuthService( - IAccessTokensService accessTokensService, - IRefreshTokensService refreshTokensService, - UserManager userManager) - : IAuthService -{ - private readonly IAccessTokensService _accessTokensService = accessTokensService; - private readonly IRefreshTokensService _refreshTokensService = refreshTokensService; - private readonly UserManager _userManager = userManager; - - /// - /// Logs a user in asynchronously. - /// - /// Data required for logging in. - /// - /// Returns a task representing the asynchronous operation, containing a - /// response with access and refresh tokens or null if authorization fails. - /// - public async Task LogInAsync(LogInDto logInDto) - { - // Try to find a user - ApplicationUser? user; - // Try to find by email - if (logInDto.UserNameOrEmail.Contains('@')) - { - user = await _userManager.FindByEmailAsync(logInDto.UserNameOrEmail); - } - // Try to find by username - else - { - user = await _userManager.FindByNameAsync(logInDto.UserNameOrEmail); - } - - // Invalid username/email - fail - if (user == null) return null; - - // Invalid password - fail - if (!await _userManager.CheckPasswordAsync(user, logInDto.Password)) - return null; - - // Generate a new refresh token for the user - string? refreshToken = await _refreshTokensService.GenerateAsync(user.Id); - if (refreshToken == null) return null; - - // Generate an access token - string accessToken = _accessTokensService.GenerateAccessToken(user); - - // Return both tokens - return new(accessToken, refreshToken); - } - - /// - /// Logs a user out asynchronously by revoking a refresh token. - /// - /// ID of the caller. - /// Data required for logging out. - /// - /// Returns a task representing the asynchronous operation, - /// indicating the success or failure of the operation. - /// - public async Task LogOutAsync(string userId, LogOutDto logOutDto) - { - // Revoke the token - return await _refreshTokensService.RevokeAsync(userId, logOutDto.RefreshToken); - } - - /// - /// Refreshes the access token asynchronously. - /// - /// Data required for token refresh. - /// - /// Returns a task representing the asynchronous operation, - /// containing a response with access and refresh tokens or null if authorization fails. - /// - public async Task RefreshAsync(RefreshDto refreshDto) - { - // Try to get a user ID - string? userId = await _accessTokensService - .GetUserIdFromExpiredTokenAsync(refreshDto.AccessToken); - if (userId == null) return null; - - // Validate the refresh token - if (!await _refreshTokensService.ValidateAsync(userId, refreshDto.RefreshToken)) - return null; - - // Find the user - ApplicationUser? user = await _userManager.FindByIdAsync(userId); - // User doesn't exist - return null - if (user == null) return null; - - // Generate access token - string accessToken = _accessTokensService.GenerateAccessToken(user); - - // Return tokens - return new LogInResponse(accessToken, refreshDto.RefreshToken); - } - - /// - /// Registers a new user asynchronously. - /// - /// Data required for user registration. - /// - /// Returns a task representing the asynchronous operation, containing the registration result. - /// - public async Task RegisterAsync(RegisterDto registerDto) - { - List errors = []; - // Check if email is available - if (await _userManager.FindByEmailAsync(registerDto.Email) != null) - errors.Add(new("Email", "Email is already taken.")); - // Check if username is available - if (await _userManager.FindByNameAsync(registerDto.UserName) != null) - errors.Add(new("UserName", "Username is already taken.")); - - if (errors.Count > 0) return RegisterResult.Failure(errors); - - // Try to register the user - ApplicationUser user = new() - { - FirstName = registerDto.FirstName, - LastName = registerDto.LastName, - Email = registerDto.Email, - UserName = registerDto.UserName - }; - var result = await _userManager.CreateAsync(user, registerDto.Password); - - // Return the result - return result.Succeeded ? - RegisterResult.Success() : - RegisterResult.Failure(IdentityErrorsToRegisterErrors(result.Errors)); - } - - private static IEnumerable IdentityErrorsToRegisterErrors(IEnumerable identityErrors) - { - foreach (var error in identityErrors) - { - // Determine the property which caused an error - string property = "$"; - if (error.Code.Contains("Password")) property = "Password"; - else if (error.Code.Contains("UserName")) property = "UserName"; - else if (error.Code.Contains("Email")) property = "Email"; - - yield return new(property, error.Description); - } - } -} diff --git a/AdvancedTodoList.Infrastructure/Services/Auth/PermissionsChecker.cs b/AdvancedTodoList.Infrastructure/Services/Auth/PermissionsChecker.cs deleted file mode 100644 index fd65c04..0000000 --- a/AdvancedTodoList.Infrastructure/Services/Auth/PermissionsChecker.cs +++ /dev/null @@ -1,102 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models; -using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Repositories; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Services.Auth; -using AdvancedTodoList.Infrastructure.Specifications; - -namespace AdvancedTodoList.Infrastructure.Services.Auth; - -/// -/// Service that checks user's permissions. -/// -public class PermissionsChecker(ITodoListMembersRepository membersRepository) : IPermissionsChecker -{ - private readonly ITodoListMembersRepository _membersRepository = membersRepository; - - /// - /// Asynchronously checks whether the user is a member of the to-do list with - /// specified ID. - /// - /// To-do list context. - /// - /// if user is a member of the list; otherwise . - /// - public async Task IsMemberOfListAsync(TodoListContext context) - { - return await _membersRepository.FindAsync(context.TodoListId, context.CallerId) != null; - } - - /// - /// Asynchronously checks whether the user is a member of the to-do list and - /// has a permission defined by the funciton . - /// - /// To-do list context. - /// Function that should return if user has required permission. - /// - /// if user is a member of the list and has required permission; - /// otherwise . - /// - public async Task HasPermissionAsync(TodoListContext context, Func permission) - { - MemberPermissionsSpecification specification = new(context.TodoListId, context.CallerId); - var member = await _membersRepository.GetAggregateAsync(specification); - // User is not a member or has no role - return false - if (member == null || member.Role == null) return false; - - return permission(member.Role.Permissions); - } - - /// - /// Asynchronously checks whether the user can touch an entity. - /// - /// - /// This method firstly checks whether implements - /// interface and if yes, checks if the user is the owner of the entity and is a member of the to-do list; - /// otherwise the method checks if user has the permission defined by the function . - /// - /// Type of the entity. - /// Type of the unique identifier used by the entity. - /// To-do list context. - /// ID of the entity. - /// Function that should return if user has required permission. - /// - /// if user is either an owner of the entity and a member of a to-do list, - /// or he/she/they has permission defined by ; otherwise . - /// - Task IPermissionsChecker.CanTouchEntityAsync(TodoListContext context, TEntity entity, Func permission) - { - // If user owns entity only check if he/she/they is member - if (entity is IHasOwner ownedEntity && ownedEntity.OwnerId == context.CallerId) - { - return IsMemberOfListAsync(context); - } - // Otherwise check if user has permission - return HasPermissionAsync(context, permission); - } - - /// - /// Asynchronously checks whether the user has a permission to change the role - /// with the priority of . - /// - /// To-do list context. - /// ID of the role. - /// Function that should return if user has required permission. - /// - /// if user has and highest role priority than - /// the ; otherwise . - /// - public async Task HasPermissionOverRoleAsync(TodoListContext context, int rolePriority, Func permission) - { - MemberPermissionsSpecification specification = new(context.TodoListId, context.CallerId); - var member = await _membersRepository.GetAggregateAsync(specification); - - // User is not a member, has no role or permission - return false - if (member == null || member.Role == null || !permission(member.Role.Permissions)) - return false; - - // Check if user has a higher priority - return member.Role.Priority < rolePriority; - } -} diff --git a/AdvancedTodoList.Infrastructure/Services/Auth/RefreshTokensService.cs b/AdvancedTodoList.Infrastructure/Services/Auth/RefreshTokensService.cs deleted file mode 100644 index fca58e4..0000000 --- a/AdvancedTodoList.Infrastructure/Services/Auth/RefreshTokensService.cs +++ /dev/null @@ -1,87 +0,0 @@ -using AdvancedTodoList.Core.Models.Auth; -using AdvancedTodoList.Core.Options; -using AdvancedTodoList.Core.Repositories; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Services.Auth; -using Microsoft.Extensions.Options; -using System.Security.Cryptography; - -namespace AdvancedTodoList.Infrastructure.Services.Auth; - -public class RefreshTokensService( - IUserRefreshTokensRepository repository, - IOptions options, - IEntityExistenceChecker existenceChecker) : IRefreshTokensService -{ - private readonly IUserRefreshTokensRepository _repository = repository; - private readonly RefreshTokenOptions _tokenOptions = options.Value; - private readonly IEntityExistenceChecker _existenceChecker = existenceChecker; - - /// - /// Generates a refresh token for the user and saves it asynchronously. - /// - /// ID of the user who will receive the token. - /// - /// A string that represents a refresh token or if user does not exist. - /// - public async Task GenerateAsync(string userId) - { - // Check if user exists - if (!await _existenceChecker.ExistsAsync(userId)) - return null; - - // Generate a token - using RandomNumberGenerator rng = RandomNumberGenerator.Create(); - byte[] refreshTokenBytes = new byte[_tokenOptions.Size]; - rng.GetBytes(refreshTokenBytes); - - // Set the expiration date and assign token to the user - int expirationDays = _tokenOptions.ExpirationDays; - UserRefreshToken tokenEntity = new() - { - Token = Convert.ToBase64String(refreshTokenBytes), - UserId = userId, - ValidTo = DateTime.UtcNow.AddDays(expirationDays) - }; - - // Save the token - await _repository.AddAsync(tokenEntity); - - // Return the token value - return tokenEntity.Token; - } - - /// - /// Revokes the refresh token of the user asynchronously - /// - /// ID of the user whose token is being revoked. - /// Value of the token to be revoked. - /// - /// on success; otherwise. - /// - public async Task RevokeAsync(string userId, string token) - { - var tokenEntity = await _repository.FindAsync(userId, token); - if (tokenEntity == null) return false; - - // Delete the token - await _repository.DeleteAsync(tokenEntity); - - return true; - } - - /// - /// Checks whether refresh token is valid asynchronously. - /// - /// ID of the user whose token is being validated. - /// Value of the token to be validated. - /// - /// if token is valid; - /// otherwise. - /// - public async Task ValidateAsync(string userId, string token) - { - var tokenEntity = await _repository.FindAsync(userId, token); - return tokenEntity != null && DateTime.UtcNow < tokenEntity.ValidTo; - } -} diff --git a/AdvancedTodoList.Infrastructure/Services/EntityExistenceChecker.cs b/AdvancedTodoList.Infrastructure/Services/EntityExistenceChecker.cs deleted file mode 100644 index 0aefb2a..0000000 --- a/AdvancedTodoList.Infrastructure/Services/EntityExistenceChecker.cs +++ /dev/null @@ -1,33 +0,0 @@ -using AdvancedTodoList.Core.Models; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Infrastructure.Data; -using Microsoft.EntityFrameworkCore; - -namespace AdvancedTodoList.Infrastructure.Services; - -/// -/// A service that checks whether an entity with an ID exists. -/// -public class EntityExistenceChecker(ApplicationDbContext dbContext) : IEntityExistenceChecker -{ - private readonly ApplicationDbContext _dbContext = dbContext; - - /// - /// Asynchronously checks whether an entity of type with an ID - /// of type exists. - /// - /// Type of the entity. - /// Type which ID of the entity has. - /// ID of the entity which existence is checked. - /// - /// A task representing the asynchronous operation. The task result contains - /// if entity with the given ID exists; otherwise - /// . - /// - public async Task ExistsAsync(TId id) - where TEntity : class, IEntity - where TId : IEquatable - { - return await _dbContext.Set().AnyAsync(x => x.Id.Equals(id)); - } -} diff --git a/AdvancedTodoList.Infrastructure/Services/InvitationLinksService.cs b/AdvancedTodoList.Infrastructure/Services/InvitationLinksService.cs deleted file mode 100644 index 1987d0b..0000000 --- a/AdvancedTodoList.Infrastructure/Services/InvitationLinksService.cs +++ /dev/null @@ -1,156 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Options; -using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Repositories; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Services.Auth; -using AdvancedTodoList.Infrastructure.Specifications; -using Mapster; -using Microsoft.Extensions.Options; -using System.Security.Cryptography; - -namespace AdvancedTodoList.Infrastructure.Services; - -/// -/// A service that manages invitation links. -/// -public class InvitationLinksService( - IPermissionsChecker permissionsChecker, - IInvitationLinksRepository linksRepository, - ITodoListMembersRepository membersRepository, - IEntityExistenceChecker existenceChecker, - IOptions options - ) : IInvitationLinksService -{ - private readonly IPermissionsChecker _permissionsChecker = permissionsChecker; - private readonly IInvitationLinksRepository _linksRepository = linksRepository; - private readonly ITodoListMembersRepository _membersRepository = membersRepository; - private readonly IEntityExistenceChecker _existenceChecker = existenceChecker; - private readonly InvitationLinkOptions _options = options.Value; - - /// - /// Joins the caller to the to-do list by invitation list asynchronously. - /// - /// ID of the caller. - /// Invitation link to use. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public async Task JoinAsync(string callerId, string invitationLinkValue) - { - // Try to find a link - InvitationLink? invitationLink = await _linksRepository.FindAsync(invitationLinkValue); - if (invitationLink == null) return new(JoinByInvitationLinkStatus.NotFound); - - // Check if link is still valid - if (invitationLink.ValidTo < DateTime.UtcNow) return new(JoinByInvitationLinkStatus.Expired); - - // Check if user is not already a member - var member = await _membersRepository.FindAsync(invitationLink.TodoListId, callerId); - if (member != null) return new(JoinByInvitationLinkStatus.UserIsAlreadyMember); - - // Add a new member - TodoListMember newMember = new() - { - TodoListId = invitationLink.TodoListId, - UserId = callerId - }; - await _membersRepository.AddAsync(newMember); - var dto = newMember.Adapt(); - return new(JoinByInvitationLinkStatus.Success, dto); - } - - /// - /// Gets invitation links associated with the to-do list asynchronously. - /// - /// To-do list context of the operation. - /// Pagination parameters to use. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public async Task>> GetInvitationLinksAsync(TodoListContext context, - PaginationParameters parameters) - { - // Check if to-do list exists - if (!await _existenceChecker.ExistsAsync(context.TodoListId)) - return new(ServiceResponseStatus.NotFound); - - // Check if user has the permission to see links - if (!await _permissionsChecker.HasPermissionAsync( - context, x => x.ManageInvitationLinks || x.AddMembers)) - return new(ServiceResponseStatus.Forbidden); - - // Get the requested page - InvitationLinksSpecification specification = new(context.TodoListId); - var page = await _linksRepository - .GetPageAsync(parameters, specification); - // Return the page - return new(ServiceResponseStatus.Success, page); - } - - /// - /// Creates an invitation link associated to the to-do list asynchronously. - /// - /// To-do list context. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public async Task> CreateAsync(TodoListContext context) - { - // Check if to-do list exists - if (!await _existenceChecker.ExistsAsync(context.TodoListId)) - return new(ServiceResponseStatus.NotFound); - // Check if the user has the permission - if (!await _permissionsChecker.HasPermissionAsync(context, x => x.AddMembers)) - return new(ServiceResponseStatus.Forbidden); - - // Generate the link - using RandomNumberGenerator rng = RandomNumberGenerator.Create(); - byte[] valueBytes = new byte[_options.Size]; - rng.GetBytes(valueBytes); - - // Create the link - InvitationLink link = new() - { - TodoListId = context.TodoListId, - Value = Convert.ToBase64String(valueBytes), - ValidTo = DateTime.UtcNow.AddDays(_options.ExpirationDays) - }; - // Save it - await _linksRepository.AddAsync(link); - // Map it to DTO and return - var result = link.Adapt(); - return new(ServiceResponseStatus.Success, result); - } - - /// - /// Deletes an invitation link associted to the to-do list asynchronously. - /// - /// To-do list context. - /// ID of the link. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public async Task DeleteAsync(TodoListContext context, int linkId) - { - // Get the model of a link - var link = await _linksRepository.GetByIdAsync(linkId); - // Check if it's valid - if (link == null || link.TodoListId != context.TodoListId) - return ServiceResponseStatus.NotFound; - // Check if user has the permission - if (!await _permissionsChecker.HasPermissionAsync(context, x => x.ManageInvitationLinks)) - return ServiceResponseStatus.Forbidden; - - // Delete the link - await _linksRepository.DeleteAsync(link); - - return ServiceResponseStatus.Success; - } -} diff --git a/AdvancedTodoList.Infrastructure/Services/TodoItemCategoriesService.cs b/AdvancedTodoList.Infrastructure/Services/TodoItemCategoriesService.cs deleted file mode 100644 index ad404df..0000000 --- a/AdvancedTodoList.Infrastructure/Services/TodoItemCategoriesService.cs +++ /dev/null @@ -1,111 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Repositories; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Infrastructure.Specifications; - -namespace AdvancedTodoList.Infrastructure.Services; - -/// -/// A service that manages to-do list items categories. -/// -public class TodoItemCategoriesService( - ITodoListDependantEntitiesService helperService, - IRepository categoriesRepository - ) : ITodoItemCategoriesService -{ - private readonly ITodoListDependantEntitiesService _helperService = helperService; - private readonly IRepository _categoriesRepository = categoriesRepository; - - /// - /// Asynchronously checks whether the category ID is valid for the given context. - /// - /// - /// ID is considered as valid. - /// - /// To-do list context. - /// ID of the category to validate. - /// - /// A task representing the asynchronous operation. - /// if ID is valid, otherwise. - /// - public async Task IsCategoryValidForContext(TodoListContext context, int? categoryId) - { - // Return true, null category is allowed for any context - if (categoryId == null) return true; - - var category = await _categoriesRepository.GetByIdAsync(categoryId.Value); - return category != null && category.TodoListId == context.TodoListId; - } - - /// - /// Retrieves a page of to-do list items categories of the list with the specified ID. - /// - /// To-do list context. - /// Pagination parameters to use. - /// Optional name to filter categories by. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task>> GetCategoriesOfListAsync( - TodoListContext context, PaginationParameters paginationParameters, string? name = null) - { - TodoListDependantEntitiesSpecification specification = new(context.TodoListId, name); - return _helperService.GetPageAsync(context, specification, paginationParameters); - } - - /// - /// Retrieves a to-do list item category by its ID asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to retrieve. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task> GetByIdAsync(TodoListContext context, int categoryId) - { - return _helperService.GetByIdAsync(context, categoryId); - } - - /// - /// Creates a new to-do list item category asynchronously. - /// - /// To-do list context. - /// The DTO containing information for creating the to-do list item. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task> CreateAsync(TodoListContext context, TodoItemCategoryCreateDto dto) - { - return _helperService.CreateAsync(context, dto, x => x.EditCategories); - } - - /// - /// Edits a to-do list item category asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to edit. - /// The DTO containing information for editing the to-do list item. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task EditAsync(TodoListContext context, int categoryId, TodoItemCategoryCreateDto dto) - { - return _helperService.UpdateAsync(context, categoryId, dto, x => x.EditCategories); - } - - /// - /// Deletes a to-do list item category asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to delete. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - - public Task DeleteAsync(TodoListContext context, int categoryId) - { - return _helperService.DeleteAsync(context, categoryId, x => x.EditCategories); - } -} diff --git a/AdvancedTodoList.Infrastructure/Services/TodoItemsService.cs b/AdvancedTodoList.Infrastructure/Services/TodoItemsService.cs deleted file mode 100644 index 25e9bf0..0000000 --- a/AdvancedTodoList.Infrastructure/Services/TodoItemsService.cs +++ /dev/null @@ -1,144 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Repositories; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Services.Auth; -using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; - -namespace AdvancedTodoList.Infrastructure.Services; - -/// -/// A service that manages to-do lists items. -/// -public class TodoItemsService( - ITodoListDependantEntitiesService helperService, - IRepository repository, - ITodoItemCategoriesService categoriesService, - IPermissionsChecker permissionsChecker - ) : ITodoItemsService -{ - private readonly ITodoListDependantEntitiesService _helperService = helperService; - private readonly IRepository _repository = repository; - private readonly ITodoItemCategoriesService _categoriesService = categoriesService; - private readonly IPermissionsChecker _permissionsChecker = permissionsChecker; - - /// - /// Retrieves a page of to-do list items of the list with the specified ID. - /// - /// To-do list context. - /// Pagination parameters to use. - /// Filter parameters to apply. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task>> GetItemsOfListAsync(TodoListContext context, - PaginationParameters paginationParameters, TodoItemsFilter filter) - { - TodoItemsSpecification specification = new(context.TodoListId, filter); - return _helperService.GetPageAsync(context, specification, paginationParameters); - } - - /// - /// Retrieves a to-do list item by its ID asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to retrieve. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public async Task> GetByIdAsync(TodoListContext context, int itemId) - { - // Check is user is a member of the to-do list - if (!await _permissionsChecker.IsMemberOfListAsync(context)) - return new(ServiceResponseStatus.Forbidden); - - TodoItemAggregateSpecification specification = new(itemId); - // Get the aggregate - var dto = await _repository.GetAggregateAsync(specification); - // Check if it's valid - if (dto == null || dto.TodoListId != context.TodoListId) - return new(ServiceResponseStatus.NotFound); - - // Return requested DTO - return new(ServiceResponseStatus.Success, dto); - } - - /// - /// Creates a new to-do list item asynchronously. - /// - /// To-do list context. - /// The DTO containing information for creating the to-do list item. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public async Task CreateAsync(TodoListContext context, TodoItemCreateDto dto) - { - // Validate the category - if (!await _categoriesService.IsCategoryValidForContext(context, dto.CategoryId)) - return new(TodoItemsServiceStatus.InvalidCategoryId); - - var response = await _helperService.CreateAsync - (context, dto, x => x.AddItems); - - return new(ToTodoItemsServiceStatus(response.Status), response.Result); - } - - /// - /// Edits a to-do list item asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to edit. - /// The DTO containing information for editing the to-do list item. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public async Task EditAsync(TodoListContext context, int itemId, TodoItemCreateDto dto) - { - // Validate the category - if (!await _categoriesService.IsCategoryValidForContext(context, dto.CategoryId)) - return TodoItemsServiceStatus.InvalidCategoryId; - - var response = await _helperService.UpdateAsync(context, itemId, dto, x => x.EditItems); - return ToTodoItemsServiceStatus(response); - } - - /// - /// Updates the state of a to-do list item asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to update the state. - /// The DTO which contains the state of the to-do item to set. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task UpdateStateAsync(TodoListContext context, int itemId, TodoItemUpdateStateDto stateDto) - { - return _helperService.UpdateAsync(context, itemId, stateDto, x => x.SetItemsState); - } - - /// - /// Deletes a to-do list item asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list item to delete. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task DeleteAsync(TodoListContext context, int itemId) - { - return _helperService.DeleteAsync(context, itemId, x => x.DeleteItems); - } - - private static TodoItemsServiceStatus ToTodoItemsServiceStatus(ServiceResponseStatus status) - { - return status switch - { - ServiceResponseStatus.Success => TodoItemsServiceStatus.Success, - ServiceResponseStatus.NotFound => TodoItemsServiceStatus.NotFound, - ServiceResponseStatus.Forbidden => TodoItemsServiceStatus.Forbidden, - _ => throw new ArgumentException("Invalid service response", nameof(status)) - }; - } -} diff --git a/AdvancedTodoList.Infrastructure/Services/TodoListDependantEntitiesService.cs b/AdvancedTodoList.Infrastructure/Services/TodoListDependantEntitiesService.cs deleted file mode 100644 index ee599b3..0000000 --- a/AdvancedTodoList.Infrastructure/Services/TodoListDependantEntitiesService.cs +++ /dev/null @@ -1,189 +0,0 @@ -using AdvancedTodoList.Core.Models; -using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Repositories; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Services.Auth; -using AdvancedTodoList.Core.Specifications; -using Mapster; - -namespace AdvancedTodoList.Infrastructure.Services; - -/// -/// A service that performs CRUD operations -/// on to-do list dependant entities and maps/unmaps DTOs. -/// -/// Type of the to-do list dependant entity. -/// Type of the unique identifier used by the entity. -public sealed class TodoListDependantEntitiesService( - IRepository repository, - IEntityExistenceChecker existenceChecker, - IPermissionsChecker permissionsChecker) : ITodoListDependantEntitiesService - where TEntity : class, IEntity, ITodoListDependant - where TKey : IEquatable -{ - private readonly IRepository _repository = repository; - private readonly IEntityExistenceChecker _existenceChecker = existenceChecker; - private readonly IPermissionsChecker _permissionsChecker = permissionsChecker; - - /// - /// Retrieves a page of to-do list dependant entities mapped to . - /// - /// - /// This method checks if to-do list ID is valid, but doesn't filter by it. - /// Filtering should be done in . - /// - /// To-do list context. - /// Specification to apply to entities. - /// Pagination parameters to use. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation and the requested page on success. - /// - public async Task>> GetPageAsync(TodoListContext context, - ISpecification specification, PaginationParameters paginationParameters) - { - // Check if to-do list exists - if (!await _existenceChecker.ExistsAsync(context.TodoListId)) - return new(ServiceResponseStatus.NotFound); - - // Check if user is a member of the to-do list - if (!await _permissionsChecker.IsMemberOfListAsync(context)) - return new(ServiceResponseStatus.Forbidden); - - // Get the requested page - var page = await _repository.GetPageAsync(paginationParameters, specification); - // Return the page - return new(ServiceResponseStatus.Success, page); - } - - /// - /// Retrieves a to-do list dependant entity by its ID asynchronously and maps it to . - /// - /// To-do list context. - /// The ID of the entity to retrieve. - /// DTO to map entity to. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation and the requested dto on success. - /// - public async Task> GetByIdAsync(TodoListContext context, TKey entityId) - where TDto : class - { - // Check if user is a member of the to-do list - if (!await _permissionsChecker.IsMemberOfListAsync(context)) - return new(ServiceResponseStatus.Forbidden); - - // Get the model - var entity = await _repository.GetByIdAsync(entityId); - // Return null if model is null or has wrong to-do list ID - if (entity == null || entity.TodoListId != context.TodoListId) - return new(ServiceResponseStatus.NotFound); - - // Map it to DTO and return - var result = entity.Adapt(); - return new(ServiceResponseStatus.Success, result); - } - - /// - /// Creates a new a to-do list dependant entity asynchronously from the DTO. - /// - /// - /// If implements the interface, then - /// this method will set the caller as an owner. - /// - /// To-do list context. - /// The DTO containing information for creating the entity. - /// Optional accessor for the permission required for the user to perform the action. - /// - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation and the created mapped to - /// on success. - /// - public async Task> CreateAsync( - TodoListContext context, TInputDto dto, Func? permission = null) - where TOutputDto : class - { - // Check if to-do list exists - if (!await _existenceChecker.ExistsAsync(context.TodoListId)) - return new(ServiceResponseStatus.NotFound); - // Check if the user has the permission - if (permission != null && !await _permissionsChecker.HasPermissionAsync(context, permission)) - return new(ServiceResponseStatus.Forbidden); - - // Create the model - var entity = dto.Adapt(); - // Set the foreign key - entity.TodoListId = context.TodoListId; - // Set the owner - if (entity is IHasOwner hasOwnerEntity) - hasOwnerEntity.OwnerId = context.CallerId; - // Save it - await _repository.AddAsync(entity); - // Map it to DTO and return - var result = entity.Adapt(); - return new(ServiceResponseStatus.Success, result); - } - - /// - /// Edits a to-do list dependant entity asynchronously. - /// - /// To-do list context. - /// The ID of the entity to edit. - /// The DTO containing information for editing the entity. - /// Optional accessor for the permission required for the user to perform the action. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public async Task UpdateAsync( - TodoListContext context, TKey entityId, TDto dto, Func? permission = null) - { - // Get the model of a to-do list item - var entity = await _repository.GetByIdAsync(entityId); - // Check if it's valid - if (entity == null || entity.TodoListId != context.TodoListId) - return ServiceResponseStatus.NotFound; - - // Check if user has the permission - if (permission != null && !await _permissionsChecker.CanTouchEntityAsync(context, entity, permission)) - return ServiceResponseStatus.Forbidden; - - // Update the model - dto.Adapt(entity); - // Save changes - await _repository.UpdateAsync(entity); - - return ServiceResponseStatus.Success; - } - - /// - /// Deletes a to-do list dependant entity asynchronously. - /// - /// To-do list context. - /// The ID of the entity to delete. - /// Optional accessor for the permission required for the user to perform the action. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public async Task DeleteAsync( - TodoListContext context, TKey entityId, Func? permission = null) - { - // Get the model of a to-do list item - var todoItem = await _repository.GetByIdAsync(entityId); - // Check if it's valid - if (todoItem == null || todoItem.TodoListId != context.TodoListId) - return ServiceResponseStatus.NotFound; - // Check if user has the permission - if (permission != null && !await _permissionsChecker.CanTouchEntityAsync(context, todoItem, permission)) - return ServiceResponseStatus.Forbidden; - - // Delete the model - await _repository.DeleteAsync(todoItem); - - return ServiceResponseStatus.Success; - } -} diff --git a/AdvancedTodoList.Infrastructure/Services/TodoListMembersService.cs b/AdvancedTodoList.Infrastructure/Services/TodoListMembersService.cs deleted file mode 100644 index 92270d5..0000000 --- a/AdvancedTodoList.Infrastructure/Services/TodoListMembersService.cs +++ /dev/null @@ -1,145 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Repositories; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Services.Auth; -using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; -using Mapster; - -namespace AdvancedTodoList.Infrastructure.Services; - -/// -/// A service that manages to-do list members. -/// -public class TodoListMembersService( - ITodoListDependantEntitiesService helperService, - ITodoListMembersRepository membersRepository, - IRepository rolesRepository, - IPermissionsChecker permissionsChecker) : - ITodoListMembersService -{ - private readonly ITodoListDependantEntitiesService _helperService = helperService; - private readonly ITodoListMembersRepository _membersRepository = membersRepository; - private readonly IRepository _rolesRepository = rolesRepository; - private readonly IPermissionsChecker _permissionsChecker = permissionsChecker; - - /// - /// Gets a page with members of a to-do list asynchronously. - /// - /// To-do list context. - /// Pagination parameters to use. - /// Filter parameters to use. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task>> GetMembersAsync(TodoListContext context, - PaginationParameters paginationParameters, TodoListMembersFilter filter) - { - TodoListMembersSpecification specification = new(context.TodoListId, filter); - return _helperService.GetPageAsync( - context, specification, paginationParameters - ); - } - - /// - /// Adds a member to a to-do list asynchronously. - /// - /// To-do list context. - /// DTO that contains information needed for adding a member. Supossed to be valid. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public async Task AddMemberAsync(TodoListContext context, TodoListMemberAddDto dto) - { - // Check if user has the permission - if (!await _permissionsChecker.HasPermissionAsync(context, x => x.AddMembers)) - return new(TodoListMemberServiceResultStatus.Forbidden); - - // Try to find already existing member - var member = await _membersRepository.FindAsync(context.TodoListId, dto.UserId); - // Return error if it exists - if (member != null) return new(TodoListMemberServiceResultStatus.UserAlreadyAdded); - - // Add member - var response = await _helperService - .CreateAsync(context, dto); - - return response.Status switch - { - ServiceResponseStatus.Success => new(TodoListMemberServiceResultStatus.Success, response.Result), - ServiceResponseStatus.NotFound => new(TodoListMemberServiceResultStatus.NotFound), - _ => throw new InvalidOperationException("Invalid to-do lists dependant entities (members) service response.") - }; - } - - /// - /// Updates a role of the member of a to-do list asynchronously. - /// - /// To-do list context. - /// ID of the member. - /// DTO that contains information needed for updating a role. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public async Task UpdateMemberRoleAsync(TodoListContext context, int memberId, TodoListMemberUpdateRoleDto dto) - { - // I think that this method does too much and has many test cases, probably needs refactoring - - // Check if the caller has the permission to assign roles - MemberPermissionsSpecification specification = new(context.TodoListId, context.CallerId); - var caller = await _membersRepository.GetAggregateAsync(specification); - if (caller == null || caller.Role == null || !caller.Role.Permissions.AssignRoles) - return TodoListMemberServiceResultStatus.Forbidden; - - // Check if user has the permission to assign the role - if (dto.RoleId != null) - { - var role = await _rolesRepository.GetByIdAsync(dto.RoleId.Value); - // Validate RoleId - if (role == null || role.TodoListId != context.TodoListId) - return TodoListMemberServiceResultStatus.InvalidRoleId; - // Check priority - if (role.Priority <= caller.Role.Priority) - return TodoListMemberServiceResultStatus.Forbidden; - } - - // Get the model of a member - var member = await _membersRepository.GetByIdAsync(memberId); - // Check if it's valid - if (member == null || member.TodoListId != context.TodoListId) - return TodoListMemberServiceResultStatus.NotFound; - - // Check if user has a permission to assign roles to the member - if (member.RoleId != null) - { - // Get the member's role - var role = await _rolesRepository.GetByIdAsync(member.RoleId.Value) ?? - throw new InvalidOperationException("Member's role is not found"); - - if (role.Priority <= caller.Role.Priority) - return TodoListMemberServiceResultStatus.Forbidden; - } - - // Update the model - dto.Adapt(member); - // Save changes - await _membersRepository.UpdateAsync(member); - - return TodoListMemberServiceResultStatus.Success; - } - - /// - /// Removes a member from a to-do list asynchronously. - /// - /// To-do list context. - /// ID of the member. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task RemoveMemberAsync(TodoListContext context, int memberId) - { - return _helperService.DeleteAsync(context, memberId, x => x.RemoveMembers); - } -} diff --git a/AdvancedTodoList.Infrastructure/Services/TodoListRolesService.cs b/AdvancedTodoList.Infrastructure/Services/TodoListRolesService.cs deleted file mode 100644 index 160048b..0000000 --- a/AdvancedTodoList.Infrastructure/Services/TodoListRolesService.cs +++ /dev/null @@ -1,132 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Repositories; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Services.Auth; -using AdvancedTodoList.Infrastructure.Specifications; -using Mapster; - -namespace AdvancedTodoList.Infrastructure.Services; - -/// -/// A service that manages to-do lists roles. -/// -public class TodoListRolesService( - ITodoListDependantEntitiesService helperService, - IRepository rolesRepository, - IPermissionsChecker permissionsChecker - ) : ITodoListRolesService -{ - private readonly ITodoListDependantEntitiesService _helperService = helperService; - private readonly IRepository _rolesRepository = rolesRepository; - private readonly IPermissionsChecker _permissionsChecker = permissionsChecker; - - /// - /// Retrieves a page of to-do list roles of the list with the specified ID. - /// - /// To-do list context. - /// Pagination parameters to use. - /// Optional name to filter categories by. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task>> GetRolesOfListAsync( - TodoListContext context, PaginationParameters paginationParameters, string? name = null) - { - TodoListDependantEntitiesSpecification specification = new(context.TodoListId, name); - return _helperService.GetPageAsync(context, specification, paginationParameters); - } - - /// - /// Retrieves a to-do list role by its ID asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list role to retrieve. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task> GetByIdAsync(TodoListContext context, int roleId) - { - return _helperService.GetByIdAsync(context, roleId); - } - - /// - /// Creates a new to-do list role asynchronously. - /// - /// To-do list context. - /// The DTO containing information for creating the to-do list role. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// It's not practically possible for this implementation to have the result that indicates - /// 'NotFound' status. - /// - public async Task> CreateAsync(TodoListContext context, TodoListRoleCreateDto dto) - { - // Check if user has a permission to create a role with the given priority - if (!await _permissionsChecker.HasPermissionOverRoleAsync(context, dto.Priority, x => x.EditRoles)) - return new(ServiceResponseStatus.Forbidden); - - // Pass null as the third argument to not check permissions twice - return await _helperService.CreateAsync( - context, dto, null); - } - - /// - /// Edits a to-do list role asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list role to edit. - /// The DTO containing information for editing the to-do list role. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public async Task EditAsync(TodoListContext context, int roleId, TodoListRoleCreateDto dto) - { - // Get the model of a role - var role = await _rolesRepository.GetByIdAsync(roleId); - // Check if it's valid - if (role == null || role.TodoListId != context.TodoListId) - return ServiceResponseStatus.NotFound; - - // Check if user has a permission to change it - // Checking only the minimal priority is sufficient, because both - // values should be greater than caller's role priority. - int minPriority = Math.Min(role.Priority, dto.Priority); - if (!await _permissionsChecker.HasPermissionOverRoleAsync(context, minPriority, x => x.EditRoles)) - return ServiceResponseStatus.Forbidden; - - // Update the model - dto.Adapt(role); - // Save changes - await _rolesRepository.UpdateAsync(role); - - return ServiceResponseStatus.Success; - } - - /// - /// Deletes a to-do list role asynchronously. - /// - /// To-do list context. - /// The ID of the to-do list role to delete. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public async Task DeleteAsync(TodoListContext context, int roleId) - { - // Get the model of a role - var role = await _rolesRepository.GetByIdAsync(roleId); - // Check if it's valid - if (role == null || role.TodoListId != context.TodoListId) - return ServiceResponseStatus.NotFound; - - // Check if user has the permission to delete the role - if (!await _permissionsChecker.HasPermissionOverRoleAsync(context, role.Priority, x => x.EditRoles)) - return ServiceResponseStatus.Forbidden; - - // Delete the role - await _rolesRepository.DeleteAsync(role); - - return ServiceResponseStatus.Success; - } -} diff --git a/AdvancedTodoList.Infrastructure/Services/TodoListsService.cs b/AdvancedTodoList.Infrastructure/Services/TodoListsService.cs deleted file mode 100644 index 6594474..0000000 --- a/AdvancedTodoList.Infrastructure/Services/TodoListsService.cs +++ /dev/null @@ -1,182 +0,0 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Repositories; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Services.Auth; -using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; -using Mapster; - -namespace AdvancedTodoList.Infrastructure.Services; - -/// -/// A service that manages to-do lists. -/// -public class TodoListsService( - IPermissionsChecker permissionsChecker, - IRepository todoListsRepository, - IRepository rolesRepository, - ITodoListMembersRepository membersRepository, - IUnitOfWork unitOfWork - ) : ITodoListsService -{ - private readonly IPermissionsChecker _permissionsChecker = permissionsChecker; - private readonly IRepository _todoListsRepository = todoListsRepository; - private readonly IRepository _rolesRepository = rolesRepository; - private readonly IRepository _membersRepository = membersRepository; - private readonly IUnitOfWork _unitOfWork = unitOfWork; - - /// - /// Retrieves a page of to-do lists, with the requirement that the user - /// is a member of those lists. - /// - /// Id of the user - /// Pagination parameters to use. - /// Filter parameters to apply. - /// - /// A task representing the asynchronous operation containing the result of operation. - /// - public Task> GetListsOfUserAsync(string userId, - PaginationParameters paginationParameters, TodoListsFilter filter) - { - TodoListsSpecification specification = new(userId, filter); - return _todoListsRepository.GetPageAsync( - paginationParameters, specification); - } - - /// - /// Retrieves a to-do list by its ID asynchronously. - /// - /// To-do list context. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public async Task> GetByIdAsync(TodoListContext context) - { - TodoListAggregateSpecification specification = new(context.TodoListId); - var result = await _todoListsRepository.GetAggregateAsync(specification); - // Check if to-do list exists - if (result == null) return new(ServiceResponseStatus.NotFound); - - // Check if the user has a permission to view the to-do list - if (!await _permissionsChecker.IsMemberOfListAsync(context)) - return new(ServiceResponseStatus.Forbidden); - - return new(ServiceResponseStatus.Success, result); - } - - /// - /// Creates a new to-do list asynchronously. - /// - /// - /// This method should also create an "Owner" role with all permissions and assign the caller to it. - /// - /// The DTO containing information for creating the to-do list. - /// ID of the user who creates the to-do list. - /// - /// A task representing the asynchronous operation. The task contains - /// a created model mapped to . - /// - public async Task CreateAsync(TodoListCreateDto dto, string callerId) - { - // Map DTO to the model - var todoList = dto.Adapt(); - // Set the owner - todoList.OwnerId = callerId; - - // Begin a transaction - await _unitOfWork.BeginTransactionAsync(); - - try - { - // Add the list to the database - await _todoListsRepository.AddAsync(todoList); - - // Create an "Owner" role - TodoListRole ownerRole = new() - { - Name = "Owner", - Priority = 0, - TodoListId = todoList.Id, - Permissions = RolePermissions.All - }; - await _rolesRepository.AddAsync(ownerRole); - - // Assign the caller to it - TodoListMember member = new() - { - UserId = callerId, - TodoListId = todoList.Id, - RoleId = ownerRole.Id - }; - await _membersRepository.AddAsync(member); - } - catch (Exception) - { - // Rollback in a case of error - await _unitOfWork.RollbackAsync(); - throw; - } - - // Commit changes - await _unitOfWork.CommitAsync(); - - // Return a DTO of created model - return todoList.Adapt(); - } - - /// - /// Edits a to-do list asynchronously. - /// - /// To-do list context. - /// The DTO containing information for editing the to-do list. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public async Task EditAsync(TodoListContext context, TodoListCreateDto dto) - { - // Get the model - var todoList = await _todoListsRepository.GetByIdAsync(context.TodoListId); - // Return NotFound if the model doesn't exist - if (todoList == null) return ServiceResponseStatus.NotFound; - // Check if the user has a permission to edit the list - if (!await _permissionsChecker.CanTouchEntityAsync( - context, todoList, x => x.EditItems)) - { - return ServiceResponseStatus.Forbidden; - } - - // Update the model - dto.Adapt(todoList); - await _todoListsRepository.UpdateAsync(todoList); - - return ServiceResponseStatus.Success; - } - - /// - /// Deletes a to-do list asynchronously. - /// - /// To-do list context. - /// - /// A task representing the asynchronous operation. The task contains - /// a result of the operation. - /// - public async Task DeleteAsync(TodoListContext context) - { - // Get the model - var todoList = await _todoListsRepository.GetByIdAsync(context.TodoListId); - // Return NotFound if the model doesn't exist - if (todoList == null) return ServiceResponseStatus.NotFound; - // Check if the user is an owner of the list - if (todoList.OwnerId != context.CallerId) return ServiceResponseStatus.Forbidden; - - // Delete the model - await _todoListsRepository.DeleteAsync(todoList); - - return ServiceResponseStatus.Success; - } -} diff --git a/AdvancedTodoList.Infrastructure/Specifications/GetByIdSpecification.cs b/AdvancedTodoList.Infrastructure/Specifications/GetByIdSpecification.cs deleted file mode 100644 index 1a0c20c..0000000 --- a/AdvancedTodoList.Infrastructure/Specifications/GetByIdSpecification.cs +++ /dev/null @@ -1,39 +0,0 @@ -using AdvancedTodoList.Core.Models; -using AdvancedTodoList.Core.Specifications; -using System.Linq.Expressions; - -namespace AdvancedTodoList.Infrastructure.Specifications; - -/// -/// Represents a specification that defines criteria for obtaining entities by ID. -/// -/// The type of entity. -/// The type of the entity unique identifier. -/// The unique identifier to filter by. -public class GetByIdSpecification(TKey id) : ISpecification - where TEntity : class, IEntity - where TKey : IEquatable -{ - /// - /// The unique identifier to filter by. - /// - public TKey Id { get; } = id; - - /// - /// Gets the criteria expression that defines the filtering conditions. - /// Filters by ID provided in the constructor when not overriden. - /// - public Expression> Criteria => x => x.Id.Equals(Id); - - /// - /// Gets the list of include expressions specifying related entities to be included in the query results. - /// Is empty when not overriden. - /// - public virtual List>> Includes => []; - - /// - /// Gets the list of include strings specifying related entities to be included in the query results. - /// Is empty when not overriden. - /// - public virtual List IncludeStrings => []; -} diff --git a/AdvancedTodoList.Infrastructure/Specifications/InvitationLinksSpecification.cs b/AdvancedTodoList.Infrastructure/Specifications/InvitationLinksSpecification.cs deleted file mode 100644 index 2900c07..0000000 --- a/AdvancedTodoList.Infrastructure/Specifications/InvitationLinksSpecification.cs +++ /dev/null @@ -1,32 +0,0 @@ -using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Specifications; -using System.Linq.Expressions; - -namespace AdvancedTodoList.Infrastructure.Specifications; - -/// -/// Represents a specification that defines criteria for filtering invitation links. -/// -/// ID of the list invitation links of which will be obtained. -public class InvitationLinksSpecification(string todoListId) : ISpecification -{ - /// - /// Gets the ID of the to-do list to filter entities by. - /// - public string? TodoListId { get; } = todoListId; - - /// - /// Gets the criteria expression that defines the filtering conditions. - /// - public virtual Expression> Criteria => x => x.TodoListId == TodoListId; - - /// - /// Gets the list of include expressions specifying related entities to be included in the query results. - /// - public virtual List>> Includes { get; init; } = []; - - /// - /// Gets the list of include strings specifying related entities to be included in the query results. - /// - public virtual List IncludeStrings { get; init; } = []; -} diff --git a/AdvancedTodoList.Infrastructure/Specifications/MemberPermissionsSpecification.cs b/AdvancedTodoList.Infrastructure/Specifications/MemberPermissionsSpecification.cs deleted file mode 100644 index ec792b5..0000000 --- a/AdvancedTodoList.Infrastructure/Specifications/MemberPermissionsSpecification.cs +++ /dev/null @@ -1,43 +0,0 @@ -using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Services.Auth; -using System.Linq.Expressions; - -namespace AdvancedTodoList.Infrastructure.Specifications; - -/// -/// Represents a specification for obtaining an aggregate containg the to-do list member -/// and his/her/their role, used in . -/// -/// ID of the to-do list. -/// ID of the user. -public class MemberPermissionsSpecification(string todoListId, string userId) : ISpecification -{ - /// - /// Gets the to-do list ID. - /// - public string TodoListId { get; } = todoListId; - /// - /// Gets the user ID. - /// - public string UserId { get; } = userId; - - /// - /// Gets the criteria expression that defines the filtering conditions. - /// - public Expression> Criteria => - x => x.TodoListId == TodoListId && x.UserId == UserId; - - /// - /// Gets the list of include expressions specifying a to-do list role to be included in the query results. - /// - public List>> Includes => - [ - x => x.Role - ]; - - /// - /// Gets the list of include strings specifying related entities to be included in the query results. - /// - public List IncludeStrings { get; } = []; -} diff --git a/AdvancedTodoList.Infrastructure/Specifications/QueryableSpecificationExtensions.cs b/AdvancedTodoList.Infrastructure/Specifications/QueryableSpecificationExtensions.cs deleted file mode 100644 index c7660bb..0000000 --- a/AdvancedTodoList.Infrastructure/Specifications/QueryableSpecificationExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using AdvancedTodoList.Core.Specifications; -using Microsoft.EntityFrameworkCore; - -namespace AdvancedTodoList.Infrastructure.Specifications; - -public static class QueryableSpecificationExtensions -{ - public static IQueryable ApplySpecification(this IQueryable queryable, ISpecification specification) - where T : class - { - // Include all expression-based includes - var queryableResultWithIncludes = specification.Includes - .Aggregate(queryable, (current, include) => current.Include(include)); - - // Include string-based include statements - var secondaryResult = specification.IncludeStrings - .Aggregate(queryableResultWithIncludes, - (current, include) => current.Include(include)); - - // Apply criteria and return the result - return secondaryResult.Where(specification.Criteria); - } -} diff --git a/AdvancedTodoList.Infrastructure/Specifications/TodoItemsSpecification.cs b/AdvancedTodoList.Infrastructure/Specifications/TodoItemsSpecification.cs deleted file mode 100644 index d752eaf..0000000 --- a/AdvancedTodoList.Infrastructure/Specifications/TodoItemsSpecification.cs +++ /dev/null @@ -1,59 +0,0 @@ -using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Specifications; -using System.Linq.Expressions; - -namespace AdvancedTodoList.Infrastructure.Specifications; - -/// -/// Represents a specification that defines criteria for filtering to-do list members -/// and adds includes for users and roles models. -/// -/// ID of the to-do list items of which will be obtained. -/// Filter parameters. -public class TodoItemsSpecification(string todoListId, TodoItemsFilter filter) : - ISpecification -{ - /// - /// Gets the ID of the to-do list to filter items by. - /// - public string? TodoListId { get; } = todoListId; - /// - /// Gets the filter parameters. - /// - public TodoItemsFilter Filter { get; } = filter; - - /// - /// Gets the criteria expression that defines the filtering conditions. - /// - public Expression> Criteria => x => - // Filter by to-do list - x.TodoListId == TodoListId && - // Filter by name - (Filter.Name == null || x.Name.Contains(Filter.Name)) && - // Filter by owner's ID - (Filter.OwnerId == null || x.OwnerId == Filter.OwnerId) && - // Filter by states - (Filter.State == null || !Filter.State.Any() || Filter.State.Contains(x.State)) && - // Filter by categories - (Filter.CategoryId == null || !Filter.CategoryId.Any() || Filter.CategoryId.Contains(x.CategoryId)) && - // Filter by priority - (Filter.MinPriority == null || x.Priority >= Filter.MinPriority) && - (Filter.MaxPriority == null || x.Priority <= Filter.MaxPriority) && - // Filter by deadline date - (Filter.MinDeadlineDate == null || x.DeadlineDate >= Filter.MinDeadlineDate) && - (Filter.MaxDeadlineDate == null || x.DeadlineDate <= Filter.MaxDeadlineDate); - - /// - /// Gets the list of include expressions specifying related entities to be included in the query results. - /// - public List>> Includes => - [ - x => x.Owner, - x => x.Category - ]; - - /// - /// Gets the list of include strings specifying related entities to be included in the query results. - /// - public List IncludeStrings => []; -} diff --git a/AdvancedTodoList.Infrastructure/Specifications/TodoListDependantEntitiesSpecification.cs b/AdvancedTodoList.Infrastructure/Specifications/TodoListDependantEntitiesSpecification.cs deleted file mode 100644 index 761e5f9..0000000 --- a/AdvancedTodoList.Infrastructure/Specifications/TodoListDependantEntitiesSpecification.cs +++ /dev/null @@ -1,46 +0,0 @@ -using AdvancedTodoList.Core.Models; -using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Specifications; -using System.Linq.Expressions; - -namespace AdvancedTodoList.Infrastructure.Specifications; - -/// -/// Represents a specification that defines criteria for filtering any todo-list dependant entities with name. -/// -/// ID of the list items of which will be obtained. -/// -/// Optional name to filter by. -/// Entries which have this substring in the name will be returned. -/// -public class TodoListDependantEntitiesSpecification(string todoListId, string? name = null) : ISpecification - where TEntity : ITodoListDependant, IHasName -{ - /// - /// Gets the ID of the to-do list to filter entities by. - /// - public string? TodoListId { get; } = todoListId; - /// - /// Gets the name to filter entities, which implement by. - /// - public string? Name { get; } = name; - - /// - /// Gets the criteria expression that defines the filtering conditions. - /// Filters only by the to-do list ID and the name when not overriden. - /// - public virtual Expression> Criteria => x => - x.TodoListId == TodoListId && (Name == null || x.Name.Contains(Name)); - - /// - /// Gets the list of include expressions specifying related entities to be included in the query results. - /// Is empty when not overriden. - /// - public virtual List>> Includes { get; init; } = []; - - /// - /// Gets the list of include strings specifying related entities to be included in the query results. - /// Is empty when not overriden. - /// - public virtual List IncludeStrings { get; init; } = []; -} diff --git a/AdvancedTodoList.Infrastructure/Specifications/TodoListMembersSpecification.cs b/AdvancedTodoList.Infrastructure/Specifications/TodoListMembersSpecification.cs deleted file mode 100644 index 0b7f7fd..0000000 --- a/AdvancedTodoList.Infrastructure/Specifications/TodoListMembersSpecification.cs +++ /dev/null @@ -1,51 +0,0 @@ -using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Specifications; -using System.Linq.Expressions; - -namespace AdvancedTodoList.Infrastructure.Specifications; - -/// -/// Represents a specification that defines criteria for filtering to-do list members -/// and adds includes for users and roles models. -/// -/// ID of the to-do list items of which will be obtained. -/// Filter parameters. -public class TodoListMembersSpecification(string todoListId, TodoListMembersFilter filter) : ISpecification -{ - /// - /// Gets the ID of the to-do list to filter entities by. - /// - public string? TodoListId { get; } = todoListId; - /// - /// Gets the filter parameters. - /// - public TodoListMembersFilter Filter { get; } = filter; - - /// - /// Gets the criteria expression that defines the filtering conditions. - /// - public Expression> Criteria => - x => x.TodoListId == TodoListId && - // Filter by roles - (Filter.RoleId == null || !Filter.RoleId.Any() || Filter.RoleId.Contains(x.RoleId)) && - // Filter by user ID - (Filter.UserId == null || x.UserId == Filter.UserId) && - // Filter by username - (Filter.UserName == null || x.User!.UserName!.Contains(Filter.UserName)) && - // Filter by full name - (Filter.FullName == null || (x.User!.FirstName + ' ' + x.User!.LastName).Contains(Filter.FullName)); - - /// - /// Gets the list of include expressions specifying related entities to be included in the query results. - /// - public List>> Includes => - [ - x => x.Role, - x => x.User - ]; - - /// - /// Gets the list of include strings specifying related entities to be included in the query results. - /// - public List IncludeStrings => []; -} diff --git a/AdvancedTodoList.Infrastructure/Specifications/TodoListsSpecification.cs b/AdvancedTodoList.Infrastructure/Specifications/TodoListsSpecification.cs deleted file mode 100644 index 33fb344..0000000 --- a/AdvancedTodoList.Infrastructure/Specifications/TodoListsSpecification.cs +++ /dev/null @@ -1,45 +0,0 @@ -using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Specifications; -using System.Linq.Expressions; - -namespace AdvancedTodoList.Infrastructure.Specifications; - -/// -/// Represents a specification that defines criteria for filtering to-do lists, -/// with the requirement that the user is a member of those lists. -/// -/// ID of the user. -/// Filter parameters. -public class TodoListsSpecification(string userId, TodoListsFilter filter) : ISpecification -{ - /// - /// Gets the ID of the user, to get lists where this user is a member. - /// - public string UserId { get; } = userId; - /// - /// Gets the filter parameters. - /// - public TodoListsFilter Filter { get; } = filter; - - /// - /// Gets the criteria expression that defines the filtering conditions. - /// - public Expression> Criteria => x => - // User is a member requirement - x.TodoListMembers.Any(m => m.UserId == UserId) && - // Filter by name - (Filter.Name == null || x.Name.Contains(Filter.Name)); - - /// - /// Gets the list of include expressions specifying related entities to be included in the query results. - /// - public List>> Includes => - [ - x => x.TodoListMembers - ]; - - /// - /// Gets the list of include strings specifying related entities to be included in the query results. - /// - public List IncludeStrings => []; -} diff --git a/AdvancedTodoList.IntegrationTests/AdvancedTodoList.IntegrationTests.csproj b/AdvancedTodoList.IntegrationTests/AdvancedTodoList.IntegrationTests.csproj index 41f281f..d4f0a3d 100644 --- a/AdvancedTodoList.IntegrationTests/AdvancedTodoList.IntegrationTests.csproj +++ b/AdvancedTodoList.IntegrationTests/AdvancedTodoList.IntegrationTests.csproj @@ -11,17 +11,24 @@ - - - - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - + diff --git a/AdvancedTodoList.IntegrationTests/Endpoints/AuthEndpointsTests.cs b/AdvancedTodoList.IntegrationTests/Endpoints/AuthEndpointsTests.cs index e8b66a9..d111171 100644 --- a/AdvancedTodoList.IntegrationTests/Endpoints/AuthEndpointsTests.cs +++ b/AdvancedTodoList.IntegrationTests/Endpoints/AuthEndpointsTests.cs @@ -1,5 +1,5 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Services.Auth; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions.Auth; using AdvancedTodoList.IntegrationTests.Fixtures; using System.Net; using System.Net.Http.Json; @@ -9,227 +9,227 @@ namespace AdvancedTodoList.IntegrationTests.Endpoints; [TestFixture] public class AuthEndpointsTests : EndpointsFixture { - [Test] - public async Task LogIn_ValidCredentials_SucceedsAndReturnsValidResponse() - { - // Arrange - LogInDto logInDto = new("UserName", "Password"); - LogInResponse expectedResponse = new("Access", "Refresh"); - using var client = WebApplicationFactory.CreateClient(); - - WebApplicationFactory.AuthService - .LogInAsync(logInDto) - .Returns(expectedResponse); - - // Act - var result = await client.PostAsJsonAsync("api/auth/logIn", logInDto); - var body = await result.Content.ReadFromJsonAsync(); - - // Assert success status code - result.EnsureSuccessStatusCode(); - // Assert that response is valid - Assert.That(body, Is.EqualTo(expectedResponse)); - } - - [Test] - public async Task LogIn_InvalidDto_Returns400() - { - // Arrange - LogInDto logInDto = new("", ""); - using var client = WebApplicationFactory.CreateClient(); - - // Act - var result = await client.PostAsJsonAsync("api/auth/logIn", logInDto); - - // Arrange - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task LogIn_InvalidCredentials_Returns401() - { - // Arrange - LogInDto logInDto = new("UserName", "FakePassword"); - using var client = WebApplicationFactory.CreateClient(); - - WebApplicationFactory.AuthService - .LogInAsync(logInDto) - .ReturnsNull(); - - // Act - var result = await client.PostAsJsonAsync("api/auth/logIn", logInDto); - - // Arrange - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task Register_ValidRegisterAttempt_Succeeds() - { - // Arrange - RegisterDto registerDto = new("email@example.com", "UserName", "1st", "last", "M@ster_Pa$5w0r4"); - using var client = WebApplicationFactory.CreateClient(); - - WebApplicationFactory.AuthService - .RegisterAsync(registerDto) - .Returns(RegisterResult.Success()); - - // Act - var result = await client.PostAsJsonAsync("api/auth/register", registerDto); - - // Arrange - result.EnsureSuccessStatusCode(); - } - - [Test] - public async Task Register_InvalidRegisterAttempt_Returns400() - { - // Arrange - RegisterDto registerDto = new("email@example.com", "UserName", "1st", "last", "M@ster_Pa$5w0r4"); - using var client = WebApplicationFactory.CreateClient(); - - WebApplicationFactory.AuthService - .RegisterAsync(registerDto) - .Returns(RegisterResult.Failure([new("$", "error")])); - - // Act - var result = await client.PostAsJsonAsync("api/auth/register", registerDto); - - // Arrange - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task Register_InvalidRegisterDto_Returns400() - { - // Arrange - RegisterDto registerDto = new("invalid email", "~$#", "", "", "weakpassword"); - using var client = WebApplicationFactory.CreateClient(); - - // Act - var result = await client.PostAsJsonAsync("api/auth/register", registerDto); - - // Arrange - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task Refresh_ValidTokens_SucceedsAndReturnsValidResponse() - { - // Arrange - RefreshDto refreshDto = new("Access", "Refresh"); - LogInResponse expectedResponse = new("Access", "Refresh"); - using var client = WebApplicationFactory.CreateClient(); - - WebApplicationFactory.AuthService - .RefreshAsync(refreshDto) - .Returns(expectedResponse); - - // Act - var result = await client.PostAsJsonAsync("api/auth/refresh", refreshDto); - var body = await result.Content.ReadFromJsonAsync(); - - // Assert success status code - result.EnsureSuccessStatusCode(); - // Assert that response is valid - Assert.That(body, Is.EqualTo(expectedResponse)); - } - - [Test] - public async Task Refresh_InvalidRefreshDto_Returns400() - { - // Arrange - RefreshDto refreshDto = new("", " "); - using var client = WebApplicationFactory.CreateClient(); - - // Act - var result = await client.PostAsJsonAsync("api/auth/refresh", refreshDto); - - // Arrange - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task Refresh_InvalidTokens_Returns401() - { - // Arrange - RefreshDto refreshDto = new("InvalidAccess", "InvalidRefresh"); - using var client = WebApplicationFactory.CreateClient(); - - WebApplicationFactory.AuthService - .RefreshAsync(refreshDto) - .ReturnsNull(); - - // Act - var result = await client.PostAsJsonAsync("api/auth/refresh", refreshDto); - - // Arrange - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task LogOut_ValidTokenProvided_Succeeds() - { - // Arrange - LogOutDto dto = new("Refresh"); - string userId = "UserId"; - using var client = CreateAuthorizedHttpClient(userId); - - WebApplicationFactory.AuthService - .LogOutAsync(userId, dto) - .Returns(true); - - // Act - var result = await client.PostAsJsonAsync("api/auth/logOut", dto); - - // Assert - result.EnsureSuccessStatusCode(); - } - - [Test] - public async Task LogOut_InvalidTokenProvided_Returns401() - { - // Arrange - LogOutDto dto = new("Invalid"); - string userId = "UserId"; - using var client = CreateAuthorizedHttpClient(userId); - - WebApplicationFactory.AuthService - .LogOutAsync(userId, dto) - .Returns(false); - - // Act - var result = await client.PostAsJsonAsync("api/auth/logOut", dto); - - // Assert - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task LogOut_NoAuthHeaderProvided_Returns401() - { - // Arrange - LogOutDto dto = new("Refresh"); - using var client = WebApplicationFactory.CreateClient(); - - // Act - var result = await client.PostAsJsonAsync("api/auth/logOut", dto); - - // Assert - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task LogOut_InvalidDto_Returns400() - { - // Arrange - LogOutDto dto = new(""); - using var client = CreateAuthorizedHttpClient(); - - // Act - var result = await client.PostAsJsonAsync("api/auth/logOut", dto); - - // Assert - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } + [Test] + public async Task LogIn_ValidCredentials_SucceedsAndReturnsValidResponse() + { + // Arrange + LogInDto logInDto = new("UserName", "Password"); + LogInResponse expectedResponse = new("Access", "Refresh"); + using var client = WebApplicationFactory.CreateClient(); + + WebApplicationFactory.AuthService + .LogInAsync(logInDto) + .Returns(expectedResponse); + + // Act + var result = await client.PostAsJsonAsync("api/auth/logIn", logInDto); + var body = await result.Content.ReadFromJsonAsync(); + + // Assert success status code + result.EnsureSuccessStatusCode(); + // Assert that response is valid + Assert.That(body, Is.EqualTo(expectedResponse)); + } + + [Test] + public async Task LogIn_InvalidDto_Returns400() + { + // Arrange + LogInDto logInDto = new("", ""); + using var client = WebApplicationFactory.CreateClient(); + + // Act + var result = await client.PostAsJsonAsync("api/auth/logIn", logInDto); + + // Arrange + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task LogIn_InvalidCredentials_Returns401() + { + // Arrange + LogInDto logInDto = new("UserName", "FakePassword"); + using var client = WebApplicationFactory.CreateClient(); + + WebApplicationFactory.AuthService + .LogInAsync(logInDto) + .ReturnsNull(); + + // Act + var result = await client.PostAsJsonAsync("api/auth/logIn", logInDto); + + // Arrange + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task Register_ValidRegisterAttempt_Succeeds() + { + // Arrange + RegisterDto registerDto = new("email@example.com", "UserName", "1st", "last", "M@ster_Pa$5w0r4"); + using var client = WebApplicationFactory.CreateClient(); + + WebApplicationFactory.AuthService + .RegisterAsync(registerDto) + .Returns(RegisterResult.Success()); + + // Act + var result = await client.PostAsJsonAsync("api/auth/register", registerDto); + + // Arrange + result.EnsureSuccessStatusCode(); + } + + [Test] + public async Task Register_InvalidRegisterAttempt_Returns400() + { + // Arrange + RegisterDto registerDto = new("email@example.com", "UserName", "1st", "last", "M@ster_Pa$5w0r4"); + using var client = WebApplicationFactory.CreateClient(); + + WebApplicationFactory.AuthService + .RegisterAsync(registerDto) + .Returns(RegisterResult.Failure([new("$", "error")])); + + // Act + var result = await client.PostAsJsonAsync("api/auth/register", registerDto); + + // Arrange + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task Register_InvalidRegisterDto_Returns400() + { + // Arrange + RegisterDto registerDto = new("invalid email", "~$#", "", "", "weakpassword"); + using var client = WebApplicationFactory.CreateClient(); + + // Act + var result = await client.PostAsJsonAsync("api/auth/register", registerDto); + + // Arrange + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task Refresh_ValidTokens_SucceedsAndReturnsValidResponse() + { + // Arrange + RefreshDto refreshDto = new("Access", "Refresh"); + LogInResponse expectedResponse = new("Access", "Refresh"); + using var client = WebApplicationFactory.CreateClient(); + + WebApplicationFactory.AuthService + .RefreshAsync(refreshDto) + .Returns(expectedResponse); + + // Act + var result = await client.PostAsJsonAsync("api/auth/refresh", refreshDto); + var body = await result.Content.ReadFromJsonAsync(); + + // Assert success status code + result.EnsureSuccessStatusCode(); + // Assert that response is valid + Assert.That(body, Is.EqualTo(expectedResponse)); + } + + [Test] + public async Task Refresh_InvalidRefreshDto_Returns400() + { + // Arrange + RefreshDto refreshDto = new("", " "); + using var client = WebApplicationFactory.CreateClient(); + + // Act + var result = await client.PostAsJsonAsync("api/auth/refresh", refreshDto); + + // Arrange + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task Refresh_InvalidTokens_Returns401() + { + // Arrange + RefreshDto refreshDto = new("InvalidAccess", "InvalidRefresh"); + using var client = WebApplicationFactory.CreateClient(); + + WebApplicationFactory.AuthService + .RefreshAsync(refreshDto) + .ReturnsNull(); + + // Act + var result = await client.PostAsJsonAsync("api/auth/refresh", refreshDto); + + // Arrange + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task LogOut_ValidTokenProvided_Succeeds() + { + // Arrange + LogOutDto dto = new("Refresh"); + string userId = "UserId"; + using var client = CreateAuthorizedHttpClient(userId); + + WebApplicationFactory.AuthService + .LogOutAsync(userId, dto) + .Returns(true); + + // Act + var result = await client.PostAsJsonAsync("api/auth/logOut", dto); + + // Assert + result.EnsureSuccessStatusCode(); + } + + [Test] + public async Task LogOut_InvalidTokenProvided_Returns401() + { + // Arrange + LogOutDto dto = new("Invalid"); + string userId = "UserId"; + using var client = CreateAuthorizedHttpClient(userId); + + WebApplicationFactory.AuthService + .LogOutAsync(userId, dto) + .Returns(false); + + // Act + var result = await client.PostAsJsonAsync("api/auth/logOut", dto); + + // Assert + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task LogOut_NoAuthHeaderProvided_Returns401() + { + // Arrange + LogOutDto dto = new("Refresh"); + using var client = WebApplicationFactory.CreateClient(); + + // Act + var result = await client.PostAsJsonAsync("api/auth/logOut", dto); + + // Assert + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task LogOut_InvalidDto_Returns400() + { + // Arrange + LogOutDto dto = new(""); + using var client = CreateAuthorizedHttpClient(); + + // Act + var result = await client.PostAsJsonAsync("api/auth/logOut", dto); + + // Assert + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } } diff --git a/AdvancedTodoList.IntegrationTests/Endpoints/InvitationLinksEndpointsTests.cs b/AdvancedTodoList.IntegrationTests/Endpoints/InvitationLinksEndpointsTests.cs index 28e92e3..a76132c 100644 --- a/AdvancedTodoList.IntegrationTests/Endpoints/InvitationLinksEndpointsTests.cs +++ b/AdvancedTodoList.IntegrationTests/Endpoints/InvitationLinksEndpointsTests.cs @@ -1,6 +1,6 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; using AdvancedTodoList.IntegrationTests.Fixtures; using System.Net; using System.Net.Http.Json; @@ -10,232 +10,232 @@ namespace AdvancedTodoList.IntegrationTests.Endpoints; [TestFixture] public class InvitationLinksEndpointsTests : EndpointsFixture { - private readonly TodoListContext TestContext = new("TodoListId", TestUserId); - - [Test] - public async Task GetInvitationLinks_ValidCall_SucceedsAndReturnsLinks() - { - // Arrange - PaginationParameters parameters = new(Page: 2, PageSize: 20); - InvitationLinkDto[] invitationLinks = - [ - new(1, "1", DateTime.UtcNow.AddDays(-30)), - new(2, "2", DateTime.UtcNow), - ]; - WebApplicationFactory.InvitationLinksService - .GetInvitationLinksAsync(TestContext, parameters) - .Returns(x => new ServiceResponse>( - ServiceResponseStatus.Success, new(invitationLinks, ((PaginationParameters)x[1]).Page, - ((PaginationParameters)x[1]).PageSize, 22))); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/invitationLinks?page={parameters.Page}&pageSize={parameters.PageSize}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that valid page was returned - var returnedPage = await result.Content.ReadFromJsonAsync>(); - Assert.That(returnedPage, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(returnedPage.PageNumber, Is.EqualTo(parameters.Page)); - Assert.That(returnedPage.PageSize, Is.EqualTo(parameters.PageSize)); - Assert.That(returnedPage.Items, Is.EquivalentTo(invitationLinks)); - }); - } - - [Test] - public async Task GetInvitationLinks_WrongPaginationParams_Returns400() - { - // Arrange - using HttpClient client = CreateAuthorizedHttpClient(); - int page = -1; - int pageSize = 0; - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/invitationLinks?page={page}&pageSize={pageSize}"); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task GetInvitationLinks_NoAuthHeaderProvided_Returns401() - { - // Arrange - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/invitationLinks?page=1&pageSize=20"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task GetInvitationLinks_NotFoundStatus_Returns404() - { - // Arrange - WebApplicationFactory.InvitationLinksService - .GetInvitationLinksAsync(TestContext, Arg.Any()) - .Returns(x => new ServiceResponse>(ServiceResponseStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/invitationLinks?page=1&pageSize=20"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task GetInvitationLinks_ForbiddenStatus_Returns403() - { - // Arrange - WebApplicationFactory.InvitationLinksService - .GetInvitationLinksAsync(TestContext, Arg.Any()) - .Returns(x => new ServiceResponse>(ServiceResponseStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/invitationLinks?page=1&pageSize=20"); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PostInvitationLink_ValidCall_Succeeds() - { - // Arrange - WebApplicationFactory.InvitationLinksService - .CreateAsync(TestContext) - .Returns(new ServiceResponse(ServiceResponseStatus.Success)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsync($"api/todo/{TestContext.TodoListId}/invitationLinks", null); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that create method was called - await WebApplicationFactory.InvitationLinksService - .Received() - .CreateAsync(TestContext); - } - - [Test] - public async Task PostInvitationLink_NotFoundStatus_Returns404() - { - // Arrange - WebApplicationFactory.InvitationLinksService - .CreateAsync(TestContext) - .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsync($"api/todo/{TestContext.TodoListId}/invitationLinks", null); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task PostInvitationLink_ForbiddenStatus_Returns403() - { - // Arrange - WebApplicationFactory.InvitationLinksService - .CreateAsync(TestContext) - .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsync($"api/todo/{TestContext.TodoListId}/invitationLinks", null); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PostInvitationLink_NoAuthHeaderProvided_Returns401() - { - // Arrange - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PostAsync($"api/todo/{TestContext.TodoListId}/invitationLinks", null); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task DeleteInvitationLink_ValidCall_Succeeds() - { - // Arrange - int testInvitationLinkId = 504030; - WebApplicationFactory.InvitationLinksService - .DeleteAsync(TestContext, testInvitationLinkId) - .Returns(ServiceResponseStatus.Success); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/invitationLinks/{testInvitationLinkId}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that delete was called - await WebApplicationFactory.InvitationLinksService - .Received() - .DeleteAsync(TestContext, testInvitationLinkId); - } - - [Test] - public async Task DeleteInvitationLink_NotFoundStatus_Returns404() - { - // Arrange - int testInvitationLinkId = 504030; - WebApplicationFactory.InvitationLinksService - .DeleteAsync(TestContext, testInvitationLinkId) - .Returns(ServiceResponseStatus.NotFound); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/invitationLinks/{testInvitationLinkId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task DeleteInvitationLink_ForbiddenStatus_Returns403() - { - // Arrange - int testInvitationLinkId = 504030; - WebApplicationFactory.InvitationLinksService - .DeleteAsync(TestContext, testInvitationLinkId) - .Returns(ServiceResponseStatus.Forbidden); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/invitationLinks/{testInvitationLinkId}"); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task DeleteInvitationLink_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testInvitationLinkId = 504030; - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/invitationLinks/{testInvitationLinkId}"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } + private readonly TodoListContext TestContext = new("TodoListId", TestUserId); + + [Test] + public async Task GetInvitationLinks_ValidCall_SucceedsAndReturnsLinks() + { + // Arrange + PaginationParameters parameters = new(Page: 2, PageSize: 20); + InvitationLinkDto[] invitationLinks = + [ + new(1, "1", DateTime.UtcNow.AddDays(-30)), + new(2, "2", DateTime.UtcNow), + ]; + WebApplicationFactory.InvitationLinksService + .GetInvitationLinksAsync(TestContext, parameters) + .Returns(x => new ServiceResponse>( + ServiceResponseStatus.Success, new(invitationLinks, ((PaginationParameters)x[1]).Page, + ((PaginationParameters)x[1]).PageSize, 22))); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/invitationLinks?page={parameters.Page}&pageSize={parameters.PageSize}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that valid page was returned + var returnedPage = await result.Content.ReadFromJsonAsync>(); + Assert.That(returnedPage, Is.Not.Null); + using (Assert.EnterMultipleScope()) + { + Assert.That(returnedPage.PageNumber, Is.EqualTo(parameters.Page)); + Assert.That(returnedPage.PageSize, Is.EqualTo(parameters.PageSize)); + Assert.That(returnedPage.Items, Is.EquivalentTo(invitationLinks)); + } + } + + [Test] + public async Task GetInvitationLinks_WrongPaginationParams_Returns400() + { + // Arrange + using HttpClient client = CreateAuthorizedHttpClient(); + int page = -1; + int pageSize = 0; + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/invitationLinks?page={page}&pageSize={pageSize}"); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task GetInvitationLinks_NoAuthHeaderProvided_Returns401() + { + // Arrange + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/invitationLinks?page=1&pageSize=20"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task GetInvitationLinks_NotFoundStatus_Returns404() + { + // Arrange + WebApplicationFactory.InvitationLinksService + .GetInvitationLinksAsync(TestContext, Arg.Any()) + .Returns(x => new ServiceResponse>(ServiceResponseStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/invitationLinks?page=1&pageSize=20"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task GetInvitationLinks_ForbiddenStatus_Returns403() + { + // Arrange + WebApplicationFactory.InvitationLinksService + .GetInvitationLinksAsync(TestContext, Arg.Any()) + .Returns(x => new ServiceResponse>(ServiceResponseStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/invitationLinks?page=1&pageSize=20"); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PostInvitationLink_ValidCall_Succeeds() + { + // Arrange + WebApplicationFactory.InvitationLinksService + .CreateAsync(TestContext) + .Returns(new ServiceResponse(ServiceResponseStatus.Success)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsync($"api/todo/{TestContext.TodoListId}/invitationLinks", null); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that create method was called + await WebApplicationFactory.InvitationLinksService + .Received() + .CreateAsync(TestContext); + } + + [Test] + public async Task PostInvitationLink_NotFoundStatus_Returns404() + { + // Arrange + WebApplicationFactory.InvitationLinksService + .CreateAsync(TestContext) + .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsync($"api/todo/{TestContext.TodoListId}/invitationLinks", null); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task PostInvitationLink_ForbiddenStatus_Returns403() + { + // Arrange + WebApplicationFactory.InvitationLinksService + .CreateAsync(TestContext) + .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsync($"api/todo/{TestContext.TodoListId}/invitationLinks", null); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PostInvitationLink_NoAuthHeaderProvided_Returns401() + { + // Arrange + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PostAsync($"api/todo/{TestContext.TodoListId}/invitationLinks", null); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task DeleteInvitationLink_ValidCall_Succeeds() + { + // Arrange + int testInvitationLinkId = 504030; + WebApplicationFactory.InvitationLinksService + .DeleteAsync(TestContext, testInvitationLinkId) + .Returns(ServiceResponseStatus.Success); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/invitationLinks/{testInvitationLinkId}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that delete was called + await WebApplicationFactory.InvitationLinksService + .Received() + .DeleteAsync(TestContext, testInvitationLinkId); + } + + [Test] + public async Task DeleteInvitationLink_NotFoundStatus_Returns404() + { + // Arrange + int testInvitationLinkId = 504030; + WebApplicationFactory.InvitationLinksService + .DeleteAsync(TestContext, testInvitationLinkId) + .Returns(ServiceResponseStatus.NotFound); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/invitationLinks/{testInvitationLinkId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task DeleteInvitationLink_ForbiddenStatus_Returns403() + { + // Arrange + int testInvitationLinkId = 504030; + WebApplicationFactory.InvitationLinksService + .DeleteAsync(TestContext, testInvitationLinkId) + .Returns(ServiceResponseStatus.Forbidden); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/invitationLinks/{testInvitationLinkId}"); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task DeleteInvitationLink_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testInvitationLinkId = 504030; + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/invitationLinks/{testInvitationLinkId}"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } } diff --git a/AdvancedTodoList.IntegrationTests/Endpoints/JoinTodoListEndpointsTests.cs b/AdvancedTodoList.IntegrationTests/Endpoints/JoinTodoListEndpointsTests.cs index 1300777..6bb46e2 100644 --- a/AdvancedTodoList.IntegrationTests/Endpoints/JoinTodoListEndpointsTests.cs +++ b/AdvancedTodoList.IntegrationTests/Endpoints/JoinTodoListEndpointsTests.cs @@ -1,5 +1,5 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Services; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.IntegrationTests.Fixtures; using System.Net; using System.Net.Http.Json; @@ -9,88 +9,88 @@ namespace AdvancedTodoList.IntegrationTests.Endpoints; [TestFixture] public class JoinTodoListEndpointsTests : EndpointsFixture { - private const string TestCallerId = TestUserId; - private const string TestInvitationLink = "abc"; - - [Test] - public async Task JoinByInvitationLink_ValidCall_Succeeds() - { - // Arrange - TodoListMemberMinimalViewDto expectedDto = new(1, TestCallerId, "to-do-list-id", null); - JoinByInvitationLinkResult response = new(JoinByInvitationLinkStatus.Success, expectedDto); - WebApplicationFactory.InvitationLinksService - .JoinAsync(TestCallerId, TestInvitationLink) - .Returns(response); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsync($"api/todo/join/{TestInvitationLink}", null); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that expected dto was returned - var returnedDto = await result.Content.ReadFromJsonAsync(); - Assert.That(returnedDto, Is.EqualTo(expectedDto)); - } - - [Test] - public async Task JoinByInvitationLink_ExpiredLinkStatus_Fails() - { - // Arrange - WebApplicationFactory.InvitationLinksService - .JoinAsync(TestCallerId, TestInvitationLink) - .Returns(new JoinByInvitationLinkResult(JoinByInvitationLinkStatus.Expired)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsync($"api/todo/join/{TestInvitationLink}", null); - - // Assert that response code indicates failure - Assert.That(result.IsSuccessStatusCode, Is.False, "Status code that indicated failure was expected."); - } - - [Test] - public async Task JoinByInvitationLink_UserIsAlreadyMemberStatus_Fails() - { - // Arrange - WebApplicationFactory.InvitationLinksService - .JoinAsync(TestCallerId, TestInvitationLink) - .Returns(new JoinByInvitationLinkResult(JoinByInvitationLinkStatus.UserIsAlreadyMember)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsync($"api/todo/join/{TestInvitationLink}", null); - - // Assert that response code indicates failure - Assert.That(result.IsSuccessStatusCode, Is.False, "Status code that indicated failure was expected."); - } - - [Test] - public async Task JoinByInvitationLink_NotFoundStatus_Returns404() - { - // Arrange - WebApplicationFactory.InvitationLinksService - .JoinAsync(TestCallerId, TestInvitationLink) - .Returns(new JoinByInvitationLinkResult(JoinByInvitationLinkStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsync($"api/todo/join/{TestInvitationLink}", null); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task JoinByInvitationLink_NoAuthHeaderProvided_Returns401() - { - // Arrange - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PostAsync($"api/todo/join/{TestInvitationLink}", null); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } + private const string TestCallerId = TestUserId; + private const string TestInvitationLink = "abc"; + + [Test] + public async Task JoinByInvitationLink_ValidCall_Succeeds() + { + // Arrange + TodoListMemberMinimalViewDto expectedDto = new(1, TestCallerId, "to-do-list-id", null); + JoinByInvitationLinkResult response = new(JoinByInvitationLinkStatus.Success, expectedDto); + WebApplicationFactory.InvitationLinksService + .JoinAsync(TestCallerId, TestInvitationLink) + .Returns(response); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsync($"api/todo/join/{TestInvitationLink}", null); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that expected dto was returned + var returnedDto = await result.Content.ReadFromJsonAsync(); + Assert.That(returnedDto, Is.EqualTo(expectedDto)); + } + + [Test] + public async Task JoinByInvitationLink_ExpiredLinkStatus_Fails() + { + // Arrange + WebApplicationFactory.InvitationLinksService + .JoinAsync(TestCallerId, TestInvitationLink) + .Returns(new JoinByInvitationLinkResult(JoinByInvitationLinkStatus.Expired)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsync($"api/todo/join/{TestInvitationLink}", null); + + // Assert that response code indicates failure + Assert.That(result.IsSuccessStatusCode, Is.False, "Status code that indicated failure was expected."); + } + + [Test] + public async Task JoinByInvitationLink_UserIsAlreadyMemberStatus_Fails() + { + // Arrange + WebApplicationFactory.InvitationLinksService + .JoinAsync(TestCallerId, TestInvitationLink) + .Returns(new JoinByInvitationLinkResult(JoinByInvitationLinkStatus.UserIsAlreadyMember)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsync($"api/todo/join/{TestInvitationLink}", null); + + // Assert that response code indicates failure + Assert.That(result.IsSuccessStatusCode, Is.False, "Status code that indicated failure was expected."); + } + + [Test] + public async Task JoinByInvitationLink_NotFoundStatus_Returns404() + { + // Arrange + WebApplicationFactory.InvitationLinksService + .JoinAsync(TestCallerId, TestInvitationLink) + .Returns(new JoinByInvitationLinkResult(JoinByInvitationLinkStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsync($"api/todo/join/{TestInvitationLink}", null); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task JoinByInvitationLink_NoAuthHeaderProvided_Returns401() + { + // Arrange + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PostAsync($"api/todo/join/{TestInvitationLink}", null); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } } diff --git a/AdvancedTodoList.IntegrationTests/Endpoints/TodoItemCategoriesEndpointsTests.cs b/AdvancedTodoList.IntegrationTests/Endpoints/TodoItemCategoriesEndpointsTests.cs index 5c09b9d..fc4f6f2 100644 --- a/AdvancedTodoList.IntegrationTests/Endpoints/TodoItemCategoriesEndpointsTests.cs +++ b/AdvancedTodoList.IntegrationTests/Endpoints/TodoItemCategoriesEndpointsTests.cs @@ -1,6 +1,6 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; using AdvancedTodoList.IntegrationTests.Fixtures; using System.Net; using System.Net.Http.Json; @@ -10,409 +10,409 @@ namespace AdvancedTodoList.IntegrationTests.Endpoints; [TestFixture] public class TodoItemCategoriesEndpointsTests : EndpointsFixture { - private readonly TodoListContext TestContext = new("TodoListId", TestUserId); - - [Test] - public async Task GetTodoItemCategoriesAsync_ValidCall_SucceedsAndReturnsItems() - { - // Arrange - PaginationParameters parameters = new(Page: 2, PageSize: 20); - TodoItemCategoryViewDto[] categories = - [ - new(1, "1"), - new(2, "2"), - ]; - string name = "n"; - WebApplicationFactory.TodoItemCategoriesService - .GetCategoriesOfListAsync(TestContext, parameters, name) - .Returns(x => new ServiceResponse>( - ServiceResponseStatus.Success, new(categories, ((PaginationParameters)x[1]).Page, - ((PaginationParameters)x[1]).PageSize, 22))); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories?page={parameters.Page}&pageSize={parameters.PageSize}&name={name}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that valid page was returned - var returnedPage = await result.Content.ReadFromJsonAsync>(); - Assert.That(returnedPage, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(returnedPage.PageNumber, Is.EqualTo(parameters.Page)); - Assert.That(returnedPage.PageSize, Is.EqualTo(parameters.PageSize)); - Assert.That(returnedPage.Items, Is.EquivalentTo(categories)); - }); - } - - [Test] - public async Task GetTodoItemCategoriesAsync_WrongPaginationParams_Returns400() - { - // Arrange - using HttpClient client = CreateAuthorizedHttpClient(); - int page = -1; - int pageSize = 0; - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories?page={page}&pageSize={pageSize}"); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task GetTodoItemCategoriesAsync_NoAuthHeaderProvided_Returns401() - { - // Arrange - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories?page=1&pageSize=20"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task GetTodoItemCategoriesAsync_NotFoundStatus_Returns404() - { - // Arrange - WebApplicationFactory.TodoItemCategoriesService - .GetCategoriesOfListAsync(TestContext, Arg.Any()) - .Returns(x => new ServiceResponse>(ServiceResponseStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories?page=1&pageSize=20"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task GetTodoItemCategoriesAsync_ForbiddenStatus_Returns403() - { - // Arrange - WebApplicationFactory.TodoItemCategoriesService - .GetCategoriesOfListAsync(TestContext, Arg.Any()) - .Returns(x => new ServiceResponse>(ServiceResponseStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories?page=1&pageSize=20"); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task GetTodoItemCategoryById_ValidCall_ReturnsElement() - { - // Arrange - int testCategoryId = 777; - TodoItemCategoryViewDto testDto = new(testCategoryId, "Name"); - - WebApplicationFactory.TodoItemCategoriesService - .GetByIdAsync(TestContext, testCategoryId) - .Returns(new ServiceResponse(ServiceResponseStatus.Success, testDto)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that valid object was returned - var returnedDto = await result.Content.ReadFromJsonAsync(); - Assert.That(returnedDto, Is.EqualTo(testDto)); - } - - [Test] - public async Task GetTodoItemCategoryById_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testCategoryId = 777; - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task GetTodoItemCategoryById_NotFoundStatus_Returns404() - { - // Arrange - int testCategoryId = 777; - WebApplicationFactory.TodoItemCategoriesService - .GetByIdAsync(TestContext, testCategoryId) - .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task GetTodoItemCategoryById_ForbiddenStatus_Returns404() - { - // Arrange - int testCategoryId = 777; - WebApplicationFactory.TodoItemCategoriesService - .GetByIdAsync(TestContext, testCategoryId) - .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PostTodoItemCategory_ValidCall_Succeeds() - { - // Arrange - TodoItemCategoryCreateDto dto = new("Name"); - WebApplicationFactory.TodoItemCategoriesService - .CreateAsync(TestContext, dto) - .Returns(new ServiceResponse(ServiceResponseStatus.Success)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories", dto); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that create method was called - await WebApplicationFactory.TodoItemCategoriesService - .Received() - .CreateAsync(TestContext, dto); - } - - [Test] - public async Task PostTodoItemCategory_NotFoundStatus_Returns404() - { - // Arrange - TodoItemCategoryCreateDto dto = new("Name"); - WebApplicationFactory.TodoItemCategoriesService - .CreateAsync(TestContext, dto) - .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories", dto); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task PostTodoItemCategory_ForbiddenStatus_Returns403() - { - // Arrange - TodoItemCategoryCreateDto dto = new("Name"); - WebApplicationFactory.TodoItemCategoriesService - .CreateAsync(TestContext, dto) - .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories", dto); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PostTodoItemCategory_NoAuthHeaderProvided_Returns401() - { - // Arrange - TodoItemCategoryCreateDto dto = new("Joker"); - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories", dto); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task PostTodoItemCategory_InvalidDto_Returns400() - { - // Arrange - TodoItemCategoryCreateDto invalidDto = new(string.Empty); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories", invalidDto); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task PutTodoItemCategory_ValidCall_Succeeds() - { - // Arrange - int testCategoryId = 891349; - TodoItemCategoryCreateDto dto = new("New name"); - WebApplicationFactory.TodoItemCategoriesService - .EditAsync(TestContext, testCategoryId, dto) - .Returns(ServiceResponseStatus.Success); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}", dto); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that edit was called - await WebApplicationFactory.TodoItemCategoriesService - .Received() - .EditAsync(TestContext, testCategoryId, dto); - } - - [Test] - public async Task PutTodoItemCategory_NotFoundStatus_Returns404() - { - // Arrange - int testCategoryId = 12412; - TodoItemCategoryCreateDto dto = new("New name"); - WebApplicationFactory.TodoItemCategoriesService - .EditAsync(TestContext, testCategoryId, dto) - .Returns(ServiceResponseStatus.NotFound); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}", dto); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task PutTodoItemCategory_ForbiddenStatus_Returns403() - { - // Arrange - int testCategoryId = 12412; - TodoItemCategoryCreateDto dto = new("New name"); - WebApplicationFactory.TodoItemCategoriesService - .EditAsync(TestContext, testCategoryId, dto) - .Returns(ServiceResponseStatus.Forbidden); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}", dto); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PutTodoItemCategory_InvalidDto_Returns400() - { - // Arrange - int testCategoryId = 891349; - TodoItemCategoryCreateDto invalidDto = new(string.Empty); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}", invalidDto); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task PutTodoItemCategory_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testCategoryId = 891349; - TodoItemCategoryCreateDto dto = new("New name"); - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}", dto); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task DeleteTodoItemCategory_ValidCall_Succeeds() - { - // Arrange - int testCategoryId = 504030; - WebApplicationFactory.TodoItemCategoriesService - .DeleteAsync(TestContext, testCategoryId) - .Returns(ServiceResponseStatus.Success); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that delete was called - await WebApplicationFactory.TodoItemCategoriesService - .Received() - .DeleteAsync(TestContext, testCategoryId); - } - - [Test] - public async Task DeleteTodoItemCategory_NotFoundStatus_Returns404() - { - // Arrange - int testCategoryId = 504030; - WebApplicationFactory.TodoItemCategoriesService - .DeleteAsync(TestContext, testCategoryId) - .Returns(ServiceResponseStatus.NotFound); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task DeleteTodoItemCategory_ForbiddenStatus_Returns403() - { - // Arrange - int testCategoryId = 504030; - WebApplicationFactory.TodoItemCategoriesService - .DeleteAsync(TestContext, testCategoryId) - .Returns(ServiceResponseStatus.Forbidden); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task DeleteTodoItemCategory_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testCategoryId = 504030; - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } + private readonly TodoListContext TestContext = new("TodoListId", TestUserId); + + [Test] + public async Task GetTodoItemCategoriesAsync_ValidCall_SucceedsAndReturnsItems() + { + // Arrange + PaginationParameters parameters = new(Page: 2, PageSize: 20); + TodoItemCategoryViewDto[] categories = + [ + new(1, "1"), + new(2, "2"), + ]; + string name = "n"; + WebApplicationFactory.TodoItemCategoriesService + .GetCategoriesOfListAsync(TestContext, parameters, name) + .Returns(x => new ServiceResponse>( + ServiceResponseStatus.Success, new(categories, ((PaginationParameters)x[1]).Page, + ((PaginationParameters)x[1]).PageSize, 22))); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories?page={parameters.Page}&pageSize={parameters.PageSize}&name={name}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that valid page was returned + var returnedPage = await result.Content.ReadFromJsonAsync>(); + Assert.That(returnedPage, Is.Not.Null); + using (Assert.EnterMultipleScope()) + { + Assert.That(returnedPage.PageNumber, Is.EqualTo(parameters.Page)); + Assert.That(returnedPage.PageSize, Is.EqualTo(parameters.PageSize)); + Assert.That(returnedPage.Items, Is.EquivalentTo(categories)); + } + } + + [Test] + public async Task GetTodoItemCategoriesAsync_WrongPaginationParams_Returns400() + { + // Arrange + using HttpClient client = CreateAuthorizedHttpClient(); + int page = -1; + int pageSize = 0; + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories?page={page}&pageSize={pageSize}"); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task GetTodoItemCategoriesAsync_NoAuthHeaderProvided_Returns401() + { + // Arrange + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories?page=1&pageSize=20"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task GetTodoItemCategoriesAsync_NotFoundStatus_Returns404() + { + // Arrange + WebApplicationFactory.TodoItemCategoriesService + .GetCategoriesOfListAsync(TestContext, Arg.Any()) + .Returns(x => new ServiceResponse>(ServiceResponseStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories?page=1&pageSize=20"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task GetTodoItemCategoriesAsync_ForbiddenStatus_Returns403() + { + // Arrange + WebApplicationFactory.TodoItemCategoriesService + .GetCategoriesOfListAsync(TestContext, Arg.Any()) + .Returns(x => new ServiceResponse>(ServiceResponseStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories?page=1&pageSize=20"); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task GetTodoItemCategoryById_ValidCall_ReturnsElement() + { + // Arrange + int testCategoryId = 777; + TodoItemCategoryViewDto testDto = new(testCategoryId, "Name"); + + WebApplicationFactory.TodoItemCategoriesService + .GetByIdAsync(TestContext, testCategoryId) + .Returns(new ServiceResponse(ServiceResponseStatus.Success, testDto)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that valid object was returned + var returnedDto = await result.Content.ReadFromJsonAsync(); + Assert.That(returnedDto, Is.EqualTo(testDto)); + } + + [Test] + public async Task GetTodoItemCategoryById_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testCategoryId = 777; + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task GetTodoItemCategoryById_NotFoundStatus_Returns404() + { + // Arrange + int testCategoryId = 777; + WebApplicationFactory.TodoItemCategoriesService + .GetByIdAsync(TestContext, testCategoryId) + .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task GetTodoItemCategoryById_ForbiddenStatus_Returns404() + { + // Arrange + int testCategoryId = 777; + WebApplicationFactory.TodoItemCategoriesService + .GetByIdAsync(TestContext, testCategoryId) + .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PostTodoItemCategory_ValidCall_Succeeds() + { + // Arrange + TodoItemCategoryCreateDto dto = new("Name"); + WebApplicationFactory.TodoItemCategoriesService + .CreateAsync(TestContext, dto) + .Returns(new ServiceResponse(ServiceResponseStatus.Success)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories", dto); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that create method was called + await WebApplicationFactory.TodoItemCategoriesService + .Received() + .CreateAsync(TestContext, dto); + } + + [Test] + public async Task PostTodoItemCategory_NotFoundStatus_Returns404() + { + // Arrange + TodoItemCategoryCreateDto dto = new("Name"); + WebApplicationFactory.TodoItemCategoriesService + .CreateAsync(TestContext, dto) + .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories", dto); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task PostTodoItemCategory_ForbiddenStatus_Returns403() + { + // Arrange + TodoItemCategoryCreateDto dto = new("Name"); + WebApplicationFactory.TodoItemCategoriesService + .CreateAsync(TestContext, dto) + .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories", dto); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PostTodoItemCategory_NoAuthHeaderProvided_Returns401() + { + // Arrange + TodoItemCategoryCreateDto dto = new("Joker"); + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories", dto); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task PostTodoItemCategory_InvalidDto_Returns400() + { + // Arrange + TodoItemCategoryCreateDto invalidDto = new(string.Empty); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories", invalidDto); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task PutTodoItemCategory_ValidCall_Succeeds() + { + // Arrange + int testCategoryId = 891349; + TodoItemCategoryCreateDto dto = new("New name"); + WebApplicationFactory.TodoItemCategoriesService + .EditAsync(TestContext, testCategoryId, dto) + .Returns(ServiceResponseStatus.Success); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}", dto); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that edit was called + await WebApplicationFactory.TodoItemCategoriesService + .Received() + .EditAsync(TestContext, testCategoryId, dto); + } + + [Test] + public async Task PutTodoItemCategory_NotFoundStatus_Returns404() + { + // Arrange + int testCategoryId = 12412; + TodoItemCategoryCreateDto dto = new("New name"); + WebApplicationFactory.TodoItemCategoriesService + .EditAsync(TestContext, testCategoryId, dto) + .Returns(ServiceResponseStatus.NotFound); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}", dto); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task PutTodoItemCategory_ForbiddenStatus_Returns403() + { + // Arrange + int testCategoryId = 12412; + TodoItemCategoryCreateDto dto = new("New name"); + WebApplicationFactory.TodoItemCategoriesService + .EditAsync(TestContext, testCategoryId, dto) + .Returns(ServiceResponseStatus.Forbidden); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}", dto); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PutTodoItemCategory_InvalidDto_Returns400() + { + // Arrange + int testCategoryId = 891349; + TodoItemCategoryCreateDto invalidDto = new(string.Empty); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}", invalidDto); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task PutTodoItemCategory_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testCategoryId = 891349; + TodoItemCategoryCreateDto dto = new("New name"); + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}", dto); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task DeleteTodoItemCategory_ValidCall_Succeeds() + { + // Arrange + int testCategoryId = 504030; + WebApplicationFactory.TodoItemCategoriesService + .DeleteAsync(TestContext, testCategoryId) + .Returns(ServiceResponseStatus.Success); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that delete was called + await WebApplicationFactory.TodoItemCategoriesService + .Received() + .DeleteAsync(TestContext, testCategoryId); + } + + [Test] + public async Task DeleteTodoItemCategory_NotFoundStatus_Returns404() + { + // Arrange + int testCategoryId = 504030; + WebApplicationFactory.TodoItemCategoriesService + .DeleteAsync(TestContext, testCategoryId) + .Returns(ServiceResponseStatus.NotFound); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task DeleteTodoItemCategory_ForbiddenStatus_Returns403() + { + // Arrange + int testCategoryId = 504030; + WebApplicationFactory.TodoItemCategoriesService + .DeleteAsync(TestContext, testCategoryId) + .Returns(ServiceResponseStatus.Forbidden); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task DeleteTodoItemCategory_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testCategoryId = 504030; + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/categories/{testCategoryId}"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } } diff --git a/AdvancedTodoList.IntegrationTests/Endpoints/TodoItemsEndpointsTests.cs b/AdvancedTodoList.IntegrationTests/Endpoints/TodoItemsEndpointsTests.cs index 48aa74a..e2fe31e 100644 --- a/AdvancedTodoList.IntegrationTests/Endpoints/TodoItemsEndpointsTests.cs +++ b/AdvancedTodoList.IntegrationTests/Endpoints/TodoItemsEndpointsTests.cs @@ -1,8 +1,8 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Models.TodoLists; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Specifications; +using AdvancedTodoList.Core.Specifications.Filters; using AdvancedTodoList.IntegrationTests.Fixtures; using System.Net; using System.Net.Http.Json; @@ -15,551 +15,551 @@ namespace AdvancedTodoList.IntegrationTests.Endpoints; [TestFixture] public class TodoItemsEndpointsTests : EndpointsFixture { - private readonly TodoListContext TestContext = new("TodoListId", TestUserId); - - [Test] - public async Task GetListItemsAsync_ValidCall_SucceedsAndReturnsItems() - { - // Arrange - PaginationParameters parameters = new(Page: 2, PageSize: 20); - TodoItemPreviewDto[] items = - [ - new(124013, TestContext.TodoListId, "1", null, TodoItemState.Active, 3, null!, null), - new(124124, TestContext.TodoListId, "2", null, TodoItemState.Completed, 3, null!, null) - ]; - WebApplicationFactory.TodoItemsService - .GetItemsOfListAsync(TestContext, parameters, Arg.Any()) - .Returns(x => new ServiceResponse>( - ServiceResponseStatus.Success, new(items, ((PaginationParameters)x[1]).Page, - ((PaginationParameters)x[1]).PageSize, 22))); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items?page={parameters.Page}&pageSize={parameters.PageSize}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that valid page was returned - var returnedPage = await result.Content.ReadFromJsonAsync>(); - Assert.That(returnedPage, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(returnedPage.PageNumber, Is.EqualTo(parameters.Page)); - Assert.That(returnedPage.PageSize, Is.EqualTo(parameters.PageSize)); - Assert.That(returnedPage.Items, Is.EquivalentTo(items)); - }); - } - - [Test] - public async Task GetListItemsAsync_WrongPaginationParams_Returns400() - { - // Arrange - using HttpClient client = CreateAuthorizedHttpClient(); - int page = -1; - int pageSize = 0; - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items?page={page}&pageSize={pageSize}"); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task GetListItemsAsync_NoAuthHeaderProvided_Returns401() - { - // Arrange - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items?page=1&pageSize=20"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task GetListItemsAsync_NotFoundStatus_Returns404() - { - // Arrange - WebApplicationFactory.TodoItemsService - .GetItemsOfListAsync(TestContext, Arg.Any(), Arg.Any()) - .Returns(new ServiceResponse>(ServiceResponseStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items?page=1&pageSize=20"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task GetListItemsAsync_ForbiddenStatus_Returns403() - { - // Arrange - WebApplicationFactory.TodoItemsService - .GetItemsOfListAsync(TestContext, Arg.Any(), Arg.Any()) - .Returns(new ServiceResponse>(ServiceResponseStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items?page=1&pageSize=20"); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task GetTodoItemById_ValidCall_ReturnsElement() - { - // Arrange - int testItemId = 777; - TodoItemGetByIdDto testDto = new(testItemId, TestContext.TodoListId, "Test todo item", - "...", null, TodoItemState.Active, 5, new("Id", "User"), null); - - WebApplicationFactory.TodoItemsService - .GetByIdAsync(TestContext, testItemId) - .Returns(new ServiceResponse(ServiceResponseStatus.Success, testDto)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that valid object was returned - var returnedDto = await result.Content.ReadFromJsonAsync(); - Assert.That(returnedDto, Is.EqualTo(testDto)); - } - - [Test] - public async Task GetTodoItemById_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testItemId = 777; - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task GetTodoItemById_NotFoundStatus_Returns404() - { - // Arrange - int testItemId = 777; - WebApplicationFactory.TodoItemsService - .GetByIdAsync(TestContext, testItemId) - .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task GetTodoItemById_ForbiddenStatus_Returns403() - { - // Arrange - int testItemId = 777; - WebApplicationFactory.TodoItemsService - .GetByIdAsync(TestContext, testItemId) - .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PostTodoItem_ValidCall_Succeeds() - { - // Arrange - TodoItemCreateDto dto = new("Item", "...", DateTime.MaxValue, 9, null); - WebApplicationFactory.TodoItemsService - .CreateAsync(TestContext, dto) - .Returns(new TodoItemsServiceResponse(TodoItemsServiceStatus.Success)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/items", dto); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that create method was called - await WebApplicationFactory.TodoItemsService - .Received() - .CreateAsync(TestContext, dto); - } - - [Test] - public async Task PostTodoItem_NotFoundStatus_Returns404() - { - // Arrange - TodoItemCreateDto dto = new("Item", "...", DateTime.MaxValue, 9, null); - WebApplicationFactory.TodoItemsService - .CreateAsync(TestContext, dto) - .Returns(new TodoItemsServiceResponse(TodoItemsServiceStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/items", dto); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task PostTodoItem_ForbiddenStatus_Returns403() - { - // Arrange - TodoItemCreateDto dto = new("Item", "...", DateTime.MaxValue, 9, null); - WebApplicationFactory.TodoItemsService - .CreateAsync(TestContext, dto) - .Returns(new TodoItemsServiceResponse(TodoItemsServiceStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/items", dto); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PostTodoItem_InvalidCategoryIdStatus_Returns400() - { - // Arrange - TodoItemCreateDto dto = new("Item", "...", DateTime.MaxValue, 9, null); - WebApplicationFactory.TodoItemsService - .CreateAsync(TestContext, dto) - .Returns(new TodoItemsServiceResponse(TodoItemsServiceStatus.InvalidCategoryId)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/items", dto); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task PostTodoItem_NoAuthHeaderProvided_Returns401() - { - // Arrange - TodoItemCreateDto dto = new("Name", "Desc", DateTime.UtcNow.AddDays(5), 9, null); - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/items", dto); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task PostTodoItem_InvalidDto_Returns400() - { - // Arrange - TodoItemCreateDto invalidDto = new(string.Empty, string.Empty, DateTime.MinValue, 9, null); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/items", invalidDto); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task PutTodoItem_ValidCall_Succeeds() - { - // Arrange - int testItemId = 891349; - TodoItemCreateDto dto = new("Do nothing for entire day", "...", DateTime.MaxValue, 9, null); - WebApplicationFactory.TodoItemsService - .EditAsync(TestContext, testItemId, dto) - .Returns(TodoItemsServiceStatus.Success); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", dto); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that edit was called - await WebApplicationFactory.TodoItemsService - .Received() - .EditAsync(TestContext, testItemId, dto); - } - - [Test] - public async Task PutTodoItem_NotFoundStatus_Returns404() - { - // Arrange - int testItemId = 12412; - TodoItemCreateDto dto = new("New name", "New description", null, 5, null); - WebApplicationFactory.TodoItemsService - .EditAsync(TestContext, testItemId, dto) - .Returns(TodoItemsServiceStatus.NotFound); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", dto); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task PutTodoItem_ForbiddenStatus_Returns403() - { - // Arrange - int testItemId = 12412; - TodoItemCreateDto dto = new("New name", "New description", null, 5, null); - WebApplicationFactory.TodoItemsService - .EditAsync(TestContext, testItemId, dto) - .Returns(TodoItemsServiceStatus.Forbidden); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", dto); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PutTodoItem_InvalidCategoryIdStatus_Returns400() - { - // Arrange - int testItemId = 12412; - TodoItemCreateDto dto = new("New name", "New description", null, 5, null); - WebApplicationFactory.TodoItemsService - .EditAsync(TestContext, testItemId, dto) - .Returns(TodoItemsServiceStatus.InvalidCategoryId); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", dto); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task PutTodoItem_InvalidDto_Returns400() - { - // Arrange - int testItemId = 891349; - TodoItemCreateDto invalidDto = new(string.Empty, string.Empty, DateTime.MinValue, 5, null); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", invalidDto); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task PutTodoItem_InvalidCategoryId_Returns400() - { - // Arrange - int testItemId = 891349; - TodoItemCreateDto dto = new("New name", "New description", null, 5, 2); - WebApplicationFactory.TodoItemsService - .EditAsync(TestContext, testItemId, dto) - .Returns(TodoItemsServiceStatus.InvalidCategoryId); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", dto); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task PutTodoItem_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testItemId = 891349; - TodoItemCreateDto dto = new("Name", "Desc", DateTime.UtcNow.AddDays(5), 5, null); - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", dto); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task PutTodoItemState_ElementExists_Succeeds() - { - // Arrange - int testItemId = 891349; - TodoItemUpdateStateDto dto = new(TodoItemState.Completed); - WebApplicationFactory.TodoItemsService - .UpdateStateAsync(TestContext, testItemId, dto) - .Returns(ServiceResponseStatus.Success); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}/state", dto); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that edit was called - await WebApplicationFactory.TodoItemsService - .Received() - .UpdateStateAsync(TestContext, testItemId, dto); - } - - [Test] - public async Task PutTodoItemState_NotFoundStatus_Returns404() - { - // Arrange - int testItemId = 12412; - TodoItemUpdateStateDto dto = new(TodoItemState.Skipped); - WebApplicationFactory.TodoItemsService - .UpdateStateAsync(TestContext, testItemId, dto) - .Returns(ServiceResponseStatus.NotFound); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}/state", dto); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task PutTodoItemState_ForbiddenStatus_Returns403() - { - // Arrange - int testItemId = 12412; - TodoItemUpdateStateDto dto = new(TodoItemState.Skipped); - WebApplicationFactory.TodoItemsService - .UpdateStateAsync(TestContext, testItemId, dto) - .Returns(ServiceResponseStatus.Forbidden); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}/state", dto); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PutTodoItemState_InvalidDto_Returns400() - { - // Arrange - int testItemId = 891349; - TodoItemUpdateStateDto dto = new((TodoItemState)213); - - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}/state", dto); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task PutTodoItemState_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testItemId = 891349; - TodoItemUpdateStateDto dto = new(TodoItemState.Active); - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}/state", dto); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task DeleteTodoItem_ElementExists_Succeeds() - { - // Arrange - int testItemId = 504030; - WebApplicationFactory.TodoItemsService - .DeleteAsync(TestContext, testItemId) - .Returns(ServiceResponseStatus.Success); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that delete was called - await WebApplicationFactory.TodoItemsService - .Received() - .DeleteAsync(TestContext, testItemId); - } - - [Test] - public async Task DeleteTodoItem_NotFound_Returns404() - { - // Arrange - int testItemId = 504030; - WebApplicationFactory.TodoItemsService - .DeleteAsync(TestContext, testItemId) - .Returns(ServiceResponseStatus.NotFound); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task DeleteTodoItem_Forbidden_Returns403() - { - // Arrange - int testItemId = 504030; - WebApplicationFactory.TodoItemsService - .DeleteAsync(TestContext, testItemId) - .Returns(ServiceResponseStatus.Forbidden); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task DeleteTodoItem_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testItemId = 504030; - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } + private readonly TodoListContext TestContext = new("TodoListId", TestUserId); + + [Test] + public async Task GetListItemsAsync_ValidCall_SucceedsAndReturnsItems() + { + // Arrange + PaginationParameters parameters = new(Page: 2, PageSize: 20); + TodoItemPreviewDto[] items = + [ + new(124013, TestContext.TodoListId, "1", null, TodoItemState.Active, 3, null!, null), + new(124124, TestContext.TodoListId, "2", null, TodoItemState.Completed, 3, null!, null) + ]; + WebApplicationFactory.TodoItemsService + .GetItemsOfListAsync(TestContext, parameters, Arg.Any()) + .Returns(x => new ServiceResponse>( + ServiceResponseStatus.Success, new(items, ((PaginationParameters)x[1]).Page, + ((PaginationParameters)x[1]).PageSize, 22))); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items?page={parameters.Page}&pageSize={parameters.PageSize}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that valid page was returned + var returnedPage = await result.Content.ReadFromJsonAsync>(); + Assert.That(returnedPage, Is.Not.Null); + using (Assert.EnterMultipleScope()) + { + Assert.That(returnedPage.PageNumber, Is.EqualTo(parameters.Page)); + Assert.That(returnedPage.PageSize, Is.EqualTo(parameters.PageSize)); + Assert.That(returnedPage.Items, Is.EquivalentTo(items)); + } + } + + [Test] + public async Task GetListItemsAsync_WrongPaginationParams_Returns400() + { + // Arrange + using HttpClient client = CreateAuthorizedHttpClient(); + int page = -1; + int pageSize = 0; + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items?page={page}&pageSize={pageSize}"); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task GetListItemsAsync_NoAuthHeaderProvided_Returns401() + { + // Arrange + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items?page=1&pageSize=20"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task GetListItemsAsync_NotFoundStatus_Returns404() + { + // Arrange + WebApplicationFactory.TodoItemsService + .GetItemsOfListAsync(TestContext, Arg.Any(), Arg.Any()) + .Returns(new ServiceResponse>(ServiceResponseStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items?page=1&pageSize=20"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task GetListItemsAsync_ForbiddenStatus_Returns403() + { + // Arrange + WebApplicationFactory.TodoItemsService + .GetItemsOfListAsync(TestContext, Arg.Any(), Arg.Any()) + .Returns(new ServiceResponse>(ServiceResponseStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items?page=1&pageSize=20"); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task GetTodoItemById_ValidCall_ReturnsElement() + { + // Arrange + int testItemId = 777; + TodoItemGetByIdDto testDto = new(testItemId, TestContext.TodoListId, "Test todo item", + "...", null, TodoItemState.Active, 5, new("Id", "User"), null); + + WebApplicationFactory.TodoItemsService + .GetByIdAsync(TestContext, testItemId) + .Returns(new ServiceResponse(ServiceResponseStatus.Success, testDto)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that valid object was returned + var returnedDto = await result.Content.ReadFromJsonAsync(); + Assert.That(returnedDto, Is.EqualTo(testDto)); + } + + [Test] + public async Task GetTodoItemById_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testItemId = 777; + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task GetTodoItemById_NotFoundStatus_Returns404() + { + // Arrange + int testItemId = 777; + WebApplicationFactory.TodoItemsService + .GetByIdAsync(TestContext, testItemId) + .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task GetTodoItemById_ForbiddenStatus_Returns403() + { + // Arrange + int testItemId = 777; + WebApplicationFactory.TodoItemsService + .GetByIdAsync(TestContext, testItemId) + .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PostTodoItem_ValidCall_Succeeds() + { + // Arrange + TodoItemCreateDto dto = new("Item", "...", DateTime.MaxValue, 9, null); + WebApplicationFactory.TodoItemsService + .CreateAsync(TestContext, dto) + .Returns(new TodoItemsServiceResponse(TodoItemsServiceStatus.Success)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/items", dto); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that create method was called + await WebApplicationFactory.TodoItemsService + .Received() + .CreateAsync(TestContext, dto); + } + + [Test] + public async Task PostTodoItem_NotFoundStatus_Returns404() + { + // Arrange + TodoItemCreateDto dto = new("Item", "...", DateTime.MaxValue, 9, null); + WebApplicationFactory.TodoItemsService + .CreateAsync(TestContext, dto) + .Returns(new TodoItemsServiceResponse(TodoItemsServiceStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/items", dto); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task PostTodoItem_ForbiddenStatus_Returns403() + { + // Arrange + TodoItemCreateDto dto = new("Item", "...", DateTime.MaxValue, 9, null); + WebApplicationFactory.TodoItemsService + .CreateAsync(TestContext, dto) + .Returns(new TodoItemsServiceResponse(TodoItemsServiceStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/items", dto); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PostTodoItem_InvalidCategoryIdStatus_Returns400() + { + // Arrange + TodoItemCreateDto dto = new("Item", "...", DateTime.MaxValue, 9, null); + WebApplicationFactory.TodoItemsService + .CreateAsync(TestContext, dto) + .Returns(new TodoItemsServiceResponse(TodoItemsServiceStatus.InvalidCategoryId)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/items", dto); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task PostTodoItem_NoAuthHeaderProvided_Returns401() + { + // Arrange + TodoItemCreateDto dto = new("Name", "Desc", DateTime.UtcNow.AddDays(5), 9, null); + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/items", dto); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task PostTodoItem_InvalidDto_Returns400() + { + // Arrange + TodoItemCreateDto invalidDto = new(string.Empty, string.Empty, DateTime.MinValue, 9, null); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/items", invalidDto); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task PutTodoItem_ValidCall_Succeeds() + { + // Arrange + int testItemId = 891349; + TodoItemCreateDto dto = new("Do nothing for entire day", "...", DateTime.MaxValue, 9, null); + WebApplicationFactory.TodoItemsService + .EditAsync(TestContext, testItemId, dto) + .Returns(TodoItemsServiceStatus.Success); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", dto); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that edit was called + await WebApplicationFactory.TodoItemsService + .Received() + .EditAsync(TestContext, testItemId, dto); + } + + [Test] + public async Task PutTodoItem_NotFoundStatus_Returns404() + { + // Arrange + int testItemId = 12412; + TodoItemCreateDto dto = new("New name", "New description", null, 5, null); + WebApplicationFactory.TodoItemsService + .EditAsync(TestContext, testItemId, dto) + .Returns(TodoItemsServiceStatus.NotFound); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", dto); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task PutTodoItem_ForbiddenStatus_Returns403() + { + // Arrange + int testItemId = 12412; + TodoItemCreateDto dto = new("New name", "New description", null, 5, null); + WebApplicationFactory.TodoItemsService + .EditAsync(TestContext, testItemId, dto) + .Returns(TodoItemsServiceStatus.Forbidden); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", dto); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PutTodoItem_InvalidCategoryIdStatus_Returns400() + { + // Arrange + int testItemId = 12412; + TodoItemCreateDto dto = new("New name", "New description", null, 5, null); + WebApplicationFactory.TodoItemsService + .EditAsync(TestContext, testItemId, dto) + .Returns(TodoItemsServiceStatus.InvalidCategoryId); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", dto); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task PutTodoItem_InvalidDto_Returns400() + { + // Arrange + int testItemId = 891349; + TodoItemCreateDto invalidDto = new(string.Empty, string.Empty, DateTime.MinValue, 5, null); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", invalidDto); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task PutTodoItem_InvalidCategoryId_Returns400() + { + // Arrange + int testItemId = 891349; + TodoItemCreateDto dto = new("New name", "New description", null, 5, 2); + WebApplicationFactory.TodoItemsService + .EditAsync(TestContext, testItemId, dto) + .Returns(TodoItemsServiceStatus.InvalidCategoryId); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", dto); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task PutTodoItem_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testItemId = 891349; + TodoItemCreateDto dto = new("Name", "Desc", DateTime.UtcNow.AddDays(5), 5, null); + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}", dto); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task PutTodoItemState_ElementExists_Succeeds() + { + // Arrange + int testItemId = 891349; + TodoItemUpdateStateDto dto = new(TodoItemState.Completed); + WebApplicationFactory.TodoItemsService + .UpdateStateAsync(TestContext, testItemId, dto) + .Returns(ServiceResponseStatus.Success); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}/state", dto); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that edit was called + await WebApplicationFactory.TodoItemsService + .Received() + .UpdateStateAsync(TestContext, testItemId, dto); + } + + [Test] + public async Task PutTodoItemState_NotFoundStatus_Returns404() + { + // Arrange + int testItemId = 12412; + TodoItemUpdateStateDto dto = new(TodoItemState.Skipped); + WebApplicationFactory.TodoItemsService + .UpdateStateAsync(TestContext, testItemId, dto) + .Returns(ServiceResponseStatus.NotFound); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}/state", dto); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task PutTodoItemState_ForbiddenStatus_Returns403() + { + // Arrange + int testItemId = 12412; + TodoItemUpdateStateDto dto = new(TodoItemState.Skipped); + WebApplicationFactory.TodoItemsService + .UpdateStateAsync(TestContext, testItemId, dto) + .Returns(ServiceResponseStatus.Forbidden); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}/state", dto); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PutTodoItemState_InvalidDto_Returns400() + { + // Arrange + int testItemId = 891349; + TodoItemUpdateStateDto dto = new((TodoItemState)213); + + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}/state", dto); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task PutTodoItemState_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testItemId = 891349; + TodoItemUpdateStateDto dto = new(TodoItemState.Active); + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}/state", dto); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task DeleteTodoItem_ElementExists_Succeeds() + { + // Arrange + int testItemId = 504030; + WebApplicationFactory.TodoItemsService + .DeleteAsync(TestContext, testItemId) + .Returns(ServiceResponseStatus.Success); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that delete was called + await WebApplicationFactory.TodoItemsService + .Received() + .DeleteAsync(TestContext, testItemId); + } + + [Test] + public async Task DeleteTodoItem_NotFound_Returns404() + { + // Arrange + int testItemId = 504030; + WebApplicationFactory.TodoItemsService + .DeleteAsync(TestContext, testItemId) + .Returns(ServiceResponseStatus.NotFound); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task DeleteTodoItem_Forbidden_Returns403() + { + // Arrange + int testItemId = 504030; + WebApplicationFactory.TodoItemsService + .DeleteAsync(TestContext, testItemId) + .Returns(ServiceResponseStatus.Forbidden); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task DeleteTodoItem_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testItemId = 504030; + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/items/{testItemId}"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } } diff --git a/AdvancedTodoList.IntegrationTests/Endpoints/TodoListMembersEndpointsTests.cs b/AdvancedTodoList.IntegrationTests/Endpoints/TodoListMembersEndpointsTests.cs index 5068519..8961578 100644 --- a/AdvancedTodoList.IntegrationTests/Endpoints/TodoListMembersEndpointsTests.cs +++ b/AdvancedTodoList.IntegrationTests/Endpoints/TodoListMembersEndpointsTests.cs @@ -1,9 +1,9 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Models.Auth; using AdvancedTodoList.Core.Models.TodoLists.Members; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Specifications; +using AdvancedTodoList.Core.Specifications.Filters; using AdvancedTodoList.IntegrationTests.Fixtures; using System.Net; using System.Net.Http.Json; @@ -13,370 +13,370 @@ namespace AdvancedTodoList.IntegrationTests.Endpoints; [TestFixture] public class TodoListMembersEndpointsTests : EndpointsFixture { - private readonly TodoListContext TestContext = new("TodoListId", TestUserId); - - [SetUp] - public void SetUp() - { - // Make validation not to fail - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(Arg.Any()) - .Returns(true); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(Arg.Any()) - .Returns(true); - } - - [Test] - public async Task GetTodoListMembersAsync_ListExists_SucceedsAndReturnsItems() - { - // Arrange - PaginationParameters parameters = new(Page: 2, PageSize: 20); - TodoListMemberPreviewDto[] members = - [ - new(124, new ApplicationUserPreviewDto("ID", "UserName"), new TodoListRolePreviewDto(700, "Admin")), - new(512, new ApplicationUserPreviewDto("Rooster", "Inspector"), new TodoListRolePreviewDto(701, "Reviewer")), - ]; - WebApplicationFactory.TodoListMembersService - .GetMembersAsync(TestContext, parameters, Arg.Any()) - .Returns(x => new ServiceResponse>(ServiceResponseStatus.Success, - new(members, ((PaginationParameters)x[1]).Page, ((PaginationParameters)x[1]).PageSize, 22))); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/members?page={parameters.Page}&pageSize={parameters.PageSize}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that valid page was returned - var returnedPage = await result.Content.ReadFromJsonAsync>(); - Assert.That(returnedPage, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(returnedPage.PageNumber, Is.EqualTo(parameters.Page)); - Assert.That(returnedPage.PageSize, Is.EqualTo(parameters.PageSize)); - Assert.That(returnedPage.Items, Is.EquivalentTo(members)); - }); - } - - [Test] - public async Task GetTodoListMembersAsync_WrongPaginationParams_Returns400() - { - // Arrange - using HttpClient client = CreateAuthorizedHttpClient(); - int page = -1; - int pageSize = 0; - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/members?page={page}&pageSize={pageSize}"); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task GetTodoListMembersAsync_NoAuthHeaderProvided_Returns401() - { - // Arrange - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/members?page=1&pageSize=20"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task GetTodoListMembersAsync_NotFoundStatus_Returns404() - { - // Arrange - WebApplicationFactory.TodoListMembersService - .GetMembersAsync(TestContext, Arg.Any(), Arg.Any()) - .Returns(new ServiceResponse>(ServiceResponseStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/members?page=1&pageSize=20"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task GetTodoListMembersAsync_ForbiddenStatus_Returns403() - { - // Arrange - WebApplicationFactory.TodoListMembersService - .GetMembersAsync(TestContext, Arg.Any(), Arg.Any()) - .Returns(new ServiceResponse>(ServiceResponseStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/members?page=1&pageSize=20"); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task AddMember_ValidCall_Succeeds() - { - // Arrange - TodoListMemberAddDto dto = new("Name"); - TodoListMemberMinimalViewDto outputDto = new(4124, dto.UserId, TestContext.TodoListId, null); - WebApplicationFactory.TodoListMembersService - .AddMemberAsync(TestContext, dto) - .Returns(new AddTodoListMemberServiceResult(TodoListMemberServiceResultStatus.Success, outputDto)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/members", dto); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that create method was called - await WebApplicationFactory.TodoListMembersService - .Received() - .AddMemberAsync(TestContext, dto); - } - - [Test] - public async Task AddMember_MemberAlreadyAdded_Fails() - { - // Arrange - TodoListMemberAddDto dto = new("Name"); - WebApplicationFactory.TodoListMembersService - .AddMemberAsync(TestContext, dto) - .Returns(new AddTodoListMemberServiceResult(TodoListMemberServiceResultStatus.UserAlreadyAdded)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/members", dto); - - // Assert that response code doesn't indicate success - Assert.That(result.IsSuccessStatusCode, Is.False, "Unsuccessfull status code was expected."); - } - - [Test] - public async Task AddMember_NotFoundStatus_Returns404() - { - // Arrange - TodoListMemberAddDto dto = new("Name"); - WebApplicationFactory.TodoListMembersService - .AddMemberAsync(TestContext, dto) - .Returns(new AddTodoListMemberServiceResult(TodoListMemberServiceResultStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/members", dto); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task AddMember_ForbiddenStatus_Returns403() - { - // Arrange - TodoListMemberAddDto dto = new("Name"); - WebApplicationFactory.TodoListMembersService - .AddMemberAsync(TestContext, dto) - .Returns(new AddTodoListMemberServiceResult(TodoListMemberServiceResultStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/members", dto); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task AddMember_NoAuthHeaderProvided_Returns401() - { - // Arrange - TodoListMemberAddDto dto = new("Name"); - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/members", dto); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task AddMember_InvalidDto_Returns400() - { - // Arrange - TodoListMemberAddDto invalidDto = new(string.Empty); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/members", invalidDto); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task UpdateMemberRole_ValidCall_Succeeds() - { - // Arrange - int testMemberId = 891349; - TodoListMemberUpdateRoleDto dto = new(777); - WebApplicationFactory.TodoListMembersService - .UpdateMemberRoleAsync(TestContext, testMemberId, dto) - .Returns(TodoListMemberServiceResultStatus.Success); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}", dto); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that edit was called - await WebApplicationFactory.TodoListMembersService - .Received() - .UpdateMemberRoleAsync(TestContext, testMemberId, dto); - } - - [Test] - public async Task UpdateMemberRole_ElementDoesNotExist_Returns404() - { - // Arrange - int testMemberId = 12412; - TodoListMemberUpdateRoleDto dto = new(777); - WebApplicationFactory.TodoListMembersService - .UpdateMemberRoleAsync(TestContext, testMemberId, dto) - .Returns(TodoListMemberServiceResultStatus.NotFound); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}", dto); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task UpdateMemberRole_InvalidRoleStatus_Fails() - { - // Arrange - int testMemberId = 12412; - TodoListMemberUpdateRoleDto dto = new(777); - WebApplicationFactory.TodoListMembersService - .UpdateMemberRoleAsync(TestContext, testMemberId, dto) - .Returns(TodoListMemberServiceResultStatus.InvalidRoleId); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}", dto); - - // Assert that response code indicates failure - Assert.That(result.IsSuccessStatusCode, Is.False, "Unsuccessfull status code was expected."); - } - - [Test] - public async Task UpdateMemberRole_ForbiddenStatus_Returns403() - { - // Arrange - int testMemberId = 12412; - TodoListMemberUpdateRoleDto dto = new(777); - WebApplicationFactory.TodoListMembersService - .UpdateMemberRoleAsync(TestContext, testMemberId, dto) - .Returns(TodoListMemberServiceResultStatus.Forbidden); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}", dto); - - // Assert - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task UpdateMemberRole_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testMemberId = 891349; - TodoListMemberAddDto dto = new("New name"); - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}", dto); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task RemoveMember_ValidCall_Succeeds() - { - // Arrange - int testMemberId = 504030; - WebApplicationFactory.TodoListMembersService - .RemoveMemberAsync(TestContext, testMemberId) - .Returns(ServiceResponseStatus.Success); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that delete was called - await WebApplicationFactory.TodoListMembersService - .Received() - .RemoveMemberAsync(TestContext, testMemberId); - } - - [Test] - public async Task RemoveMember_NotFoundStatus_Returns404() - { - // Arrange - int testMemberId = 504030; - WebApplicationFactory.TodoListMembersService - .RemoveMemberAsync(TestContext, testMemberId) - .Returns(ServiceResponseStatus.NotFound); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task RemoveMember_ForbiddenStatus_Returns403() - { - // Arrange - int testMemberId = 504030; - WebApplicationFactory.TodoListMembersService - .RemoveMemberAsync(TestContext, testMemberId) - .Returns(ServiceResponseStatus.Forbidden); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}"); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task RemoveMember_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testMemberId = 504030; - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } + private readonly TodoListContext TestContext = new("TodoListId", TestUserId); + + [SetUp] + public void SetUp() + { + // Make validation not to fail + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(Arg.Any()) + .Returns(true); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(Arg.Any()) + .Returns(true); + } + + [Test] + public async Task GetTodoListMembersAsync_ListExists_SucceedsAndReturnsItems() + { + // Arrange + PaginationParameters parameters = new(Page: 2, PageSize: 20); + TodoListMemberPreviewDto[] members = + [ + new(124, new ApplicationUserPreviewDto("ID", "UserName"), new TodoListRolePreviewDto(700, "Admin")), + new(512, new ApplicationUserPreviewDto("Rooster", "Inspector"), new TodoListRolePreviewDto(701, "Reviewer")), + ]; + WebApplicationFactory.TodoListMembersService + .GetMembersAsync(TestContext, parameters, Arg.Any()) + .Returns(x => new ServiceResponse>(ServiceResponseStatus.Success, + new(members, ((PaginationParameters)x[1]).Page, ((PaginationParameters)x[1]).PageSize, 22))); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/members?page={parameters.Page}&pageSize={parameters.PageSize}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that valid page was returned + var returnedPage = await result.Content.ReadFromJsonAsync>(); + Assert.That(returnedPage, Is.Not.Null); + using (Assert.EnterMultipleScope()) + { + Assert.That(returnedPage.PageNumber, Is.EqualTo(parameters.Page)); + Assert.That(returnedPage.PageSize, Is.EqualTo(parameters.PageSize)); + Assert.That(returnedPage.Items, Is.EquivalentTo(members)); + } + } + + [Test] + public async Task GetTodoListMembersAsync_WrongPaginationParams_Returns400() + { + // Arrange + using HttpClient client = CreateAuthorizedHttpClient(); + int page = -1; + int pageSize = 0; + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/members?page={page}&pageSize={pageSize}"); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task GetTodoListMembersAsync_NoAuthHeaderProvided_Returns401() + { + // Arrange + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/members?page=1&pageSize=20"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task GetTodoListMembersAsync_NotFoundStatus_Returns404() + { + // Arrange + WebApplicationFactory.TodoListMembersService + .GetMembersAsync(TestContext, Arg.Any(), Arg.Any()) + .Returns(new ServiceResponse>(ServiceResponseStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/members?page=1&pageSize=20"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task GetTodoListMembersAsync_ForbiddenStatus_Returns403() + { + // Arrange + WebApplicationFactory.TodoListMembersService + .GetMembersAsync(TestContext, Arg.Any(), Arg.Any()) + .Returns(new ServiceResponse>(ServiceResponseStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/members?page=1&pageSize=20"); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task AddMember_ValidCall_Succeeds() + { + // Arrange + TodoListMemberAddDto dto = new("Name"); + TodoListMemberMinimalViewDto outputDto = new(4124, dto.UserId, TestContext.TodoListId, null); + WebApplicationFactory.TodoListMembersService + .AddMemberAsync(TestContext, dto) + .Returns(new AddTodoListMemberServiceResult(TodoListMemberServiceResultStatus.Success, outputDto)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/members", dto); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that create method was called + await WebApplicationFactory.TodoListMembersService + .Received() + .AddMemberAsync(TestContext, dto); + } + + [Test] + public async Task AddMember_MemberAlreadyAdded_Fails() + { + // Arrange + TodoListMemberAddDto dto = new("Name"); + WebApplicationFactory.TodoListMembersService + .AddMemberAsync(TestContext, dto) + .Returns(new AddTodoListMemberServiceResult(TodoListMemberServiceResultStatus.UserAlreadyAdded)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/members", dto); + + // Assert that response code doesn't indicate success + Assert.That(result.IsSuccessStatusCode, Is.False, "Unsuccessfull status code was expected."); + } + + [Test] + public async Task AddMember_NotFoundStatus_Returns404() + { + // Arrange + TodoListMemberAddDto dto = new("Name"); + WebApplicationFactory.TodoListMembersService + .AddMemberAsync(TestContext, dto) + .Returns(new AddTodoListMemberServiceResult(TodoListMemberServiceResultStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/members", dto); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task AddMember_ForbiddenStatus_Returns403() + { + // Arrange + TodoListMemberAddDto dto = new("Name"); + WebApplicationFactory.TodoListMembersService + .AddMemberAsync(TestContext, dto) + .Returns(new AddTodoListMemberServiceResult(TodoListMemberServiceResultStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/members", dto); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task AddMember_NoAuthHeaderProvided_Returns401() + { + // Arrange + TodoListMemberAddDto dto = new("Name"); + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/members", dto); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task AddMember_InvalidDto_Returns400() + { + // Arrange + TodoListMemberAddDto invalidDto = new(string.Empty); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/members", invalidDto); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task UpdateMemberRole_ValidCall_Succeeds() + { + // Arrange + int testMemberId = 891349; + TodoListMemberUpdateRoleDto dto = new(777); + WebApplicationFactory.TodoListMembersService + .UpdateMemberRoleAsync(TestContext, testMemberId, dto) + .Returns(TodoListMemberServiceResultStatus.Success); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}", dto); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that edit was called + await WebApplicationFactory.TodoListMembersService + .Received() + .UpdateMemberRoleAsync(TestContext, testMemberId, dto); + } + + [Test] + public async Task UpdateMemberRole_ElementDoesNotExist_Returns404() + { + // Arrange + int testMemberId = 12412; + TodoListMemberUpdateRoleDto dto = new(777); + WebApplicationFactory.TodoListMembersService + .UpdateMemberRoleAsync(TestContext, testMemberId, dto) + .Returns(TodoListMemberServiceResultStatus.NotFound); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}", dto); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task UpdateMemberRole_InvalidRoleStatus_Fails() + { + // Arrange + int testMemberId = 12412; + TodoListMemberUpdateRoleDto dto = new(777); + WebApplicationFactory.TodoListMembersService + .UpdateMemberRoleAsync(TestContext, testMemberId, dto) + .Returns(TodoListMemberServiceResultStatus.InvalidRoleId); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}", dto); + + // Assert that response code indicates failure + Assert.That(result.IsSuccessStatusCode, Is.False, "Unsuccessfull status code was expected."); + } + + [Test] + public async Task UpdateMemberRole_ForbiddenStatus_Returns403() + { + // Arrange + int testMemberId = 12412; + TodoListMemberUpdateRoleDto dto = new(777); + WebApplicationFactory.TodoListMembersService + .UpdateMemberRoleAsync(TestContext, testMemberId, dto) + .Returns(TodoListMemberServiceResultStatus.Forbidden); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}", dto); + + // Assert + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task UpdateMemberRole_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testMemberId = 891349; + TodoListMemberAddDto dto = new("New name"); + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}", dto); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task RemoveMember_ValidCall_Succeeds() + { + // Arrange + int testMemberId = 504030; + WebApplicationFactory.TodoListMembersService + .RemoveMemberAsync(TestContext, testMemberId) + .Returns(ServiceResponseStatus.Success); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that delete was called + await WebApplicationFactory.TodoListMembersService + .Received() + .RemoveMemberAsync(TestContext, testMemberId); + } + + [Test] + public async Task RemoveMember_NotFoundStatus_Returns404() + { + // Arrange + int testMemberId = 504030; + WebApplicationFactory.TodoListMembersService + .RemoveMemberAsync(TestContext, testMemberId) + .Returns(ServiceResponseStatus.NotFound); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task RemoveMember_ForbiddenStatus_Returns403() + { + // Arrange + int testMemberId = 504030; + WebApplicationFactory.TodoListMembersService + .RemoveMemberAsync(TestContext, testMemberId) + .Returns(ServiceResponseStatus.Forbidden); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}"); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task RemoveMember_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testMemberId = 504030; + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/members/{testMemberId}"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } } diff --git a/AdvancedTodoList.IntegrationTests/Endpoints/TodoListRolesEndpointsTests.cs b/AdvancedTodoList.IntegrationTests/Endpoints/TodoListRolesEndpointsTests.cs index abad12c..0f9fd5b 100644 --- a/AdvancedTodoList.IntegrationTests/Endpoints/TodoListRolesEndpointsTests.cs +++ b/AdvancedTodoList.IntegrationTests/Endpoints/TodoListRolesEndpointsTests.cs @@ -1,6 +1,6 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; using AdvancedTodoList.IntegrationTests.Fixtures; using System.Net; using System.Net.Http.Json; @@ -10,409 +10,409 @@ namespace AdvancedTodoList.IntegrationTests.Endpoints; [TestFixture] public class TodoListRolesEndpointsTests : EndpointsFixture { - private readonly TodoListContext TestContext = new("TodoListId", TestUserId); - - [Test] - public async Task GetTodoListRolesAsync_ValidCall_SucceedsAndReturnsItems() - { - // Arrange - PaginationParameters parameters = new(Page: 2, PageSize: 20); - TodoListRolePreviewDto[] roles = - [ - new(700, "Admin"), - new(701, "Reviewer"), - ]; - string name = "n"; - WebApplicationFactory.TodoListRolesService - .GetRolesOfListAsync(TestContext, parameters, name) - .Returns(x => new ServiceResponse>( - ServiceResponseStatus.Success, new(roles, ((PaginationParameters)x[1]).Page, - ((PaginationParameters)x[1]).PageSize, 22))); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles?page={parameters.Page}&pageSize={parameters.PageSize}&name={name}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that valid page was returned - var returnedPage = await result.Content.ReadFromJsonAsync>(); - Assert.That(returnedPage, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(returnedPage.PageNumber, Is.EqualTo(parameters.Page)); - Assert.That(returnedPage.PageSize, Is.EqualTo(parameters.PageSize)); - Assert.That(returnedPage.Items, Is.EquivalentTo(roles)); - }); - } - - [Test] - public async Task GetTodoListRolesAsync_WrongPaginationParams_Returns400() - { - // Arrange - using HttpClient client = CreateAuthorizedHttpClient(); - int page = -1; - int pageSize = 0; - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles?page={page}&pageSize={pageSize}"); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task GetTodoListRolesAsync_NoAuthHeaderProvided_Returns401() - { - // Arrange - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles?page=1&pageSize=20"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task GetTodoListRolesAsync_NotFoundStatus_Returns404() - { - // Arrange - WebApplicationFactory.TodoListRolesService - .GetRolesOfListAsync(TestContext, Arg.Any()) - .Returns(x => new ServiceResponse>(ServiceResponseStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles?page=1&pageSize=20"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task GetTodoListRolesAsync_ForbiddenStatus_Returns403() - { - // Arrange - WebApplicationFactory.TodoListRolesService - .GetRolesOfListAsync(TestContext, Arg.Any()) - .Returns(x => new ServiceResponse>(ServiceResponseStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles?page=1&pageSize=20"); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task GetTodoListRoleById_ValidCall_ReturnsElement() - { - // Arrange - int testRoleId = 777; - TodoListRoleViewDto testDto = new(testRoleId, "Name", 5, new(true)); - - WebApplicationFactory.TodoListRolesService - .GetByIdAsync(TestContext, testRoleId) - .Returns(new ServiceResponse(ServiceResponseStatus.Success, testDto)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that valid object was returned - var returnedDto = await result.Content.ReadFromJsonAsync(); - Assert.That(returnedDto, Is.EqualTo(testDto)); - } - - [Test] - public async Task GetTodoListRoleById_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testRoleId = 777; - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task GetTodoListRoleById_NotFoundStatus_Returns404() - { - // Arrange - int testRoleId = 777; - WebApplicationFactory.TodoListRolesService - .GetByIdAsync(TestContext, testRoleId) - .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task GetTodoListRoleById_ForbiddenStatus_Returns404() - { - // Arrange - int testRoleId = 777; - WebApplicationFactory.TodoListRolesService - .GetByIdAsync(TestContext, testRoleId) - .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PostTodoListRole_ValidCall_Succeeds() - { - // Arrange - TodoListRoleCreateDto dto = new("Name", 5, new(true)); - WebApplicationFactory.TodoListRolesService - .CreateAsync(TestContext, dto) - .Returns(new ServiceResponse(ServiceResponseStatus.Success)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles", dto); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that create method was called - await WebApplicationFactory.TodoListRolesService - .Received() - .CreateAsync(TestContext, dto); - } - - [Test] - public async Task PostTodoListRole_NotFoundStatus_Returns404() - { - // Arrange - TodoListRoleCreateDto dto = new("Joker", 5, new(true)); - WebApplicationFactory.TodoListRolesService - .CreateAsync(TestContext, dto) - .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles", dto); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task PostTodoListRole_ForbiddenStatus_Returns403() - { - // Arrange - TodoListRoleCreateDto dto = new("Joker", 5, new(true)); - WebApplicationFactory.TodoListRolesService - .CreateAsync(TestContext, dto) - .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles", dto); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PostTodoListRole_NoAuthHeaderProvided_Returns401() - { - // Arrange - TodoListRoleCreateDto dto = new("Joker", 5, new(true)); - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles", dto); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task PostTodoListRole_InvalidDto_Returns400() - { - // Arrange - TodoListRoleCreateDto invalidDto = new(string.Empty, 5, new()); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles", invalidDto); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task PutTodoListRole_ValidCall_Succeeds() - { - // Arrange - int testRoleId = 891349; - TodoListRoleCreateDto dto = new("New name", 5, new(true, true, true)); - WebApplicationFactory.TodoListRolesService - .EditAsync(TestContext, testRoleId, dto) - .Returns(ServiceResponseStatus.Success); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}", dto); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that edit was called - await WebApplicationFactory.TodoListRolesService - .Received() - .EditAsync(TestContext, testRoleId, dto); - } - - [Test] - public async Task PutTodoListRole_NotFoundStatus_Returns404() - { - // Arrange - int testRoleId = 12412; - TodoListRoleCreateDto dto = new("New name", 5, new(true, true, true)); - WebApplicationFactory.TodoListRolesService - .EditAsync(TestContext, testRoleId, dto) - .Returns(ServiceResponseStatus.NotFound); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}", dto); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task PutTodoListRole_ForbiddenStatus_Returns403() - { - // Arrange - int testRoleId = 12412; - TodoListRoleCreateDto dto = new("New name", 5, new(true, true, true)); - WebApplicationFactory.TodoListRolesService - .EditAsync(TestContext, testRoleId, dto) - .Returns(ServiceResponseStatus.Forbidden); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}", dto); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PutTodoListRole_InvalidDto_Returns400() - { - // Arrange - int testRoleId = 891349; - TodoListRoleCreateDto invalidDto = new(string.Empty, 5, new()); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}", invalidDto); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task PutTodoListRole_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testRoleId = 891349; - TodoListRoleCreateDto dto = new("New name", 5, new(true, true, true)); - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}", dto); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task DeleteTodoListRole_ValidCall_Succeeds() - { - // Arrange - int testRoleId = 504030; - WebApplicationFactory.TodoListRolesService - .DeleteAsync(TestContext, testRoleId) - .Returns(ServiceResponseStatus.Success); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that delete was called - await WebApplicationFactory.TodoListRolesService - .Received() - .DeleteAsync(TestContext, testRoleId); - } - - [Test] - public async Task DeleteTodoListRole_NotFoundStatus_Returns404() - { - // Arrange - int testRoleId = 504030; - WebApplicationFactory.TodoListRolesService - .DeleteAsync(TestContext, testRoleId) - .Returns(ServiceResponseStatus.NotFound); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task DeleteTodoListRole_ForbiddenStatus_Returns403() - { - // Arrange - int testRoleId = 504030; - WebApplicationFactory.TodoListRolesService - .DeleteAsync(TestContext, testRoleId) - .Returns(ServiceResponseStatus.Forbidden); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); - - // Assert that response code is 403 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task DeleteTodoListRole_NoAuthHeaderProvided_Returns401() - { - // Arrange - int testRoleId = 504030; - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } + private readonly TodoListContext TestContext = new("TodoListId", TestUserId); + + [Test] + public async Task GetTodoListRolesAsync_ValidCall_SucceedsAndReturnsItems() + { + // Arrange + PaginationParameters parameters = new(Page: 2, PageSize: 20); + TodoListRolePreviewDto[] roles = + [ + new(700, "Admin"), + new(701, "Reviewer"), + ]; + string name = "n"; + WebApplicationFactory.TodoListRolesService + .GetRolesOfListAsync(TestContext, parameters, name) + .Returns(x => new ServiceResponse>( + ServiceResponseStatus.Success, new(roles, ((PaginationParameters)x[1]).Page, + ((PaginationParameters)x[1]).PageSize, 22))); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles?page={parameters.Page}&pageSize={parameters.PageSize}&name={name}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that valid page was returned + var returnedPage = await result.Content.ReadFromJsonAsync>(); + Assert.That(returnedPage, Is.Not.Null); + using (Assert.EnterMultipleScope()) + { + Assert.That(returnedPage.PageNumber, Is.EqualTo(parameters.Page)); + Assert.That(returnedPage.PageSize, Is.EqualTo(parameters.PageSize)); + Assert.That(returnedPage.Items, Is.EquivalentTo(roles)); + } + } + + [Test] + public async Task GetTodoListRolesAsync_WrongPaginationParams_Returns400() + { + // Arrange + using HttpClient client = CreateAuthorizedHttpClient(); + int page = -1; + int pageSize = 0; + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles?page={page}&pageSize={pageSize}"); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task GetTodoListRolesAsync_NoAuthHeaderProvided_Returns401() + { + // Arrange + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles?page=1&pageSize=20"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task GetTodoListRolesAsync_NotFoundStatus_Returns404() + { + // Arrange + WebApplicationFactory.TodoListRolesService + .GetRolesOfListAsync(TestContext, Arg.Any()) + .Returns(x => new ServiceResponse>(ServiceResponseStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles?page=1&pageSize=20"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task GetTodoListRolesAsync_ForbiddenStatus_Returns403() + { + // Arrange + WebApplicationFactory.TodoListRolesService + .GetRolesOfListAsync(TestContext, Arg.Any()) + .Returns(x => new ServiceResponse>(ServiceResponseStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles?page=1&pageSize=20"); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task GetTodoListRoleById_ValidCall_ReturnsElement() + { + // Arrange + int testRoleId = 777; + TodoListRoleViewDto testDto = new(testRoleId, "Name", 5, new(true)); + + WebApplicationFactory.TodoListRolesService + .GetByIdAsync(TestContext, testRoleId) + .Returns(new ServiceResponse(ServiceResponseStatus.Success, testDto)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that valid object was returned + var returnedDto = await result.Content.ReadFromJsonAsync(); + Assert.That(returnedDto, Is.EqualTo(testDto)); + } + + [Test] + public async Task GetTodoListRoleById_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testRoleId = 777; + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task GetTodoListRoleById_NotFoundStatus_Returns404() + { + // Arrange + int testRoleId = 777; + WebApplicationFactory.TodoListRolesService + .GetByIdAsync(TestContext, testRoleId) + .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task GetTodoListRoleById_ForbiddenStatus_Returns404() + { + // Arrange + int testRoleId = 777; + WebApplicationFactory.TodoListRolesService + .GetByIdAsync(TestContext, testRoleId) + .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PostTodoListRole_ValidCall_Succeeds() + { + // Arrange + TodoListRoleCreateDto dto = new("Name", 5, new(true)); + WebApplicationFactory.TodoListRolesService + .CreateAsync(TestContext, dto) + .Returns(new ServiceResponse(ServiceResponseStatus.Success)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles", dto); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that create method was called + await WebApplicationFactory.TodoListRolesService + .Received() + .CreateAsync(TestContext, dto); + } + + [Test] + public async Task PostTodoListRole_NotFoundStatus_Returns404() + { + // Arrange + TodoListRoleCreateDto dto = new("Joker", 5, new(true)); + WebApplicationFactory.TodoListRolesService + .CreateAsync(TestContext, dto) + .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles", dto); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task PostTodoListRole_ForbiddenStatus_Returns403() + { + // Arrange + TodoListRoleCreateDto dto = new("Joker", 5, new(true)); + WebApplicationFactory.TodoListRolesService + .CreateAsync(TestContext, dto) + .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles", dto); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PostTodoListRole_NoAuthHeaderProvided_Returns401() + { + // Arrange + TodoListRoleCreateDto dto = new("Joker", 5, new(true)); + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles", dto); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task PostTodoListRole_InvalidDto_Returns400() + { + // Arrange + TodoListRoleCreateDto invalidDto = new(string.Empty, 5, new()); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles", invalidDto); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task PutTodoListRole_ValidCall_Succeeds() + { + // Arrange + int testRoleId = 891349; + TodoListRoleCreateDto dto = new("New name", 5, new(true, true, true)); + WebApplicationFactory.TodoListRolesService + .EditAsync(TestContext, testRoleId, dto) + .Returns(ServiceResponseStatus.Success); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}", dto); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that edit was called + await WebApplicationFactory.TodoListRolesService + .Received() + .EditAsync(TestContext, testRoleId, dto); + } + + [Test] + public async Task PutTodoListRole_NotFoundStatus_Returns404() + { + // Arrange + int testRoleId = 12412; + TodoListRoleCreateDto dto = new("New name", 5, new(true, true, true)); + WebApplicationFactory.TodoListRolesService + .EditAsync(TestContext, testRoleId, dto) + .Returns(ServiceResponseStatus.NotFound); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}", dto); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task PutTodoListRole_ForbiddenStatus_Returns403() + { + // Arrange + int testRoleId = 12412; + TodoListRoleCreateDto dto = new("New name", 5, new(true, true, true)); + WebApplicationFactory.TodoListRolesService + .EditAsync(TestContext, testRoleId, dto) + .Returns(ServiceResponseStatus.Forbidden); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}", dto); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PutTodoListRole_InvalidDto_Returns400() + { + // Arrange + int testRoleId = 891349; + TodoListRoleCreateDto invalidDto = new(string.Empty, 5, new()); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}", invalidDto); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task PutTodoListRole_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testRoleId = 891349; + TodoListRoleCreateDto dto = new("New name", 5, new(true, true, true)); + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}", dto); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task DeleteTodoListRole_ValidCall_Succeeds() + { + // Arrange + int testRoleId = 504030; + WebApplicationFactory.TodoListRolesService + .DeleteAsync(TestContext, testRoleId) + .Returns(ServiceResponseStatus.Success); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that delete was called + await WebApplicationFactory.TodoListRolesService + .Received() + .DeleteAsync(TestContext, testRoleId); + } + + [Test] + public async Task DeleteTodoListRole_NotFoundStatus_Returns404() + { + // Arrange + int testRoleId = 504030; + WebApplicationFactory.TodoListRolesService + .DeleteAsync(TestContext, testRoleId) + .Returns(ServiceResponseStatus.NotFound); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task DeleteTodoListRole_ForbiddenStatus_Returns403() + { + // Arrange + int testRoleId = 504030; + WebApplicationFactory.TodoListRolesService + .DeleteAsync(TestContext, testRoleId) + .Returns(ServiceResponseStatus.Forbidden); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); + + // Assert that response code is 403 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task DeleteTodoListRole_NoAuthHeaderProvided_Returns401() + { + // Arrange + int testRoleId = 504030; + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}/roles/{testRoleId}"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } } diff --git a/AdvancedTodoList.IntegrationTests/Endpoints/TodoListsEndpointsTests.cs b/AdvancedTodoList.IntegrationTests/Endpoints/TodoListsEndpointsTests.cs index ad0cbd6..368a774 100644 --- a/AdvancedTodoList.IntegrationTests/Endpoints/TodoListsEndpointsTests.cs +++ b/AdvancedTodoList.IntegrationTests/Endpoints/TodoListsEndpointsTests.cs @@ -1,7 +1,7 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Specifications; +using AdvancedTodoList.Core.Specifications.Filters; using AdvancedTodoList.IntegrationTests.Fixtures; using System.Net; using System.Net.Http.Json; @@ -11,331 +11,331 @@ namespace AdvancedTodoList.IntegrationTests.Endpoints; [TestFixture] public class TodoListsEndpointsTests : EndpointsFixture { - private readonly TodoListContext TestContext = new("TestTodoListId", TestUserId); - - [Test] - public async Task GetTodoListsOfCaller_ValidCall_SucceedsAndReturnsItems() - { - // Arrange - PaginationParameters parameters = new(Page: 2, PageSize: 20); - TodoListPreviewDto[] items = - [ - new("123", "Abc"), - new("456", "Def"), - ]; - TodoListsFilter filter = new(Name: "abc"); - WebApplicationFactory.TodoListsService - .GetListsOfUserAsync(TestUserId, parameters, filter) - .Returns(x => new(items, ((PaginationParameters)x[1]).Page, - ((PaginationParameters)x[1]).PageSize, 22)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo?page={parameters.Page}&pageSize={parameters.PageSize}&name={filter.Name}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that valid page was returned - var returnedPage = await result.Content.ReadFromJsonAsync>(); - Assert.That(returnedPage, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(returnedPage.PageNumber, Is.EqualTo(parameters.Page)); - Assert.That(returnedPage.PageSize, Is.EqualTo(parameters.PageSize)); - Assert.That(returnedPage.Items, Is.EquivalentTo(items)); - }); - } - - [Test] - public async Task GetTodoListsOfCaller_WrongPaginationParams_Returns400() - { - // Arrange - using HttpClient client = CreateAuthorizedHttpClient(); - int page = -1; - int pageSize = 0; - - // Act: send the request - var result = await client.GetAsync($"api/todo?page={page}&pageSize={pageSize}"); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task GetTodoListsOfCaller_NoAuthHeaderProvided_Returns401() - { - // Arrange - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo?page=1&pageSize=20"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task GetTodoListById_ElementExists_ReturnsElement() - { - // Arrange - TodoListGetByIdDto testDto = new(TestContext.TodoListId, "Test todo list", "", new("Id", "User")); - WebApplicationFactory.TodoListsService - .GetByIdAsync(TestContext) - .Returns(new ServiceResponse(ServiceResponseStatus.Success, testDto)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that valid object was returned - var returnedDto = await result.Content.ReadFromJsonAsync(); - Assert.That(returnedDto, Is.EqualTo(testDto)); - } - - [Test] - public async Task GetTodoListById_NoAuthHeaderProvided_Returns401() - { - // Arrange - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task GetTodoListById_ElementDoesNotExist_Returns404() - { - // Arrange - WebApplicationFactory.TodoListsService - .GetByIdAsync(TestContext) - .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task GetTodoListById_UserHasNoPermission_Returns401() - { - // Arrange - WebApplicationFactory.TodoListsService - .GetByIdAsync(TestContext) - .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task PostTodoList_ValidCall_Succeeds() - { - // Arrange - WebApplicationFactory.TodoListsService - .CreateAsync(Arg.Any(), Arg.Any()) - .Returns(new TodoListGetByIdDto("Id", "", "", new("Id", "User"))); - TodoListCreateDto dto = new("Test", string.Empty); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync("api/todo", dto); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that create method was called - await WebApplicationFactory.TodoListsService - .Received() - .CreateAsync(dto, TestUserId); - } - - [Test] - public async Task PostTodoList_InvalidDto_Returns400() - { - // Arrange - TodoListCreateDto invalidDto = new(string.Empty, string.Empty); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync("api/todo", invalidDto); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task PostTodoList_NoAuthHeaderProvided_Returns401() - { - // Arrange - TodoListCreateDto dto = new("Name", "Descr"); - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PostAsJsonAsync("api/todo", dto); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task PutTodoList_ElementExists_Succeeds() - { - // Arrange - TodoListCreateDto dto = new("New name", "New description"); - - WebApplicationFactory.TodoListsService - .EditAsync(TestContext, dto) - .Returns(ServiceResponseStatus.Success); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}", dto); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that edit was called - await WebApplicationFactory.TodoListsService - .Received() - .EditAsync(TestContext, dto); - } - - [Test] - public async Task PutTodoList_InvalidDto_Returns400() - { - // Arrange - TodoListCreateDto invalidDto = new(string.Empty, string.Empty); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}", invalidDto); - - // Assert that response code is 400 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); - } - - [Test] - public async Task PutTodoList_NoAuthHeaderProvided_Returns401() - { - // Arrange - TodoListCreateDto invalidDto = new("Name", "Descr"); - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}", invalidDto); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task PutTodoList_ElementDoesNotExist_Returns404() - { - // Arrange - TodoListCreateDto dto = new("New name", "New description"); - - WebApplicationFactory.TodoListsService - .EditAsync(TestContext, dto) - .Returns(ServiceResponseStatus.NotFound); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}", dto); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task PutTodoList_UserHasNoPermission_Returns403() - { - // Arrange - TodoListCreateDto dto = new("New name", "New description"); - - WebApplicationFactory.TodoListsService - .EditAsync(TestContext, dto) - .Returns(ServiceResponseStatus.Forbidden); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}", dto); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } - - [Test] - public async Task DeleteTodoList_ElementExists_Succeeds() - { - // Arrange - WebApplicationFactory.TodoListsService - .DeleteAsync(TestContext) - .Returns(ServiceResponseStatus.Success); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}"); - - // Assert that response indicates success - result.EnsureSuccessStatusCode(); - // Assert that delete was called - await WebApplicationFactory.TodoListsService - .Received() - .DeleteAsync(TestContext); - } - - [Test] - public async Task DeleteTodoList_NoAuthHeaderProvided_Returns401() - { - // Arrange - using HttpClient client = WebApplicationFactory.CreateClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}"); - - // Assert that response code is 401 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); - } - - [Test] - public async Task DeleteTodoList_ElementDoesNotExist_Returns404() - { - // Arrange - WebApplicationFactory.TodoListsService - .DeleteAsync(TestContext) - .Returns(ServiceResponseStatus.NotFound); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); - } - - [Test] - public async Task DeleteTodoList_UserHasNoPermission_Returns401() - { - // Arrange - WebApplicationFactory.TodoListsService - .DeleteAsync(TestContext) - .Returns(ServiceResponseStatus.Forbidden); - using HttpClient client = CreateAuthorizedHttpClient(); - - // Act: send the request - var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}"); - - // Assert that response code is 404 - Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); - } + private readonly TodoListContext TestContext = new("TestTodoListId", TestUserId); + + [Test] + public async Task GetTodoListsOfCaller_ValidCall_SucceedsAndReturnsItems() + { + // Arrange + PaginationParameters parameters = new(Page: 2, PageSize: 20); + TodoListPreviewDto[] items = + [ + new("123", "Abc"), + new("456", "Def"), + ]; + TodoListsFilter filter = new(Name: "abc"); + WebApplicationFactory.TodoListsService + .GetListsOfUserAsync(TestUserId, parameters, filter) + .Returns(x => new(items, ((PaginationParameters)x[1]).Page, + ((PaginationParameters)x[1]).PageSize, 22)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo?page={parameters.Page}&pageSize={parameters.PageSize}&name={filter.Name}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that valid page was returned + var returnedPage = await result.Content.ReadFromJsonAsync>(); + Assert.That(returnedPage, Is.Not.Null); + using (Assert.EnterMultipleScope()) + { + Assert.That(returnedPage.PageNumber, Is.EqualTo(parameters.Page)); + Assert.That(returnedPage.PageSize, Is.EqualTo(parameters.PageSize)); + Assert.That(returnedPage.Items, Is.EquivalentTo(items)); + } + } + + [Test] + public async Task GetTodoListsOfCaller_WrongPaginationParams_Returns400() + { + // Arrange + using HttpClient client = CreateAuthorizedHttpClient(); + int page = -1; + int pageSize = 0; + + // Act: send the request + var result = await client.GetAsync($"api/todo?page={page}&pageSize={pageSize}"); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task GetTodoListsOfCaller_NoAuthHeaderProvided_Returns401() + { + // Arrange + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo?page=1&pageSize=20"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task GetTodoListById_ElementExists_ReturnsElement() + { + // Arrange + TodoListGetByIdDto testDto = new(TestContext.TodoListId, "Test todo list", "", new("Id", "User")); + WebApplicationFactory.TodoListsService + .GetByIdAsync(TestContext) + .Returns(new ServiceResponse(ServiceResponseStatus.Success, testDto)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that valid object was returned + var returnedDto = await result.Content.ReadFromJsonAsync(); + Assert.That(returnedDto, Is.EqualTo(testDto)); + } + + [Test] + public async Task GetTodoListById_NoAuthHeaderProvided_Returns401() + { + // Arrange + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task GetTodoListById_ElementDoesNotExist_Returns404() + { + // Arrange + WebApplicationFactory.TodoListsService + .GetByIdAsync(TestContext) + .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task GetTodoListById_UserHasNoPermission_Returns401() + { + // Arrange + WebApplicationFactory.TodoListsService + .GetByIdAsync(TestContext) + .Returns(new ServiceResponse(ServiceResponseStatus.Forbidden)); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.GetAsync($"api/todo/{TestContext.TodoListId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task PostTodoList_ValidCall_Succeeds() + { + // Arrange + WebApplicationFactory.TodoListsService + .CreateAsync(Arg.Any(), Arg.Any()) + .Returns(new TodoListGetByIdDto("Id", "", "", new("Id", "User"))); + TodoListCreateDto dto = new("Test", string.Empty); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync("api/todo", dto); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that create method was called + await WebApplicationFactory.TodoListsService + .Received() + .CreateAsync(dto, TestUserId); + } + + [Test] + public async Task PostTodoList_InvalidDto_Returns400() + { + // Arrange + TodoListCreateDto invalidDto = new(string.Empty, string.Empty); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync("api/todo", invalidDto); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task PostTodoList_NoAuthHeaderProvided_Returns401() + { + // Arrange + TodoListCreateDto dto = new("Name", "Descr"); + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PostAsJsonAsync("api/todo", dto); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task PutTodoList_ElementExists_Succeeds() + { + // Arrange + TodoListCreateDto dto = new("New name", "New description"); + + WebApplicationFactory.TodoListsService + .EditAsync(TestContext, dto) + .Returns(ServiceResponseStatus.Success); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}", dto); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that edit was called + await WebApplicationFactory.TodoListsService + .Received() + .EditAsync(TestContext, dto); + } + + [Test] + public async Task PutTodoList_InvalidDto_Returns400() + { + // Arrange + TodoListCreateDto invalidDto = new(string.Empty, string.Empty); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}", invalidDto); + + // Assert that response code is 400 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest)); + } + + [Test] + public async Task PutTodoList_NoAuthHeaderProvided_Returns401() + { + // Arrange + TodoListCreateDto invalidDto = new("Name", "Descr"); + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}", invalidDto); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task PutTodoList_ElementDoesNotExist_Returns404() + { + // Arrange + TodoListCreateDto dto = new("New name", "New description"); + + WebApplicationFactory.TodoListsService + .EditAsync(TestContext, dto) + .Returns(ServiceResponseStatus.NotFound); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}", dto); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task PutTodoList_UserHasNoPermission_Returns403() + { + // Arrange + TodoListCreateDto dto = new("New name", "New description"); + + WebApplicationFactory.TodoListsService + .EditAsync(TestContext, dto) + .Returns(ServiceResponseStatus.Forbidden); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.PutAsJsonAsync($"api/todo/{TestContext.TodoListId}", dto); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } + + [Test] + public async Task DeleteTodoList_ElementExists_Succeeds() + { + // Arrange + WebApplicationFactory.TodoListsService + .DeleteAsync(TestContext) + .Returns(ServiceResponseStatus.Success); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}"); + + // Assert that response indicates success + result.EnsureSuccessStatusCode(); + // Assert that delete was called + await WebApplicationFactory.TodoListsService + .Received() + .DeleteAsync(TestContext); + } + + [Test] + public async Task DeleteTodoList_NoAuthHeaderProvided_Returns401() + { + // Arrange + using HttpClient client = WebApplicationFactory.CreateClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}"); + + // Assert that response code is 401 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Unauthorized)); + } + + [Test] + public async Task DeleteTodoList_ElementDoesNotExist_Returns404() + { + // Arrange + WebApplicationFactory.TodoListsService + .DeleteAsync(TestContext) + .Returns(ServiceResponseStatus.NotFound); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.NotFound)); + } + + [Test] + public async Task DeleteTodoList_UserHasNoPermission_Returns401() + { + // Arrange + WebApplicationFactory.TodoListsService + .DeleteAsync(TestContext) + .Returns(ServiceResponseStatus.Forbidden); + using HttpClient client = CreateAuthorizedHttpClient(); + + // Act: send the request + var result = await client.DeleteAsync($"api/todo/{TestContext.TodoListId}"); + + // Assert that response code is 404 + Assert.That(result.StatusCode, Is.EqualTo(HttpStatusCode.Forbidden)); + } } diff --git a/AdvancedTodoList.IntegrationTests/Factories/BusinessLogicWebApplicationFactory.cs b/AdvancedTodoList.IntegrationTests/Factories/BusinessLogicWebApplicationFactory.cs index 8e368a6..99e4162 100644 --- a/AdvancedTodoList.IntegrationTests/Factories/BusinessLogicWebApplicationFactory.cs +++ b/AdvancedTodoList.IntegrationTests/Factories/BusinessLogicWebApplicationFactory.cs @@ -1,8 +1,8 @@ -using AdvancedTodoList.Core.Models.TodoLists; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models.TodoLists; using AdvancedTodoList.Core.Models.TodoLists.Members; using AdvancedTodoList.Core.Repositories; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Services.Auth; using AdvancedTodoList.IntegrationTests.Services; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; @@ -15,62 +15,62 @@ namespace AdvancedTodoList.IntegrationTests.Factories; /// public class BusinessLogicWebApplicationFactory : WebApplicationFactory { - public IEntityExistenceChecker EntityExistenceChecker { get; private set; } = null!; - public IRepository TodoListsRepository { get; private set; } = null!; - public IRepository TodoItemsRepository { get; private set; } = null!; - public IRepository TodoItemCategoriesRepository { get; private set; } = null!; - public IRepository TestTodoListDependantEntitiesRepository { get; private set; } = null!; - public IInvitationLinksRepository InvitationLinksRepository { get; private set; } = null!; - public ITodoListDependantEntitiesService TodoItemsHelperService { get; set; } = null!; - public ITodoListDependantEntitiesService TodoItemCategoriesHelperService { get; set; } = null!; - public ITodoListDependantEntitiesService TodoRolesHelperService { get; set; } = null!; - public ITodoListDependantEntitiesService TodoMembersHelperService { get; set; } = null!; - public IUserRefreshTokensRepository RefreshTokensRepository { get; private set; } = null!; - public IPermissionsChecker PermissionsChecker { get; private set; } = null!; - public IRepository TodoListRolesRepository { get; private set; } = null!; - public ITodoListMembersRepository TodoListMembersRepository { get; private set; } = null!; - public IUnitOfWork UnitOfWork { get; private set; } = null!; + public IEntityExistenceChecker EntityExistenceChecker { get; private set; } = null!; + public IRepository TodoListsRepository { get; private set; } = null!; + public IRepository TodoItemsRepository { get; private set; } = null!; + public IRepository TodoItemCategoriesRepository { get; private set; } = null!; + public IRepository TestTodoListDependantEntitiesRepository { get; private set; } = null!; + public IInvitationLinksRepository InvitationLinksRepository { get; private set; } = null!; + public ITodoListDependantEntitiesService TodoItemsHelperService { get; set; } = null!; + public ITodoListDependantEntitiesService TodoItemCategoriesHelperService { get; set; } = null!; + public ITodoListDependantEntitiesService TodoRolesHelperService { get; set; } = null!; + public ITodoListDependantEntitiesService TodoMembersHelperService { get; set; } = null!; + public IUserRefreshTokensRepository RefreshTokensRepository { get; private set; } = null!; + public IPermissionsChecker PermissionsChecker { get; private set; } = null!; + public IRepository TodoListRolesRepository { get; private set; } = null!; + public ITodoListMembersRepository TodoListMembersRepository { get; private set; } = null!; + public IUnitOfWork UnitOfWork { get; private set; } = null!; - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - // Create mocks for the services - EntityExistenceChecker = Substitute.For(); - TodoListsRepository = Substitute.For>(); - TodoItemsRepository = Substitute.For>(); - TodoItemCategoriesRepository = Substitute.For>(); - TestTodoListDependantEntitiesRepository = Substitute.For>(); - InvitationLinksRepository = Substitute.For(); - TodoItemsHelperService = Substitute.For>(); - TodoItemCategoriesHelperService = Substitute.For>(); - TodoRolesHelperService = Substitute.For>(); - TodoMembersHelperService = Substitute.For>(); - RefreshTokensRepository = Substitute.For(); - PermissionsChecker = Substitute.For(); - TodoListRolesRepository = Substitute.For>(); - TodoListMembersRepository = Substitute.For(); + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + // Create mocks for the services + EntityExistenceChecker = Substitute.For(); + TodoListsRepository = Substitute.For>(); + TodoItemsRepository = Substitute.For>(); + TodoItemCategoriesRepository = Substitute.For>(); + TestTodoListDependantEntitiesRepository = Substitute.For>(); + InvitationLinksRepository = Substitute.For(); + TodoItemsHelperService = Substitute.For>(); + TodoItemCategoriesHelperService = Substitute.For>(); + TodoRolesHelperService = Substitute.For>(); + TodoMembersHelperService = Substitute.For>(); + RefreshTokensRepository = Substitute.For(); + PermissionsChecker = Substitute.For(); + TodoListRolesRepository = Substitute.For>(); + TodoListMembersRepository = Substitute.For(); - UnitOfWork = Substitute.For(); - UnitOfWork.BeginTransactionAsync().Returns(Task.CompletedTask); - UnitOfWork.CommitAsync().Returns(Task.CompletedTask); - UnitOfWork.RollbackAsync().Returns(Task.CompletedTask); + UnitOfWork = Substitute.For(); + UnitOfWork.BeginTransactionAsync().Returns(Task.CompletedTask); + UnitOfWork.CommitAsync().Returns(Task.CompletedTask); + UnitOfWork.RollbackAsync().Returns(Task.CompletedTask); - builder.ConfigureTestServices(services => - { - services.AddScoped(_ => EntityExistenceChecker); - services.AddScoped(_ => TodoListsRepository); - services.AddScoped(_ => TodoItemsRepository); - services.AddScoped(_ => TodoItemCategoriesRepository); - services.AddScoped(_ => TestTodoListDependantEntitiesRepository); - services.AddScoped(_ => InvitationLinksRepository); - services.AddScoped(_ => TodoItemsHelperService); - services.AddScoped(_ => TodoItemCategoriesHelperService); - services.AddScoped(_ => TodoRolesHelperService); - services.AddScoped(_ => TodoMembersHelperService); - services.AddScoped(_ => RefreshTokensRepository); - services.AddScoped(_ => PermissionsChecker); - services.AddScoped(_ => TodoListRolesRepository); - services.AddScoped(_ => TodoListMembersRepository); - services.AddScoped(_ => UnitOfWork); - }); - } + builder.ConfigureTestServices(services => + { + services.AddScoped(_ => EntityExistenceChecker); + services.AddScoped(_ => TodoListsRepository); + services.AddScoped(_ => TodoItemsRepository); + services.AddScoped(_ => TodoItemCategoriesRepository); + services.AddScoped(_ => TestTodoListDependantEntitiesRepository); + services.AddScoped(_ => InvitationLinksRepository); + services.AddScoped(_ => TodoItemsHelperService); + services.AddScoped(_ => TodoItemCategoriesHelperService); + services.AddScoped(_ => TodoRolesHelperService); + services.AddScoped(_ => TodoMembersHelperService); + services.AddScoped(_ => RefreshTokensRepository); + services.AddScoped(_ => PermissionsChecker); + services.AddScoped(_ => TodoListRolesRepository); + services.AddScoped(_ => TodoListMembersRepository); + services.AddScoped(_ => UnitOfWork); + }); + } } diff --git a/AdvancedTodoList.IntegrationTests/Factories/DataAccessWebApplicationFactory.cs b/AdvancedTodoList.IntegrationTests/Factories/DataAccessWebApplicationFactory.cs index 8caa61b..61e7e73 100644 --- a/AdvancedTodoList.IntegrationTests/Factories/DataAccessWebApplicationFactory.cs +++ b/AdvancedTodoList.IntegrationTests/Factories/DataAccessWebApplicationFactory.cs @@ -11,23 +11,23 @@ namespace AdvancedTodoList.IntegrationTests.Factories; /// public class DataAccessWebApplicationFactory(MsSqlContainer testDbContainer) : WebApplicationFactory { - private readonly MsSqlContainer _testDbContainer = testDbContainer; + private readonly MsSqlContainer _testDbContainer = testDbContainer; - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - builder.ConfigureServices(services => - { - var dbContextOptions = services.SingleOrDefault(service => typeof(DbContextOptions) == service.ServiceType); - if (dbContextOptions != null) - { - services.Remove(dbContextOptions); - } - var dbConnection = services.SingleOrDefault(service => typeof(DbConnection) == service.ServiceType); - if (dbConnection != null) - { - services.Remove(dbConnection); - } - services.AddDbContext((_, option) => option.UseSqlServer(_testDbContainer.GetConnectionString())); - }); - } + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + builder.ConfigureServices(services => + { + var dbContextOptions = services.SingleOrDefault(service => typeof(DbContextOptions) == service.ServiceType); + if (dbContextOptions != null) + { + services.Remove(dbContextOptions); + } + var dbConnection = services.SingleOrDefault(service => typeof(DbConnection) == service.ServiceType); + if (dbConnection != null) + { + services.Remove(dbConnection); + } + services.AddDbContext((_, option) => option.UseSqlServer(_testDbContainer.GetConnectionString())); + }); + } } diff --git a/AdvancedTodoList.IntegrationTests/Factories/EndpointsWebApplicationFactory.cs b/AdvancedTodoList.IntegrationTests/Factories/EndpointsWebApplicationFactory.cs index 1916297..6e9cf69 100644 --- a/AdvancedTodoList.IntegrationTests/Factories/EndpointsWebApplicationFactory.cs +++ b/AdvancedTodoList.IntegrationTests/Factories/EndpointsWebApplicationFactory.cs @@ -1,5 +1,6 @@ -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Services.Auth; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Repositories; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; @@ -11,37 +12,37 @@ namespace AdvancedTodoList.IntegrationTests.Factories; /// public class EndpointsWebApplicationFactory : WebApplicationFactory { - public IAuthService AuthService { get; private set; } = null!; - public ITodoListsService TodoListsService { get; private set; } = null!; - public ITodoItemsService TodoItemsService { get; private set; } = null!; - public ITodoItemCategoriesService TodoItemCategoriesService { get; private set; } = null!; - public ITodoListRolesService TodoListRolesService { get; private set; } = null!; - public ITodoListMembersService TodoListMembersService { get; private set; } = null!; - public IInvitationLinksService InvitationLinksService { get; private set; } = null!; - public IEntityExistenceChecker EntityExistenceChecker { get; private set; } = null!; + public IAuthService AuthService { get; private set; } = null!; + public ITodoListsService TodoListsService { get; private set; } = null!; + public ITodoItemsService TodoItemsService { get; private set; } = null!; + public ITodoItemCategoriesService TodoItemCategoriesService { get; private set; } = null!; + public ITodoListRolesService TodoListRolesService { get; private set; } = null!; + public ITodoListMembersService TodoListMembersService { get; private set; } = null!; + public IInvitationLinksService InvitationLinksService { get; private set; } = null!; + public IEntityExistenceChecker EntityExistenceChecker { get; private set; } = null!; - protected override void ConfigureWebHost(IWebHostBuilder builder) - { - // Create mocks for the services - AuthService = Substitute.For(); - TodoListsService = Substitute.For(); - TodoItemsService = Substitute.For(); - TodoItemCategoriesService = Substitute.For(); - TodoListRolesService = Substitute.For(); - TodoListMembersService = Substitute.For(); - InvitationLinksService = Substitute.For(); - EntityExistenceChecker = Substitute.For(); + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + // Create mocks for the services + AuthService = Substitute.For(); + TodoListsService = Substitute.For(); + TodoItemsService = Substitute.For(); + TodoItemCategoriesService = Substitute.For(); + TodoListRolesService = Substitute.For(); + TodoListMembersService = Substitute.For(); + InvitationLinksService = Substitute.For(); + EntityExistenceChecker = Substitute.For(); - builder.ConfigureTestServices(services => - { - services.AddScoped(_ => AuthService); - services.AddScoped(_ => TodoListsService); - services.AddScoped(_ => TodoItemsService); - services.AddScoped(_ => TodoItemCategoriesService); - services.AddScoped(_ => TodoListRolesService); - services.AddScoped(_ => TodoListMembersService); - services.AddScoped(_ => InvitationLinksService); - services.AddScoped(_ => EntityExistenceChecker); - }); - } + builder.ConfigureTestServices(services => + { + services.AddScoped(_ => AuthService); + services.AddScoped(_ => TodoListsService); + services.AddScoped(_ => TodoItemsService); + services.AddScoped(_ => TodoItemCategoriesService); + services.AddScoped(_ => TodoListRolesService); + services.AddScoped(_ => TodoListMembersService); + services.AddScoped(_ => InvitationLinksService); + services.AddScoped(_ => EntityExistenceChecker); + }); + } } diff --git a/AdvancedTodoList.IntegrationTests/Fixtures/BusinessLogicFixture.cs b/AdvancedTodoList.IntegrationTests/Fixtures/BusinessLogicFixture.cs index ebccf1e..6fdb509 100644 --- a/AdvancedTodoList.IntegrationTests/Fixtures/BusinessLogicFixture.cs +++ b/AdvancedTodoList.IntegrationTests/Fixtures/BusinessLogicFixture.cs @@ -7,24 +7,24 @@ namespace AdvancedTodoList.IntegrationTests.Fixtures; /// public abstract class BusinessLogicFixture { - protected BusinessLogicWebApplicationFactory WebApplicationFactory { get; private set; } - protected IServiceScope ServiceScope { get; private set; } + protected BusinessLogicWebApplicationFactory WebApplicationFactory { get; private set; } + protected IServiceScope ServiceScope { get; private set; } - [SetUp] - public void SetUpTest() - { - // Configure web application factory - WebApplicationFactory = new BusinessLogicWebApplicationFactory(); - WebApplicationFactory.Server.PreserveExecutionContext = true; + [SetUp] + public void SetUpTest() + { + // Configure web application factory + WebApplicationFactory = new BusinessLogicWebApplicationFactory(); + WebApplicationFactory.Server.PreserveExecutionContext = true; - var scopeFactory = WebApplicationFactory.Services.GetService()!; - ServiceScope = scopeFactory.CreateScope(); - } + var scopeFactory = WebApplicationFactory.Services.GetService()!; + ServiceScope = scopeFactory.CreateScope(); + } - [TearDown] - public Task TearDownTestAsync() - { - ServiceScope.Dispose(); - return WebApplicationFactory.DisposeAsync().AsTask(); - } + [TearDown] + public async Task TearDownTestAsync() + { + ServiceScope.Dispose(); + await WebApplicationFactory.DisposeAsync(); + } } diff --git a/AdvancedTodoList.IntegrationTests/Fixtures/DataAccessFixture.cs b/AdvancedTodoList.IntegrationTests/Fixtures/DataAccessFixture.cs index 5442dc3..27f3381 100644 --- a/AdvancedTodoList.IntegrationTests/Fixtures/DataAccessFixture.cs +++ b/AdvancedTodoList.IntegrationTests/Fixtures/DataAccessFixture.cs @@ -8,39 +8,39 @@ namespace AdvancedTodoList.IntegrationTests.Fixtures; /// public abstract class DataAccessFixture { - private static bool s_migrated = false; + private static bool s_migrated = false; - protected static DataAccessWebApplicationFactory WebApplicationFactory { get; private set; } - protected IServiceScope ServiceScope { get; private set; } - protected ApplicationDbContext DbContext { get; private set; } + protected static DataAccessWebApplicationFactory WebApplicationFactory { get; private set; } + protected IServiceScope ServiceScope { get; private set; } + protected ApplicationDbContext DbContext { get; private set; } - [SetUp] - public async Task SetUpServices() - { - // Configure web application factory - var container = await TestContainersSetup.GetTestContainerAsync(); - WebApplicationFactory = new DataAccessWebApplicationFactory(container); - WebApplicationFactory.Server.PreserveExecutionContext = true; + [SetUp] + public async Task SetUpServices() + { + // Configure web application factory + var container = await TestContainersSetup.GetTestContainerAsync(); + WebApplicationFactory = new DataAccessWebApplicationFactory(container); + WebApplicationFactory.Server.PreserveExecutionContext = true; - // Get services needed for integration testing - var scopeFactory = WebApplicationFactory.Services.GetService()!; - ServiceScope = scopeFactory.CreateScope(); - DbContext = ServiceScope.ServiceProvider.GetService()!; - // Migrate database if it isn't migrated yet - if (!s_migrated) - { - await DbContext.Database.MigrateAsync(); - s_migrated = true; - } - } + // Get services needed for integration testing + var scopeFactory = WebApplicationFactory.Services.GetService()!; + ServiceScope = scopeFactory.CreateScope(); + DbContext = ServiceScope.ServiceProvider.GetService()!; + // Migrate database if it isn't migrated yet + if (!s_migrated) + { + await DbContext.Database.MigrateAsync(); + s_migrated = true; + } + } - [TearDown] - public async Task TearDownServicesAsync() - { - // Dispose resources - await DbContext.DisposeAsync(); - ServiceScope.Dispose(); + [TearDown] + public async Task TearDownServicesAsync() + { + // Dispose resources + await DbContext.DisposeAsync(); + ServiceScope.Dispose(); - await WebApplicationFactory.DisposeAsync(); - } + await WebApplicationFactory.DisposeAsync(); + } } diff --git a/AdvancedTodoList.IntegrationTests/Fixtures/EndpointsFixture.cs b/AdvancedTodoList.IntegrationTests/Fixtures/EndpointsFixture.cs index 05eef27..f2c77af 100644 --- a/AdvancedTodoList.IntegrationTests/Fixtures/EndpointsFixture.cs +++ b/AdvancedTodoList.IntegrationTests/Fixtures/EndpointsFixture.cs @@ -12,54 +12,54 @@ namespace AdvancedTodoList.IntegrationTests.Fixtures; /// public abstract class EndpointsFixture { - protected IConfiguration Configuration { get; private set; } - protected EndpointsWebApplicationFactory WebApplicationFactory { get; private set; } - protected IServiceScope ServiceScope { get; private set; } + protected IConfiguration Configuration { get; private set; } + protected EndpointsWebApplicationFactory WebApplicationFactory { get; private set; } + protected IServiceScope ServiceScope { get; private set; } - public const string TestUserId = "TestUserId"; + public const string TestUserId = "TestUserId"; - protected HttpClient CreateAuthorizedHttpClient(string userId = TestUserId) - { - // Create a valid JWT - string strKey = Configuration["Auth:AccessToken:SecretKey"]!; - SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(strKey)); + protected HttpClient CreateAuthorizedHttpClient(string userId = TestUserId) + { + // Create a valid JWT + string strKey = Configuration["Auth:AccessToken:SecretKey"]!; + SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(strKey)); - List authClaims = - [ - new(JwtRegisteredClaimNames.Sub, userId) - ]; - JwtSecurityToken token = new( - issuer: Configuration["Auth:AccessToken:ValidIssuer"], - audience: Configuration["Auth:AccessToken:ValidAudience"], - expires: DateTime.UtcNow.AddMinutes(30), - claims: authClaims, - signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) - ); + List authClaims = + [ + new(JwtRegisteredClaimNames.Sub, userId) + ]; + JwtSecurityToken token = new( + issuer: Configuration["Auth:AccessToken:ValidIssuer"], + audience: Configuration["Auth:AccessToken:ValidAudience"], + expires: DateTime.UtcNow.AddMinutes(30), + claims: authClaims, + signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) + ); - string bearerToken = new JwtSecurityTokenHandler().WriteToken(token); + string bearerToken = new JwtSecurityTokenHandler().WriteToken(token); - var client = WebApplicationFactory.CreateClient(); - client.DefaultRequestHeaders.Add("Authorization", $"Bearer {bearerToken}"); + var client = WebApplicationFactory.CreateClient(); + client.DefaultRequestHeaders.Add("Authorization", $"Bearer {bearerToken}"); - return client; - } + return client; + } - [SetUp] - public void SetUpTest() - { - // Configure web application factory - WebApplicationFactory = new EndpointsWebApplicationFactory(); - WebApplicationFactory.Server.PreserveExecutionContext = true; + [SetUp] + public void SetUpTest() + { + // Configure web application factory + WebApplicationFactory = new EndpointsWebApplicationFactory(); + WebApplicationFactory.Server.PreserveExecutionContext = true; - var scopeFactory = WebApplicationFactory.Services.GetService()!; - ServiceScope = scopeFactory.CreateScope(); - Configuration = ServiceScope.ServiceProvider.GetService()!; - } + var scopeFactory = WebApplicationFactory.Services.GetService()!; + ServiceScope = scopeFactory.CreateScope(); + Configuration = ServiceScope.ServiceProvider.GetService()!; + } - [TearDown] - public Task TearDownTestAsync() - { - ServiceScope.Dispose(); - return WebApplicationFactory.DisposeAsync().AsTask(); - } + [TearDown] + public async Task TearDownTestAsync() + { + ServiceScope.Dispose(); + await WebApplicationFactory.DisposeAsync(); + } } diff --git a/AdvancedTodoList.IntegrationTests/Repositories/BaseRepositoryTests.cs b/AdvancedTodoList.IntegrationTests/Repositories/BaseRepositoryTests.cs index 4dd7346..e10de04 100644 --- a/AdvancedTodoList.IntegrationTests/Repositories/BaseRepositoryTests.cs +++ b/AdvancedTodoList.IntegrationTests/Repositories/BaseRepositoryTests.cs @@ -13,212 +13,212 @@ namespace AdvancedTodoList.IntegrationTests.Repositories; /// The type of entity. /// The type of entity's primary key. public abstract class BaseRepositoryTests : DataAccessFixture - where TEntity : class, IEntity - where TKey : IEquatable + where TEntity : class, IEntity + where TKey : IEquatable { - protected class TestSpecification : ISpecification - { - public Expression> Criteria { get; set; } = _ => true; - - public List>> Includes { get; set; } = []; - - public List IncludeStrings { get; set; } = []; - } - - protected record TestDto(TKey Id); - - /// - /// When implemented, gets an ID of non-existing entity. - /// - protected abstract TKey NonExistingId { get; } - - protected IRepository Repository { get; private set; } - - [SetUp] - public void SetUpRepository() - { - Repository = ServiceScope.ServiceProvider.GetService>()!; - } - - /// - /// When implemented, creates a test entity which can be added to the database. - /// - /// - /// A test entity which can be added to the database. - /// - protected abstract Task CreateTestEntityAsync(); - - /// - /// When implemented, performs a test update on an entity. - /// - /// Entity to be updated. - protected abstract void UpdateEntity(TEntity entity); - - /// - /// When implemented, asserts that entity was updated. - /// - /// Entity that should've been updated. - protected abstract void AssertUpdated(TEntity updatedEntity); - - /// - /// Adds a test entity to the database and untracks it. - /// - /// - /// A test entity which was added to the database. - /// - protected async Task AddTestEntityToDbAsync() - { - TEntity entity = await CreateTestEntityAsync(); - DbContext.Set().Add(entity); - await DbContext.SaveChangesAsync(); - DbContext.ChangeTracker.Clear(); - - return entity; - } - - [Test] - public async Task AddAsync_AddsEntityToDb() - { - // Arrange - var entity = await CreateTestEntityAsync(); - - // Act - await Repository.AddAsync(entity); - - // Assert that entity was added - var foundEntity = await DbContext.Set() - .Where(x => x.Id.Equals(entity.Id)) - .FirstOrDefaultAsync(); - Assert.That(foundEntity, Is.Not.Null); - } - - [Test] - public async Task GetByIdAsync_EntityDoesNotExist_ReturnsNull() - { - // Act - var result = await Repository.GetByIdAsync(NonExistingId); - - // Assert - Assert.That(result, Is.Null); - } - - [Test] - public async Task GetByIdAsync_EntityExists_ReturnsEntityWithValidId() - { - // Arrange - var entity = await AddTestEntityToDbAsync(); - - // Act - var result = await Repository.GetByIdAsync(entity.Id); - - // Assert - Assert.That(result, Is.Not.Null); - Assert.That(result.Id, Is.EqualTo(entity.Id)); - } - - [Test] - public async Task GetAggregateAsync_TestGetByIdSpecification_ReturnsValidDto() - { - // Arrange - var entity = await AddTestEntityToDbAsync(); - TestSpecification specification = new() - { - Criteria = x => x.Id.Equals(entity.Id) - }; - - // Act - var aggregate = await Repository.GetAggregateAsync(specification); - - // Assert - Assert.That(aggregate, Is.Not.Null); - Assert.That(aggregate.Id, Is.EqualTo(entity.Id)); - } - - [Test] - public async Task GetAggregateAsync_TestNoCriteriaSpecification_ReturnsNull() - { - // Arrange - TestSpecification specification = new() - { - Criteria = _ => false - }; - - // Act - var aggregate = await Repository.GetAggregateAsync(specification); - - // Assert - Assert.That(aggregate, Is.Null); - } - - [Test] - public async Task GetPageAsync_AppliesSpecification() - { - // Arrange - var entity = await AddTestEntityToDbAsync(); - TestSpecification specification = new() - { - Criteria = x => x.Id.Equals(entity.Id) - }; - - // Act - var page = await Repository.GetPageAsync(new(1, 5), specification); - - // Assert - Assert.Multiple(() => - { - Assert.That(page.TotalCount, Is.EqualTo(1)); - Assert.That(page.Items.Single().Id, Is.EqualTo(entity.Id)); - }); - } - - [Test] - public async Task GetPageAsync_AppliesPaginationParameters() - { - // Arrange - var entity = await CreateTestEntityAsync(); - TestSpecification specification = new(); - PaginationParameters paginationParameters = new(2, 10); - - // Act - var page = await Repository.GetPageAsync(paginationParameters, specification); - - // Assert - int totalCount = await DbContext.Set().CountAsync(); - Assert.Multiple(() => - { - Assert.That(page.TotalCount, Is.EqualTo(totalCount)); - Assert.That(page.PageNumber, Is.EqualTo(paginationParameters.Page)); - Assert.That(page.PageSize, Is.EqualTo(paginationParameters.PageSize)); - }); - } - - [Test] - public async Task UpdateAsync_UpdatesEntity() - { - // Arrange - var entity = await AddTestEntityToDbAsync(); - - // Act - UpdateEntity(entity); - await Repository.UpdateAsync(entity); - - // Assert - var updatedEntity = await DbContext.Set() - .Where(x => x.Id.Equals(entity.Id)) - .FirstAsync(); - AssertUpdated(updatedEntity); - } - - [Test] - public async Task DeleteAsync_DeletesEntity() - { - // Arrange - var entity = await AddTestEntityToDbAsync(); - - // Act - await Repository.DeleteAsync(entity); - - // Assert - Assert.That(DbContext.Set().Any(x => x.Id.Equals(entity.Id)), Is.False, "Entity was not deleted."); - } + protected class TestSpecification : ISpecification + { + public Expression> Criteria { get; set; } = _ => true; + + public List>> Includes { get; set; } = []; + + public List IncludeStrings { get; set; } = []; + } + + protected record TestDto(TKey Id); + + /// + /// When implemented, gets an ID of non-existing entity. + /// + protected abstract TKey NonExistingId { get; } + + protected IRepository Repository { get; private set; } + + [SetUp] + public void SetUpRepository() + { + Repository = ServiceScope.ServiceProvider.GetService>()!; + } + + /// + /// When implemented, creates a test entity which can be added to the database. + /// + /// + /// A test entity which can be added to the database. + /// + protected abstract Task CreateTestEntityAsync(); + + /// + /// When implemented, performs a test update on an entity. + /// + /// Entity to be updated. + protected abstract void UpdateEntity(TEntity entity); + + /// + /// When implemented, asserts that entity was updated. + /// + /// Entity that should've been updated. + protected abstract void AssertUpdated(TEntity updatedEntity); + + /// + /// Adds a test entity to the database and untracks it. + /// + /// + /// A test entity which was added to the database. + /// + protected async Task AddTestEntityToDbAsync() + { + TEntity entity = await CreateTestEntityAsync(); + DbContext.Set().Add(entity); + await DbContext.SaveChangesAsync(); + DbContext.ChangeTracker.Clear(); + + return entity; + } + + [Test] + public async Task AddAsync_AddsEntityToDb() + { + // Arrange + var entity = await CreateTestEntityAsync(); + + // Act + await Repository.AddAsync(entity); + + // Assert that entity was added + var foundEntity = await DbContext.Set() + .Where(x => x.Id.Equals(entity.Id)) + .FirstOrDefaultAsync(); + Assert.That(foundEntity, Is.Not.Null); + } + + [Test] + public async Task GetByIdAsync_EntityDoesNotExist_ReturnsNull() + { + // Act + var result = await Repository.GetByIdAsync(NonExistingId); + + // Assert + Assert.That(result, Is.Null); + } + + [Test] + public async Task GetByIdAsync_EntityExists_ReturnsEntityWithValidId() + { + // Arrange + var entity = await AddTestEntityToDbAsync(); + + // Act + var result = await Repository.GetByIdAsync(entity.Id); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.Id, Is.EqualTo(entity.Id)); + } + + [Test] + public async Task GetAggregateAsync_TestGetByIdSpecification_ReturnsValidDto() + { + // Arrange + var entity = await AddTestEntityToDbAsync(); + TestSpecification specification = new() + { + Criteria = x => x.Id.Equals(entity.Id) + }; + + // Act + var aggregate = await Repository.GetAggregateAsync(specification); + + // Assert + Assert.That(aggregate, Is.Not.Null); + Assert.That(aggregate.Id, Is.EqualTo(entity.Id)); + } + + [Test] + public async Task GetAggregateAsync_TestNoCriteriaSpecification_ReturnsNull() + { + // Arrange + TestSpecification specification = new() + { + Criteria = _ => false + }; + + // Act + var aggregate = await Repository.GetAggregateAsync(specification); + + // Assert + Assert.That(aggregate, Is.Null); + } + + [Test] + public async Task GetPageAsync_AppliesSpecification() + { + // Arrange + var entity = await AddTestEntityToDbAsync(); + TestSpecification specification = new() + { + Criteria = x => x.Id.Equals(entity.Id) + }; + + // Act + var page = await Repository.GetPageAsync(new(1, 5), specification); + + // Assert + using (Assert.EnterMultipleScope()) + { + Assert.That(page.TotalCount, Is.EqualTo(1)); + Assert.That(page.Items.Single().Id, Is.EqualTo(entity.Id)); + } + } + + [Test] + public async Task GetPageAsync_AppliesPaginationParameters() + { + // Arrange + await CreateTestEntityAsync(); + TestSpecification specification = new(); + PaginationParameters paginationParameters = new(2, 10); + + // Act + var page = await Repository.GetPageAsync(paginationParameters, specification); + + // Assert + int totalCount = await DbContext.Set().CountAsync(); + using (Assert.EnterMultipleScope()) + { + Assert.That(page.TotalCount, Is.EqualTo(totalCount)); + Assert.That(page.PageNumber, Is.EqualTo(paginationParameters.Page)); + Assert.That(page.PageSize, Is.EqualTo(paginationParameters.PageSize)); + } + } + + [Test] + public async Task UpdateAsync_UpdatesEntity() + { + // Arrange + var entity = await AddTestEntityToDbAsync(); + + // Act + UpdateEntity(entity); + await Repository.UpdateAsync(entity); + + // Assert + var updatedEntity = await DbContext.Set() + .Where(x => x.Id.Equals(entity.Id)) + .FirstAsync(); + AssertUpdated(updatedEntity); + } + + [Test] + public async Task DeleteAsync_DeletesEntity() + { + // Arrange + var entity = await AddTestEntityToDbAsync(); + + // Act + await Repository.DeleteAsync(entity); + + // Assert + Assert.That(DbContext.Set().Any(x => x.Id.Equals(entity.Id)), Is.False, "Entity was not deleted."); + } } diff --git a/AdvancedTodoList.IntegrationTests/Repositories/InvitationLinksRepositoryTests.cs b/AdvancedTodoList.IntegrationTests/Repositories/InvitationLinksRepositoryTests.cs index e0e2082..0252d13 100644 --- a/AdvancedTodoList.IntegrationTests/Repositories/InvitationLinksRepositoryTests.cs +++ b/AdvancedTodoList.IntegrationTests/Repositories/InvitationLinksRepositoryTests.cs @@ -6,55 +6,55 @@ namespace AdvancedTodoList.IntegrationTests.Repositories; public class InvitationLinksRepositoryTests : BaseRepositoryTests { - private IInvitationLinksRepository LinksRepository => (IInvitationLinksRepository)Repository; + private IInvitationLinksRepository LinksRepository => (IInvitationLinksRepository)Repository; - protected override int NonExistingId => -1; + protected override int NonExistingId => -1; - private readonly DateTime UpdatedValidTo = DateTime.UtcNow.AddDays(10); + private readonly DateTime UpdatedValidTo = DateTime.UtcNow.AddDays(10); - protected override void AssertUpdated(InvitationLink updatedEntity) - { - Assert.That(updatedEntity.ValidTo, Is.EqualTo(UpdatedValidTo)); - } + protected override void AssertUpdated(InvitationLink updatedEntity) + { + Assert.That(updatedEntity.ValidTo, Is.EqualTo(UpdatedValidTo)); + } - protected override async Task CreateTestEntityAsync() - { - TodoList todoList = TestModels.CreateTestTodoList(); - DbContext.Add(todoList); - await DbContext.SaveChangesAsync(); + protected override async Task CreateTestEntityAsync() + { + TodoList todoList = TestModels.CreateTestTodoList(); + DbContext.Add(todoList); + await DbContext.SaveChangesAsync(); - return TestModels.CreateTestInvitationLink(todoList.Id); - } + return TestModels.CreateTestInvitationLink(todoList.Id); + } - protected override void UpdateEntity(InvitationLink entity) - { - entity.ValidTo = UpdatedValidTo; - } + protected override void UpdateEntity(InvitationLink entity) + { + entity.ValidTo = UpdatedValidTo; + } - [Test] - public async Task FindAsync_EntityExists_ReturnsValidLink() - { - // Arrange - var link = await AddTestEntityToDbAsync(); + [Test] + public async Task FindAsync_EntityExists_ReturnsValidLink() + { + // Arrange + var link = await AddTestEntityToDbAsync(); - // Act - var result = await LinksRepository.FindAsync(link.Value); + // Act + var result = await LinksRepository.FindAsync(link.Value); - // Assert - Assert.That(result, Is.Not.Null); - Assert.That(result.Id, Is.EqualTo(link.Id)); - } + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.Id, Is.EqualTo(link.Id)); + } - [Test] - public async Task FindAsync_EntityDoesNotExist_ReturnsNull() - { - // Arrange - string link = "bad link"; + [Test] + public async Task FindAsync_EntityDoesNotExist_ReturnsNull() + { + // Arrange + string link = "bad link"; - // Act - var result = await LinksRepository.FindAsync(link); + // Act + var result = await LinksRepository.FindAsync(link); - // Assert - Assert.That(result, Is.Null); - } + // Assert + Assert.That(result, Is.Null); + } } diff --git a/AdvancedTodoList.IntegrationTests/Repositories/TodoItemCategoriesRepositoryTests.cs b/AdvancedTodoList.IntegrationTests/Repositories/TodoItemCategoriesRepositoryTests.cs index 49833d9..d98e8f0 100644 --- a/AdvancedTodoList.IntegrationTests/Repositories/TodoItemCategoriesRepositoryTests.cs +++ b/AdvancedTodoList.IntegrationTests/Repositories/TodoItemCategoriesRepositoryTests.cs @@ -6,24 +6,24 @@ namespace AdvancedTodoList.IntegrationTests.Repositories; [TestFixture] public class TodoItemCategoriesRepositoryTests : BaseRepositoryTests { - protected override int NonExistingId => -1; - protected const string UpdatedName = "New name"; + protected override int NonExistingId => -1; + protected const string UpdatedName = "New name"; - protected override void AssertUpdated(TodoItemCategory updatedEntity) - { - Assert.That(updatedEntity.Name, Is.EqualTo(UpdatedName)); - } + protected override void AssertUpdated(TodoItemCategory updatedEntity) + { + Assert.That(updatedEntity.Name, Is.EqualTo(UpdatedName)); + } - protected override async Task CreateTestEntityAsync() - { - var todoList = TestModels.CreateTestTodoList(); - DbContext.Add(todoList); - await DbContext.SaveChangesAsync(); - return TestModels.CreateTestTodoItemCategory(todoList.Id); - } + protected override async Task CreateTestEntityAsync() + { + var todoList = TestModels.CreateTestTodoList(); + DbContext.Add(todoList); + await DbContext.SaveChangesAsync(); + return TestModels.CreateTestTodoItemCategory(todoList.Id); + } - protected override void UpdateEntity(TodoItemCategory entity) - { - entity.Name = UpdatedName; - } + protected override void UpdateEntity(TodoItemCategory entity) + { + entity.Name = UpdatedName; + } } diff --git a/AdvancedTodoList.IntegrationTests/Repositories/TodoItemsRepositoryTests.cs b/AdvancedTodoList.IntegrationTests/Repositories/TodoItemsRepositoryTests.cs index 4e23811..ae21aab 100644 --- a/AdvancedTodoList.IntegrationTests/Repositories/TodoItemsRepositoryTests.cs +++ b/AdvancedTodoList.IntegrationTests/Repositories/TodoItemsRepositoryTests.cs @@ -1,104 +1,108 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.Core.Specifications.Todo; using AdvancedTodoList.IntegrationTests.Utils; namespace AdvancedTodoList.IntegrationTests.Repositories; public class TodoItemsRepositoryTests : BaseRepositoryTests { - protected override int NonExistingId => -1; + protected override int NonExistingId => -1; - protected const string UpdatedName = "New name"; - protected const string UpdatedDescription = "New description"; - protected const TodoItemState UpdatedState = TodoItemState.Completed; - protected readonly DateTime UpdatedDeadlineDate = DateTime.UtcNow.AddDays(100); + protected const string UpdatedName = "New name"; + protected const string UpdatedDescription = "New description"; + protected const TodoItemState UpdatedState = TodoItemState.Completed; + protected readonly DateTime UpdatedDeadlineDate = DateTime.UtcNow.AddDays(100); - protected override void UpdateEntity(TodoItem entity) - { - entity.Name = UpdatedName; - entity.Description = UpdatedDescription; - entity.State = UpdatedState; - entity.DeadlineDate = UpdatedDeadlineDate; - } + protected override void UpdateEntity(TodoItem entity) + { + entity.Name = UpdatedName; + entity.Description = UpdatedDescription; + entity.State = UpdatedState; + entity.DeadlineDate = UpdatedDeadlineDate; + } - protected override void AssertUpdated(TodoItem updatedEntity) - { - Assert.Multiple(() => - { - Assert.That(updatedEntity.Name, Is.EqualTo(UpdatedName)); - Assert.That(updatedEntity.Description, Is.EqualTo(UpdatedDescription)); - Assert.That(updatedEntity.State, Is.EqualTo(UpdatedState)); - Assert.That(updatedEntity.DeadlineDate, Is.EqualTo(UpdatedDeadlineDate)); - }); - } + protected override void AssertUpdated(TodoItem updatedEntity) + { + using (Assert.EnterMultipleScope()) + { + Assert.That(updatedEntity.Name, Is.EqualTo(UpdatedName)); + Assert.That(updatedEntity.Description, Is.EqualTo(UpdatedDescription)); + Assert.That(updatedEntity.State, Is.EqualTo(UpdatedState)); + Assert.That(updatedEntity.DeadlineDate, Is.EqualTo(UpdatedDeadlineDate)); + } + } - protected override async Task CreateTestEntityAsync() - { - var todoList = TestModels.CreateTestTodoList(); - DbContext.Add(todoList); - await DbContext.SaveChangesAsync(); - return TestModels.CreateTestTodoItem(todoList.Id); - } + protected override async Task CreateTestEntityAsync() + { + var todoList = TestModels.CreateTestTodoList(); + DbContext.Add(todoList); + await DbContext.SaveChangesAsync(); + return TestModels.CreateTestTodoItem(todoList.Id); + } - private async Task CreateItemWithOwnerAndCategoryAsync() - { - var owner = TestModels.CreateTestUser(); - DbContext.Add(owner); - await DbContext.SaveChangesAsync(); - var todoList = TestModels.CreateTestTodoList(); - DbContext.Add(todoList); - await DbContext.SaveChangesAsync(); - var category = TestModels.CreateTestTodoItemCategory(todoList.Id); - DbContext.Add(category); - await DbContext.SaveChangesAsync(); - var todoItem = TestModels.CreateTestTodoItem(todoList.Id, owner.Id, category.Id); - DbContext.Add(todoItem); - await DbContext.SaveChangesAsync(); + private async Task CreateItemWithOwnerAndCategoryAsync() + { + var owner = TestModels.CreateTestUser(); + DbContext.Add(owner); + await DbContext.SaveChangesAsync(); + var todoList = TestModels.CreateTestTodoList(); + DbContext.Add(todoList); + await DbContext.SaveChangesAsync(); + var category = TestModels.CreateTestTodoItemCategory(todoList.Id); + DbContext.Add(category); + await DbContext.SaveChangesAsync(); + var todoItem = TestModels.CreateTestTodoItem(todoList.Id, owner.Id, category.Id); + DbContext.Add(todoItem); + await DbContext.SaveChangesAsync(); - return todoItem; - } + return todoItem; + } - [Test] - public async Task GetAggregateAsync_TodoListAggregateSpecification_IncludesOwnerAndCategory() - { + [Test] + public async Task GetAggregateAsync_TodoListAggregateSpecification_IncludesOwnerAndCategory() + { +#pragma warning disable IDE0079 // Remove unnecessary suppression #pragma warning disable NUnit2045 // Use Assert.Multiple - // Arrange - var todoItem = await CreateItemWithOwnerAndCategoryAsync(); - TodoItemAggregateSpecification specification = new(todoItem.Id); + // Arrange + var todoItem = await CreateItemWithOwnerAndCategoryAsync(); + TodoItemAggregateSpecification specification = new(todoItem.Id); - // Act - var aggregate = await Repository.GetAggregateAsync(specification); + // Act + var aggregate = await Repository.GetAggregateAsync(specification); - // Assert - Assert.That(aggregate, Is.Not.Null); - Assert.That(aggregate.Owner, Is.Not.Null); - Assert.That(aggregate.Owner.Id, Is.EqualTo(todoItem.Owner!.Id)); - Assert.That(aggregate.Category, Is.Not.Null); - Assert.That(aggregate.Category.Id, Is.EqualTo(todoItem.Category!.Id)); + // Assert + Assert.That(aggregate, Is.Not.Null); + Assert.That(aggregate.Owner, Is.Not.Null); + Assert.That(aggregate.Owner.Id, Is.EqualTo(todoItem.Owner!.Id)); + Assert.That(aggregate.Category, Is.Not.Null); + Assert.That(aggregate.Category.Id, Is.EqualTo(todoItem.Category!.Id)); #pragma warning restore NUnit2045 // Use Assert.Multiple - } +#pragma warning restore IDE0079 // Remove unnecessary suppression + } - [Test] - public async Task GetPageAsync_IntegratesWithTodoItemsSpecification() - { + [Test] + public async Task GetPageAsync_IntegratesWithTodoItemsSpecification() + { +#pragma warning disable IDE0079 // Remove unnecessary suppression #pragma warning disable NUnit2045 // Use Assert.Multiple - // Arrange - var todoItem = await CreateItemWithOwnerAndCategoryAsync(); - TodoItemsFilter filter = new(todoItem.Name, todoItem.OwnerId, [todoItem.State], [todoItem.CategoryId]); - TodoItemsSpecification specification = new(todoItem.TodoListId, filter); + // Arrange + var todoItem = await CreateItemWithOwnerAndCategoryAsync(); + TodoItemsFilter filter = new(todoItem.Name, todoItem.OwnerId, [todoItem.State], [todoItem.CategoryId]); + TodoItemsSpecification specification = new(todoItem.TodoListId, filter); - // Act - var page = await Repository.GetPageAsync(new(1, 5), specification); + // Act + var page = await Repository.GetPageAsync(new(1, 5), specification); - // Assert - var dto = page.Items.Where(x => x.Id == todoItem.Id).SingleOrDefault(); - Assert.That(dto, Is.Not.Null); - Assert.That(dto.Category, Is.Not.Null); - Assert.That(dto.Category.Id, Is.EqualTo(todoItem.CategoryId)); - Assert.That(dto.Owner, Is.Not.Null); - Assert.That(dto.Owner.Id, Is.EqualTo(todoItem.OwnerId)); + // Assert + var dto = page.Items.Where(x => x.Id == todoItem.Id).SingleOrDefault(); + Assert.That(dto, Is.Not.Null); + Assert.That(dto.Category, Is.Not.Null); + Assert.That(dto.Category.Id, Is.EqualTo(todoItem.CategoryId)); + Assert.That(dto.Owner, Is.Not.Null); + Assert.That(dto.Owner.Id, Is.EqualTo(todoItem.OwnerId)); #pragma warning restore NUnit2045 // Use Assert.Multiple - } +#pragma warning restore IDE0079 // Remove unnecessary suppression + } } diff --git a/AdvancedTodoList.IntegrationTests/Repositories/TodoListMembersRepositoryTests.cs b/AdvancedTodoList.IntegrationTests/Repositories/TodoListMembersRepositoryTests.cs index ac5fc40..75c8363 100644 --- a/AdvancedTodoList.IntegrationTests/Repositories/TodoListMembersRepositoryTests.cs +++ b/AdvancedTodoList.IntegrationTests/Repositories/TodoListMembersRepositoryTests.cs @@ -1,8 +1,8 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; using AdvancedTodoList.Core.Models.TodoLists.Members; using AdvancedTodoList.Core.Repositories; -using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.Core.Specifications.Todo; using AdvancedTodoList.IntegrationTests.Utils; namespace AdvancedTodoList.IntegrationTests.Repositories; @@ -10,93 +10,95 @@ namespace AdvancedTodoList.IntegrationTests.Repositories; [TestFixture] public class TodoListMembersRepositoryTests : BaseRepositoryTests { - private ITodoListMembersRepository MembersRepository => (ITodoListMembersRepository)Repository; - - protected override int NonExistingId => -1; - - private readonly int? UpdatedRoleId = null; - - protected override void AssertUpdated(TodoListMember updatedEntity) - { - Assert.That(updatedEntity.RoleId, Is.EqualTo(UpdatedRoleId)); - } - - protected override async Task CreateTestEntityAsync() - { - var user = TestModels.CreateTestUser(); - DbContext.Add(user); - var todoList = TestModels.CreateTestTodoList(); - DbContext.Add(todoList); - await DbContext.SaveChangesAsync(); - var role = TestModels.CreateTestRole(todoList.Id); - DbContext.Add(role); - await DbContext.SaveChangesAsync(); - - return new() - { - TodoListId = todoList.Id, - UserId = user.Id, - RoleId = role.Id - }; - } - - protected override void UpdateEntity(TodoListMember entity) - { - entity.RoleId = UpdatedRoleId; - } - - [Test] - public async Task FindAsync_MemberExists_ReturnsValidMember() - { - // Arrange - var member = await AddTestEntityToDbAsync(); - - // Act - var result = await MembersRepository.FindAsync(member.TodoListId, member.UserId); - - // Assert - Assert.That(result, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(result.TodoListId, Is.EqualTo(member.TodoListId)); - Assert.That(result.UserId, Is.EqualTo(member.UserId)); - }); - } - - [Test] - public async Task FindAsync_MemberDoesNotExist_ReturnsNull() - { - // Arrange - string todoListId = "wrong_todo_list"; - string userId = "no user :("; - - // Act - var result = await MembersRepository.FindAsync(todoListId, userId); - - // Assert - Assert.That(result, Is.Null); - } - - [Test] - public async Task GetPageAsync_IntegratesWithMembersSpecification() - { + private ITodoListMembersRepository MembersRepository => (ITodoListMembersRepository)Repository; + + protected override int NonExistingId => -1; + + private readonly int? UpdatedRoleId = null; + + protected override void AssertUpdated(TodoListMember updatedEntity) + { + Assert.That(updatedEntity.RoleId, Is.EqualTo(UpdatedRoleId)); + } + + protected override async Task CreateTestEntityAsync() + { + var user = TestModels.CreateTestUser(); + DbContext.Add(user); + var todoList = TestModels.CreateTestTodoList(); + DbContext.Add(todoList); + await DbContext.SaveChangesAsync(); + var role = TestModels.CreateTestRole(todoList.Id); + DbContext.Add(role); + await DbContext.SaveChangesAsync(); + + return new() + { + TodoListId = todoList.Id, + UserId = user.Id, + RoleId = role.Id + }; + } + + protected override void UpdateEntity(TodoListMember entity) + { + entity.RoleId = UpdatedRoleId; + } + + [Test] + public async Task FindAsync_MemberExists_ReturnsValidMember() + { + // Arrange + var member = await AddTestEntityToDbAsync(); + + // Act + var result = await MembersRepository.FindAsync(member.TodoListId, member.UserId); + + // Assert + Assert.That(result, Is.Not.Null); + using (Assert.EnterMultipleScope()) + { + Assert.That(result.TodoListId, Is.EqualTo(member.TodoListId)); + Assert.That(result.UserId, Is.EqualTo(member.UserId)); + } + } + + [Test] + public async Task FindAsync_MemberDoesNotExist_ReturnsNull() + { + // Arrange + string todoListId = "wrong_todo_list"; + string userId = "no user :("; + + // Act + var result = await MembersRepository.FindAsync(todoListId, userId); + + // Assert + Assert.That(result, Is.Null); + } + + [Test] + public async Task GetPageAsync_IntegratesWithMembersSpecification() + { +#pragma warning disable IDE0079 // Remove unnecessary suppression #pragma warning disable NUnit2045 // Use Assert.Multiple - // Arrange - var entity = await AddTestEntityToDbAsync(); - TodoListMembersFilter filter = new([entity.RoleId], entity.UserId, - entity.User.UserName, entity.User.FirstName); - TodoListMembersSpecification specification = new(entity.TodoListId, filter); - - // Act - var page = await Repository.GetPageAsync(new(1, 5), specification); - - // Assert - var dto = page.Items.Where(x => x.Id == entity.Id).SingleOrDefault(); - Assert.That(dto, Is.Not.Null); - Assert.That(dto.User, Is.Not.Null); - Assert.That(dto.User.Id, Is.EqualTo(entity.UserId)); - Assert.That(dto.Role, Is.Not.Null); - Assert.That(dto.Role.Id, Is.EqualTo(entity.RoleId)); + // Arrange + var entity = await AddTestEntityToDbAsync(); + TodoListMembersFilter filter = new([entity.RoleId], entity.UserId, + entity.User.UserName, entity.User.FirstName); + TodoListMembersSpecification specification = new(entity.TodoListId, filter); + + // Act + var page = await Repository.GetPageAsync(new(1, 5), specification); + + // Assert + var dto = page.Items.Where(x => x.Id == entity.Id).SingleOrDefault(); + Assert.That(dto, Is.Not.Null); + Assert.That(dto.User, Is.Not.Null); + Assert.That(dto.User.Id, Is.EqualTo(entity.UserId)); + Assert.That(dto.Role, Is.Not.Null); + Assert.That(dto.Role.Id, Is.EqualTo(entity.RoleId)); #pragma warning restore NUnit2045 // Use Assert.Multiple - } +#pragma warning disable IDE0079 // Remove unnecessary suppression + } } diff --git a/AdvancedTodoList.IntegrationTests/Repositories/TodoListRepositoryTests.cs b/AdvancedTodoList.IntegrationTests/Repositories/TodoListRepositoryTests.cs index c1a59a5..bd97428 100644 --- a/AdvancedTodoList.IntegrationTests/Repositories/TodoListRepositoryTests.cs +++ b/AdvancedTodoList.IntegrationTests/Repositories/TodoListRepositoryTests.cs @@ -1,8 +1,8 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; using AdvancedTodoList.Core.Models.TodoLists; using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.Core.Specifications.Todo; using AdvancedTodoList.IntegrationTests.Utils; namespace AdvancedTodoList.IntegrationTests.Repositories; @@ -10,76 +10,76 @@ namespace AdvancedTodoList.IntegrationTests.Repositories; [TestFixture] public class TodoListRepositoryTests : BaseRepositoryTests { - protected override string NonExistingId => string.Empty; + protected override string NonExistingId => string.Empty; - protected const string UpdatedName = "update"; - protected const string UpdatedDescription = "new"; + protected const string UpdatedName = "update"; + protected const string UpdatedDescription = "new"; - protected override void UpdateEntity(TodoList entity) - { - entity.Name = UpdatedName; - entity.Description = UpdatedDescription; - } + protected override void UpdateEntity(TodoList entity) + { + entity.Name = UpdatedName; + entity.Description = UpdatedDescription; + } - protected override void AssertUpdated(TodoList updatedEntity) - { - Assert.Multiple(() => - { - Assert.That(updatedEntity.Name, Is.EqualTo(UpdatedName)); - Assert.That(updatedEntity.Description, Is.EqualTo(UpdatedDescription)); - }); - } + protected override void AssertUpdated(TodoList updatedEntity) + { + using (Assert.EnterMultipleScope()) + { + Assert.That(updatedEntity.Name, Is.EqualTo(UpdatedName)); + Assert.That(updatedEntity.Description, Is.EqualTo(UpdatedDescription)); + } + } - protected override Task CreateTestEntityAsync() - { - return Task.FromResult(TestModels.CreateTestTodoList()); - } + protected override Task CreateTestEntityAsync() + { + return Task.FromResult(TestModels.CreateTestTodoList()); + } - [Test] - public async Task GetAggregateAsync_TodoListAggregateSpecification_IncludesOwner() - { - // Arrange - var owner = TestModels.CreateTestUser(); - DbContext.Add(owner); - await DbContext.SaveChangesAsync(); - var todoList = TestModels.CreateTestTodoList(owner.Id); - DbContext.Add(todoList); - await DbContext.SaveChangesAsync(); - TodoListAggregateSpecification specification = new(todoList.Id); + [Test] + public async Task GetAggregateAsync_TodoListAggregateSpecification_IncludesOwner() + { + // Arrange + var owner = TestModels.CreateTestUser(); + DbContext.Add(owner); + await DbContext.SaveChangesAsync(); + var todoList = TestModels.CreateTestTodoList(owner.Id); + DbContext.Add(todoList); + await DbContext.SaveChangesAsync(); + TodoListAggregateSpecification specification = new(todoList.Id); - // Act - var aggregate = await Repository.GetAggregateAsync(specification); + // Act + var aggregate = await Repository.GetAggregateAsync(specification); - // Assert - Assert.That(aggregate, Is.Not.Null); - Assert.That(aggregate.Owner, Is.Not.Null); - Assert.That(aggregate.Owner.Id, Is.EqualTo(owner.Id)); - } + // Assert + Assert.That(aggregate, Is.Not.Null); + Assert.That(aggregate.Owner, Is.Not.Null); + Assert.That(aggregate.Owner.Id, Is.EqualTo(owner.Id)); + } - [Test] - public async Task GetPageAsync_IntegratesWithTodoListsSpecification() - { - // Arrange - var todoList = await AddTestEntityToDbAsync(); - var user = TestModels.CreateTestUser(); - DbContext.Add(user); - await DbContext.SaveChangesAsync(); - TodoListMember member = new() - { - UserId = user.Id, - TodoListId = todoList.Id - }; - DbContext.Add(member); - await DbContext.SaveChangesAsync(); - TodoListsFilter filter = new(todoList.Name); - TodoListsSpecification specification = new(user.Id, filter); + [Test] + public async Task GetPageAsync_IntegratesWithTodoListsSpecification() + { + // Arrange + var todoList = await AddTestEntityToDbAsync(); + var user = TestModels.CreateTestUser(); + DbContext.Add(user); + await DbContext.SaveChangesAsync(); + TodoListMember member = new() + { + UserId = user.Id, + TodoListId = todoList.Id + }; + DbContext.Add(member); + await DbContext.SaveChangesAsync(); + TodoListsFilter filter = new(todoList.Name); + TodoListsSpecification specification = new(user.Id, filter); - // Act - var page = await Repository.GetPageAsync(new(1, 5), specification); + // Act + var page = await Repository.GetPageAsync(new(1, 5), specification); - // Assert - var dto = page.Items.SingleOrDefault(); - Assert.That(dto, Is.Not.Null); - Assert.That(dto.Id, Is.EqualTo(todoList.Id)); - } + // Assert + var dto = page.Items.SingleOrDefault(); + Assert.That(dto, Is.Not.Null); + Assert.That(dto.Id, Is.EqualTo(todoList.Id)); + } } diff --git a/AdvancedTodoList.IntegrationTests/Repositories/TodoListRolesRepositoryTests.cs b/AdvancedTodoList.IntegrationTests/Repositories/TodoListRolesRepositoryTests.cs index 4a5b2cc..db1e1ab 100644 --- a/AdvancedTodoList.IntegrationTests/Repositories/TodoListRolesRepositoryTests.cs +++ b/AdvancedTodoList.IntegrationTests/Repositories/TodoListRolesRepositoryTests.cs @@ -5,34 +5,34 @@ namespace AdvancedTodoList.IntegrationTests.Repositories; public class TodoListRolesRepositoryTests : BaseRepositoryTests { - protected override int NonExistingId => -1; + protected override int NonExistingId => -1; - private const int UpdatedPriority = 5; - private readonly RolePermissions UpdatedPermissions = new(AddMembers: true, SetItemsState: true); + private const int UpdatedPriority = 5; + private readonly RolePermissions UpdatedPermissions = new(AddMembers: true, SetItemsState: true); - protected override void AssertUpdated(TodoListRole updatedEntity) - { - Assert.Multiple(() => - { - Assert.That(updatedEntity.Priority, Is.EqualTo(UpdatedPriority)); - Assert.That(updatedEntity.Permissions, Is.EqualTo(UpdatedPermissions)); - }); - } + protected override void AssertUpdated(TodoListRole updatedEntity) + { + using (Assert.EnterMultipleScope()) + { + Assert.That(updatedEntity.Priority, Is.EqualTo(UpdatedPriority)); + Assert.That(updatedEntity.Permissions, Is.EqualTo(UpdatedPermissions)); + } + } - protected override async Task CreateTestEntityAsync() - { - var user = TestModels.CreateTestUser(); - DbContext.Add(user); - var todoList = TestModels.CreateTestTodoList(); - DbContext.Add(todoList); - await DbContext.SaveChangesAsync(); + protected override async Task CreateTestEntityAsync() + { + var user = TestModels.CreateTestUser(); + DbContext.Add(user); + var todoList = TestModels.CreateTestTodoList(); + DbContext.Add(todoList); + await DbContext.SaveChangesAsync(); - return TestModels.CreateTestRole(todoList.Id); - } + return TestModels.CreateTestRole(todoList.Id); + } - protected override void UpdateEntity(TodoListRole entity) - { - entity.Priority = UpdatedPriority; - entity.Permissions = UpdatedPermissions; - } + protected override void UpdateEntity(TodoListRole entity) + { + entity.Priority = UpdatedPriority; + entity.Permissions = UpdatedPermissions; + } } diff --git a/AdvancedTodoList.IntegrationTests/Repositories/UnitOfWorkTests.cs b/AdvancedTodoList.IntegrationTests/Repositories/UnitOfWorkTests.cs index 89907dd..73b8751 100644 --- a/AdvancedTodoList.IntegrationTests/Repositories/UnitOfWorkTests.cs +++ b/AdvancedTodoList.IntegrationTests/Repositories/UnitOfWorkTests.cs @@ -8,43 +8,43 @@ namespace AdvancedTodoList.IntegrationTests.Repositories; [TestFixture] public class UnitOfWorkTests : DataAccessFixture { - private IUnitOfWork _unitOfWork; - - [SetUp] - public void SetUpUnitOfWork() - { - _unitOfWork = ServiceScope.ServiceProvider.GetService()!; - } - - [Test] - public async Task Commit_SavesChanges() - { - // Arrange - TodoList todoList = TestModels.CreateTestTodoList(); - - // Act - await _unitOfWork.BeginTransactionAsync(); - DbContext.Add(todoList); - await DbContext.SaveChangesAsync(); - await _unitOfWork.CommitAsync(); - - // Assert - Assert.That(DbContext.TodoLists.Any(x => x.Id == todoList.Id)); - } - - [Test] - public async Task Rollback_DoesNotSaveChanges() - { - // Arrange - TodoList todoList = TestModels.CreateTestTodoList(); - - // Act - await _unitOfWork.BeginTransactionAsync(); - DbContext.Add(todoList); - await DbContext.SaveChangesAsync(); - await _unitOfWork.RollbackAsync(); - - // Assert - Assert.That(DbContext.TodoLists.Any(x => x.Id == todoList.Id), Is.False); - } + private IUnitOfWork _unitOfWork; + + [SetUp] + public void SetUpUnitOfWork() + { + _unitOfWork = ServiceScope.ServiceProvider.GetService()!; + } + + [Test] + public async Task Commit_SavesChanges() + { + // Arrange + TodoList todoList = TestModels.CreateTestTodoList(); + + // Act + await _unitOfWork.BeginTransactionAsync(); + DbContext.Add(todoList); + await DbContext.SaveChangesAsync(); + await _unitOfWork.CommitAsync(); + + // Assert + Assert.That(DbContext.TodoLists.Any(x => x.Id == todoList.Id)); + } + + [Test] + public async Task Rollback_DoesNotSaveChanges() + { + // Arrange + TodoList todoList = TestModels.CreateTestTodoList(); + + // Act + await _unitOfWork.BeginTransactionAsync(); + DbContext.Add(todoList); + await DbContext.SaveChangesAsync(); + await _unitOfWork.RollbackAsync(); + + // Assert + Assert.That(DbContext.TodoLists.Any(x => x.Id == todoList.Id), Is.False); + } } diff --git a/AdvancedTodoList.IntegrationTests/Repositories/UserRefreshTokensRepository.cs b/AdvancedTodoList.IntegrationTests/Repositories/UserRefreshTokensRepository.cs index 9068876..d3f0e44 100644 --- a/AdvancedTodoList.IntegrationTests/Repositories/UserRefreshTokensRepository.cs +++ b/AdvancedTodoList.IntegrationTests/Repositories/UserRefreshTokensRepository.cs @@ -7,61 +7,61 @@ namespace AdvancedTodoList.IntegrationTests.Repositories; [TestFixture] public class UserRefreshTokensRepositoryTests : BaseRepositoryTests { - private IUserRefreshTokensRepository TokensRepository => (IUserRefreshTokensRepository)Repository; + private IUserRefreshTokensRepository TokensRepository => (IUserRefreshTokensRepository)Repository; - protected override int NonExistingId => -1; + protected override int NonExistingId => -1; - private readonly DateTime UpdatedValidTo = DateTime.UtcNow.AddDays(180); + private readonly DateTime UpdatedValidTo = DateTime.UtcNow.AddDays(180); - protected override void UpdateEntity(UserRefreshToken entity) - { - entity.ValidTo = UpdatedValidTo; - } + protected override void UpdateEntity(UserRefreshToken entity) + { + entity.ValidTo = UpdatedValidTo; + } - protected override void AssertUpdated(UserRefreshToken updatedEntity) - { - Assert.That(updatedEntity.ValidTo, Is.EqualTo(UpdatedValidTo)); - } + protected override void AssertUpdated(UserRefreshToken updatedEntity) + { + Assert.That(updatedEntity.ValidTo, Is.EqualTo(UpdatedValidTo)); + } - protected override async Task CreateTestEntityAsync() - { - var user = TestModels.CreateTestUser(); - DbContext.Add(user); - await DbContext.SaveChangesAsync(); + protected override async Task CreateTestEntityAsync() + { + var user = TestModels.CreateTestUser(); + DbContext.Add(user); + await DbContext.SaveChangesAsync(); - return TestModels.CreateTestUserRefreshToken(user.Id); - } + return TestModels.CreateTestUserRefreshToken(user.Id); + } - [Test] - public async Task FindAsync_TokenExists_ReturnsValidToken() - { - // Arrange - var user = TestModels.CreateTestUser(); - DbContext.Add(user); - await DbContext.SaveChangesAsync(); - var token = TestModels.CreateTestUserRefreshToken(user.Id); - DbContext.Add(token); - await DbContext.SaveChangesAsync(); + [Test] + public async Task FindAsync_TokenExists_ReturnsValidToken() + { + // Arrange + var user = TestModels.CreateTestUser(); + DbContext.Add(user); + await DbContext.SaveChangesAsync(); + var token = TestModels.CreateTestUserRefreshToken(user.Id); + DbContext.Add(token); + await DbContext.SaveChangesAsync(); - // Act - var result = await TokensRepository.FindAsync(user.Id, token.Token); + // Act + var result = await TokensRepository.FindAsync(user.Id, token.Token); - // Assert - Assert.That(result, Is.Not.Null); - Assert.That(result.Id, Is.EqualTo(token.Id)); - } + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.Id, Is.EqualTo(token.Id)); + } - [Test] - public async Task FindAsync_TokenDoesNotExist_ReturnsNull() - { - // Arrange - string userId = "user_id"; - string token = "no token :("; + [Test] + public async Task FindAsync_TokenDoesNotExist_ReturnsNull() + { + // Arrange + string userId = "user_id"; + string token = "no token :("; - // Act - var result = await TokensRepository.FindAsync(userId, token); + // Act + var result = await TokensRepository.FindAsync(userId, token); - // Assert - Assert.That(result, Is.Null); - } + // Assert + Assert.That(result, Is.Null); + } } diff --git a/AdvancedTodoList.IntegrationTests/Services/Auth/AuthServiceTests.cs b/AdvancedTodoList.IntegrationTests/Services/Auth/AuthServiceTests.cs index e6d8ad2..a305bf2 100644 --- a/AdvancedTodoList.IntegrationTests/Services/Auth/AuthServiceTests.cs +++ b/AdvancedTodoList.IntegrationTests/Services/Auth/AuthServiceTests.cs @@ -1,6 +1,6 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions.Auth; using AdvancedTodoList.Core.Models.Auth; -using AdvancedTodoList.Core.Services.Auth; using AdvancedTodoList.IntegrationTests.Fixtures; using AdvancedTodoList.IntegrationTests.Utils; using Microsoft.AspNetCore.Identity; @@ -15,339 +15,340 @@ namespace AdvancedTodoList.IntegrationTests.Services.Auth; [TestFixture] public class AuthServiceTests : DataAccessFixture { - private UserManager _userManager; - private IAuthService _authService; - private IConfiguration _configuration; - private const string TestPassword = "VeWyStW0nGPa$$w0rD"; - - // Creates a test user and adds it to the DB - private async Task CreateTestUserAsync() - { - ApplicationUser user = TestModels.CreateTestUser(); - - var result = await _userManager.CreateAsync(user, TestPassword); - Assert.That(result.Succeeded, "Failed to create a test user"); - - return user; - } - - // Creates a test refresh token for the user and adds it to the DB - private async Task CreateTestRefreshTokenAsync(ApplicationUser user, bool expired = false) - { - UserRefreshToken token = new() - { - UserId = user.Id, - ValidTo = expired ? DateTime.UtcNow - TimeSpan.FromDays(366) : DateTime.UtcNow + TimeSpan.FromDays(366), - Token = Guid.NewGuid().ToString().Replace('-', 'A') - }; - DbContext.UserRefreshTokens.Add(token); - await DbContext.SaveChangesAsync(); - - return token; - } - - // Creates a test JWT for a user - private string CreateTestJwt(ApplicationUser user, bool valid = true, DateTime? expires = null) - { - string strKey = valid ? _configuration["Auth:AccessToken:SecretKey"]! : "Invalid" + _configuration["Auth:AccessToken:SecretKey"]!; - SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(strKey)); - - List authClaims = - [ - new(JwtRegisteredClaimNames.Sub, user.Id) - ]; - JwtSecurityToken token = new( - issuer: _configuration["Auth:AccessToken:ValidIssuer"], - audience: _configuration["Auth:AccessToken:ValidAudience"], - expires: expires ?? DateTime.UtcNow.AddMinutes(30), - claims: authClaims, - signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) - ); - - return new JwtSecurityTokenHandler().WriteToken(token); - } - - [SetUp] - public void SetUp() - { - _userManager = ServiceScope.ServiceProvider.GetService>()!; - _authService = ServiceScope.ServiceProvider.GetService()!; - _configuration = ServiceScope.ServiceProvider.GetService()!; - } - - [TearDown] - public void TearDown() - { - _userManager.Dispose(); - } - - // Assert that LogInResponse is valid - private async Task AssertSuccessLogInAsync(LogInResponse? response, ApplicationUser user) - { - // Assert that response is not null (success) - Assert.That(response, Is.Not.Null); - // Assert that access token is provided - Assert.That(response.AccessToken, Is.Not.Null); - // Assert that access token is valid - JwtSecurityTokenHandler tokenHandler = new(); - string key = _configuration["Auth:AccessToken:SecretKey"]!; - TokenValidationParameters validationParameters = new() - { - ValidIssuer = _configuration["Auth:AccessToken:ValidIssuer"], - ValidAudience = _configuration["Auth:AccessToken:ValidAudience"], - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) - }; - tokenHandler.ValidateToken(response.AccessToken, validationParameters, out var token); - var jwtToken = (JwtSecurityToken)token; - var claims = jwtToken.Claims; - - // Assert that claims are correct - Assert.Multiple(() => - { - Assert.That(claims.Single(x => x.Type == JwtRegisteredClaimNames.Sub).Value, Is.EqualTo(user.Id)); - Assert.That(claims.Single(x => x.Type == JwtRegisteredClaimNames.GivenName).Value, Is.EqualTo(user.FirstName)); - Assert.That(claims.Single(x => x.Type == JwtRegisteredClaimNames.FamilyName).Value, Is.EqualTo(user.LastName)); - Assert.That(claims.Single(x => x.Type == JwtRegisteredClaimNames.UniqueName).Value, Is.EqualTo(user.UserName)); - Assert.That(claims.Single(x => x.Type == JwtRegisteredClaimNames.Email).Value, Is.EqualTo(user.Email)); - }); - + private UserManager _userManager; + private IAuthService _authService; + private IConfiguration _configuration; + private const string TestPassword = "VeWyStW0nGPa$$w0rD"; + + // Creates a test user and adds it to the DB + private async Task CreateTestUserAsync() + { + ApplicationUser user = TestModels.CreateTestUser(); + + var result = await _userManager.CreateAsync(user, TestPassword); + Assert.That(result.Succeeded, "Failed to create a test user"); + + return user; + } + + // Creates a test refresh token for the user and adds it to the DB + private async Task CreateTestRefreshTokenAsync(ApplicationUser user, bool expired = false) + { + UserRefreshToken token = new() + { + UserId = user.Id, + ValidTo = expired ? DateTime.UtcNow - TimeSpan.FromDays(366) : DateTime.UtcNow + TimeSpan.FromDays(366), + Token = Guid.NewGuid().ToString().Replace('-', 'A') + }; + DbContext.UserRefreshTokens.Add(token); + await DbContext.SaveChangesAsync(); + + return token; + } + + // Creates a test JWT for a user + private string CreateTestJwt(ApplicationUser user, bool valid = true, DateTime? expires = null) + { + string strKey = valid ? _configuration["Auth:AccessToken:SecretKey"]! : "Invalid" + _configuration["Auth:AccessToken:SecretKey"]!; + SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(strKey)); + + List authClaims = + [ + new(JwtRegisteredClaimNames.Sub, user.Id) + ]; + JwtSecurityToken token = new( + issuer: _configuration["Auth:AccessToken:ValidIssuer"], + audience: _configuration["Auth:AccessToken:ValidAudience"], + expires: expires ?? DateTime.UtcNow.AddMinutes(30), + claims: authClaims, + signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) + ); + + return new JwtSecurityTokenHandler().WriteToken(token); + } + + [SetUp] + public void SetUp() + { + _userManager = ServiceScope.ServiceProvider.GetService>()!; + _authService = ServiceScope.ServiceProvider.GetService()!; + _configuration = ServiceScope.ServiceProvider.GetService()!; + } + + [TearDown] + public void TearDown() + { + _userManager.Dispose(); + } + + // Assert that LogInResponse is valid + private async Task AssertSuccessLogInAsync(LogInResponse? response, ApplicationUser user) + { + // Assert that response is not null (success) + Assert.That(response, Is.Not.Null); + // Assert that access token is provided + Assert.That(response.AccessToken, Is.Not.Null); + // Assert that access token is valid + JwtSecurityTokenHandler tokenHandler = new(); + string key = _configuration["Auth:AccessToken:SecretKey"]!; + TokenValidationParameters validationParameters = new() + { + ValidIssuer = _configuration["Auth:AccessToken:ValidIssuer"], + ValidAudience = _configuration["Auth:AccessToken:ValidAudience"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)) + }; + tokenHandler.ValidateToken(response.AccessToken, validationParameters, out var token); + var jwtToken = (JwtSecurityToken)token; + var claims = jwtToken.Claims; + + // Assert that claims are correct + using (Assert.EnterMultipleScope()) + { + Assert.That(claims.Single(x => x.Type == JwtRegisteredClaimNames.Sub).Value, Is.EqualTo(user.Id)); + Assert.That(claims.Single(x => x.Type == JwtRegisteredClaimNames.GivenName).Value, Is.EqualTo(user.FirstName)); + Assert.That(claims.Single(x => x.Type == JwtRegisteredClaimNames.FamilyName).Value, Is.EqualTo(user.LastName)); + Assert.That(claims.Single(x => x.Type == JwtRegisteredClaimNames.UniqueName).Value, Is.EqualTo(user.UserName)); + Assert.That(claims.Single(x => x.Type == JwtRegisteredClaimNames.Email).Value, Is.EqualTo(user.Email)); + } + +#pragma warning disable IDE0079 // Remove unnecessary suppression #pragma warning disable NUnit2045 // Use Assert.Multiple - // Assert that refresh token is present - Assert.That( - await DbContext.UserRefreshTokens - .AnyAsync(x => x.Token == response.RefreshToken && x.UserId == user.Id) - ); - // Assert that refresh token is not a default base 64 string - Assert.That(Convert.FromBase64String(response.RefreshToken).All(x => x == 0), Is.False); + // Assert that refresh token is present + Assert.That( + await DbContext.UserRefreshTokens + .AnyAsync(x => x.Token == response.RefreshToken && x.UserId == user.Id) + ); + // Assert that refresh token is not a default base 64 string + Assert.That(Convert.FromBase64String(response.RefreshToken).All(x => x == 0), Is.False); #pragma warning restore NUnit2045 // Use Assert.Multiple - } - - [Test] - public async Task LogInAsync_UserNameUsed_LogsIn() - { - // Arrange - ApplicationUser user = await CreateTestUserAsync(); - LogInDto dto = new(user.UserName!, TestPassword); - - // Act - var response = await _authService.LogInAsync(dto); - - // Assert - await AssertSuccessLogInAsync(response, user); - } - - [Test] - public async Task LogInAsync_EmailUsed_LogsIn() - { - // Arrange - ApplicationUser user = await CreateTestUserAsync(); - LogInDto dto = new(user.Email!, TestPassword); - - // Act - var response = await _authService.LogInAsync(dto); - - // Assert - await AssertSuccessLogInAsync(response, user); - } - - [Test] - public async Task LogInAsync_InvalidCredentials_Fails() - { - // Arrange - ApplicationUser user = await CreateTestUserAsync(); - LogInDto dto = new(user.Email!, "wrong"); - - // Act - var response = await _authService.LogInAsync(dto); - - // Assert failure - Assert.That(response, Is.Null); - } - - [Test] - public async Task RegisterAsync_ValidData_CreatesUser() - { - // Arrange - RegisterDto dto = new("uniq@example.com", "Username", "First", "Last", TestPassword); - - // Act - var result = await _authService.RegisterAsync(dto); - - // Assert that result indicates success - Assert.That(result.IsSuccess); - // Assert that user is created - var user = await DbContext.Users.Where( - x => x.Email == dto.Email && x.UserName == dto.UserName && - x.FirstName == dto.FirstName && x.LastName == dto.LastName - ) - .SingleOrDefaultAsync(); - Assert.That(user, Is.Not.Null); - // Assert that password is correct - var passwordResult = await _userManager.CheckPasswordAsync(user, dto.Password); - Assert.That(passwordResult); - } - - [Test] - public async Task RegisterAsync_DuplicateEmail_Fails() - { - // Arrange - var testUser = await CreateTestUserAsync(); - RegisterDto dto = new(testUser.Email!, "Username", "First", "Last", TestPassword); - - // Act - var result = await _authService.RegisterAsync(dto); - Assert.Multiple(() => - { - - // Assert that result does not indicate success - Assert.That(result.IsSuccess, Is.False); - // Assert that 'Email' property caused this error - Assert.That(result.Errors.Any(x => x.Property == "Email"), "RegisterResult does not indicate that 'Email' property caused the error."); - }); - } - - [Test] - public async Task RegisterAsync_DuplicateUserName_Fails() - { - // Arrange - var testUser = await CreateTestUserAsync(); - RegisterDto dto = new("uniqe@example.com", testUser.UserName!, "First", "Last", TestPassword); - - // Act - var result = await _authService.RegisterAsync(dto); - Assert.Multiple(() => - { - // Assert that result does not indicate success - Assert.That(result.IsSuccess, Is.False); - // Assert that 'UserName' property caused this error - Assert.That(result.Errors.Any(x => x.Property == "UserName"), "RegisterResult does not indicate that 'UserName' property caused the error."); - }); - } - - [Test] - public async Task RefreshAsync_ValidTokens_Succeeds() - { - // Arrange - var user = await CreateTestUserAsync(); - UserRefreshToken token = await CreateTestRefreshTokenAsync(user); - string expiredToken = CreateTestJwt(user, expires: DateTime.UtcNow.AddDays(-5)); - RefreshDto dto = new(expiredToken, token.Token); - - // Act - var response = await _authService.RefreshAsync(dto); - - // Assert - await AssertSuccessLogInAsync(response, user); - } - - [Test] - public async Task RefreshAsync_RefreshTokenOfAnotherUser_Fails() - { - // Arrange - var user = await CreateTestUserAsync(); - var user2 = await CreateTestUserAsync(); - UserRefreshToken token = await CreateTestRefreshTokenAsync(user2); - RefreshDto dto = new(CreateTestJwt(user), token.Token); - - // Act - var response = await _authService.RefreshAsync(dto); - - // Assert - Assert.That(response, Is.Null); - } - - [Test] - public async Task RefreshAsync_InvalidRefreshToken_Fails() - { - // Arrange - var user = await CreateTestUserAsync(); - RefreshDto dto = new(CreateTestJwt(user), "Token which does not exist"); - - // Act - var response = await _authService.RefreshAsync(dto); - - // Assert - Assert.That(response, Is.Null); - } - - [Test] - public async Task RefreshAsync_ExpiredRefreshToken_Fails() - { - // Arrange - var user = await CreateTestUserAsync(); - UserRefreshToken token = await CreateTestRefreshTokenAsync(user, expired: true); - RefreshDto dto = new(CreateTestJwt(user), token.Token); - - // Act - var response = await _authService.RefreshAsync(dto); - - // Assert - Assert.That(response, Is.Null); - } - - [Test] - public async Task RefreshAsync_InvalidAccessToken_Fails() - { - // Arrange - var user = await CreateTestUserAsync(); - UserRefreshToken token = new() - { - UserId = user.Id, - ValidTo = DateTime.UtcNow + TimeSpan.FromDays(366), - Token = Guid.NewGuid().ToString() - }; - RefreshDto dto = new(CreateTestJwt(user, valid: false), token.Token); - - // Act - var response = await _authService.RefreshAsync(dto); - - // Assert - Assert.That(response, Is.Null); - } - - [Test] - public async Task LogOutAsync_ValidInput_ReturnsTrueAndRevokesToken() - { - // Arrange - var user = await CreateTestUserAsync(); - var refreshToken = await CreateTestRefreshTokenAsync(user); - LogOutDto dto = new(refreshToken.Token); - - // Act - var result = await _authService.LogOutAsync(user.Id, dto); - - // Assert - Assert.That(result); - } - - [Test] - public async Task LogOutAsync_InvalidUserId_ReturnsFalse() - { - // Arrange - var user = await CreateTestUserAsync(); - var refreshToken = await CreateTestRefreshTokenAsync(user); - LogOutDto dto = new(refreshToken.Token); - - // Act - var result = await _authService.LogOutAsync("InvalidId", dto); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task LogOutAsync_InvalidRefreshToken_ReturnsFalse() - { - // Arrange - var user = await CreateTestUserAsync(); - LogOutDto dto = new("Invalid refresh token"); - - // Act - var result = await _authService.LogOutAsync(user.Id, dto); - - // Assert - Assert.That(result, Is.False); - } +#pragma warning restore IDE0079 // Remove unnecessary suppression + } + + [Test] + public async Task LogInAsync_UserNameUsed_LogsIn() + { + // Arrange + ApplicationUser user = await CreateTestUserAsync(); + LogInDto dto = new(user.UserName!, TestPassword); + + // Act + var response = await _authService.LogInAsync(dto); + + // Assert + await AssertSuccessLogInAsync(response, user); + } + + [Test] + public async Task LogInAsync_EmailUsed_LogsIn() + { + // Arrange + ApplicationUser user = await CreateTestUserAsync(); + LogInDto dto = new(user.Email!, TestPassword); + + // Act + var response = await _authService.LogInAsync(dto); + + // Assert + await AssertSuccessLogInAsync(response, user); + } + + [Test] + public async Task LogInAsync_InvalidCredentials_Fails() + { + // Arrange + ApplicationUser user = await CreateTestUserAsync(); + LogInDto dto = new(user.Email!, "wrong"); + + // Act + var response = await _authService.LogInAsync(dto); + + // Assert failure + Assert.That(response, Is.Null); + } + + [Test] + public async Task RegisterAsync_ValidData_CreatesUser() + { + // Arrange + RegisterDto dto = new("uniq@example.com", "Username", "First", "Last", TestPassword); + + // Act + var result = await _authService.RegisterAsync(dto); + + // Assert that result indicates success + Assert.That(result.IsSuccess); + // Assert that user is created + var user = await DbContext.Users.Where( + x => x.Email == dto.Email && x.UserName == dto.UserName && + x.FirstName == dto.FirstName && x.LastName == dto.LastName + ) + .SingleOrDefaultAsync(); + Assert.That(user, Is.Not.Null); + // Assert that password is correct + var passwordResult = await _userManager.CheckPasswordAsync(user, dto.Password); + Assert.That(passwordResult); + } + + [Test] + public async Task RegisterAsync_DuplicateEmail_Fails() + { + // Arrange + var testUser = await CreateTestUserAsync(); + RegisterDto dto = new(testUser.Email!, "Username", "First", "Last", TestPassword); + + // Act + var result = await _authService.RegisterAsync(dto); + using (Assert.EnterMultipleScope()) + { + // Assert that result does not indicate success + Assert.That(result.IsSuccess, Is.False); + // Assert that 'Email' property caused this error + Assert.That(result.Errors.Any(x => x.Property == "Email"), "RegisterResult does not indicate that 'Email' property caused the error."); + } + } + + [Test] + public async Task RegisterAsync_DuplicateUserName_Fails() + { + // Arrange + var testUser = await CreateTestUserAsync(); + RegisterDto dto = new("uniqe@example.com", testUser.UserName!, "First", "Last", TestPassword); + + // Act + var result = await _authService.RegisterAsync(dto); + using (Assert.EnterMultipleScope()) + { + // Assert that result does not indicate success + Assert.That(result.IsSuccess, Is.False); + // Assert that 'UserName' property caused this error + Assert.That(result.Errors.Any(x => x.Property == "UserName"), "RegisterResult does not indicate that 'UserName' property caused the error."); + } + } + + [Test] + public async Task RefreshAsync_ValidTokens_Succeeds() + { + // Arrange + var user = await CreateTestUserAsync(); + UserRefreshToken token = await CreateTestRefreshTokenAsync(user); + string expiredToken = CreateTestJwt(user, expires: DateTime.UtcNow.AddDays(-5)); + RefreshDto dto = new(expiredToken, token.Token); + + // Act + var response = await _authService.RefreshAsync(dto); + + // Assert + await AssertSuccessLogInAsync(response, user); + } + + [Test] + public async Task RefreshAsync_RefreshTokenOfAnotherUser_Fails() + { + // Arrange + var user = await CreateTestUserAsync(); + var user2 = await CreateTestUserAsync(); + UserRefreshToken token = await CreateTestRefreshTokenAsync(user2); + RefreshDto dto = new(CreateTestJwt(user), token.Token); + + // Act + var response = await _authService.RefreshAsync(dto); + + // Assert + Assert.That(response, Is.Null); + } + + [Test] + public async Task RefreshAsync_InvalidRefreshToken_Fails() + { + // Arrange + var user = await CreateTestUserAsync(); + RefreshDto dto = new(CreateTestJwt(user), "Token which does not exist"); + + // Act + var response = await _authService.RefreshAsync(dto); + + // Assert + Assert.That(response, Is.Null); + } + + [Test] + public async Task RefreshAsync_ExpiredRefreshToken_Fails() + { + // Arrange + var user = await CreateTestUserAsync(); + UserRefreshToken token = await CreateTestRefreshTokenAsync(user, expired: true); + RefreshDto dto = new(CreateTestJwt(user), token.Token); + + // Act + var response = await _authService.RefreshAsync(dto); + + // Assert + Assert.That(response, Is.Null); + } + + [Test] + public async Task RefreshAsync_InvalidAccessToken_Fails() + { + // Arrange + var user = await CreateTestUserAsync(); + UserRefreshToken token = new() + { + UserId = user.Id, + ValidTo = DateTime.UtcNow + TimeSpan.FromDays(366), + Token = Guid.NewGuid().ToString() + }; + RefreshDto dto = new(CreateTestJwt(user, valid: false), token.Token); + + // Act + var response = await _authService.RefreshAsync(dto); + + // Assert + Assert.That(response, Is.Null); + } + + [Test] + public async Task LogOutAsync_ValidInput_ReturnsTrueAndRevokesToken() + { + // Arrange + var user = await CreateTestUserAsync(); + var refreshToken = await CreateTestRefreshTokenAsync(user); + LogOutDto dto = new(refreshToken.Token); + + // Act + var result = await _authService.LogOutAsync(user.Id, dto); + + // Assert + Assert.That(result); + } + + [Test] + public async Task LogOutAsync_InvalidUserId_ReturnsFalse() + { + // Arrange + var user = await CreateTestUserAsync(); + var refreshToken = await CreateTestRefreshTokenAsync(user); + LogOutDto dto = new(refreshToken.Token); + + // Act + var result = await _authService.LogOutAsync("InvalidId", dto); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task LogOutAsync_InvalidRefreshToken_ReturnsFalse() + { + // Arrange + var user = await CreateTestUserAsync(); + LogOutDto dto = new("Invalid refresh token"); + + // Act + var result = await _authService.LogOutAsync(user.Id, dto); + + // Assert + Assert.That(result, Is.False); + } } diff --git a/AdvancedTodoList.IntegrationTests/Services/Auth/PermissionsCheckerTests.cs b/AdvancedTodoList.IntegrationTests/Services/Auth/PermissionsCheckerTests.cs index c219f9a..99085b8 100644 --- a/AdvancedTodoList.IntegrationTests/Services/Auth/PermissionsCheckerTests.cs +++ b/AdvancedTodoList.IntegrationTests/Services/Auth/PermissionsCheckerTests.cs @@ -1,8 +1,8 @@ -using AdvancedTodoList.Core.Models.Auth; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models.Auth; using AdvancedTodoList.Core.Models.TodoLists; using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Services.Auth; using AdvancedTodoList.IntegrationTests.Fixtures; using AdvancedTodoList.IntegrationTests.Utils; @@ -10,358 +10,358 @@ namespace AdvancedTodoList.IntegrationTests.Services.Auth; public class PermissionsCheckerTests : DataAccessFixture { - private IPermissionsChecker _permissionsChecker; - - [SetUp] - public void SetUp() - { - _permissionsChecker = ServiceScope.ServiceProvider.GetService()!; - } - - private async Task CreateTestUserAsync() - { - ApplicationUser user = TestModels.CreateTestUser(); - DbContext.Add(user); - await DbContext.SaveChangesAsync(); - return user; - } - private async Task CreateTestTodoListAsync() - { - TodoList todoList = TestModels.CreateTestTodoList(); - DbContext.Add(todoList); - await DbContext.SaveChangesAsync(); - return todoList; - } - private async Task CreateTestRoleAsync(string todoListId, int priority, RolePermissions permissions) - { - TodoListRole role = new() - { - Name = "Role", - TodoListId = todoListId, - Priority = priority, - Permissions = permissions - }; - DbContext.Add(role); - await DbContext.SaveChangesAsync(); - - return role; - } - private async Task CreateTestMemberAsync() - { - var user = await CreateTestUserAsync(); - var list = await CreateTestTodoListAsync(); - - return await CreateTestMemberAsync(user.Id, list.Id, null); - } - private async Task CreateTestMemberAsync(string userId, string todoListId, int? roleId) - { - TodoListMember member = new() - { - UserId = userId, - TodoListId = todoListId, - RoleId = roleId - }; - DbContext.Add(member); - await DbContext.SaveChangesAsync(); - return member; - } - - private TodoList GetTestEntity(string? ownerId) - { - return new TodoList() - { - Name = "N", - Description = "D", - OwnerId = ownerId - }; - } - - [Test] - public async Task IsMemberOfListAsync_UserIsMember_ReturnsTrue() - { - // Arrange - var member = await CreateTestMemberAsync(); - TodoListContext context = new(member.TodoListId, member.UserId); - - // Act - bool result = await _permissionsChecker.IsMemberOfListAsync(context); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public async Task IsMemberOfListAsync_UserIsNotMember_ReturnsFalse() - { - // Arrange - var user = await CreateTestUserAsync(); - var list = await CreateTestTodoListAsync(); - TodoListContext context = new(list.Id, user.Id); - - // Act - bool result = await _permissionsChecker.IsMemberOfListAsync(context); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task HasPermissionAsync_UserHasPermission_ReturnsTrue() - { - // Arrange - var todoList = await CreateTestTodoListAsync(); - var user = await CreateTestUserAsync(); - var role = await CreateTestRoleAsync(todoList.Id, 3, new(AddMembers: true)); - var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); - TodoListContext context = new(todoList.Id, user.Id); - - // Act - bool result = await _permissionsChecker.HasPermissionAsync(context, x => x.AddMembers); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public async Task HasPermissionAsync_UserHasNoPermission_ReturnsFalse() - { - // Arrange - var todoList = await CreateTestTodoListAsync(); - var user = await CreateTestUserAsync(); - var role = await CreateTestRoleAsync(todoList.Id, 3, new(AddMembers: true, AddItems: false)); - var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); - TodoListContext context = new(todoList.Id, user.Id); - - // Act - bool result = await _permissionsChecker.HasPermissionAsync(context, x => x.AddItems); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task HasPermissionAsync_UserHasNoRole_ReturnsFalse() - { - // Arrange - var member = await CreateTestMemberAsync(); - TodoListContext context = new(member.TodoListId, member.UserId); - - // Act - bool result = await _permissionsChecker.HasPermissionAsync(context, x => x.AddItems); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task HasPermissionAsync_UserIsNotMember_ReturnsFalse() - { - // Arrange - var user = await CreateTestUserAsync(); - var list = await CreateTestTodoListAsync(); - TodoListContext context = new(list.Id, user.Id); - - // Act - bool result = await _permissionsChecker.HasPermissionAsync(context, x => x.AddItems); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task CanTouchEntityAsync_UserHasPermission_ReturnsTrue() - { - // Arrange - var todoList = await CreateTestTodoListAsync(); - var user = await CreateTestUserAsync(); - var role = await CreateTestRoleAsync(todoList.Id, 3, new(EditItems: true)); - var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); - var testEntity = GetTestEntity("some-random-user-id"); - TodoListContext context = new(todoList.Id, user.Id); - - // Act - bool result = await _permissionsChecker - .CanTouchEntityAsync(context, testEntity, x => x.EditItems); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public async Task CanTouchEntityAsync_UserIsMemberAndOwnsEntity_ReturnsTrue() - { - // Arrange - var todoList = await CreateTestTodoListAsync(); - var user = await CreateTestUserAsync(); - var role = await CreateTestRoleAsync(todoList.Id, 3, new(EditItems: false)); - var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); - var testEntity = GetTestEntity(user.Id); - TodoListContext context = new(todoList.Id, user.Id); - - // Act - bool result = await _permissionsChecker - .CanTouchEntityAsync(context, testEntity, x => x.EditItems); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public async Task CanTouchEntityAsync_UserHasNoPermission_ReturnsFalse() - { - // Arrange - var todoList = await CreateTestTodoListAsync(); - var user = await CreateTestUserAsync(); - var role = await CreateTestRoleAsync(todoList.Id, 3, new(EditItems: false)); - var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); - var testEntity = GetTestEntity("some-random-user-id"); - TodoListContext context = new(todoList.Id, user.Id); - - // Act - bool result = await _permissionsChecker - .CanTouchEntityAsync(context, testEntity, x => x.EditItems); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task CanTouchEntityAsync_UserHasNoRole_ReturnsFalse() - { - // Arrange - var member = await CreateTestMemberAsync(); - var testEntity = GetTestEntity("some-random-user-id"); - TodoListContext context = new(member.TodoListId, member.UserId); - - // Act - bool result = await _permissionsChecker - .CanTouchEntityAsync(context, testEntity, x => x.EditItems); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task CanTouchEntityAsync_UserIsNotMember_ReturnsFalse() - { - // Arrange - var todoList = await CreateTestTodoListAsync(); - var user = await CreateTestUserAsync(); - var testEntity = GetTestEntity("some-random-user-id"); - TodoListContext context = new(todoList.Id, user.Id); - - // Act - bool result = await _permissionsChecker - .CanTouchEntityAsync(context, testEntity, x => x.EditItems); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task CanTouchEntityAsync_UserIsNotMemberButOwnsEntity_ReturnsFalse() - { - // Arrange - var todoList = await CreateTestTodoListAsync(); - var user = await CreateTestUserAsync(); - var testEntity = GetTestEntity(user.Id); - TodoListContext context = new(todoList.Id, user.Id); - - // Act - bool result = await _permissionsChecker - .CanTouchEntityAsync(context, testEntity, x => x.EditItems); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task HasPermissionOverRoleAsync_UserHasPermission_ReturnsTrue() - { - // Arrange - var todoList = await CreateTestTodoListAsync(); - var user = await CreateTestUserAsync(); - var role = await CreateTestRoleAsync(todoList.Id, 3, new(EditRoles: true)); - const int otherRolePriority = 4; - var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); - TodoListContext context = new(todoList.Id, user.Id); - - // Act - bool result = await _permissionsChecker - .HasPermissionOverRoleAsync(context, otherRolePriority, x => x.EditRoles); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public async Task HasPermissionOverRoleAsync_UserHasNoPermission_ReturnsFalse() - { - // Arrange - var todoList = await CreateTestTodoListAsync(); - var user = await CreateTestUserAsync(); - var role = await CreateTestRoleAsync(todoList.Id, 3, new(EditRoles: false)); - const int otherRolePriority = 4; - var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); - TodoListContext context = new(todoList.Id, user.Id); - - // Act - bool result = await _permissionsChecker - .HasPermissionOverRoleAsync(context, otherRolePriority, x => x.EditRoles); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task HasPermissionOverRoleAsync_UserHasNoRole_ReturnsFalse() - { - // Arrange - var member = await CreateTestMemberAsync(); - const int otherRolePriority = 4; - TodoListContext context = new(member.TodoListId, member.UserId); - - // Act - bool result = await _permissionsChecker - .HasPermissionOverRoleAsync(context, otherRolePriority, x => x.EditItems); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task HasPermissionOverRoleAsync_UserIsNotMember_ReturnsFalse() - { - // Arrange - var todoList = await CreateTestTodoListAsync(); - var user = await CreateTestUserAsync(); - const int otherRolePriority = 4; - TodoListContext context = new(todoList.Id, user.Id); - - // Act - bool result = await _permissionsChecker - .HasPermissionOverRoleAsync(context, otherRolePriority, x => x.EditItems); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task HasPermissionOverRoleAsync_UserHasPermissionButHasLowerRole_ReturnsFalse() - { - // Arrange - var todoList = await CreateTestTodoListAsync(); - var user = await CreateTestUserAsync(); - var role = await CreateTestRoleAsync(todoList.Id, 3, new(EditRoles: true)); - const int otherRolePriority = 2; - var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); - TodoListContext context = new(todoList.Id, user.Id); - - // Act - bool result = await _permissionsChecker - .HasPermissionOverRoleAsync(context, otherRolePriority, x => x.EditRoles); - - // Assert - Assert.That(result, Is.False); - } + private IPermissionsChecker _permissionsChecker; + + [SetUp] + public void SetUp() + { + _permissionsChecker = ServiceScope.ServiceProvider.GetService()!; + } + + private async Task CreateTestUserAsync() + { + ApplicationUser user = TestModels.CreateTestUser(); + DbContext.Add(user); + await DbContext.SaveChangesAsync(); + return user; + } + private async Task CreateTestTodoListAsync() + { + TodoList todoList = TestModels.CreateTestTodoList(); + DbContext.Add(todoList); + await DbContext.SaveChangesAsync(); + return todoList; + } + private async Task CreateTestRoleAsync(string todoListId, int priority, RolePermissions permissions) + { + TodoListRole role = new() + { + Name = "Role", + TodoListId = todoListId, + Priority = priority, + Permissions = permissions + }; + DbContext.Add(role); + await DbContext.SaveChangesAsync(); + + return role; + } + private async Task CreateTestMemberAsync() + { + var user = await CreateTestUserAsync(); + var list = await CreateTestTodoListAsync(); + + return await CreateTestMemberAsync(user.Id, list.Id, null); + } + private async Task CreateTestMemberAsync(string userId, string todoListId, int? roleId) + { + TodoListMember member = new() + { + UserId = userId, + TodoListId = todoListId, + RoleId = roleId + }; + DbContext.Add(member); + await DbContext.SaveChangesAsync(); + return member; + } + + private static TodoList GetTestEntity(string? ownerId) + { + return new TodoList() + { + Name = "N", + Description = "D", + OwnerId = ownerId + }; + } + + [Test] + public async Task IsMemberOfListAsync_UserIsMember_ReturnsTrue() + { + // Arrange + var member = await CreateTestMemberAsync(); + TodoListContext context = new(member.TodoListId, member.UserId); + + // Act + bool result = await _permissionsChecker.IsMemberOfListAsync(context); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public async Task IsMemberOfListAsync_UserIsNotMember_ReturnsFalse() + { + // Arrange + var user = await CreateTestUserAsync(); + var list = await CreateTestTodoListAsync(); + TodoListContext context = new(list.Id, user.Id); + + // Act + bool result = await _permissionsChecker.IsMemberOfListAsync(context); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task HasPermissionAsync_UserHasPermission_ReturnsTrue() + { + // Arrange + var todoList = await CreateTestTodoListAsync(); + var user = await CreateTestUserAsync(); + var role = await CreateTestRoleAsync(todoList.Id, 3, new(AddMembers: true)); + var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); + TodoListContext context = new(todoList.Id, user.Id); + + // Act + bool result = await _permissionsChecker.HasPermissionAsync(context, x => x.AddMembers); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public async Task HasPermissionAsync_UserHasNoPermission_ReturnsFalse() + { + // Arrange + var todoList = await CreateTestTodoListAsync(); + var user = await CreateTestUserAsync(); + var role = await CreateTestRoleAsync(todoList.Id, 3, new(AddMembers: true, AddItems: false)); + var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); + TodoListContext context = new(todoList.Id, user.Id); + + // Act + bool result = await _permissionsChecker.HasPermissionAsync(context, x => x.AddItems); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task HasPermissionAsync_UserHasNoRole_ReturnsFalse() + { + // Arrange + var member = await CreateTestMemberAsync(); + TodoListContext context = new(member.TodoListId, member.UserId); + + // Act + bool result = await _permissionsChecker.HasPermissionAsync(context, x => x.AddItems); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task HasPermissionAsync_UserIsNotMember_ReturnsFalse() + { + // Arrange + var user = await CreateTestUserAsync(); + var list = await CreateTestTodoListAsync(); + TodoListContext context = new(list.Id, user.Id); + + // Act + bool result = await _permissionsChecker.HasPermissionAsync(context, x => x.AddItems); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task CanTouchEntityAsync_UserHasPermission_ReturnsTrue() + { + // Arrange + var todoList = await CreateTestTodoListAsync(); + var user = await CreateTestUserAsync(); + var role = await CreateTestRoleAsync(todoList.Id, 3, new(EditItems: true)); + var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); + var testEntity = GetTestEntity("some-random-user-id"); + TodoListContext context = new(todoList.Id, user.Id); + + // Act + bool result = await _permissionsChecker + .CanTouchEntityAsync(context, testEntity, x => x.EditItems); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public async Task CanTouchEntityAsync_UserIsMemberAndOwnsEntity_ReturnsTrue() + { + // Arrange + var todoList = await CreateTestTodoListAsync(); + var user = await CreateTestUserAsync(); + var role = await CreateTestRoleAsync(todoList.Id, 3, new(EditItems: false)); + var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); + var testEntity = GetTestEntity(user.Id); + TodoListContext context = new(todoList.Id, user.Id); + + // Act + bool result = await _permissionsChecker + .CanTouchEntityAsync(context, testEntity, x => x.EditItems); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public async Task CanTouchEntityAsync_UserHasNoPermission_ReturnsFalse() + { + // Arrange + var todoList = await CreateTestTodoListAsync(); + var user = await CreateTestUserAsync(); + var role = await CreateTestRoleAsync(todoList.Id, 3, new(EditItems: false)); + var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); + var testEntity = GetTestEntity("some-random-user-id"); + TodoListContext context = new(todoList.Id, user.Id); + + // Act + bool result = await _permissionsChecker + .CanTouchEntityAsync(context, testEntity, x => x.EditItems); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task CanTouchEntityAsync_UserHasNoRole_ReturnsFalse() + { + // Arrange + var member = await CreateTestMemberAsync(); + var testEntity = GetTestEntity("some-random-user-id"); + TodoListContext context = new(member.TodoListId, member.UserId); + + // Act + bool result = await _permissionsChecker + .CanTouchEntityAsync(context, testEntity, x => x.EditItems); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task CanTouchEntityAsync_UserIsNotMember_ReturnsFalse() + { + // Arrange + var todoList = await CreateTestTodoListAsync(); + var user = await CreateTestUserAsync(); + var testEntity = GetTestEntity("some-random-user-id"); + TodoListContext context = new(todoList.Id, user.Id); + + // Act + bool result = await _permissionsChecker + .CanTouchEntityAsync(context, testEntity, x => x.EditItems); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task CanTouchEntityAsync_UserIsNotMemberButOwnsEntity_ReturnsFalse() + { + // Arrange + var todoList = await CreateTestTodoListAsync(); + var user = await CreateTestUserAsync(); + var testEntity = GetTestEntity(user.Id); + TodoListContext context = new(todoList.Id, user.Id); + + // Act + bool result = await _permissionsChecker + .CanTouchEntityAsync(context, testEntity, x => x.EditItems); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task HasPermissionOverRoleAsync_UserHasPermission_ReturnsTrue() + { + // Arrange + var todoList = await CreateTestTodoListAsync(); + var user = await CreateTestUserAsync(); + var role = await CreateTestRoleAsync(todoList.Id, 3, new(EditRoles: true)); + const int otherRolePriority = 4; + var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); + TodoListContext context = new(todoList.Id, user.Id); + + // Act + bool result = await _permissionsChecker + .HasPermissionOverRoleAsync(context, otherRolePriority, x => x.EditRoles); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public async Task HasPermissionOverRoleAsync_UserHasNoPermission_ReturnsFalse() + { + // Arrange + var todoList = await CreateTestTodoListAsync(); + var user = await CreateTestUserAsync(); + var role = await CreateTestRoleAsync(todoList.Id, 3, new(EditRoles: false)); + const int otherRolePriority = 4; + var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); + TodoListContext context = new(todoList.Id, user.Id); + + // Act + bool result = await _permissionsChecker + .HasPermissionOverRoleAsync(context, otherRolePriority, x => x.EditRoles); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task HasPermissionOverRoleAsync_UserHasNoRole_ReturnsFalse() + { + // Arrange + var member = await CreateTestMemberAsync(); + const int otherRolePriority = 4; + TodoListContext context = new(member.TodoListId, member.UserId); + + // Act + bool result = await _permissionsChecker + .HasPermissionOverRoleAsync(context, otherRolePriority, x => x.EditItems); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task HasPermissionOverRoleAsync_UserIsNotMember_ReturnsFalse() + { + // Arrange + var todoList = await CreateTestTodoListAsync(); + var user = await CreateTestUserAsync(); + const int otherRolePriority = 4; + TodoListContext context = new(todoList.Id, user.Id); + + // Act + bool result = await _permissionsChecker + .HasPermissionOverRoleAsync(context, otherRolePriority, x => x.EditItems); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task HasPermissionOverRoleAsync_UserHasPermissionButHasLowerRole_ReturnsFalse() + { + // Arrange + var todoList = await CreateTestTodoListAsync(); + var user = await CreateTestUserAsync(); + var role = await CreateTestRoleAsync(todoList.Id, 3, new(EditRoles: true)); + const int otherRolePriority = 2; + var member = await CreateTestMemberAsync(user.Id, todoList.Id, role.Id); + TodoListContext context = new(todoList.Id, user.Id); + + // Act + bool result = await _permissionsChecker + .HasPermissionOverRoleAsync(context, otherRolePriority, x => x.EditRoles); + + // Assert + Assert.That(result, Is.False); + } } diff --git a/AdvancedTodoList.IntegrationTests/Services/Auth/RefreshTokensServiceTests.cs b/AdvancedTodoList.IntegrationTests/Services/Auth/RefreshTokensServiceTests.cs index 5f8962c..535bad8 100644 --- a/AdvancedTodoList.IntegrationTests/Services/Auth/RefreshTokensServiceTests.cs +++ b/AdvancedTodoList.IntegrationTests/Services/Auth/RefreshTokensServiceTests.cs @@ -1,5 +1,5 @@ -using AdvancedTodoList.Core.Models.Auth; -using AdvancedTodoList.Core.Services.Auth; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Core.Models.Auth; using AdvancedTodoList.IntegrationTests.Fixtures; using AdvancedTodoList.IntegrationTests.Utils; @@ -8,146 +8,146 @@ namespace AdvancedTodoList.IntegrationTests.Services.Auth; [TestFixture] public class RefreshTokensServiceTests : BusinessLogicFixture { - private IRefreshTokensService _service; - - [SetUp] - public void SetUp() - { - _service = ServiceScope.ServiceProvider.GetService()!; - } - - [Test] - public async Task GenerateAsync_UserExists_ReturnsValidToken() - { - // Arrange - string userId = "userId"; - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(userId) - .Returns(true); - WebApplicationFactory.RefreshTokensRepository - .AddAsync(Arg.Any()) - .Returns(Task.FromResult); - - // Act - var result = await _service.GenerateAsync(userId); - - // Assert - Assert.That(result, Is.Not.Null); - // Refresh token is not a default base64 string - byte[] base64Token = Convert.FromBase64String(result); - Assert.That(base64Token.All(x => x == 0), Is.False); - // Refresh token was created - await WebApplicationFactory.RefreshTokensRepository - .Received() - .AddAsync(Arg.Is(x => x.Token == result && x.UserId == userId)); - } - - [Test] - public async Task GenerateAsync_UserDoesNotExist_ReturnsNull() - { - // Arrange - string userId = "userId"; - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(userId) - .Returns(false); - - // Act - var result = await _service.GenerateAsync(userId); - - // Assert - Assert.That(result, Is.Null); - } - - [Test] - public async Task RevokeAsync_TokenExists_ReturnsTrueAndDeletesToken() - { - // Arrange - string userId = "userId"; - UserRefreshToken refreshToken = TestModels.CreateTestUserRefreshToken(userId); - WebApplicationFactory.RefreshTokensRepository - .FindAsync(userId, refreshToken.Token) - .Returns(refreshToken); - WebApplicationFactory.RefreshTokensRepository - .DeleteAsync(Arg.Any()) - .Returns(Task.FromResult); - - // Act - var result = await _service.RevokeAsync(userId, refreshToken.Token); - - // Assert - Assert.That(result, Is.True); - // Refresh token was deleted - await WebApplicationFactory.RefreshTokensRepository - .Received() - .DeleteAsync(Arg.Is(x => x.Token == refreshToken.Token && x.UserId == userId)); - } - - [Test] - public async Task RevokeAsync_TokenDoesNotExist_ReturnsFalse() - { - // Arrange - string userId = "userId"; - UserRefreshToken refreshToken = TestModels.CreateTestUserRefreshToken(userId); - WebApplicationFactory.RefreshTokensRepository - .FindAsync(userId, refreshToken.Token) - .ReturnsNull(); - - // Act - var result = await _service.RevokeAsync(userId, refreshToken.Token); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task ValidateAsync_ValidToken_ReturnsTrue() - { - // Arrange - string userId = "userId"; - UserRefreshToken refreshToken = TestModels.CreateTestUserRefreshToken(userId); - WebApplicationFactory.RefreshTokensRepository - .FindAsync(userId, refreshToken.Token) - .Returns(refreshToken); - - // Act - var result = await _service.ValidateAsync(userId, refreshToken.Token); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public async Task ValidateAsync_TokenDoesNotExist_ReturnsFalse() - { - // Arrange - string userId = "userId"; - UserRefreshToken refreshToken = TestModels.CreateTestUserRefreshToken(userId); - WebApplicationFactory.RefreshTokensRepository - .FindAsync(userId, refreshToken.Token) - .ReturnsNull(); - - // Act - var result = await _service.ValidateAsync(userId, refreshToken.Token); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task ValidateAsync_OutdatedToken_ReturnsFalse() - { - // Arrange - string userId = "userId"; - UserRefreshToken refreshToken = TestModels.CreateTestUserRefreshToken(userId); - refreshToken.ValidTo = DateTime.UtcNow.AddDays(-30); - WebApplicationFactory.RefreshTokensRepository - .FindAsync(userId, refreshToken.Token) - .Returns(refreshToken); - - // Act - var result = await _service.ValidateAsync(userId, refreshToken.Token); - - // Assert - Assert.That(result, Is.False); - } + private IRefreshTokensService _service; + + [SetUp] + public void SetUp() + { + _service = ServiceScope.ServiceProvider.GetService()!; + } + + [Test] + public async Task GenerateAsync_UserExists_ReturnsValidToken() + { + // Arrange + string userId = "userId"; + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(userId) + .Returns(true); + WebApplicationFactory.RefreshTokensRepository + .AddAsync(Arg.Any()) + .Returns(Task.FromResult); + + // Act + var result = await _service.GenerateAsync(userId); + + // Assert + Assert.That(result, Is.Not.Null); + // Refresh token is not a default base64 string + byte[] base64Token = Convert.FromBase64String(result); + Assert.That(base64Token.All(x => x == 0), Is.False); + // Refresh token was created + await WebApplicationFactory.RefreshTokensRepository + .Received() + .AddAsync(Arg.Is(x => x.Token == result && x.UserId == userId)); + } + + [Test] + public async Task GenerateAsync_UserDoesNotExist_ReturnsNull() + { + // Arrange + string userId = "userId"; + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(userId) + .Returns(false); + + // Act + var result = await _service.GenerateAsync(userId); + + // Assert + Assert.That(result, Is.Null); + } + + [Test] + public async Task RevokeAsync_TokenExists_ReturnsTrueAndDeletesToken() + { + // Arrange + string userId = "userId"; + UserRefreshToken refreshToken = TestModels.CreateTestUserRefreshToken(userId); + WebApplicationFactory.RefreshTokensRepository + .FindAsync(userId, refreshToken.Token) + .Returns(refreshToken); + WebApplicationFactory.RefreshTokensRepository + .DeleteAsync(Arg.Any()) + .Returns(Task.FromResult); + + // Act + var result = await _service.RevokeAsync(userId, refreshToken.Token); + + // Assert + Assert.That(result, Is.True); + // Refresh token was deleted + await WebApplicationFactory.RefreshTokensRepository + .Received() + .DeleteAsync(Arg.Is(x => x.Token == refreshToken.Token && x.UserId == userId)); + } + + [Test] + public async Task RevokeAsync_TokenDoesNotExist_ReturnsFalse() + { + // Arrange + string userId = "userId"; + UserRefreshToken refreshToken = TestModels.CreateTestUserRefreshToken(userId); + WebApplicationFactory.RefreshTokensRepository + .FindAsync(userId, refreshToken.Token) + .ReturnsNull(); + + // Act + var result = await _service.RevokeAsync(userId, refreshToken.Token); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task ValidateAsync_ValidToken_ReturnsTrue() + { + // Arrange + string userId = "userId"; + UserRefreshToken refreshToken = TestModels.CreateTestUserRefreshToken(userId); + WebApplicationFactory.RefreshTokensRepository + .FindAsync(userId, refreshToken.Token) + .Returns(refreshToken); + + // Act + var result = await _service.ValidateAsync(userId, refreshToken.Token); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public async Task ValidateAsync_TokenDoesNotExist_ReturnsFalse() + { + // Arrange + string userId = "userId"; + UserRefreshToken refreshToken = TestModels.CreateTestUserRefreshToken(userId); + WebApplicationFactory.RefreshTokensRepository + .FindAsync(userId, refreshToken.Token) + .ReturnsNull(); + + // Act + var result = await _service.ValidateAsync(userId, refreshToken.Token); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task ValidateAsync_OutdatedToken_ReturnsFalse() + { + // Arrange + string userId = "userId"; + UserRefreshToken refreshToken = TestModels.CreateTestUserRefreshToken(userId); + refreshToken.ValidTo = DateTime.UtcNow.AddDays(-30); + WebApplicationFactory.RefreshTokensRepository + .FindAsync(userId, refreshToken.Token) + .Returns(refreshToken); + + // Act + var result = await _service.ValidateAsync(userId, refreshToken.Token); + + // Assert + Assert.That(result, Is.False); + } } diff --git a/AdvancedTodoList.IntegrationTests/Services/InvitationLinksServiceTests.cs b/AdvancedTodoList.IntegrationTests/Services/InvitationLinksServiceTests.cs index 89dca79..40385c2 100644 --- a/AdvancedTodoList.IntegrationTests/Services/InvitationLinksServiceTests.cs +++ b/AdvancedTodoList.IntegrationTests/Services/InvitationLinksServiceTests.cs @@ -1,10 +1,9 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Models.TodoLists; using AdvancedTodoList.Core.Models.TodoLists.Members; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; using AdvancedTodoList.IntegrationTests.Fixtures; using AdvancedTodoList.IntegrationTests.Utils; @@ -13,308 +12,308 @@ namespace AdvancedTodoList.IntegrationTests.Services; [TestFixture] public class InvitationLinksServiceTests : BusinessLogicFixture { - private const string TestCallerId = "CallerId"; - private const string TestTodoListId = "ListId"; - private readonly TodoListContext TestContext = new("ListId", "TestUserId"); - private IInvitationLinksService _service; - - [SetUp] - public void SetUp() - { - _service = ServiceScope.ServiceProvider.GetService()!; - } - - [Test] - public async Task JoinAsync_ValidCall_ReturnsSuccess() - { - // Arrange - InvitationLink link = TestModels.CreateTestInvitationLink(TestTodoListId); - WebApplicationFactory.InvitationLinksRepository - .FindAsync(link.Value) - .Returns(link); - WebApplicationFactory.TodoListMembersRepository - .FindAsync(TestTodoListId, TestCallerId) - .ReturnsNull(); - - // Act - var response = await _service.JoinAsync(TestCallerId, link.Value); - - // Assert - Assert.Multiple(() => - { - Assert.That(response.Status, Is.EqualTo(JoinByInvitationLinkStatus.Success)); - Assert.That(response.Dto, Is.Not.Null); - }); - } - - [Test] - public async Task JoinAsync_LinkDoesNotExist_ReturnsNotFound() - { - // Arrange - string linkValue = "0wnh0w93n"; - WebApplicationFactory.InvitationLinksRepository - .FindAsync(linkValue) - .ReturnsNull(); - - // Act - var response = await _service.JoinAsync(TestCallerId, linkValue); - - // Assert - Assert.That(response.Status, Is.EqualTo(JoinByInvitationLinkStatus.NotFound)); - } - - [Test] - public async Task JoinAsync_ExpiredLink_ReturnsExpired() - { - // Arrange - InvitationLink link = TestModels.CreateTestInvitationLink( - TestTodoListId, DateTime.UtcNow.AddDays(-30)); - WebApplicationFactory.InvitationLinksRepository - .FindAsync(link.Value) - .Returns(link); - - // Act - var response = await _service.JoinAsync(TestCallerId, link.Value); - - // Assert - Assert.That(response.Status, Is.EqualTo(JoinByInvitationLinkStatus.Expired)); - } - - [Test] - public async Task JoinAsync_CallerIsTodoListMember_ReturnsUserIsAlreadyMember() - { - // Arrange - InvitationLink link = TestModels.CreateTestInvitationLink(TestTodoListId); - WebApplicationFactory.InvitationLinksRepository - .FindAsync(link.Value) - .Returns(link); - WebApplicationFactory.TodoListMembersRepository - .FindAsync(TestTodoListId, TestCallerId) - .Returns(new TodoListMember() { TodoListId = TestTodoListId, UserId = TestCallerId }); - - // Act - var response = await _service.JoinAsync(TestCallerId, link.Value); - - // Assert - Assert.That(response.Status, Is.EqualTo(JoinByInvitationLinkStatus.UserIsAlreadyMember)); - } - - [Test] - [TestCase(true, false)] - [TestCase(false, true)] - [TestCase(true, true)] - public async Task GetPageAsync_ListExists_AppliesSpecification(bool addMembersPermission, bool manageLinksPermission) - { - // Arrange - PaginationParameters parameters = new(2, 5); - RolePermissions validPermissions = new( - AddMembers: addMembersPermission, - ManageInvitationLinks: manageLinksPermission); - Page page = new([], parameters.Page, parameters.PageSize, 0); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(TestContext.TodoListId) - .Returns(true); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(x => ((Func)x[1])(validPermissions)); - WebApplicationFactory.InvitationLinksRepository - .GetPageAsync(Arg.Any(), - Arg.Any>()) - .Returns(page); - - // Act - var result = await _service.GetInvitationLinksAsync(TestContext, parameters); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Success)); - await WebApplicationFactory.InvitationLinksRepository - .Received() - .GetPageAsync(parameters, Arg.Any()); - } - - [Test] - public async Task GetPageAsync_ListDoesNotExist_ReturnsNotFoundStatus() - { - // Arrange - PaginationParameters parameters = new(2, 5); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(TestContext.TodoListId) - .Returns(false); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(x => true); - - // Act - var result = await _service.GetInvitationLinksAsync(TestContext, parameters); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task GetPageAsync_UserIsNotMember_ReturnsForbidden() - { - // Arrange - PaginationParameters parameters = new(2, 5); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(TestContext.TodoListId) - .Returns(true); - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(TestContext) - .Returns(false); - - // Act - var result = await _service.GetInvitationLinksAsync(TestContext, parameters); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } - - - [Test] - public async Task CreateAsync_ValidCall_AddsEntityToDb() - { - // Arrange - RolePermissions validPermissions = new(AddMembers: true); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(x => ((Func)x[1])(validPermissions)); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(TestContext.TodoListId) - .Returns(true); - WebApplicationFactory.InvitationLinksRepository - .AddAsync(Arg.Any()) - .Returns(Task.FromResult); - - // Act - var response = await _service.CreateAsync(TestContext); - - // Assert - Assert.That(response.Status, Is.EqualTo(ServiceResponseStatus.Success)); - await WebApplicationFactory.InvitationLinksRepository - .Received() - .AddAsync(Arg.Is(x => x.TodoListId == TestContext.TodoListId)); - Assert.That(response.Result, Is.Not.Null); - // Refresh token is not a default base64 string - byte[] base64Link = Convert.FromBase64String(response.Result.Value); - Assert.That(base64Link.All(x => x == 0), Is.False); - } - - [Test] - public async Task CreateAsync_TodoListDoesNotExist_ReturnsNotFound() - { - // Arrange - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(true); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(TestContext.TodoListId) - .Returns(false); - - // Act: call the method - var result = await _service.CreateAsync(TestContext); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task CreateAsync_UserHasNoPermssion_ReturnsForbidden() - { - // Arrange - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(false); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(TestContext.TodoListId) - .Returns(true); - - // Act: call the method - var result = await _service.CreateAsync(TestContext); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } - - [Test] - public async Task DeleteAsync_ValidCall_Succeeds() - { - // Arrange - var entity = TestModels.CreateTestInvitationLink(TestContext.TodoListId); - RolePermissions validPermissions = new(ManageInvitationLinks: true); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(x => ((Func)x[1])(validPermissions)); - WebApplicationFactory.InvitationLinksRepository - .GetByIdAsync(entity.Id) - .Returns(entity); - WebApplicationFactory.InvitationLinksRepository - .DeleteAsync(Arg.Any()) - .Returns(Task.FromResult); - - // Act - var result = await _service.DeleteAsync(TestContext, entity.Id); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); - // Assert that delete was called - await WebApplicationFactory.InvitationLinksRepository - .Received() - .DeleteAsync(Arg.Is(x => x.Id == entity.Id)); - } - - [Test] - public async Task DeleteAsync_EntityDoesNotExist_ReturnsNotFound() - { - // Arrange - int entityId = 500; - WebApplicationFactory.InvitationLinksRepository - .GetByIdAsync(entityId) - .ReturnsNull(); - - // Act - var result = await _service.DeleteAsync(TestContext, entityId); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task DeleteAsync_InvalidTodoListId_ReturnsNotFound() - { - // Arrange - int entityId = 500; - var entity = TestModels.CreateTestInvitationLink("Wrong to-do list ID"); - WebApplicationFactory.InvitationLinksRepository - .GetByIdAsync(entity.Id) - .Returns(entity); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(x => false); - - // Act - var result = await _service.DeleteAsync(TestContext, entityId); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task DeleteAsync_UserHasNoPermission_ReturnsForbidden() - { - // Arrange - var entity = TestModels.CreateTestInvitationLink(TestContext.TodoListId); - WebApplicationFactory.InvitationLinksRepository - .GetByIdAsync(entity.Id) - .Returns(entity); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(x => false); - - // Act - var result = await _service.DeleteAsync(TestContext, entity.Id); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } + private const string TestCallerId = "CallerId"; + private const string TestTodoListId = "ListId"; + private readonly TodoListContext TestContext = new("ListId", "TestUserId"); + private IInvitationLinksService _service; + + [SetUp] + public void SetUp() + { + _service = ServiceScope.ServiceProvider.GetService()!; + } + + [Test] + public async Task JoinAsync_ValidCall_ReturnsSuccess() + { + // Arrange + InvitationLink link = TestModels.CreateTestInvitationLink(TestTodoListId); + WebApplicationFactory.InvitationLinksRepository + .FindAsync(link.Value) + .Returns(link); + WebApplicationFactory.TodoListMembersRepository + .FindAsync(TestTodoListId, TestCallerId) + .ReturnsNull(); + + // Act + var response = await _service.JoinAsync(TestCallerId, link.Value); + + // Assert + using (Assert.EnterMultipleScope()) + { + Assert.That(response.Status, Is.EqualTo(JoinByInvitationLinkStatus.Success)); + Assert.That(response.Dto, Is.Not.Null); + } + } + + [Test] + public async Task JoinAsync_LinkDoesNotExist_ReturnsNotFound() + { + // Arrange + string linkValue = "0wnh0w93n"; + WebApplicationFactory.InvitationLinksRepository + .FindAsync(linkValue) + .ReturnsNull(); + + // Act + var response = await _service.JoinAsync(TestCallerId, linkValue); + + // Assert + Assert.That(response.Status, Is.EqualTo(JoinByInvitationLinkStatus.NotFound)); + } + + [Test] + public async Task JoinAsync_ExpiredLink_ReturnsExpired() + { + // Arrange + InvitationLink link = TestModels.CreateTestInvitationLink( + TestTodoListId, DateTime.UtcNow.AddDays(-30)); + WebApplicationFactory.InvitationLinksRepository + .FindAsync(link.Value) + .Returns(link); + + // Act + var response = await _service.JoinAsync(TestCallerId, link.Value); + + // Assert + Assert.That(response.Status, Is.EqualTo(JoinByInvitationLinkStatus.Expired)); + } + + [Test] + public async Task JoinAsync_CallerIsTodoListMember_ReturnsUserIsAlreadyMember() + { + // Arrange + InvitationLink link = TestModels.CreateTestInvitationLink(TestTodoListId); + WebApplicationFactory.InvitationLinksRepository + .FindAsync(link.Value) + .Returns(link); + WebApplicationFactory.TodoListMembersRepository + .FindAsync(TestTodoListId, TestCallerId) + .Returns(new TodoListMember() { TodoListId = TestTodoListId, UserId = TestCallerId }); + + // Act + var response = await _service.JoinAsync(TestCallerId, link.Value); + + // Assert + Assert.That(response.Status, Is.EqualTo(JoinByInvitationLinkStatus.UserIsAlreadyMember)); + } + + [Test] + [TestCase(true, false)] + [TestCase(false, true)] + [TestCase(true, true)] + public async Task GetPageAsync_ListExists_AppliesSpecification(bool addMembersPermission, bool manageLinksPermission) + { + // Arrange + PaginationParameters parameters = new(2, 5); + RolePermissions validPermissions = new( + AddMembers: addMembersPermission, + ManageInvitationLinks: manageLinksPermission); + Page page = new([], parameters.Page, parameters.PageSize, 0); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(TestContext.TodoListId) + .Returns(true); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(x => ((Func)x[1])(validPermissions)); + WebApplicationFactory.InvitationLinksRepository + .GetPageAsync(Arg.Any(), + Arg.Any>()) + .Returns(page); + + // Act + var result = await _service.GetInvitationLinksAsync(TestContext, parameters); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Success)); + await WebApplicationFactory.InvitationLinksRepository + .Received() + .GetPageAsync(parameters, Arg.Any()); + } + + [Test] + public async Task GetPageAsync_ListDoesNotExist_ReturnsNotFoundStatus() + { + // Arrange + PaginationParameters parameters = new(2, 5); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(TestContext.TodoListId) + .Returns(false); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(x => true); + + // Act + var result = await _service.GetInvitationLinksAsync(TestContext, parameters); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task GetPageAsync_UserIsNotMember_ReturnsForbidden() + { + // Arrange + PaginationParameters parameters = new(2, 5); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(TestContext.TodoListId) + .Returns(true); + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(TestContext) + .Returns(false); + + // Act + var result = await _service.GetInvitationLinksAsync(TestContext, parameters); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } + + + [Test] + public async Task CreateAsync_ValidCall_AddsEntityToDb() + { + // Arrange + RolePermissions validPermissions = new(AddMembers: true); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(x => ((Func)x[1])(validPermissions)); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(TestContext.TodoListId) + .Returns(true); + WebApplicationFactory.InvitationLinksRepository + .AddAsync(Arg.Any()) + .Returns(Task.FromResult); + + // Act + var response = await _service.CreateAsync(TestContext); + + // Assert + Assert.That(response.Status, Is.EqualTo(ServiceResponseStatus.Success)); + await WebApplicationFactory.InvitationLinksRepository + .Received() + .AddAsync(Arg.Is(x => x.TodoListId == TestContext.TodoListId)); + Assert.That(response.Result, Is.Not.Null); + // Refresh token is not a default base64 string + byte[] base64Link = Convert.FromBase64String(response.Result.Value); + Assert.That(base64Link.All(x => x == 0), Is.False); + } + + [Test] + public async Task CreateAsync_TodoListDoesNotExist_ReturnsNotFound() + { + // Arrange + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(true); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(TestContext.TodoListId) + .Returns(false); + + // Act: call the method + var result = await _service.CreateAsync(TestContext); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task CreateAsync_UserHasNoPermssion_ReturnsForbidden() + { + // Arrange + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(false); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(TestContext.TodoListId) + .Returns(true); + + // Act: call the method + var result = await _service.CreateAsync(TestContext); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } + + [Test] + public async Task DeleteAsync_ValidCall_Succeeds() + { + // Arrange + var entity = TestModels.CreateTestInvitationLink(TestContext.TodoListId); + RolePermissions validPermissions = new(ManageInvitationLinks: true); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(x => ((Func)x[1])(validPermissions)); + WebApplicationFactory.InvitationLinksRepository + .GetByIdAsync(entity.Id) + .Returns(entity); + WebApplicationFactory.InvitationLinksRepository + .DeleteAsync(Arg.Any()) + .Returns(Task.FromResult); + + // Act + var result = await _service.DeleteAsync(TestContext, entity.Id); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); + // Assert that delete was called + await WebApplicationFactory.InvitationLinksRepository + .Received() + .DeleteAsync(Arg.Is(x => x.Id == entity.Id)); + } + + [Test] + public async Task DeleteAsync_EntityDoesNotExist_ReturnsNotFound() + { + // Arrange + int entityId = 500; + WebApplicationFactory.InvitationLinksRepository + .GetByIdAsync(entityId) + .ReturnsNull(); + + // Act + var result = await _service.DeleteAsync(TestContext, entityId); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task DeleteAsync_InvalidTodoListId_ReturnsNotFound() + { + // Arrange + int entityId = 500; + var entity = TestModels.CreateTestInvitationLink("Wrong to-do list ID"); + WebApplicationFactory.InvitationLinksRepository + .GetByIdAsync(entity.Id) + .Returns(entity); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(x => false); + + // Act + var result = await _service.DeleteAsync(TestContext, entityId); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task DeleteAsync_UserHasNoPermission_ReturnsForbidden() + { + // Arrange + var entity = TestModels.CreateTestInvitationLink(TestContext.TodoListId); + WebApplicationFactory.InvitationLinksRepository + .GetByIdAsync(entity.Id) + .Returns(entity); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(x => false); + + // Act + var result = await _service.DeleteAsync(TestContext, entity.Id); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } } diff --git a/AdvancedTodoList.IntegrationTests/Services/TestTodoListDependantEntity.cs b/AdvancedTodoList.IntegrationTests/Services/TestTodoListDependantEntity.cs index d8e0ddf..955a295 100644 --- a/AdvancedTodoList.IntegrationTests/Services/TestTodoListDependantEntity.cs +++ b/AdvancedTodoList.IntegrationTests/Services/TestTodoListDependantEntity.cs @@ -5,10 +5,10 @@ namespace AdvancedTodoList.IntegrationTests.Services; public class TestTodoListDependantEntity : IEntity, ITodoListDependant, IHasOwner { - public int Id { get; set; } - public string TodoListId { get; set; } = null!; - public string TestProperty { get; set; } = null!; - public string? OwnerId { get; set; } + public int Id { get; set; } + public string TodoListId { get; set; } = null!; + public string TestProperty { get; set; } = null!; + public string? OwnerId { get; set; } } public record TestTodoListDependantViewDto(int Id, string TestProperty); public record TestTodoListDependantCreateDto(string TestProperty); \ No newline at end of file diff --git a/AdvancedTodoList.IntegrationTests/Services/TodoItemCategoriesServiceTests.cs b/AdvancedTodoList.IntegrationTests/Services/TodoItemCategoriesServiceTests.cs index 3c6aa88..229617f 100644 --- a/AdvancedTodoList.IntegrationTests/Services/TodoItemCategoriesServiceTests.cs +++ b/AdvancedTodoList.IntegrationTests/Services/TodoItemCategoriesServiceTests.cs @@ -1,9 +1,9 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Models.TodoLists; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications.Todo; using AdvancedTodoList.IntegrationTests.Fixtures; using AdvancedTodoList.IntegrationTests.Utils; @@ -12,97 +12,97 @@ namespace AdvancedTodoList.IntegrationTests.Services; [TestFixture] public class TodoItemCategoriesServiceTests : BusinessLogicFixture { - private ITodoItemCategoriesService _service; - - private readonly TodoListContext TestContext = new("TodoListId", "TestUserId"); - - [SetUp] - public void SetUp() - { - _service = ServiceScope.ServiceProvider.GetService()!; - } - - [Test] - public async Task GetCategoriesOfListAsync_ListExists_AppliesTodoItemsSpecification() - { - // Arrange - PaginationParameters parameters = new(2, 5); - Page page = new([], 1, 1, 1); - const string name = "n"; - WebApplicationFactory.TodoItemCategoriesHelperService - .GetPageAsync(TestContext, Arg.Any>(), Arg.Any()) - .Returns(new ServiceResponse>(ServiceResponseStatus.Success, page)); - - // Act - var result = await _service.GetCategoriesOfListAsync(TestContext, parameters, name); - - // Assert - Assert.That(result, Is.Not.Null); - await WebApplicationFactory.TodoItemCategoriesHelperService - .Received() - .GetPageAsync(TestContext, - Arg.Is>( - x => x.TodoListId == TestContext.TodoListId && x.Name == name), - Arg.Any()); - } - - [Test] - public async Task IsCategoryValidForContext_CategoryIdIsNull_ReturnsTrue() - { - // Act - var result = await _service.IsCategoryValidForContext(TestContext, null); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public async Task IsCategoryValidForContext_CategoryIsValid_ReturnsTrue() - { - // Arrange - int categoryId = 1; - var category = TestModels.CreateTestTodoItemCategory(TestContext.TodoListId); - WebApplicationFactory.TodoItemCategoriesRepository - .GetByIdAsync(categoryId) - .Returns(category); - - // Act - var result = await _service.IsCategoryValidForContext(TestContext, categoryId); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public async Task IsCategoryValidForContext_CategoryDoesNotExist_ReturnsFalse() - { - // Arrange - int categoryId = 1; - WebApplicationFactory.TodoItemCategoriesRepository - .GetByIdAsync(categoryId) - .ReturnsNull(); - - // Act - var result = await _service.IsCategoryValidForContext(TestContext, categoryId); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public async Task IsCategoryValidForContext_WrongTodoListId_ReturnsFalse() - { - // Arrange - int categoryId = 1; - var category = TestModels.CreateTestTodoItemCategory("Wrong to-do list ID"); - WebApplicationFactory.TodoItemCategoriesRepository - .GetByIdAsync(categoryId) - .Returns(category); - - // Act - var result = await _service.IsCategoryValidForContext(TestContext, categoryId); - - // Assert - Assert.That(result, Is.False); - } + private ITodoItemCategoriesService _service; + + private readonly TodoListContext TestContext = new("TodoListId", "TestUserId"); + + [SetUp] + public void SetUp() + { + _service = ServiceScope.ServiceProvider.GetService()!; + } + + [Test] + public async Task GetCategoriesOfListAsync_ListExists_AppliesTodoItemsSpecification() + { + // Arrange + PaginationParameters parameters = new(2, 5); + Page page = new([], 1, 1, 1); + const string name = "n"; + WebApplicationFactory.TodoItemCategoriesHelperService + .GetPageAsync(TestContext, Arg.Any>(), Arg.Any()) + .Returns(new ServiceResponse>(ServiceResponseStatus.Success, page)); + + // Act + var result = await _service.GetCategoriesOfListAsync(TestContext, parameters, name); + + // Assert + Assert.That(result, Is.Not.Null); + await WebApplicationFactory.TodoItemCategoriesHelperService + .Received() + .GetPageAsync(TestContext, + Arg.Is>( + x => x.TodoListId == TestContext.TodoListId && x.Name == name), + Arg.Any()); + } + + [Test] + public async Task IsCategoryValidForContext_CategoryIdIsNull_ReturnsTrue() + { + // Act + var result = await _service.IsCategoryValidForContext(TestContext, null); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public async Task IsCategoryValidForContext_CategoryIsValid_ReturnsTrue() + { + // Arrange + int categoryId = 1; + var category = TestModels.CreateTestTodoItemCategory(TestContext.TodoListId); + WebApplicationFactory.TodoItemCategoriesRepository + .GetByIdAsync(categoryId) + .Returns(category); + + // Act + var result = await _service.IsCategoryValidForContext(TestContext, categoryId); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public async Task IsCategoryValidForContext_CategoryDoesNotExist_ReturnsFalse() + { + // Arrange + int categoryId = 1; + WebApplicationFactory.TodoItemCategoriesRepository + .GetByIdAsync(categoryId) + .ReturnsNull(); + + // Act + var result = await _service.IsCategoryValidForContext(TestContext, categoryId); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public async Task IsCategoryValidForContext_WrongTodoListId_ReturnsFalse() + { + // Arrange + int categoryId = 1; + var category = TestModels.CreateTestTodoItemCategory("Wrong to-do list ID"); + WebApplicationFactory.TodoItemCategoriesRepository + .GetByIdAsync(categoryId) + .Returns(category); + + // Act + var result = await _service.IsCategoryValidForContext(TestContext, categoryId); + + // Assert + Assert.That(result, Is.False); + } } diff --git a/AdvancedTodoList.IntegrationTests/Services/TodoItemsServiceTests.cs b/AdvancedTodoList.IntegrationTests/Services/TodoItemsServiceTests.cs index 6d46ded..c4f8720 100644 --- a/AdvancedTodoList.IntegrationTests/Services/TodoItemsServiceTests.cs +++ b/AdvancedTodoList.IntegrationTests/Services/TodoItemsServiceTests.cs @@ -1,10 +1,11 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Models.TodoLists; using AdvancedTodoList.Core.Models.TodoLists.Members; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.Core.Specifications.Todo; using AdvancedTodoList.IntegrationTests.Fixtures; using AdvancedTodoList.IntegrationTests.Utils; @@ -12,212 +13,212 @@ namespace AdvancedTodoList.IntegrationTests.Services; public class TodoItemsServiceTests : BusinessLogicFixture { - private ITodoItemsService _service; - - private readonly TodoListContext TestContext = new("TodoListId", "TestUserId"); - - [SetUp] - public void SetUp() - { - _service = ServiceScope.ServiceProvider.GetService()!; - } - - [Test] - public async Task GetItemsOfListAsync_ListExists_AppliesTodoItemsSpecification() - { - // Arrange - PaginationParameters parameters = new(2, 5); - Page page = new([], 1, 1, 1); - TodoItemsFilter filter = new(Name: "Name"); - WebApplicationFactory.TodoItemsHelperService - .GetPageAsync(TestContext, Arg.Any>(), Arg.Any()) - .Returns(new ServiceResponse>(ServiceResponseStatus.Success, page)); - - // Act - var result = await _service.GetItemsOfListAsync(TestContext, parameters, filter); - - // Assert - Assert.That(result, Is.Not.Null); - await WebApplicationFactory.TodoItemsHelperService - .Received() - .GetPageAsync(TestContext, - Arg.Is( - x => x.TodoListId == TestContext.TodoListId && x.Filter.Name == filter.Name), - Arg.Any()); - } - - [Test] - public async Task GetByIdAsync_EntityExists_AppliesTodoListAggregateSpecification() - { - // Arrange - int todoItemId = 123; - TodoItemGetByIdDto dto = new(todoItemId, TestContext.TodoListId, "Name", "Description", null, - TodoItemState.Active, 3, new("User", "Name"), null); - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(TestContext) - .Returns(true); - WebApplicationFactory.TodoItemsRepository - .GetAggregateAsync(Arg.Any>()) - .Returns(dto); - - // Act - var result = await _service.GetByIdAsync(TestContext, todoItemId); - - // Assert - Assert.Multiple(() => - { - Assert.That(result.Result, Is.EqualTo(dto)); - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Success)); - }); - await WebApplicationFactory.TodoItemsRepository - .Received() - .GetAggregateAsync( - Arg.Is(x => x.Id == todoItemId)); - } - - [Test] - public async Task GetByIdAsync_InvalidTodoListId_ReturnsNotFound() - { - // Arrange - int todoItemId = 123; - TodoItemGetByIdDto dto = new(todoItemId, "WrongTodoListId", "Name", "Description", null, - TodoItemState.Active, 1, new("User", "Name"), null); - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(TestContext) - .Returns(true); - WebApplicationFactory.TodoItemsRepository - .GetAggregateAsync(Arg.Any>()) - .Returns(dto); - - // Act - var result = await _service.GetByIdAsync(TestContext, todoItemId); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task GetByIdAsync_EntityDoesNotExist_ReturnsNull() - { - // Arrange - int itemId = 123; - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(TestContext) - .Returns(true); - WebApplicationFactory.TodoItemsRepository - .GetAggregateAsync(Arg.Any>()) - .ReturnsNull(); - - // Act - var result = await _service.GetByIdAsync(TestContext, itemId); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task GetByIdAsync_UserHasNoPermission_ReturnsForbidden() - { - // Arrange - int itemId = 123; - TodoItemGetByIdDto dto = new(itemId, TestContext.TodoListId, "Name", "Description", null, - TodoItemState.Active, 1, new("User", "Name"), null); - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(TestContext) - .Returns(false); - WebApplicationFactory.TodoItemsRepository - .GetAggregateAsync(Arg.Any>()) - .Returns(dto); - - // Act - var result = await _service.GetByIdAsync(TestContext, itemId); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } - - [Test] - public async Task CreateAsync_InvalidCategoryId_ReturnsInvalidCategoryId() - { - // Arrange - TodoItemCreateDto dto = new("A", "B", null, 5, 777); - WebApplicationFactory.TodoItemCategoriesRepository - .GetByIdAsync(dto.CategoryId!.Value) - .ReturnsNull(); - - // Act - var response = await _service.CreateAsync(TestContext, dto); - - // Assert - Assert.That(response.Status, Is.EqualTo(TodoItemsServiceStatus.InvalidCategoryId)); - } - - [Test] - [TestCase(ServiceResponseStatus.Success, TodoItemsServiceStatus.Success)] - [TestCase(ServiceResponseStatus.Forbidden, TodoItemsServiceStatus.Forbidden)] - [TestCase(ServiceResponseStatus.NotFound, TodoItemsServiceStatus.NotFound)] - public async Task CreateAsync_ValidCategoryId_ReturnsConvertedResponse( - ServiceResponseStatus returnedStatus, TodoItemsServiceStatus expectedStatus) - { - // Arrange - TodoItemCreateDto dto = new("A", "B", null, 5, 777); - var category = TestModels.CreateTestTodoItemCategory(TestContext.TodoListId); - WebApplicationFactory.TodoItemCategoriesRepository - .GetByIdAsync(dto.CategoryId!.Value) - .Returns(category); - WebApplicationFactory.TodoItemsHelperService - .CreateAsync(TestContext, dto, - Arg.Any>()) - .Returns(new ServiceResponse(returnedStatus)); - - // Act - var response = await _service.CreateAsync(TestContext, dto); - - // Assert - Assert.That(response.Status, Is.EqualTo(expectedStatus)); - } - - [Test] - public async Task EditAsync_InvalidCategoryId_ReturnsInvalidCategoryId() - { - // Arrange - int itemId = 123; - TodoItemCreateDto dto = new("A", "B", null, 5, 777); - WebApplicationFactory.TodoItemCategoriesRepository - .GetByIdAsync(dto.CategoryId!.Value) - .ReturnsNull(); - - // Act - var response = await _service.EditAsync(TestContext, itemId, dto); - - // Assert - Assert.That(response, Is.EqualTo(TodoItemsServiceStatus.InvalidCategoryId)); - } - - [Test] - [TestCase(ServiceResponseStatus.Success, TodoItemsServiceStatus.Success)] - [TestCase(ServiceResponseStatus.Forbidden, TodoItemsServiceStatus.Forbidden)] - [TestCase(ServiceResponseStatus.NotFound, TodoItemsServiceStatus.NotFound)] - public async Task EditAsync_ValidCategoryId_ReturnsConvertedResponse( - ServiceResponseStatus returnedStatus, TodoItemsServiceStatus expectedStatus) - { - // Arrange - int itemId = 123; - TodoItemCreateDto dto = new("A", "B", null, 5, 777); - var category = TestModels.CreateTestTodoItemCategory(TestContext.TodoListId); - WebApplicationFactory.TodoItemCategoriesRepository - .GetByIdAsync(dto.CategoryId!.Value) - .Returns(category); - WebApplicationFactory.TodoItemsHelperService - .UpdateAsync(TestContext, itemId, dto, Arg.Any>()) - .Returns(returnedStatus); - - // Act - var response = await _service.EditAsync(TestContext, itemId, dto); - - // Assert - Assert.That(response, Is.EqualTo(expectedStatus)); - } - - // Tests for other methods are useless, because they are just wrappers. + private ITodoItemsService _service; + + private readonly TodoListContext TestContext = new("TodoListId", "TestUserId"); + + [SetUp] + public void SetUp() + { + _service = ServiceScope.ServiceProvider.GetService()!; + } + + [Test] + public async Task GetItemsOfListAsync_ListExists_AppliesTodoItemsSpecification() + { + // Arrange + PaginationParameters parameters = new(2, 5); + Page page = new([], 1, 1, 1); + TodoItemsFilter filter = new(Name: "Name"); + WebApplicationFactory.TodoItemsHelperService + .GetPageAsync(TestContext, Arg.Any>(), Arg.Any()) + .Returns(new ServiceResponse>(ServiceResponseStatus.Success, page)); + + // Act + var result = await _service.GetItemsOfListAsync(TestContext, parameters, filter); + + // Assert + Assert.That(result, Is.Not.Null); + await WebApplicationFactory.TodoItemsHelperService + .Received() + .GetPageAsync(TestContext, + Arg.Is( + x => x.TodoListId == TestContext.TodoListId && x.Filter.Name == filter.Name), + Arg.Any()); + } + + [Test] + public async Task GetByIdAsync_EntityExists_AppliesTodoListAggregateSpecification() + { + // Arrange + int todoItemId = 123; + TodoItemGetByIdDto dto = new(todoItemId, TestContext.TodoListId, "Name", "Description", null, + TodoItemState.Active, 3, new("User", "Name"), null); + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(TestContext) + .Returns(true); + WebApplicationFactory.TodoItemsRepository + .GetAggregateAsync(Arg.Any>()) + .Returns(dto); + + // Act + var result = await _service.GetByIdAsync(TestContext, todoItemId); + + // Assert + using (Assert.EnterMultipleScope()) + { + Assert.That(result.Result, Is.EqualTo(dto)); + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Success)); + } + await WebApplicationFactory.TodoItemsRepository + .Received() + .GetAggregateAsync( + Arg.Is(x => x.Id == todoItemId)); + } + + [Test] + public async Task GetByIdAsync_InvalidTodoListId_ReturnsNotFound() + { + // Arrange + int todoItemId = 123; + TodoItemGetByIdDto dto = new(todoItemId, "WrongTodoListId", "Name", "Description", null, + TodoItemState.Active, 1, new("User", "Name"), null); + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(TestContext) + .Returns(true); + WebApplicationFactory.TodoItemsRepository + .GetAggregateAsync(Arg.Any>()) + .Returns(dto); + + // Act + var result = await _service.GetByIdAsync(TestContext, todoItemId); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task GetByIdAsync_EntityDoesNotExist_ReturnsNull() + { + // Arrange + int itemId = 123; + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(TestContext) + .Returns(true); + WebApplicationFactory.TodoItemsRepository + .GetAggregateAsync(Arg.Any>()) + .ReturnsNull(); + + // Act + var result = await _service.GetByIdAsync(TestContext, itemId); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task GetByIdAsync_UserHasNoPermission_ReturnsForbidden() + { + // Arrange + int itemId = 123; + TodoItemGetByIdDto dto = new(itemId, TestContext.TodoListId, "Name", "Description", null, + TodoItemState.Active, 1, new("User", "Name"), null); + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(TestContext) + .Returns(false); + WebApplicationFactory.TodoItemsRepository + .GetAggregateAsync(Arg.Any>()) + .Returns(dto); + + // Act + var result = await _service.GetByIdAsync(TestContext, itemId); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } + + [Test] + public async Task CreateAsync_InvalidCategoryId_ReturnsInvalidCategoryId() + { + // Arrange + TodoItemCreateDto dto = new("A", "B", null, 5, 777); + WebApplicationFactory.TodoItemCategoriesRepository + .GetByIdAsync(dto.CategoryId!.Value) + .ReturnsNull(); + + // Act + var response = await _service.CreateAsync(TestContext, dto); + + // Assert + Assert.That(response.Status, Is.EqualTo(TodoItemsServiceStatus.InvalidCategoryId)); + } + + [Test] + [TestCase(ServiceResponseStatus.Success, TodoItemsServiceStatus.Success)] + [TestCase(ServiceResponseStatus.Forbidden, TodoItemsServiceStatus.Forbidden)] + [TestCase(ServiceResponseStatus.NotFound, TodoItemsServiceStatus.NotFound)] + public async Task CreateAsync_ValidCategoryId_ReturnsConvertedResponse( + ServiceResponseStatus returnedStatus, TodoItemsServiceStatus expectedStatus) + { + // Arrange + TodoItemCreateDto dto = new("A", "B", null, 5, 777); + var category = TestModels.CreateTestTodoItemCategory(TestContext.TodoListId); + WebApplicationFactory.TodoItemCategoriesRepository + .GetByIdAsync(dto.CategoryId!.Value) + .Returns(category); + WebApplicationFactory.TodoItemsHelperService + .CreateAsync(TestContext, dto, + Arg.Any>()) + .Returns(new ServiceResponse(returnedStatus)); + + // Act + var response = await _service.CreateAsync(TestContext, dto); + + // Assert + Assert.That(response.Status, Is.EqualTo(expectedStatus)); + } + + [Test] + public async Task EditAsync_InvalidCategoryId_ReturnsInvalidCategoryId() + { + // Arrange + int itemId = 123; + TodoItemCreateDto dto = new("A", "B", null, 5, 777); + WebApplicationFactory.TodoItemCategoriesRepository + .GetByIdAsync(dto.CategoryId!.Value) + .ReturnsNull(); + + // Act + var response = await _service.EditAsync(TestContext, itemId, dto); + + // Assert + Assert.That(response, Is.EqualTo(TodoItemsServiceStatus.InvalidCategoryId)); + } + + [Test] + [TestCase(ServiceResponseStatus.Success, TodoItemsServiceStatus.Success)] + [TestCase(ServiceResponseStatus.Forbidden, TodoItemsServiceStatus.Forbidden)] + [TestCase(ServiceResponseStatus.NotFound, TodoItemsServiceStatus.NotFound)] + public async Task EditAsync_ValidCategoryId_ReturnsConvertedResponse( + ServiceResponseStatus returnedStatus, TodoItemsServiceStatus expectedStatus) + { + // Arrange + int itemId = 123; + TodoItemCreateDto dto = new("A", "B", null, 5, 777); + var category = TestModels.CreateTestTodoItemCategory(TestContext.TodoListId); + WebApplicationFactory.TodoItemCategoriesRepository + .GetByIdAsync(dto.CategoryId!.Value) + .Returns(category); + WebApplicationFactory.TodoItemsHelperService + .UpdateAsync(TestContext, itemId, dto, Arg.Any>()) + .Returns(returnedStatus); + + // Act + var response = await _service.EditAsync(TestContext, itemId, dto); + + // Assert + Assert.That(response, Is.EqualTo(expectedStatus)); + } + + // Tests for other methods are useless, because they are just wrappers. } diff --git a/AdvancedTodoList.IntegrationTests/Services/TodoListDependantEntitiesServiceTests.cs b/AdvancedTodoList.IntegrationTests/Services/TodoListDependantEntitiesServiceTests.cs index 777b2be..566128c 100644 --- a/AdvancedTodoList.IntegrationTests/Services/TodoListDependantEntitiesServiceTests.cs +++ b/AdvancedTodoList.IntegrationTests/Services/TodoListDependantEntitiesServiceTests.cs @@ -1,7 +1,7 @@ -using AdvancedTodoList.Core.Models.TodoLists; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Core.Models.TodoLists; using AdvancedTodoList.Core.Models.TodoLists.Members; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; using AdvancedTodoList.Core.Specifications; using AdvancedTodoList.IntegrationTests.Fixtures; using AdvancedTodoList.IntegrationTests.Utils; @@ -13,418 +13,418 @@ namespace AdvancedTodoList.IntegrationTests.Services; [TestFixture] public class TodoListDependantEntitiesServiceTests : BusinessLogicFixture { - private readonly TodoListContext TestContext = new("ListId", "TestUserId"); - private ITodoListDependantEntitiesService _service; - - [SetUp] - public void SetUp() - { - _service = ServiceScope.ServiceProvider.GetService< - ITodoListDependantEntitiesService>()!; - } - - [Test] - public async Task GetPageAsync_ListExists_AppliesSpecification() - { - // Arrange - PaginationParameters parameters = new(2, 5); - ISpecification specification = - Substitute.For>(); - Page page = new([], parameters.Page, parameters.PageSize, 0); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(TestContext.TodoListId) - .Returns(true); - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(TestContext) - .Returns(true); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetPageAsync(Arg.Any(), - Arg.Any>()) - .Returns(page); - - // Act - var result = await _service.GetPageAsync(TestContext, specification, parameters); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Success)); - await WebApplicationFactory.TestTodoListDependantEntitiesRepository - .Received() - .GetPageAsync(parameters, Arg.Any>()); - } - - [Test] - public async Task GetPageAsync_ListDoesNotExist_ReturnsNotFoundStatus() - { - // Arrange - PaginationParameters parameters = new(2, 5); - ISpecification specification = - Substitute.For>(); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(TestContext.TodoListId) - .Returns(false); - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(TestContext) - .Returns(true); - - // Act - var result = await _service.GetPageAsync(TestContext, - specification, parameters); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task GetPageAsync_UserIsNotMember_ReturnsForbidden() - { - // Arrange - PaginationParameters parameters = new(2, 5); - ISpecification specification = - Substitute.For>(); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(TestContext.TodoListId) - .Returns(true); - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(TestContext) - .Returns(false); - - // Act - var result = await _service.GetPageAsync(TestContext, - specification, parameters); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } - - [Test] - public async Task GetByIdAsync_EntityExists_SucceedsAndReturnsCorrectEntity() - { - // Arrange - TestTodoListDependantEntity entity = TestModels.CreateTestTodoListDependantEntity( - TestContext.TodoListId); - entity.Id = 500; - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(TestContext) - .Returns(true); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetByIdAsync(entity.Id) - .Returns(entity); - - // Act - var result = await _service.GetByIdAsync(TestContext, entity.Id); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Success)); - // Assert that returned DTO matches - var expectedResult = entity.Adapt(); - Assert.That(result.Result, Is.EqualTo(expectedResult)); - } - - [Test] - public async Task GetByIdAsync_WrongTodoList_ReturnsNotFound() - { - // Arrange - TestTodoListDependantEntity entity = TestModels.CreateTestTodoListDependantEntity("WrongTodoListId"); - entity.Id = 500; - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(TestContext) - .Returns(true); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetByIdAsync(entity.Id) - .Returns(entity); - - // Act - var result = await _service.GetByIdAsync(TestContext, entity.Id); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task GetByIdAsync_EntityDoesNotExist_ReturnsNotFound() - { - // Arrange - int entityId = 500; - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(TestContext) - .Returns(true); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetByIdAsync(entityId) - .ReturnsNull(); - - // Act - var result = await _service.GetByIdAsync(TestContext, entityId); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task GetByIdAsync_UserIsNotMember_ReturnsForbidden() - { - // Arrange - TestTodoListDependantEntity entity = TestModels.CreateTestTodoListDependantEntity( - TestContext.TodoListId); - int entityId = 500; - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(TestContext) - .Returns(false); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetByIdAsync(entity.Id) - .Returns(entity); - - // Act - var result = await _service.GetByIdAsync(TestContext, entityId); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } - - [Test] - public async Task CreateAsync_ValidCall_AddsEntityToDb() - { - // Arrange - TestTodoListDependantCreateDto dto = new("Test"); - RolePermissions validPermissions = new(AddItems: true); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(x => ((Func)x[1])(validPermissions)); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(TestContext.TodoListId) - .Returns(true); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .AddAsync(Arg.Any()) - .Returns(Task.FromResult); - - // Act - var result = await _service.CreateAsync(TestContext, dto, x => x.AddItems); - - // Assert - Assert.Multiple(() => - { - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Success)); - Assert.That(result.Result, Is.Not.Null); - }); - await WebApplicationFactory.TestTodoListDependantEntitiesRepository - .Received() - .AddAsync(Arg.Is(x => - x.TestProperty == dto.TestProperty && x.TodoListId == TestContext.TodoListId && - x.OwnerId == TestContext.CallerId)); - } - - [Test] - public async Task CreateAsync_TodoListDoesNotExist_ReturnsNotFound() - { - // Arrange - TestTodoListDependantCreateDto dto = new("Test"); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(true); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(TestContext.TodoListId) - .Returns(false); - - // Act: call the method - var result = await _service.CreateAsync(TestContext, dto, x => x.AddItems); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task CreateAsync_UserHasNoPermssion_ReturnsForbidden() - { - // Arrange - TestTodoListDependantCreateDto dto = new("Test"); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(false); - WebApplicationFactory.EntityExistenceChecker - .ExistsAsync(TestContext.TodoListId) - .Returns(true); - - // Act: call the method - var result = await _service.CreateAsync(TestContext, dto, x => x.AddItems); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } - - [Test] - public async Task UpdateAsync_EntityExists_Succeeds() - { - // Arrange - var entity = TestModels.CreateTestTodoListDependantEntity(TestContext.TodoListId); - TestTodoListDependantCreateDto updateDto = new("New test"); - RolePermissions validPermissions = new(EditItems: true); - WebApplicationFactory.PermissionsChecker - .CanTouchEntityAsync(TestContext, - Arg.Any(), Arg.Any>()) - .Returns(x => ((Func)x[2])(validPermissions)); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetByIdAsync(entity.Id) - .Returns(entity); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .UpdateAsync(Arg.Any()) - .Returns(Task.FromResult); - - // Act - var result = await _service.UpdateAsync(TestContext, entity.Id, updateDto, x => x.EditItems); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); - // Assert that update was called - await WebApplicationFactory.TestTodoListDependantEntitiesRepository - .Received() - .UpdateAsync(Arg.Is(x => - x.Id == entity.Id && x.TestProperty == updateDto.TestProperty)); - } - - [Test] - public async Task UpdateAsync_EntityDoesNotExist_ReturnsNotFound() - { - // Arrange - int entityId = 500; - TestTodoListDependantCreateDto updateDto = new("New test"); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetByIdAsync(entityId) - .ReturnsNull(); - - // Act - var result = await _service.UpdateAsync(TestContext, entityId, updateDto, x => x.EditItems); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task UpdateAsync_WrongTodoListId_ReturnsNotFound() - { - // Arrange - var entity = TestModels.CreateTestTodoListDependantEntity("Wrong to-do list ID"); - int entityId = 500; - TestTodoListDependantCreateDto updateDto = new("New test"); - WebApplicationFactory.PermissionsChecker - .CanTouchEntityAsync(TestContext, - Arg.Any(), Arg.Any>()) - .Returns(true); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetByIdAsync(entity.Id) - .Returns(entity); - - // Act - var result = await _service.UpdateAsync(TestContext, entityId, updateDto, x => x.EditItems); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task UpdateAsync_UserHasNoPermission_ReturnsForbidden() - { - // Arrange - TestTodoListDependantCreateDto updateDto = new("New test"); - var entity = TestModels.CreateTestTodoListDependantEntity(TestContext.TodoListId); - WebApplicationFactory.PermissionsChecker - .CanTouchEntityAsync(TestContext, - Arg.Any(), Arg.Any>()) - .Returns(false); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetByIdAsync(entity.Id) - .Returns(entity); - - // Act - var result = await _service.UpdateAsync(TestContext, entity.Id, updateDto, x => x.EditItems); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } - - [Test] - public async Task DeleteAsync_ValidCall_Succeeds() - { - // Arrange - var entity = TestModels.CreateTestTodoListDependantEntity(TestContext.TodoListId); - RolePermissions validPermissions = new(DeleteItems: true); - WebApplicationFactory.PermissionsChecker - .CanTouchEntityAsync(TestContext, - Arg.Any(), Arg.Any>()) - .Returns(x => ((Func)x[2])(validPermissions)); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetByIdAsync(entity.Id) - .Returns(entity); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .DeleteAsync(Arg.Any()) - .Returns(Task.FromResult); - - // Act - var result = await _service.DeleteAsync(TestContext, entity.Id, x => x.DeleteItems); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); - // Assert that delete was called - await WebApplicationFactory.TestTodoListDependantEntitiesRepository - .Received() - .DeleteAsync(Arg.Is(x => x.Id == entity.Id)); - } - - [Test] - public async Task DeleteAsync_EntityDoesNotExist_ReturnsNotFound() - { - // Arrange - int entityId = 500; - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetByIdAsync(entityId) - .ReturnsNull(); - - // Act - var result = await _service.DeleteAsync(TestContext, entityId, x => x.DeleteItems); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task DeleteAsync_InvalidTodoListId_ReturnsNotFound() - { - // Arrange - int entityId = 500; - var entity = TestModels.CreateTestTodoListDependantEntity("Wrong to-do list ID"); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetByIdAsync(entity.Id) - .Returns(entity); - WebApplicationFactory.PermissionsChecker - .CanTouchEntityAsync(TestContext, - Arg.Any(), Arg.Any>()) - .Returns(true); - - // Act - var result = await _service.DeleteAsync(TestContext, entityId, x => x.DeleteItems); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task DeleteAsync_UserHasNoPermission_ReturnsForbidden() - { - // Arrange - var entity = TestModels.CreateTestTodoListDependantEntity(TestContext.TodoListId); - WebApplicationFactory.TestTodoListDependantEntitiesRepository - .GetByIdAsync(entity.Id) - .Returns(entity); - WebApplicationFactory.PermissionsChecker - .CanTouchEntityAsync(TestContext, - Arg.Any(), Arg.Any>()) - .Returns(false); - - // Act - var result = await _service.DeleteAsync(TestContext, entity.Id, x => x.DeleteItems); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } + private readonly TodoListContext TestContext = new("ListId", "TestUserId"); + private ITodoListDependantEntitiesService _service; + + [SetUp] + public void SetUp() + { + _service = ServiceScope.ServiceProvider.GetService< + ITodoListDependantEntitiesService>()!; + } + + [Test] + public async Task GetPageAsync_ListExists_AppliesSpecification() + { + // Arrange + PaginationParameters parameters = new(2, 5); + ISpecification specification = + Substitute.For>(); + Page page = new([], parameters.Page, parameters.PageSize, 0); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(TestContext.TodoListId) + .Returns(true); + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(TestContext) + .Returns(true); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetPageAsync(Arg.Any(), + Arg.Any>()) + .Returns(page); + + // Act + var result = await _service.GetPageAsync(TestContext, specification, parameters); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Success)); + await WebApplicationFactory.TestTodoListDependantEntitiesRepository + .Received() + .GetPageAsync(parameters, Arg.Any>()); + } + + [Test] + public async Task GetPageAsync_ListDoesNotExist_ReturnsNotFoundStatus() + { + // Arrange + PaginationParameters parameters = new(2, 5); + ISpecification specification = + Substitute.For>(); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(TestContext.TodoListId) + .Returns(false); + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(TestContext) + .Returns(true); + + // Act + var result = await _service.GetPageAsync(TestContext, + specification, parameters); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task GetPageAsync_UserIsNotMember_ReturnsForbidden() + { + // Arrange + PaginationParameters parameters = new(2, 5); + ISpecification specification = + Substitute.For>(); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(TestContext.TodoListId) + .Returns(true); + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(TestContext) + .Returns(false); + + // Act + var result = await _service.GetPageAsync(TestContext, + specification, parameters); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } + + [Test] + public async Task GetByIdAsync_EntityExists_SucceedsAndReturnsCorrectEntity() + { + // Arrange + TestTodoListDependantEntity entity = TestModels.CreateTestTodoListDependantEntity( + TestContext.TodoListId); + entity.Id = 500; + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(TestContext) + .Returns(true); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetByIdAsync(entity.Id) + .Returns(entity); + + // Act + var result = await _service.GetByIdAsync(TestContext, entity.Id); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Success)); + // Assert that returned DTO matches + var expectedResult = entity.Adapt(); + Assert.That(result.Result, Is.EqualTo(expectedResult)); + } + + [Test] + public async Task GetByIdAsync_WrongTodoList_ReturnsNotFound() + { + // Arrange + TestTodoListDependantEntity entity = TestModels.CreateTestTodoListDependantEntity("WrongTodoListId"); + entity.Id = 500; + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(TestContext) + .Returns(true); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetByIdAsync(entity.Id) + .Returns(entity); + + // Act + var result = await _service.GetByIdAsync(TestContext, entity.Id); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task GetByIdAsync_EntityDoesNotExist_ReturnsNotFound() + { + // Arrange + int entityId = 500; + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(TestContext) + .Returns(true); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetByIdAsync(entityId) + .ReturnsNull(); + + // Act + var result = await _service.GetByIdAsync(TestContext, entityId); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task GetByIdAsync_UserIsNotMember_ReturnsForbidden() + { + // Arrange + TestTodoListDependantEntity entity = TestModels.CreateTestTodoListDependantEntity( + TestContext.TodoListId); + int entityId = 500; + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(TestContext) + .Returns(false); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetByIdAsync(entity.Id) + .Returns(entity); + + // Act + var result = await _service.GetByIdAsync(TestContext, entityId); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } + + [Test] + public async Task CreateAsync_ValidCall_AddsEntityToDb() + { + // Arrange + TestTodoListDependantCreateDto dto = new("Test"); + RolePermissions validPermissions = new(AddItems: true); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(x => ((Func)x[1])(validPermissions)); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(TestContext.TodoListId) + .Returns(true); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .AddAsync(Arg.Any()) + .Returns(Task.FromResult); + + // Act + var result = await _service.CreateAsync(TestContext, dto, x => x.AddItems); + + // Assert + using (Assert.EnterMultipleScope()) + { + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Success)); + Assert.That(result.Result, Is.Not.Null); + } + await WebApplicationFactory.TestTodoListDependantEntitiesRepository + .Received() + .AddAsync(Arg.Is(x => + x.TestProperty == dto.TestProperty && x.TodoListId == TestContext.TodoListId && + x.OwnerId == TestContext.CallerId)); + } + + [Test] + public async Task CreateAsync_TodoListDoesNotExist_ReturnsNotFound() + { + // Arrange + TestTodoListDependantCreateDto dto = new("Test"); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(true); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(TestContext.TodoListId) + .Returns(false); + + // Act: call the method + var result = await _service.CreateAsync(TestContext, dto, x => x.AddItems); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task CreateAsync_UserHasNoPermssion_ReturnsForbidden() + { + // Arrange + TestTodoListDependantCreateDto dto = new("Test"); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(false); + WebApplicationFactory.EntityExistenceChecker + .ExistsAsync(TestContext.TodoListId) + .Returns(true); + + // Act: call the method + var result = await _service.CreateAsync(TestContext, dto, x => x.AddItems); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } + + [Test] + public async Task UpdateAsync_EntityExists_Succeeds() + { + // Arrange + var entity = TestModels.CreateTestTodoListDependantEntity(TestContext.TodoListId); + TestTodoListDependantCreateDto updateDto = new("New test"); + RolePermissions validPermissions = new(EditItems: true); + WebApplicationFactory.PermissionsChecker + .CanTouchEntityAsync(TestContext, + Arg.Any(), Arg.Any>()) + .Returns(x => ((Func)x[2])(validPermissions)); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetByIdAsync(entity.Id) + .Returns(entity); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .UpdateAsync(Arg.Any()) + .Returns(Task.FromResult); + + // Act + var result = await _service.UpdateAsync(TestContext, entity.Id, updateDto, x => x.EditItems); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); + // Assert that update was called + await WebApplicationFactory.TestTodoListDependantEntitiesRepository + .Received() + .UpdateAsync(Arg.Is(x => + x.Id == entity.Id && x.TestProperty == updateDto.TestProperty)); + } + + [Test] + public async Task UpdateAsync_EntityDoesNotExist_ReturnsNotFound() + { + // Arrange + int entityId = 500; + TestTodoListDependantCreateDto updateDto = new("New test"); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetByIdAsync(entityId) + .ReturnsNull(); + + // Act + var result = await _service.UpdateAsync(TestContext, entityId, updateDto, x => x.EditItems); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task UpdateAsync_WrongTodoListId_ReturnsNotFound() + { + // Arrange + var entity = TestModels.CreateTestTodoListDependantEntity("Wrong to-do list ID"); + int entityId = 500; + TestTodoListDependantCreateDto updateDto = new("New test"); + WebApplicationFactory.PermissionsChecker + .CanTouchEntityAsync(TestContext, + Arg.Any(), Arg.Any>()) + .Returns(true); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetByIdAsync(entity.Id) + .Returns(entity); + + // Act + var result = await _service.UpdateAsync(TestContext, entityId, updateDto, x => x.EditItems); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task UpdateAsync_UserHasNoPermission_ReturnsForbidden() + { + // Arrange + TestTodoListDependantCreateDto updateDto = new("New test"); + var entity = TestModels.CreateTestTodoListDependantEntity(TestContext.TodoListId); + WebApplicationFactory.PermissionsChecker + .CanTouchEntityAsync(TestContext, + Arg.Any(), Arg.Any>()) + .Returns(false); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetByIdAsync(entity.Id) + .Returns(entity); + + // Act + var result = await _service.UpdateAsync(TestContext, entity.Id, updateDto, x => x.EditItems); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } + + [Test] + public async Task DeleteAsync_ValidCall_Succeeds() + { + // Arrange + var entity = TestModels.CreateTestTodoListDependantEntity(TestContext.TodoListId); + RolePermissions validPermissions = new(DeleteItems: true); + WebApplicationFactory.PermissionsChecker + .CanTouchEntityAsync(TestContext, + Arg.Any(), Arg.Any>()) + .Returns(x => ((Func)x[2])(validPermissions)); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetByIdAsync(entity.Id) + .Returns(entity); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .DeleteAsync(Arg.Any()) + .Returns(Task.FromResult); + + // Act + var result = await _service.DeleteAsync(TestContext, entity.Id, x => x.DeleteItems); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); + // Assert that delete was called + await WebApplicationFactory.TestTodoListDependantEntitiesRepository + .Received() + .DeleteAsync(Arg.Is(x => x.Id == entity.Id)); + } + + [Test] + public async Task DeleteAsync_EntityDoesNotExist_ReturnsNotFound() + { + // Arrange + int entityId = 500; + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetByIdAsync(entityId) + .ReturnsNull(); + + // Act + var result = await _service.DeleteAsync(TestContext, entityId, x => x.DeleteItems); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task DeleteAsync_InvalidTodoListId_ReturnsNotFound() + { + // Arrange + int entityId = 500; + var entity = TestModels.CreateTestTodoListDependantEntity("Wrong to-do list ID"); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetByIdAsync(entity.Id) + .Returns(entity); + WebApplicationFactory.PermissionsChecker + .CanTouchEntityAsync(TestContext, + Arg.Any(), Arg.Any>()) + .Returns(true); + + // Act + var result = await _service.DeleteAsync(TestContext, entityId, x => x.DeleteItems); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task DeleteAsync_UserHasNoPermission_ReturnsForbidden() + { + // Arrange + var entity = TestModels.CreateTestTodoListDependantEntity(TestContext.TodoListId); + WebApplicationFactory.TestTodoListDependantEntitiesRepository + .GetByIdAsync(entity.Id) + .Returns(entity); + WebApplicationFactory.PermissionsChecker + .CanTouchEntityAsync(TestContext, + Arg.Any(), Arg.Any>()) + .Returns(false); + + // Act + var result = await _service.DeleteAsync(TestContext, entity.Id, x => x.DeleteItems); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } } diff --git a/AdvancedTodoList.IntegrationTests/Services/TodoListMembersServiceTests.cs b/AdvancedTodoList.IntegrationTests/Services/TodoListMembersServiceTests.cs index 7daeef9..1dd9a5f 100644 --- a/AdvancedTodoList.IntegrationTests/Services/TodoListMembersServiceTests.cs +++ b/AdvancedTodoList.IntegrationTests/Services/TodoListMembersServiceTests.cs @@ -1,9 +1,10 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Models.TodoLists.Members; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.Core.Specifications.Todo; using AdvancedTodoList.IntegrationTests.Fixtures; using AdvancedTodoList.IntegrationTests.Utils; using NUnit.Framework.Internal; @@ -13,361 +14,361 @@ namespace AdvancedTodoList.IntegrationTests.Services; [TestFixture] public class TodoListMembersServiceTests : BusinessLogicFixture { - private ITodoListMembersService _service; - private readonly TodoListContext TestContext = new("TestTodoListId", "TestUserId"); - private const int DefaultPriority = 1; - - private void RoleExists(int roleId, string todoListId) - { - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(roleId) - .Returns(new TodoListRole() { Id = roleId, TodoListId = todoListId, Name = "Ok", Priority = DefaultPriority }); - } - - [SetUp] - public void SetUp() - { - _service = ServiceScope.ServiceProvider.GetService()!; - } - - [Test] - public async Task GetMembersAsync_ListExists_AppliesTodoListMembersSpecification() - { - // Arrange - PaginationParameters parameters = new(2, 5); - TodoListMembersFilter filter = new([], "Test"); - Page testPage = new([], 1, 1, 1); - WebApplicationFactory.TodoMembersHelperService - .GetPageAsync(TestContext, Arg.Any>(), Arg.Any()) - .Returns(new ServiceResponse>(ServiceResponseStatus.Success, testPage)); - - // Act - var result = await _service.GetMembersAsync(TestContext, parameters, filter); - - // Assert - Assert.That(result, Is.Not.Null); - await WebApplicationFactory.TodoMembersHelperService - .Received() - .GetPageAsync(TestContext, - Arg.Is(x => - x.TodoListId == TestContext.TodoListId && x.Filter.UserId == filter.UserId), - Arg.Any()); - } - - [Test] - public async Task AddMemberAsync_MemberDoesNotExist_IndicatesSuccess() - { - // Arrange - string todoListId = "Id"; - string userId = "UserId"; - TodoListMemberAddDto inputDto = new(userId); - TodoListMemberMinimalViewDto outputDto = new(500, userId, todoListId, null); - WebApplicationFactory.TodoListMembersRepository - .FindAsync(todoListId, userId) - .ReturnsNull(); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(true); - WebApplicationFactory.TodoMembersHelperService - .CreateAsync( - TestContext, inputDto, null) - .Returns(new ServiceResponse( - ServiceResponseStatus.Success, outputDto)); - - // Act - var result = await _service.AddMemberAsync(TestContext, inputDto); - - // Assert - Assert.Multiple(() => - { - Assert.That(result.Status, Is.EqualTo(TodoListMemberServiceResultStatus.Success)); - Assert.That(result.Dto, Is.EqualTo(outputDto)); - }); - // Assert that create async was called with valid permission accessor - // (should return true if AddMembers is true) - RolePermissions addMembers = new(AddMembers: true); - await WebApplicationFactory.TodoMembersHelperService - .Received() - .CreateAsync( - TestContext, inputDto, null); - await WebApplicationFactory.PermissionsChecker - .Received() - .HasPermissionAsync(TestContext, Arg.Is>(x => x(addMembers))); - } - - [Test] - public async Task AddMemberAsync_MemberExists_ReturnsUserAlreadyAddedStatus() - { - // Arrange - string userId = "UserId"; - TodoListMemberAddDto inputDto = new(userId); - WebApplicationFactory.TodoListMembersRepository - .FindAsync(TestContext.TodoListId, userId) - .Returns(new TodoListMember() - { - TodoListId = TestContext.TodoListId, - UserId = userId - }); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(true); - - // Act - var result = await _service.AddMemberAsync(TestContext, inputDto); - - // Assert - Assert.That(result.Status, Is.EqualTo(TodoListMemberServiceResultStatus.UserAlreadyAdded)); - } - - [Test] - public async Task AddMemberAsync_TodoListDoesNotExist_ReturnsNotFoundStatus() - { - // Arrange - string todoListId = "Id"; - string userId = "UserId"; - TodoListMemberAddDto inputDto = new(userId); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(true); - WebApplicationFactory.TodoListMembersRepository - .FindAsync(todoListId, userId) - .ReturnsNull(); - WebApplicationFactory.TodoMembersHelperService - .CreateAsync(TestContext, inputDto, null) - .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); - - // Act - var result = await _service.AddMemberAsync(TestContext, inputDto); - - // Assert - Assert.That(result.Status, Is.EqualTo(TodoListMemberServiceResultStatus.NotFound)); - } - - [Test] - public async Task AddMemberAsync_UserHasNoPermission_ReturnsForbiddenStatus() - { - // Arrange - string userId = "UserId"; - TodoListMemberAddDto inputDto = new(userId); - WebApplicationFactory.PermissionsChecker - .HasPermissionAsync(TestContext, Arg.Any>()) - .Returns(false); - - // Act - var result = await _service.AddMemberAsync(TestContext, inputDto); - - // Assert - Assert.That(result.Status, Is.EqualTo(TodoListMemberServiceResultStatus.Forbidden)); - } - - private ISpecification ValidSpecification => - Arg.Is(x => x.UserId == TestContext.CallerId && x.TodoListId == TestContext.TodoListId); - - private const int TestCallerPriority = 3; - private readonly TodoListMemberUpdateRoleDto TestUpdateRoleDto = new(321); - private readonly PermissionsAggregate ValidPermissions = new( - new(DefaultPriority, new RolePermissions(AssignRoles: true))); - - private static IEnumerable CallerHasNoAssignRolePermissionCases => - [ - null, - new PermissionsAggregate(null), - new PermissionsAggregate(new RoleEssentials(TestCallerPriority, new RolePermissions(AssignRoles: false))) - ]; - - [Test] - [TestCaseSource(nameof(CallerHasNoAssignRolePermissionCases))] - public async Task UpdateMemberRoleAsync_CallerHasNoAssignRolePermission_ReturnsForbidden(PermissionsAggregate? aggregate) - { - // Arrange - const int memberId = 123; - WebApplicationFactory.TodoListMembersRepository - .GetAggregateAsync(ValidSpecification) - .Returns(aggregate); - - // Act - var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); - - // Assert - Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.Forbidden)); - } - - [Test] - public async Task UpdateMemberRoleAsync_NewRoleDoesNotExist_ReturnsInvalidRoleId() - { - // Arrange - const int memberId = 123; - WebApplicationFactory.TodoListMembersRepository - .GetAggregateAsync(ValidSpecification) - .Returns(ValidPermissions); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) - .ReturnsNull(); - - // Act - var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); - - // Assert - Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.InvalidRoleId)); - } - - [Test] - public async Task UpdateMemberRoleAsync_NewRoleIsFromAnotherTodoList_ReturnsInvalidRoleId() - { - // Arrange - const int memberId = 123; - WebApplicationFactory.TodoListMembersRepository - .GetAggregateAsync(ValidSpecification) - .Returns(ValidPermissions); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) - .Returns(TestModels.CreateTestRole("Wrong to-do list ID")); - - // Act - var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); - - // Assert - Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.InvalidRoleId)); - } - - [Test] - public async Task UpdateMemberRoleAsync_CallerCannotAssignNewRole_ReturnsForbidden() - { - // Arrange - const int memberId = 123; - WebApplicationFactory.TodoListMembersRepository - .GetAggregateAsync(ValidSpecification) - .Returns(ValidPermissions); - var role = TestModels.CreateTestRole(TestContext.TodoListId); - role.Priority = 0; - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) - .Returns(role); - - // Act - var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); - - // Assert - Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.Forbidden)); - } - - [Test] - public async Task UpdateMemberRoleAsync_MemberDoesNotExist_ReturnsNotFound() - { - // Arrange - const int memberId = 123; - WebApplicationFactory.TodoListMembersRepository - .GetAggregateAsync(ValidSpecification) - .Returns(ValidPermissions); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) - .Returns(TestModels.CreateTestRole(TestContext.TodoListId)); - WebApplicationFactory.TodoListMembersRepository - .GetByIdAsync(memberId) - .ReturnsNull(); - - // Act - var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); - - // Assert - Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.NotFound)); - } - - [Test] - public async Task UpdateMemberRoleAsync_MemberIsFromAnotherTodoList_ReturnsNotFound() - { - // Arrange - const int memberId = 123; - WebApplicationFactory.TodoListMembersRepository - .GetAggregateAsync(ValidSpecification) - .Returns(ValidPermissions); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) - .Returns(TestModels.CreateTestRole(TestContext.TodoListId)); - WebApplicationFactory.TodoListMembersRepository - .GetByIdAsync(memberId) - .Returns(new TodoListMember() { TodoListId = "Wrong", UserId = "UserID" }); - - // Act - var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); - - // Assert - Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.NotFound)); - } - - [Test] - public async Task UpdateMemberRoleAsync_MemberHasHigherPriorityThanCaller_ReturnsForbidden() - { - // Arrange - const int memberId = 123; - const int memberRoleId = 777; - WebApplicationFactory.TodoListMembersRepository - .GetAggregateAsync(ValidSpecification) - .Returns(ValidPermissions); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) - .Returns(TestModels.CreateTestRole(TestContext.TodoListId)); - WebApplicationFactory.TodoListMembersRepository - .GetByIdAsync(memberId) - .Returns(new TodoListMember() { TodoListId = TestContext.TodoListId, UserId = "UserID", RoleId = memberRoleId }); - var memberRole = TestModels.CreateTestRole(TestContext.TodoListId); - memberRole.Priority = 0; - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(memberRoleId) - .Returns(memberRole); - - // Act - var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); - - // Assert - Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.Forbidden)); - } - - [Test] - public async Task UpdateMemberRoleAsync_AssignNullRole_Succeeds() - { - // Arrange - const int memberId = 123; - const int memberRoleId = 777; - TodoListMemberUpdateRoleDto dto = new(null); - WebApplicationFactory.TodoListMembersRepository - .GetAggregateAsync(ValidSpecification) - .Returns(ValidPermissions); - WebApplicationFactory.TodoListMembersRepository - .GetByIdAsync(memberId) - .Returns(new TodoListMember() { TodoListId = TestContext.TodoListId, UserId = "UserID", RoleId = memberRoleId }); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(memberRoleId) - .Returns(TestModels.CreateTestRole(TestContext.TodoListId)); - - // Act - var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, dto); - - // Assert - Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.Success)); - } - - [Test] - public async Task UpdateMemberRoleAsync_AssignToMemberWithNoRole_Succeeds() - { - // Arrange - const int memberId = 123; - WebApplicationFactory.TodoListMembersRepository - .GetAggregateAsync(ValidSpecification) - .Returns(ValidPermissions); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) - .Returns(TestModels.CreateTestRole(TestContext.TodoListId)); - WebApplicationFactory.TodoListMembersRepository - .GetByIdAsync(memberId) - .Returns(new TodoListMember() { TodoListId = TestContext.TodoListId, UserId = "UserID", RoleId = null }); - - // Act - var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); - - // Assert - Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.Success)); - } - - // Tests for other methods are useless, because they are just wrappers. + private ITodoListMembersService _service; + private readonly TodoListContext TestContext = new("TestTodoListId", "TestUserId"); + private const int DefaultPriority = 1; + + private void RoleExists(int roleId, string todoListId) + { + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(roleId) + .Returns(new TodoListRole() { Id = roleId, TodoListId = todoListId, Name = "Ok", Priority = DefaultPriority }); + } + + [SetUp] + public void SetUp() + { + _service = ServiceScope.ServiceProvider.GetService()!; + } + + [Test] + public async Task GetMembersAsync_ListExists_AppliesTodoListMembersSpecification() + { + // Arrange + PaginationParameters parameters = new(2, 5); + TodoListMembersFilter filter = new([], "Test"); + Page testPage = new([], 1, 1, 1); + WebApplicationFactory.TodoMembersHelperService + .GetPageAsync(TestContext, Arg.Any>(), Arg.Any()) + .Returns(new ServiceResponse>(ServiceResponseStatus.Success, testPage)); + + // Act + var result = await _service.GetMembersAsync(TestContext, parameters, filter); + + // Assert + Assert.That(result, Is.Not.Null); + await WebApplicationFactory.TodoMembersHelperService + .Received() + .GetPageAsync(TestContext, + Arg.Is(x => + x.TodoListId == TestContext.TodoListId && x.Filter.UserId == filter.UserId), + Arg.Any()); + } + + [Test] + public async Task AddMemberAsync_MemberDoesNotExist_IndicatesSuccess() + { + // Arrange + string todoListId = "Id"; + string userId = "UserId"; + TodoListMemberAddDto inputDto = new(userId); + TodoListMemberMinimalViewDto outputDto = new(500, userId, todoListId, null); + WebApplicationFactory.TodoListMembersRepository + .FindAsync(todoListId, userId) + .ReturnsNull(); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(true); + WebApplicationFactory.TodoMembersHelperService + .CreateAsync( + TestContext, inputDto, null) + .Returns(new ServiceResponse( + ServiceResponseStatus.Success, outputDto)); + + // Act + var result = await _service.AddMemberAsync(TestContext, inputDto); + + // Assert + using (Assert.EnterMultipleScope()) + { + Assert.That(result.Status, Is.EqualTo(TodoListMemberServiceResultStatus.Success)); + Assert.That(result.Dto, Is.EqualTo(outputDto)); + } + // Assert that create async was called with valid permission accessor + // (should return true if AddMembers is true) + RolePermissions addMembers = new(AddMembers: true); + await WebApplicationFactory.TodoMembersHelperService + .Received() + .CreateAsync( + TestContext, inputDto, null); + await WebApplicationFactory.PermissionsChecker + .Received() + .HasPermissionAsync(TestContext, Arg.Is>(x => x(addMembers))); + } + + [Test] + public async Task AddMemberAsync_MemberExists_ReturnsUserAlreadyAddedStatus() + { + // Arrange + string userId = "UserId"; + TodoListMemberAddDto inputDto = new(userId); + WebApplicationFactory.TodoListMembersRepository + .FindAsync(TestContext.TodoListId, userId) + .Returns(new TodoListMember() + { + TodoListId = TestContext.TodoListId, + UserId = userId + }); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(true); + + // Act + var result = await _service.AddMemberAsync(TestContext, inputDto); + + // Assert + Assert.That(result.Status, Is.EqualTo(TodoListMemberServiceResultStatus.UserAlreadyAdded)); + } + + [Test] + public async Task AddMemberAsync_TodoListDoesNotExist_ReturnsNotFoundStatus() + { + // Arrange + string todoListId = "Id"; + string userId = "UserId"; + TodoListMemberAddDto inputDto = new(userId); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(true); + WebApplicationFactory.TodoListMembersRepository + .FindAsync(todoListId, userId) + .ReturnsNull(); + WebApplicationFactory.TodoMembersHelperService + .CreateAsync(TestContext, inputDto, null) + .Returns(new ServiceResponse(ServiceResponseStatus.NotFound)); + + // Act + var result = await _service.AddMemberAsync(TestContext, inputDto); + + // Assert + Assert.That(result.Status, Is.EqualTo(TodoListMemberServiceResultStatus.NotFound)); + } + + [Test] + public async Task AddMemberAsync_UserHasNoPermission_ReturnsForbiddenStatus() + { + // Arrange + string userId = "UserId"; + TodoListMemberAddDto inputDto = new(userId); + WebApplicationFactory.PermissionsChecker + .HasPermissionAsync(TestContext, Arg.Any>()) + .Returns(false); + + // Act + var result = await _service.AddMemberAsync(TestContext, inputDto); + + // Assert + Assert.That(result.Status, Is.EqualTo(TodoListMemberServiceResultStatus.Forbidden)); + } + + private ISpecification ValidSpecification => + Arg.Is(x => x.UserId == TestContext.CallerId && x.TodoListId == TestContext.TodoListId); + + private const int TestCallerPriority = 3; + private readonly TodoListMemberUpdateRoleDto TestUpdateRoleDto = new(321); + private readonly PermissionsAggregate ValidPermissions = new( + new(DefaultPriority, new RolePermissions(AssignRoles: true))); + + private static IEnumerable CallerHasNoAssignRolePermissionCases => + [ + null, + new PermissionsAggregate(null), + new PermissionsAggregate(new RoleEssentials(TestCallerPriority, new RolePermissions(AssignRoles: false))) + ]; + + [Test] + [TestCaseSource(nameof(CallerHasNoAssignRolePermissionCases))] + public async Task UpdateMemberRoleAsync_CallerHasNoAssignRolePermission_ReturnsForbidden(PermissionsAggregate? aggregate) + { + // Arrange + const int memberId = 123; + WebApplicationFactory.TodoListMembersRepository + .GetAggregateAsync(ValidSpecification) + .Returns(aggregate); + + // Act + var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); + + // Assert + Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.Forbidden)); + } + + [Test] + public async Task UpdateMemberRoleAsync_NewRoleDoesNotExist_ReturnsInvalidRoleId() + { + // Arrange + const int memberId = 123; + WebApplicationFactory.TodoListMembersRepository + .GetAggregateAsync(ValidSpecification) + .Returns(ValidPermissions); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) + .ReturnsNull(); + + // Act + var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); + + // Assert + Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.InvalidRoleId)); + } + + [Test] + public async Task UpdateMemberRoleAsync_NewRoleIsFromAnotherTodoList_ReturnsInvalidRoleId() + { + // Arrange + const int memberId = 123; + WebApplicationFactory.TodoListMembersRepository + .GetAggregateAsync(ValidSpecification) + .Returns(ValidPermissions); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) + .Returns(TestModels.CreateTestRole("Wrong to-do list ID")); + + // Act + var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); + + // Assert + Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.InvalidRoleId)); + } + + [Test] + public async Task UpdateMemberRoleAsync_CallerCannotAssignNewRole_ReturnsForbidden() + { + // Arrange + const int memberId = 123; + WebApplicationFactory.TodoListMembersRepository + .GetAggregateAsync(ValidSpecification) + .Returns(ValidPermissions); + var role = TestModels.CreateTestRole(TestContext.TodoListId); + role.Priority = 0; + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) + .Returns(role); + + // Act + var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); + + // Assert + Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.Forbidden)); + } + + [Test] + public async Task UpdateMemberRoleAsync_MemberDoesNotExist_ReturnsNotFound() + { + // Arrange + const int memberId = 123; + WebApplicationFactory.TodoListMembersRepository + .GetAggregateAsync(ValidSpecification) + .Returns(ValidPermissions); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) + .Returns(TestModels.CreateTestRole(TestContext.TodoListId)); + WebApplicationFactory.TodoListMembersRepository + .GetByIdAsync(memberId) + .ReturnsNull(); + + // Act + var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); + + // Assert + Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.NotFound)); + } + + [Test] + public async Task UpdateMemberRoleAsync_MemberIsFromAnotherTodoList_ReturnsNotFound() + { + // Arrange + const int memberId = 123; + WebApplicationFactory.TodoListMembersRepository + .GetAggregateAsync(ValidSpecification) + .Returns(ValidPermissions); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) + .Returns(TestModels.CreateTestRole(TestContext.TodoListId)); + WebApplicationFactory.TodoListMembersRepository + .GetByIdAsync(memberId) + .Returns(new TodoListMember() { TodoListId = "Wrong", UserId = "UserID" }); + + // Act + var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); + + // Assert + Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.NotFound)); + } + + [Test] + public async Task UpdateMemberRoleAsync_MemberHasHigherPriorityThanCaller_ReturnsForbidden() + { + // Arrange + const int memberId = 123; + const int memberRoleId = 777; + WebApplicationFactory.TodoListMembersRepository + .GetAggregateAsync(ValidSpecification) + .Returns(ValidPermissions); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) + .Returns(TestModels.CreateTestRole(TestContext.TodoListId)); + WebApplicationFactory.TodoListMembersRepository + .GetByIdAsync(memberId) + .Returns(new TodoListMember() { TodoListId = TestContext.TodoListId, UserId = "UserID", RoleId = memberRoleId }); + var memberRole = TestModels.CreateTestRole(TestContext.TodoListId); + memberRole.Priority = 0; + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(memberRoleId) + .Returns(memberRole); + + // Act + var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); + + // Assert + Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.Forbidden)); + } + + [Test] + public async Task UpdateMemberRoleAsync_AssignNullRole_Succeeds() + { + // Arrange + const int memberId = 123; + const int memberRoleId = 777; + TodoListMemberUpdateRoleDto dto = new(null); + WebApplicationFactory.TodoListMembersRepository + .GetAggregateAsync(ValidSpecification) + .Returns(ValidPermissions); + WebApplicationFactory.TodoListMembersRepository + .GetByIdAsync(memberId) + .Returns(new TodoListMember() { TodoListId = TestContext.TodoListId, UserId = "UserID", RoleId = memberRoleId }); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(memberRoleId) + .Returns(TestModels.CreateTestRole(TestContext.TodoListId)); + + // Act + var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, dto); + + // Assert + Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.Success)); + } + + [Test] + public async Task UpdateMemberRoleAsync_AssignToMemberWithNoRole_Succeeds() + { + // Arrange + const int memberId = 123; + WebApplicationFactory.TodoListMembersRepository + .GetAggregateAsync(ValidSpecification) + .Returns(ValidPermissions); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(TestUpdateRoleDto.RoleId!.Value) + .Returns(TestModels.CreateTestRole(TestContext.TodoListId)); + WebApplicationFactory.TodoListMembersRepository + .GetByIdAsync(memberId) + .Returns(new TodoListMember() { TodoListId = TestContext.TodoListId, UserId = "UserID", RoleId = null }); + + // Act + var response = await _service.UpdateMemberRoleAsync(TestContext, memberId, TestUpdateRoleDto); + + // Assert + Assert.That(response, Is.EqualTo(TodoListMemberServiceResultStatus.Success)); + } + + // Tests for other methods are useless, because they are just wrappers. } diff --git a/AdvancedTodoList.IntegrationTests/Services/TodoListRolesServiceTests.cs b/AdvancedTodoList.IntegrationTests/Services/TodoListRolesServiceTests.cs index 03b3ab5..94b83f3 100644 --- a/AdvancedTodoList.IntegrationTests/Services/TodoListRolesServiceTests.cs +++ b/AdvancedTodoList.IntegrationTests/Services/TodoListRolesServiceTests.cs @@ -1,9 +1,9 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Models.TodoLists.Members; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications.Todo; using AdvancedTodoList.IntegrationTests.Fixtures; using AdvancedTodoList.IntegrationTests.Utils; @@ -12,240 +12,240 @@ namespace AdvancedTodoList.IntegrationTests.Services; [TestFixture] public class TodoListRolesServiceTests : BusinessLogicFixture { - private ITodoListRolesService _service; - private readonly TodoListContext TestContext = new("TodoListId", "CallerId"); - private readonly TodoListRoleCreateDto TestInputDto = new("A role", 3, RolePermissions.All); - - [SetUp] - public void SetUp() - { - _service = ServiceScope.ServiceProvider.GetService()!; - } - - [Test] - public async Task GetRolesOfListAsync_ListExists_AppliesTodoItemsSpecification() - { - // Arrange - PaginationParameters parameters = new(2, 5); - Page page = new([], 1, 1, 1); - const string name = "Role #1"; - WebApplicationFactory.TodoRolesHelperService - .GetPageAsync(TestContext, Arg.Any>(), Arg.Any()) - .Returns(new ServiceResponse>(ServiceResponseStatus.Success, page)); - - // Act - var result = await _service.GetRolesOfListAsync(TestContext, parameters, name); - - // Assert - Assert.That(result, Is.Not.Null); - await WebApplicationFactory.TodoRolesHelperService - .Received() - .GetPageAsync(TestContext, - Arg.Is>( - x => x.TodoListId == TestContext.TodoListId && x.Name == name), - Arg.Any()); - } - - [Test] - public async Task CreateAsync_UserHasNoPermission_ReturnsForbidden() - { - // Arrange - WebApplicationFactory.PermissionsChecker - .HasPermissionOverRoleAsync(TestContext, TestInputDto.Priority, Arg.Any>()) - .Returns(false); - - // Act - var response = await _service.CreateAsync(TestContext, TestInputDto); - - // Arrange - Assert.That(response.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } - - [Test] - public async Task CreateAsync_UserHasPermission_CallsHelperService() - { - // Arrange - RolePermissions validPermissions = new(EditRoles: true); - WebApplicationFactory.PermissionsChecker - .HasPermissionOverRoleAsync(TestContext, TestInputDto.Priority, Arg.Any>()) - .Returns(x => ((Func)x[2])(validPermissions)); - WebApplicationFactory.TodoRolesHelperService - .CreateAsync( - Arg.Any(), Arg.Any(), null) - .Returns(new ServiceResponse(ServiceResponseStatus.Success)); - - // Act - var response = await _service.CreateAsync(TestContext, TestInputDto); - - // Arrange - await WebApplicationFactory.TodoRolesHelperService - .Received(1) - .CreateAsync(TestContext, TestInputDto, null); - } - - [Test] - public async Task EditAsync_EntityExists_Succeeds() - { - // Arrange - var role = TestModels.CreateTestRole(TestContext.TodoListId); - RolePermissions validPermissions = new(EditRoles: true); - WebApplicationFactory.PermissionsChecker - .HasPermissionOverRoleAsync(TestContext, Math.Min(TestInputDto.Priority, role.Priority), Arg.Any>()) - .Returns(x => ((Func)x[2])(validPermissions)); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(role.Id) - .Returns(role); - WebApplicationFactory.TodoListRolesRepository - .UpdateAsync(Arg.Any()) - .Returns(Task.FromResult); - - // Act - var result = await _service.EditAsync(TestContext, role.Id, TestInputDto); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); - // Assert that update was called - await WebApplicationFactory.TodoListRolesRepository - .Received() - .UpdateAsync(Arg.Is(x => - x.Id == role.Id && x.Permissions == TestInputDto.Permissions && x.TodoListId == TestContext.TodoListId)); - } - - [Test] - public async Task UpdateAsync_EntityDoesNotExist_ReturnsNotFound() - { - // Arrange - int roleId = 500; - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(roleId) - .ReturnsNull(); - - // Act - var result = await _service.EditAsync(TestContext, roleId, TestInputDto); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task UpdateAsync_WrongTodoListId_ReturnsNotFound() - { - // Arrange - var role = TestModels.CreateTestRole("Wrong to-do list ID"); - int roleId = 500; - WebApplicationFactory.PermissionsChecker - .CanTouchEntityAsync(TestContext, - Arg.Any(), Arg.Any>()) - .Returns(true); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(role.Id) - .Returns(role); - - // Act - var result = await _service.EditAsync(TestContext, roleId, TestInputDto); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task UpdateAsync_UserHasNoPermission_ReturnsForbidden() - { - // Arrange - var role = TestModels.CreateTestRole(TestContext.TodoListId); - WebApplicationFactory.PermissionsChecker - .HasPermissionOverRoleAsync(TestContext, Arg.Any(), Arg.Any>()) - .Returns(false); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(role.Id) - .Returns(role); - - // Act - var result = await _service.EditAsync(TestContext, role.Id, TestInputDto); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } - - [Test] - public async Task DeleteAsync_ValidCall_Succeeds() - { - // Arrange - var role = TestModels.CreateTestRole(TestContext.TodoListId); - RolePermissions validPermissions = new(EditRoles: true); - WebApplicationFactory.PermissionsChecker - .HasPermissionOverRoleAsync(TestContext, role.Priority, Arg.Any>()) - .Returns(x => ((Func)x[2])(validPermissions)); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(role.Id) - .Returns(role); - WebApplicationFactory.TodoListRolesRepository - .DeleteAsync(Arg.Any()) - .Returns(Task.FromResult); - - // Act - var result = await _service.DeleteAsync(TestContext, role.Id); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); - // Assert that delete was called - await WebApplicationFactory.TodoListRolesRepository - .Received() - .DeleteAsync(Arg.Is(x => x.Id == role.Id)); - } - - [Test] - public async Task DeleteAsync_EntityDoesNotExist_ReturnsNotFound() - { - // Arrange - int roleId = 500; - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(roleId) - .ReturnsNull(); - - // Act - var result = await _service.DeleteAsync(TestContext, roleId); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task DeleteAsync_InvalidTodoListId_ReturnsNotFound() - { - // Arrange - int roleId = 500; - var role = TestModels.CreateTestRole("Wrong to-do list ID"); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(role.Id) - .Returns(role); - WebApplicationFactory.PermissionsChecker - .HasPermissionOverRoleAsync(TestContext, Math.Min(TestInputDto.Priority, role.Priority), Arg.Any>()) - .Returns(true); - - // Act - var result = await _service.DeleteAsync(TestContext, roleId); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task DeleteAsync_UserHasNoPermission_ReturnsForbidden() - { - // Arrange - var role = TestModels.CreateTestRole(TestContext.TodoListId); - WebApplicationFactory.TodoListRolesRepository - .GetByIdAsync(role.Id) - .Returns(role); - WebApplicationFactory.PermissionsChecker - .HasPermissionOverRoleAsync(TestContext, Math.Min(TestInputDto.Priority, role.Priority), Arg.Any>()) - .Returns(false); - - // Act - var result = await _service.DeleteAsync(TestContext, role.Id); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } + private ITodoListRolesService _service; + private readonly TodoListContext TestContext = new("TodoListId", "CallerId"); + private readonly TodoListRoleCreateDto TestInputDto = new("A role", 3, RolePermissions.All); + + [SetUp] + public void SetUp() + { + _service = ServiceScope.ServiceProvider.GetService()!; + } + + [Test] + public async Task GetRolesOfListAsync_ListExists_AppliesTodoItemsSpecification() + { + // Arrange + PaginationParameters parameters = new(2, 5); + Page page = new([], 1, 1, 1); + const string name = "Role #1"; + WebApplicationFactory.TodoRolesHelperService + .GetPageAsync(TestContext, Arg.Any>(), Arg.Any()) + .Returns(new ServiceResponse>(ServiceResponseStatus.Success, page)); + + // Act + var result = await _service.GetRolesOfListAsync(TestContext, parameters, name); + + // Assert + Assert.That(result, Is.Not.Null); + await WebApplicationFactory.TodoRolesHelperService + .Received() + .GetPageAsync(TestContext, + Arg.Is>( + x => x.TodoListId == TestContext.TodoListId && x.Name == name), + Arg.Any()); + } + + [Test] + public async Task CreateAsync_UserHasNoPermission_ReturnsForbidden() + { + // Arrange + WebApplicationFactory.PermissionsChecker + .HasPermissionOverRoleAsync(TestContext, TestInputDto.Priority, Arg.Any>()) + .Returns(false); + + // Act + var response = await _service.CreateAsync(TestContext, TestInputDto); + + // Arrange + Assert.That(response.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } + + [Test] + public async Task CreateAsync_UserHasPermission_CallsHelperService() + { + // Arrange + RolePermissions validPermissions = new(EditRoles: true); + WebApplicationFactory.PermissionsChecker + .HasPermissionOverRoleAsync(TestContext, TestInputDto.Priority, Arg.Any>()) + .Returns(x => ((Func)x[2])(validPermissions)); + WebApplicationFactory.TodoRolesHelperService + .CreateAsync( + Arg.Any(), Arg.Any(), null) + .Returns(new ServiceResponse(ServiceResponseStatus.Success)); + + // Act + var response = await _service.CreateAsync(TestContext, TestInputDto); + + // Arrange + await WebApplicationFactory.TodoRolesHelperService + .Received(1) + .CreateAsync(TestContext, TestInputDto, null); + } + + [Test] + public async Task EditAsync_EntityExists_Succeeds() + { + // Arrange + var role = TestModels.CreateTestRole(TestContext.TodoListId); + RolePermissions validPermissions = new(EditRoles: true); + WebApplicationFactory.PermissionsChecker + .HasPermissionOverRoleAsync(TestContext, Math.Min(TestInputDto.Priority, role.Priority), Arg.Any>()) + .Returns(x => ((Func)x[2])(validPermissions)); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(role.Id) + .Returns(role); + WebApplicationFactory.TodoListRolesRepository + .UpdateAsync(Arg.Any()) + .Returns(Task.FromResult); + + // Act + var result = await _service.EditAsync(TestContext, role.Id, TestInputDto); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); + // Assert that update was called + await WebApplicationFactory.TodoListRolesRepository + .Received() + .UpdateAsync(Arg.Is(x => + x.Id == role.Id && x.Permissions == TestInputDto.Permissions && x.TodoListId == TestContext.TodoListId)); + } + + [Test] + public async Task UpdateAsync_EntityDoesNotExist_ReturnsNotFound() + { + // Arrange + int roleId = 500; + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(roleId) + .ReturnsNull(); + + // Act + var result = await _service.EditAsync(TestContext, roleId, TestInputDto); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task UpdateAsync_WrongTodoListId_ReturnsNotFound() + { + // Arrange + var role = TestModels.CreateTestRole("Wrong to-do list ID"); + int roleId = 500; + WebApplicationFactory.PermissionsChecker + .CanTouchEntityAsync(TestContext, + Arg.Any(), Arg.Any>()) + .Returns(true); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(role.Id) + .Returns(role); + + // Act + var result = await _service.EditAsync(TestContext, roleId, TestInputDto); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task UpdateAsync_UserHasNoPermission_ReturnsForbidden() + { + // Arrange + var role = TestModels.CreateTestRole(TestContext.TodoListId); + WebApplicationFactory.PermissionsChecker + .HasPermissionOverRoleAsync(TestContext, Arg.Any(), Arg.Any>()) + .Returns(false); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(role.Id) + .Returns(role); + + // Act + var result = await _service.EditAsync(TestContext, role.Id, TestInputDto); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } + + [Test] + public async Task DeleteAsync_ValidCall_Succeeds() + { + // Arrange + var role = TestModels.CreateTestRole(TestContext.TodoListId); + RolePermissions validPermissions = new(EditRoles: true); + WebApplicationFactory.PermissionsChecker + .HasPermissionOverRoleAsync(TestContext, role.Priority, Arg.Any>()) + .Returns(x => ((Func)x[2])(validPermissions)); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(role.Id) + .Returns(role); + WebApplicationFactory.TodoListRolesRepository + .DeleteAsync(Arg.Any()) + .Returns(Task.FromResult); + + // Act + var result = await _service.DeleteAsync(TestContext, role.Id); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); + // Assert that delete was called + await WebApplicationFactory.TodoListRolesRepository + .Received() + .DeleteAsync(Arg.Is(x => x.Id == role.Id)); + } + + [Test] + public async Task DeleteAsync_EntityDoesNotExist_ReturnsNotFound() + { + // Arrange + int roleId = 500; + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(roleId) + .ReturnsNull(); + + // Act + var result = await _service.DeleteAsync(TestContext, roleId); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task DeleteAsync_InvalidTodoListId_ReturnsNotFound() + { + // Arrange + int roleId = 500; + var role = TestModels.CreateTestRole("Wrong to-do list ID"); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(role.Id) + .Returns(role); + WebApplicationFactory.PermissionsChecker + .HasPermissionOverRoleAsync(TestContext, Math.Min(TestInputDto.Priority, role.Priority), Arg.Any>()) + .Returns(true); + + // Act + var result = await _service.DeleteAsync(TestContext, roleId); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task DeleteAsync_UserHasNoPermission_ReturnsForbidden() + { + // Arrange + var role = TestModels.CreateTestRole(TestContext.TodoListId); + WebApplicationFactory.TodoListRolesRepository + .GetByIdAsync(role.Id) + .Returns(role); + WebApplicationFactory.PermissionsChecker + .HasPermissionOverRoleAsync(TestContext, Math.Min(TestInputDto.Priority, role.Priority), Arg.Any>()) + .Returns(false); + + // Act + var result = await _service.DeleteAsync(TestContext, role.Id); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } } diff --git a/AdvancedTodoList.IntegrationTests/Services/TodoListsServiceTests.cs b/AdvancedTodoList.IntegrationTests/Services/TodoListsServiceTests.cs index 5f68ba0..1f13d43 100644 --- a/AdvancedTodoList.IntegrationTests/Services/TodoListsServiceTests.cs +++ b/AdvancedTodoList.IntegrationTests/Services/TodoListsServiceTests.cs @@ -1,10 +1,11 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Models.TodoLists; using AdvancedTodoList.Core.Models.TodoLists.Members; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.Core.Specifications.Todo; using AdvancedTodoList.IntegrationTests.Fixtures; using AdvancedTodoList.IntegrationTests.Utils; using NSubstitute.ExceptionExtensions; @@ -14,298 +15,298 @@ namespace AdvancedTodoList.IntegrationTests.Services; [TestFixture] public class TodoListsServiceTests : BusinessLogicFixture { - private ITodoListsService _service; - private const string TestUserId = "UserId"; - - [SetUp] - public void SetUp() - { - _service = ServiceScope.ServiceProvider.GetService()!; - } - - [Test] - public async Task GetListsOfUserAsync_AppliesTodoListsSpecification() - { - // Arrange - PaginationParameters parameters = new(2, 5); - Page page = new([], 1, 1, 1); - TodoListsFilter filter = new(Name: "Name"); - const string userId = "abcd"; - WebApplicationFactory.TodoListsRepository - .GetPageAsync(Arg.Any(), Arg.Any>()) - .Returns(page); - - // Act - var result = await _service.GetListsOfUserAsync(userId, parameters, filter); - - // Assert - Assert.That(result, Is.Not.Null); - await WebApplicationFactory.TodoListsRepository - .Received() - .GetPageAsync(parameters, Arg.Is( - x => x.Filter == filter && x.UserId == userId)); - } - - [Test] - public async Task GetByIdAsync_EntityExists_AppliesTodoListAggregateSpecification() - { - // Arrange - string todoListId = "TodoListId"; - TodoListGetByIdDto dto = new("Id", "name", "", new("Id", "User")); - TodoListContext context = new(todoListId, TestUserId); - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(new(todoListId, TestUserId)) - .Returns(true); - WebApplicationFactory.TodoListsRepository - .GetAggregateAsync(Arg.Any>()) - .Returns(dto); - - // Act - var result = await _service.GetByIdAsync(context); - - // Assert that valid specification was applied - await WebApplicationFactory.TodoListsRepository - .Received() - .GetAggregateAsync( - Arg.Is(x => x.Id == todoListId)); - } - - [Test] - public async Task GetByIdAsync_UserHasNoPermission_ReturnsForbidden() - { - // Arrange - string todoListId = "ID"; - TodoListGetByIdDto dto = new("Id", "name", "", new("Id", "User")); - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(new(todoListId, TestUserId)) - .Returns(false); - WebApplicationFactory.TodoListsRepository - .GetAggregateAsync(Arg.Any>()) - .Returns(dto); - TodoListContext context = new(todoListId, TestUserId); - - // Act - var result = await _service.GetByIdAsync(context); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } - - [Test] - public async Task GetByIdAsync_EntityDoesNotExist_ReturnsNotFound() - { - // Arrange - string todoListId = "ID"; - TodoListContext context = new(todoListId, TestUserId); - WebApplicationFactory.PermissionsChecker - .IsMemberOfListAsync(new(todoListId, TestUserId)) - .Returns(true); - WebApplicationFactory.TodoListsRepository - .GetAggregateAsync(Arg.Any>()) - .ReturnsNull(); - - // Act - var result = await _service.GetByIdAsync(context); - - // Assert - Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task CreateAsync_AddsEntityToDb() - { - // Arrange - string callerId = "CallerId"; - string listId = "ListId"; - int ownerRoleId = 777; - TodoListCreateDto dto = new("Test entity", "..."); - WebApplicationFactory.TodoListsRepository - .AddAsync(Arg.Any()) - .Returns(Task.FromResult) - .AndDoes(x => ((TodoList)x[0]).Id = listId); - WebApplicationFactory.TodoListRolesRepository - .AddAsync(Arg.Any()) - .Returns(Task.FromResult) - .AndDoes(x => ((TodoListRole)x[0]).Id = ownerRoleId); - WebApplicationFactory.TodoListMembersRepository - .AddAsync(Arg.Any()) - .Returns(Task.FromResult); - - // Act - var result = await _service.CreateAsync(dto, callerId); - - // Assert that list was created - await WebApplicationFactory.TodoListsRepository - .Received() - .AddAsync(Arg.Is(x => x.Name == dto.Name && - x.Description == dto.Description && x.OwnerId == callerId)); - // Assert that "Owner" role was created - await WebApplicationFactory.TodoListRolesRepository - .Received() - .AddAsync(Arg.Is(x => x.Priority == 0 && - x.TodoListId == listId && x.Permissions == RolePermissions.All)); - // Assert that the caller was assigned to the "Owner" role - await WebApplicationFactory.TodoListMembersRepository - .Received() - .AddAsync(Arg.Is(x => x.TodoListId == listId && - x.UserId == callerId && x.RoleId == ownerRoleId)); - // Assert that changes were commited - await WebApplicationFactory.UnitOfWork - .Received() - .CommitAsync(); - } - - [Test] - public async Task CreateAsync_OnException_RethrowsAndRollbacksChanges() - { - // Arrange - string callerId = "CallerId"; - TodoListCreateDto dto = new("Test entity", "..."); - WebApplicationFactory.TodoListsRepository - .AddAsync(Arg.Any()) - .Throws(); - - // Act/Assert - Assert.ThrowsAsync(() => _service.CreateAsync(dto, callerId)); - - // Assert - await WebApplicationFactory.UnitOfWork - .Received() - .RollbackAsync(); - - } - - [Test] - public async Task EditAsync_EntityExists_Succeeds() - { - // Arrange - var todoList = TestModels.CreateTestTodoList(); - RolePermissions validPermissions = new(EditItems: true); - TodoListCreateDto updateDto = new("NewName", "NewDescription"); - TodoListContext context = new(todoList.Id, TestUserId); - WebApplicationFactory.PermissionsChecker - .CanTouchEntityAsync(new(todoList.Id, TestUserId), - Arg.Any(), Arg.Any>()) - .Returns(x => ((Func)x[2])(validPermissions)); - WebApplicationFactory.TodoListsRepository - .GetByIdAsync(todoList.Id) - .Returns(todoList); - WebApplicationFactory.TodoListsRepository - .UpdateAsync(Arg.Any()) - .Returns(Task.FromResult); - - // Act - var result = await _service.EditAsync(context, updateDto); - - // Assert that result indicates success - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); - // Assert that update was called - await WebApplicationFactory.TodoListsRepository - .Received() - .UpdateAsync(Arg.Is(x => x.Id == todoList.Id && - x.Name == updateDto.Name && - x.Description == updateDto.Description)); - } - - [Test] - public async Task EditAsync_UserHasNoPermission_ReturnsForbidden() - { - // Arrange - var todoList = TestModels.CreateTestTodoList(); - TodoListCreateDto updateDto = new("Name", "Description"); - TodoListContext context = new(todoList.Id, TestUserId); - WebApplicationFactory.TodoListsRepository - .GetByIdAsync(todoList.Id) - .Returns(todoList); - WebApplicationFactory.PermissionsChecker - .CanTouchEntityAsync(new(todoList.Id, TestUserId), - Arg.Any(), Arg.Any>()) - .Returns(false); - - // Act - var result = await _service.EditAsync(context, updateDto); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); - } - - [Test] - public async Task EditAsync_EntityDoesNotExist_ReturnsNotFound() - { - // Arrange - string todoListId = "ID"; - TodoListCreateDto updateDto = new("Name", "Description"); - TodoListContext context = new(todoListId, TestUserId); - WebApplicationFactory.TodoListsRepository - .GetByIdAsync(todoListId) - .ReturnsNull(); - - // Act - var result = await _service.EditAsync(context, updateDto); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task DeleteAsync_UserIsOwner_Succeeds() - { - // Arrange - var todoList = TestModels.CreateTestTodoList(TestUserId); - TodoListContext context = new(todoList.Id, TestUserId); - WebApplicationFactory.TodoListsRepository - .GetByIdAsync(todoList.Id) - .Returns(todoList); - WebApplicationFactory.TodoListsRepository - .DeleteAsync(Arg.Any()) - .Returns(Task.FromResult); - - // Act - var result = await _service.DeleteAsync(context); - - // Assert that result indicates success - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); - // Assert that delete was called - await WebApplicationFactory.TodoListsRepository - .Received() - .DeleteAsync(Arg.Is(x => x.Id == todoList.Id)); - } - - [Test] - public async Task DeleteAsync_EntityDoesNotExist_ReturnsNotFound() - { - // Arrange - string todoListId = "ID"; - TodoListContext context = new(todoListId, TestUserId); - WebApplicationFactory.TodoListsRepository - .GetByIdAsync(todoListId) - .ReturnsNull(); - - // Act - var result = await _service.DeleteAsync(context); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); - } - - [Test] - public async Task DeleteAsync_UserIsNotOwner_ReturnsForbidden() - { - // Arrange - var todoList = TestModels.CreateTestTodoList("invalid user ID"); - TodoListContext context = new(todoList.Id, TestUserId); - WebApplicationFactory.TodoListsRepository - .GetByIdAsync(todoList.Id) - .Returns(todoList); - - // Act - var result = await _service.DeleteAsync(context); - - // Assert - Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); - // Assert that delete was not called - await WebApplicationFactory.TodoListsRepository - .DidNotReceive() - .DeleteAsync(Arg.Any()); - } + private ITodoListsService _service; + private const string TestUserId = "UserId"; + + [SetUp] + public void SetUp() + { + _service = ServiceScope.ServiceProvider.GetService()!; + } + + [Test] + public async Task GetListsOfUserAsync_AppliesTodoListsSpecification() + { + // Arrange + PaginationParameters parameters = new(2, 5); + Page page = new([], 1, 1, 1); + TodoListsFilter filter = new(Name: "Name"); + const string userId = "abcd"; + WebApplicationFactory.TodoListsRepository + .GetPageAsync(Arg.Any(), Arg.Any>()) + .Returns(page); + + // Act + var result = await _service.GetListsOfUserAsync(userId, parameters, filter); + + // Assert + Assert.That(result, Is.Not.Null); + await WebApplicationFactory.TodoListsRepository + .Received() + .GetPageAsync(parameters, Arg.Is( + x => x.Filter == filter && x.UserId == userId)); + } + + [Test] + public async Task GetByIdAsync_EntityExists_AppliesTodoListAggregateSpecification() + { + // Arrange + string todoListId = "TodoListId"; + TodoListGetByIdDto dto = new("Id", "name", "", new("Id", "User")); + TodoListContext context = new(todoListId, TestUserId); + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(new(todoListId, TestUserId)) + .Returns(true); + WebApplicationFactory.TodoListsRepository + .GetAggregateAsync(Arg.Any>()) + .Returns(dto); + + // Act + var result = await _service.GetByIdAsync(context); + + // Assert that valid specification was applied + await WebApplicationFactory.TodoListsRepository + .Received() + .GetAggregateAsync( + Arg.Is(x => x.Id == todoListId)); + } + + [Test] + public async Task GetByIdAsync_UserHasNoPermission_ReturnsForbidden() + { + // Arrange + string todoListId = "ID"; + TodoListGetByIdDto dto = new("Id", "name", "", new("Id", "User")); + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(new(todoListId, TestUserId)) + .Returns(false); + WebApplicationFactory.TodoListsRepository + .GetAggregateAsync(Arg.Any>()) + .Returns(dto); + TodoListContext context = new(todoListId, TestUserId); + + // Act + var result = await _service.GetByIdAsync(context); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } + + [Test] + public async Task GetByIdAsync_EntityDoesNotExist_ReturnsNotFound() + { + // Arrange + string todoListId = "ID"; + TodoListContext context = new(todoListId, TestUserId); + WebApplicationFactory.PermissionsChecker + .IsMemberOfListAsync(new(todoListId, TestUserId)) + .Returns(true); + WebApplicationFactory.TodoListsRepository + .GetAggregateAsync(Arg.Any>()) + .ReturnsNull(); + + // Act + var result = await _service.GetByIdAsync(context); + + // Assert + Assert.That(result.Status, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task CreateAsync_AddsEntityToDb() + { + // Arrange + string callerId = "CallerId"; + string listId = "ListId"; + int ownerRoleId = 777; + TodoListCreateDto dto = new("Test entity", "..."); + WebApplicationFactory.TodoListsRepository + .AddAsync(Arg.Any()) + .Returns(Task.FromResult) + .AndDoes(x => ((TodoList)x[0]).Id = listId); + WebApplicationFactory.TodoListRolesRepository + .AddAsync(Arg.Any()) + .Returns(Task.FromResult) + .AndDoes(x => ((TodoListRole)x[0]).Id = ownerRoleId); + WebApplicationFactory.TodoListMembersRepository + .AddAsync(Arg.Any()) + .Returns(Task.FromResult); + + // Act + var result = await _service.CreateAsync(dto, callerId); + + // Assert that list was created + await WebApplicationFactory.TodoListsRepository + .Received() + .AddAsync(Arg.Is(x => x.Name == dto.Name && + x.Description == dto.Description && x.OwnerId == callerId)); + // Assert that "Owner" role was created + await WebApplicationFactory.TodoListRolesRepository + .Received() + .AddAsync(Arg.Is(x => x.Priority == 0 && + x.TodoListId == listId && x.Permissions == RolePermissions.All)); + // Assert that the caller was assigned to the "Owner" role + await WebApplicationFactory.TodoListMembersRepository + .Received() + .AddAsync(Arg.Is(x => x.TodoListId == listId && + x.UserId == callerId && x.RoleId == ownerRoleId)); + // Assert that changes were commited + await WebApplicationFactory.UnitOfWork + .Received() + .CommitAsync(); + } + + [Test] + public async Task CreateAsync_OnException_RethrowsAndRollbacksChanges() + { + // Arrange + string callerId = "CallerId"; + TodoListCreateDto dto = new("Test entity", "..."); + WebApplicationFactory.TodoListsRepository + .AddAsync(Arg.Any()) + .Throws(); + + // Act/Assert + Assert.ThrowsAsync(() => _service.CreateAsync(dto, callerId)); + + // Assert + await WebApplicationFactory.UnitOfWork + .Received() + .RollbackAsync(); + + } + + [Test] + public async Task EditAsync_EntityExists_Succeeds() + { + // Arrange + var todoList = TestModels.CreateTestTodoList(); + RolePermissions validPermissions = new(EditItems: true); + TodoListCreateDto updateDto = new("NewName", "NewDescription"); + TodoListContext context = new(todoList.Id, TestUserId); + WebApplicationFactory.PermissionsChecker + .CanTouchEntityAsync(new(todoList.Id, TestUserId), + Arg.Any(), Arg.Any>()) + .Returns(x => ((Func)x[2])(validPermissions)); + WebApplicationFactory.TodoListsRepository + .GetByIdAsync(todoList.Id) + .Returns(todoList); + WebApplicationFactory.TodoListsRepository + .UpdateAsync(Arg.Any()) + .Returns(Task.FromResult); + + // Act + var result = await _service.EditAsync(context, updateDto); + + // Assert that result indicates success + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); + // Assert that update was called + await WebApplicationFactory.TodoListsRepository + .Received() + .UpdateAsync(Arg.Is(x => x.Id == todoList.Id && + x.Name == updateDto.Name && + x.Description == updateDto.Description)); + } + + [Test] + public async Task EditAsync_UserHasNoPermission_ReturnsForbidden() + { + // Arrange + var todoList = TestModels.CreateTestTodoList(); + TodoListCreateDto updateDto = new("Name", "Description"); + TodoListContext context = new(todoList.Id, TestUserId); + WebApplicationFactory.TodoListsRepository + .GetByIdAsync(todoList.Id) + .Returns(todoList); + WebApplicationFactory.PermissionsChecker + .CanTouchEntityAsync(new(todoList.Id, TestUserId), + Arg.Any(), Arg.Any>()) + .Returns(false); + + // Act + var result = await _service.EditAsync(context, updateDto); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); + } + + [Test] + public async Task EditAsync_EntityDoesNotExist_ReturnsNotFound() + { + // Arrange + string todoListId = "ID"; + TodoListCreateDto updateDto = new("Name", "Description"); + TodoListContext context = new(todoListId, TestUserId); + WebApplicationFactory.TodoListsRepository + .GetByIdAsync(todoListId) + .ReturnsNull(); + + // Act + var result = await _service.EditAsync(context, updateDto); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task DeleteAsync_UserIsOwner_Succeeds() + { + // Arrange + var todoList = TestModels.CreateTestTodoList(TestUserId); + TodoListContext context = new(todoList.Id, TestUserId); + WebApplicationFactory.TodoListsRepository + .GetByIdAsync(todoList.Id) + .Returns(todoList); + WebApplicationFactory.TodoListsRepository + .DeleteAsync(Arg.Any()) + .Returns(Task.FromResult); + + // Act + var result = await _service.DeleteAsync(context); + + // Assert that result indicates success + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Success)); + // Assert that delete was called + await WebApplicationFactory.TodoListsRepository + .Received() + .DeleteAsync(Arg.Is(x => x.Id == todoList.Id)); + } + + [Test] + public async Task DeleteAsync_EntityDoesNotExist_ReturnsNotFound() + { + // Arrange + string todoListId = "ID"; + TodoListContext context = new(todoListId, TestUserId); + WebApplicationFactory.TodoListsRepository + .GetByIdAsync(todoListId) + .ReturnsNull(); + + // Act + var result = await _service.DeleteAsync(context); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.NotFound)); + } + + [Test] + public async Task DeleteAsync_UserIsNotOwner_ReturnsForbidden() + { + // Arrange + var todoList = TestModels.CreateTestTodoList("invalid user ID"); + TodoListContext context = new(todoList.Id, TestUserId); + WebApplicationFactory.TodoListsRepository + .GetByIdAsync(todoList.Id) + .Returns(todoList); + + // Act + var result = await _service.DeleteAsync(context); + + // Assert + Assert.That(result, Is.EqualTo(ServiceResponseStatus.Forbidden)); + // Assert that delete was not called + await WebApplicationFactory.TodoListsRepository + .DidNotReceive() + .DeleteAsync(Arg.Any()); + } } diff --git a/AdvancedTodoList.IntegrationTests/TestContainersSetup.cs b/AdvancedTodoList.IntegrationTests/TestContainersSetup.cs index dde9845..5c72274 100644 --- a/AdvancedTodoList.IntegrationTests/TestContainersSetup.cs +++ b/AdvancedTodoList.IntegrationTests/TestContainersSetup.cs @@ -2,38 +2,50 @@ namespace AdvancedTodoList.IntegrationTests; +#pragma warning disable IDE0079 +#pragma warning disable NUnit1028 +#pragma warning disable NUnit1032 + /// /// Class that sets up integration testing environment. /// [SetUpFixture] public static class TestContainersSetup { - /// - /// Test container that contains a database. - /// - private static MsSqlContainer? s_testDbContainer; + /// + /// Test container that contains a database. + /// + private static MsSqlContainer? s_testDbContainer; - /// - /// Gets a running test container or creates it if it's the first call. - /// - /// - /// A running testing MS SQL container. - /// - public static async Task GetTestContainerAsync() - { - if (s_testDbContainer == null) - { - // Initialize and start a container with test DB - s_testDbContainer = new MsSqlBuilder().Build(); - await s_testDbContainer.StartAsync(); - } - return s_testDbContainer; - } + /// + /// Gets a running test container or creates it if it's the first call. + /// + /// + /// A running testing MS SQL container. + /// + public static async Task GetTestContainerAsync() + { + if (s_testDbContainer == null) + { + // Initialize and start a container with test DB + s_testDbContainer = new MsSqlBuilder().Build(); + await s_testDbContainer.StartAsync(); + } + return s_testDbContainer; + } - [OneTimeTearDown] - public static async Task TearDown() - { - // Stop the DB container - if (s_testDbContainer != null) await s_testDbContainer.StopAsync(); - } + [OneTimeTearDown] + public static async Task TearDown() + { + // Stop the DB container + if (s_testDbContainer != null) + { + await s_testDbContainer.StopAsync(); + await s_testDbContainer.DisposeAsync(); + } + } } + +#pragma warning restore NUnit1028 +#pragma warning restore NUnit1032 +#pragma warning restore IDE0079 \ No newline at end of file diff --git a/AdvancedTodoList.IntegrationTests/Utils/TestModels.cs b/AdvancedTodoList.IntegrationTests/Utils/TestModels.cs index ff92285..418cddc 100644 --- a/AdvancedTodoList.IntegrationTests/Utils/TestModels.cs +++ b/AdvancedTodoList.IntegrationTests/Utils/TestModels.cs @@ -12,85 +12,85 @@ namespace AdvancedTodoList.IntegrationTests.Utils; [SetUpFixture] public static class TestModels { - /// - /// Creates and returns a valid model of a to-do list which can be added to the DB. - /// - public static TodoList CreateTestTodoList(string? ownerId = null) => new() - { - Name = "Name", - Description = "Description", - OwnerId = ownerId - }; - /// - /// Creates and returns a valid model of a to-do list item which can be added to the DB. - /// - public static TodoItem CreateTestTodoItem(string todoListId, string? ownerId = null, int? categoryId = null) => new() - { - Name = "Name", - Description = "Description", - OwnerId = ownerId, - CategoryId = categoryId, - Priority = 5, - State = TodoItemState.Completed, - DeadlineDate = DateTime.UtcNow.AddDays(365), - TodoListId = todoListId - }; - /// - /// Creates and returns a valid model of a to-do list item category which can be added to the DB. - /// - public static TodoItemCategory CreateTestTodoItemCategory(string todoListId) => new() - { - Name = "Category", - TodoListId = todoListId - }; - /// - /// Creates and returns a valid model of a test to-do list dependant entity. - /// - public static TestTodoListDependantEntity CreateTestTodoListDependantEntity(string todoListId) => new() - { - TestProperty = "Test", - TodoListId = todoListId - }; - /// - /// Creates and returns a valid model of an application user. - /// - public static ApplicationUser CreateTestUser() - { - string userName = Guid.NewGuid().ToString(); - return new() - { - UserName = userName, - FirstName = "Test", - LastName = "User", - Email = $"{userName}@example.com" - }; - } - /// - /// Creates and returns a valid model of a refresh token. - /// - public static UserRefreshToken CreateTestUserRefreshToken(string userId) => new() - { - UserId = userId, - Token = "TestToken", - ValidTo = DateTime.UtcNow.AddDays(180) - }; - /// - /// Creates and returns a valid model of a todo-list role. - /// - public static TodoListRole CreateTestRole(string todoListId) => new() - { - Name = "Role1", - Priority = 5, - TodoListId = todoListId - }; + /// + /// Creates and returns a valid model of a to-do list which can be added to the DB. + /// + public static TodoList CreateTestTodoList(string? ownerId = null) => new() + { + Name = "Name", + Description = "Description", + OwnerId = ownerId + }; + /// + /// Creates and returns a valid model of a to-do list item which can be added to the DB. + /// + public static TodoItem CreateTestTodoItem(string todoListId, string? ownerId = null, int? categoryId = null) => new() + { + Name = "Name", + Description = "Description", + OwnerId = ownerId, + CategoryId = categoryId, + Priority = 5, + State = TodoItemState.Completed, + DeadlineDate = DateTime.UtcNow.AddDays(365), + TodoListId = todoListId + }; + /// + /// Creates and returns a valid model of a to-do list item category which can be added to the DB. + /// + public static TodoItemCategory CreateTestTodoItemCategory(string todoListId) => new() + { + Name = "Category", + TodoListId = todoListId + }; + /// + /// Creates and returns a valid model of a test to-do list dependant entity. + /// + public static TestTodoListDependantEntity CreateTestTodoListDependantEntity(string todoListId) => new() + { + TestProperty = "Test", + TodoListId = todoListId + }; + /// + /// Creates and returns a valid model of an application user. + /// + public static ApplicationUser CreateTestUser() + { + string userName = Guid.NewGuid().ToString(); + return new() + { + UserName = userName, + FirstName = "Test", + LastName = "User", + Email = $"{userName}@example.com" + }; + } + /// + /// Creates and returns a valid model of a refresh token. + /// + public static UserRefreshToken CreateTestUserRefreshToken(string userId) => new() + { + UserId = userId, + Token = "TestToken", + ValidTo = DateTime.UtcNow.AddDays(180) + }; + /// + /// Creates and returns a valid model of a todo-list role. + /// + public static TodoListRole CreateTestRole(string todoListId) => new() + { + Name = "Role1", + Priority = 5, + TodoListId = todoListId + }; - /// - /// Creates and returns a valid model of an invitation link. - /// - public static InvitationLink CreateTestInvitationLink(string todoListId, DateTime? validTo = null) => new() - { - Value = Guid.NewGuid().ToString(), - TodoListId = todoListId, - ValidTo = validTo ?? DateTime.Now.AddDays(5) - }; + /// + /// Creates and returns a valid model of an invitation link. + /// + public static InvitationLink CreateTestInvitationLink(string todoListId, DateTime? validTo = null) => new() + { + Value = Guid.NewGuid().ToString(), + TodoListId = todoListId, + ValidTo = validTo ?? DateTime.Now.AddDays(5) + }; } diff --git a/AdvancedTodoList.UnitTests/AdvancedTodoList.UnitTests.csproj b/AdvancedTodoList.UnitTests/AdvancedTodoList.UnitTests.csproj index 0c23934..ae234d4 100644 --- a/AdvancedTodoList.UnitTests/AdvancedTodoList.UnitTests.csproj +++ b/AdvancedTodoList.UnitTests/AdvancedTodoList.UnitTests.csproj @@ -10,16 +10,23 @@ - - - - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - + diff --git a/AdvancedTodoList.UnitTests/Mapping/InputSanitizingTests.cs b/AdvancedTodoList.UnitTests/Mapping/InputSanitizingTests.cs index acc520c..1ad9ac2 100644 --- a/AdvancedTodoList.UnitTests/Mapping/InputSanitizingTests.cs +++ b/AdvancedTodoList.UnitTests/Mapping/InputSanitizingTests.cs @@ -1,4 +1,4 @@ -using AdvancedTodoList.Core.Mapping; +using AdvancedTodoList.Application.Mapping; using Mapster; namespace AdvancedTodoList.UnitTests.Mapping; @@ -6,52 +6,52 @@ namespace AdvancedTodoList.UnitTests.Mapping; [TestFixture] public class InputSanitizingTests { - private class Poco - { - public string? Text { get; set; } - public DateTime Date { get; set; } - public DateTime? NullableDate { get; set; } - } - - private record TextDto(string Text); - private record DateDto(DateTime Date, DateTime? NullableDate); - - [SetUp] - public void SetUp() - { - MappingGlobalSettings.Apply(); - } - - [Test] - public void NullString_MapsIntoEmptyString() - { - // Arrange - Poco poco = new() - { - Text = null - }; - - // Act - var dto = poco.Adapt(); - - // Assert - Assert.That(dto.Text, Is.EqualTo(string.Empty)); - } - - [Test] - public void String_MapsIntoTrimmedString() - { - // Arrange - string expectedText = "Lorem ipsum dolor sit amet, ..."; - Poco poco = new() - { - Text = $"\t\r {expectedText} \t\t\r " - }; - - // Act - var dto = poco.Adapt(); - - // Assert - Assert.That(dto.Text, Is.EqualTo(expectedText)); - } + private class Poco + { + public string? Text { get; set; } + public DateTime Date { get; set; } + public DateTime? NullableDate { get; set; } + } + + private record TextDto(string Text); + private record DateDto(DateTime Date, DateTime? NullableDate); + + [SetUp] + public void SetUp() + { + MappingGlobalSettings.Apply(); + } + + [Test] + public void NullString_MapsIntoEmptyString() + { + // Arrange + Poco poco = new() + { + Text = null + }; + + // Act + var dto = poco.Adapt(); + + // Assert + Assert.That(dto.Text, Is.EqualTo(string.Empty)); + } + + [Test] + public void String_MapsIntoTrimmedString() + { + // Arrange + string expectedText = "Lorem ipsum dolor sit amet, ..."; + Poco poco = new() + { + Text = $"\t\r {expectedText} \t\t\r " + }; + + // Act + var dto = poco.Adapt(); + + // Assert + Assert.That(dto.Text, Is.EqualTo(expectedText)); + } } diff --git a/AdvancedTodoList.UnitTests/Pagination/QueryablePaginationExtensionsTests.cs b/AdvancedTodoList.UnitTests/Pagination/QueryablePaginationExtensionsTests.cs index a5e9a64..4b89f19 100644 --- a/AdvancedTodoList.UnitTests/Pagination/QueryablePaginationExtensionsTests.cs +++ b/AdvancedTodoList.UnitTests/Pagination/QueryablePaginationExtensionsTests.cs @@ -5,31 +5,31 @@ namespace AdvancedTodoList.UnitTests.Pagination; public class QueryablePaginationExtensionsTests { - [Test] - public void ApplyPagination_ValidPage_ReturnsValidResults() - { - // Arrange - var data = Enumerable.Range(1, 50).AsQueryable(); - PaginationParameters properties = new(2, 10); + [Test] + public void ApplyPagination_ValidPage_ReturnsValidResults() + { + // Arrange + var data = Enumerable.Range(1, 50).AsQueryable(); + PaginationParameters properties = new(2, 10); - // Act - var page = data.ApplyPagination(properties); + // Act + var page = data.ApplyPagination(properties); - // Assert - Assert.That(page, Is.EquivalentTo(Enumerable.Range(11, 10))); - } + // Assert + Assert.That(page, Is.EquivalentTo(Enumerable.Range(11, 10))); + } - [Test] - public void ApplyPagination_WrongPage_ReturnsEmptyResults() - { - // Arrange - var data = Enumerable.Range(1, 50).AsQueryable(); - PaginationParameters properties = new(111, 10); + [Test] + public void ApplyPagination_WrongPage_ReturnsEmptyResults() + { + // Arrange + var data = Enumerable.Range(1, 50).AsQueryable(); + PaginationParameters properties = new(111, 10); - // Act - var page = data.ApplyPagination(properties); + // Act + var page = data.ApplyPagination(properties); - // Assert - Assert.That(page, Is.Empty); - } + // Assert + Assert.That(page, Is.Empty); + } } diff --git a/AdvancedTodoList.UnitTests/Services/Auth/AccessTokensServiceTests.cs b/AdvancedTodoList.UnitTests/Services/Auth/AccessTokensServiceTests.cs index f345964..d8b31d8 100644 --- a/AdvancedTodoList.UnitTests/Services/Auth/AccessTokensServiceTests.cs +++ b/AdvancedTodoList.UnitTests/Services/Auth/AccessTokensServiceTests.cs @@ -1,6 +1,6 @@ -using AdvancedTodoList.Core.Models.Auth; -using AdvancedTodoList.Core.Options; -using AdvancedTodoList.Infrastructure.Services.Auth; +using AdvancedTodoList.Application.Options; +using AdvancedTodoList.Application.Services.Implementations.Auth; +using AdvancedTodoList.Core.Models.Auth; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; @@ -11,75 +11,75 @@ namespace AdvancedTodoList.UnitTests.Services.Auth; [TestFixture] public class AccessTokensServiceTests { - private AccessTokenOptions _accessTokenOptions; - private IOptions _options; + private AccessTokenOptions _accessTokenOptions; + private IOptions _options; - [SetUp] - public void Setup() - { - _accessTokenOptions = new() - { - SecretKey = "TestSecretKeyWhichIsNotGoingToBeUsedInProductionOfCourse", - ValidIssuer = "valid_issuer", - ValidAudience = "valid_audience", - ExpirationSeconds = 3600 // 1 hour expiration - }; - _options = Substitute.For>(); - _options.Value.Returns(_accessTokenOptions); - } + [SetUp] + public void Setup() + { + _accessTokenOptions = new() + { + SecretKey = "TestSecretKeyWhichIsNotGoingToBeUsedInProductionOfCourse", + ValidIssuer = "valid_issuer", + ValidAudience = "valid_audience", + ExpirationSeconds = 3600 // 1 hour expiration + }; + _options = Substitute.For>(); + _options.Value.Returns(_accessTokenOptions); + } - [Test] - public async Task GenerateAccessToken_ValidUser_ReturnsValidToken() - { - // Arrange - var user = new ApplicationUser - { - Id = "user_id", - Email = "user@example.com", - UserName = "username", - FirstName = "John", - LastName = "Doe" - }; - var service = new AccessTokensService(_options); + [Test] + public async Task GenerateAccessToken_ValidUser_ReturnsValidToken() + { + // Arrange + var user = new ApplicationUser + { + Id = "user_id", + Email = "user@example.com", + UserName = "username", + FirstName = "John", + LastName = "Doe" + }; + var service = new AccessTokensService(_options); - // Act - string accessToken = service.GenerateAccessToken(user); + // Act + string accessToken = service.GenerateAccessToken(user); - // Assert - TokenValidationParameters validationParameters = new() - { - ValidIssuer = _accessTokenOptions.ValidIssuer, - ValidAudience = _accessTokenOptions.ValidAudience, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_accessTokenOptions.SecretKey)) - }; - JwtSecurityTokenHandler tokenHandler = new(); - var validationResult = await tokenHandler.ValidateTokenAsync( - accessToken, validationParameters); - // Assert that token is valid - Assert.That(validationResult.IsValid); - // Assert that token has all needed claims - var token = (JwtSecurityToken)validationResult.SecurityToken; - Assert.Multiple(() => - { - Assert.That(token.Claims.First(x => x.Type == JwtRegisteredClaimNames.Sub).Value, - Is.EqualTo(user.Id)); - Assert.That(token.Claims.First(x => x.Type == JwtRegisteredClaimNames.Email).Value, - Is.EqualTo(user.Email)); - Assert.That(token.Claims.First(x => x.Type == JwtRegisteredClaimNames.UniqueName).Value, - Is.EqualTo(user.UserName)); - Assert.That(token.Claims.First(x => x.Type == JwtRegisteredClaimNames.GivenName).Value, - Is.EqualTo(user.FirstName)); - Assert.That(token.Claims.First(x => x.Type == JwtRegisteredClaimNames.FamilyName).Value, - Is.EqualTo(user.LastName)); - }); - } + // Assert + TokenValidationParameters validationParameters = new() + { + ValidIssuer = _accessTokenOptions.ValidIssuer, + ValidAudience = _accessTokenOptions.ValidAudience, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_accessTokenOptions.SecretKey)) + }; + JwtSecurityTokenHandler tokenHandler = new(); + var validationResult = await tokenHandler.ValidateTokenAsync( + accessToken, validationParameters); + // Assert that token is valid + Assert.That(validationResult.IsValid); + // Assert that token has all needed claims + var token = (JwtSecurityToken)validationResult.SecurityToken; + using (Assert.EnterMultipleScope()) + { + Assert.That(token.Claims.First(x => x.Type == JwtRegisteredClaimNames.Sub).Value, + Is.EqualTo(user.Id)); + Assert.That(token.Claims.First(x => x.Type == JwtRegisteredClaimNames.Email).Value, + Is.EqualTo(user.Email)); + Assert.That(token.Claims.First(x => x.Type == JwtRegisteredClaimNames.UniqueName).Value, + Is.EqualTo(user.UserName)); + Assert.That(token.Claims.First(x => x.Type == JwtRegisteredClaimNames.GivenName).Value, + Is.EqualTo(user.FirstName)); + Assert.That(token.Claims.First(x => x.Type == JwtRegisteredClaimNames.FamilyName).Value, + Is.EqualTo(user.LastName)); + } + } - [Test] - public async Task GetUserIdFromExpiredTokenAsync_ValidToken_ReturnsUserId() - { - // Arrange - // The access token is signed using '_options.SecretKey' key and has this payload: - /* + [Test] + public async Task GetUserIdFromExpiredTokenAsync_ValidToken_ReturnsUserId() + { + // Arrange + // The access token is signed using '_options.SecretKey' key and has this payload: + /* { "sub": "test", "exp": 1577833200, @@ -87,23 +87,23 @@ public async Task GetUserIdFromExpiredTokenAsync_ValidToken_ReturnsUserId() "aud": "valid_audience" } */ - // Token is expired in 01.01.2020 - var accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNTc3ODMzMjAwLCJpc3MiOiJ2YWxpZF9pc3N1ZXIiLCJhdWQiOiJ2YWxpZF9hdWRpZW5jZSJ9.ONo1hvc8wnKU3kcBZUr5xH--kdTLHXFAx2j3grC65CE"; - var service = new AccessTokensService(_options); + // Token is expired in 01.01.2020 + var accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNTc3ODMzMjAwLCJpc3MiOiJ2YWxpZF9pc3N1ZXIiLCJhdWQiOiJ2YWxpZF9hdWRpZW5jZSJ9.ONo1hvc8wnKU3kcBZUr5xH--kdTLHXFAx2j3grC65CE"; + var service = new AccessTokensService(_options); - // Act - string? userId = await service.GetUserIdFromExpiredTokenAsync(accessToken); + // Act + string? userId = await service.GetUserIdFromExpiredTokenAsync(accessToken); - // Assert - Assert.That(userId, Is.EqualTo("test")); - } + // Assert + Assert.That(userId, Is.EqualTo("test")); + } - [Test] - public async Task GetUserIdFromExpiredTokenAsync_InvalidToken_ReturnsNull() - { - // Arrange - // The access token is signed using 'invalid' key and has this payload: - /* + [Test] + public async Task GetUserIdFromExpiredTokenAsync_InvalidToken_ReturnsNull() + { + // Arrange + // The access token is signed using 'invalid' key and has this payload: + /* { "sub": "test", "exp": 1577833200, @@ -111,14 +111,14 @@ public async Task GetUserIdFromExpiredTokenAsync_InvalidToken_ReturnsNull() "aud": "valid_audience" } */ - // Token is expired in 01.01.2020 - var accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNTc3ODMzMjAwLCJpc3MiOiJ2YWxpZF9pc3N1ZXIiLCJhdWQiOiJ2YWxpZF9hdWRpZW5jZSJ9.P_dE9VKK8KGMi5PMU9MW7_l0rrNrynxg4wThYxntXUs"; - var service = new AccessTokensService(_options); + // Token is expired in 01.01.2020 + var accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNTc3ODMzMjAwLCJpc3MiOiJ2YWxpZF9pc3N1ZXIiLCJhdWQiOiJ2YWxpZF9hdWRpZW5jZSJ9.P_dE9VKK8KGMi5PMU9MW7_l0rrNrynxg4wThYxntXUs"; + var service = new AccessTokensService(_options); - // Act - string? userId = await service.GetUserIdFromExpiredTokenAsync(accessToken); + // Act + string? userId = await service.GetUserIdFromExpiredTokenAsync(accessToken); - // Assert - Assert.That(userId, Is.Null); - } + // Assert + Assert.That(userId, Is.Null); + } } diff --git a/AdvancedTodoList.UnitTests/Specifications/GetByIdSpecificationTests.cs b/AdvancedTodoList.UnitTests/Specifications/GetByIdSpecificationTests.cs index 004e798..406b711 100644 --- a/AdvancedTodoList.UnitTests/Specifications/GetByIdSpecificationTests.cs +++ b/AdvancedTodoList.UnitTests/Specifications/GetByIdSpecificationTests.cs @@ -1,45 +1,45 @@ using AdvancedTodoList.Core.Models; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications; namespace AdvancedTodoList.UnitTests.Specifications; [TestFixture] public class GetByIdSpecificationTests { - private class TestEntity : IEntity - { - public int Id { get; set; } - } - - [Test] - public void Criteria_IdMatches_ReturnsTrue() - { - // Arrange - int testId = 123; - TestEntity entity = new() { Id = testId }; - GetByIdSpecification specification = new(testId); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(entity); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_IdDoesNotMatch_ReturnsFalse() - { - // Arrange - int testId = 123; - TestEntity entity = new() { Id = testId }; - GetByIdSpecification specification = new(-1); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(entity); - - // Assert - Assert.That(result, Is.False); - } + private class TestEntity : IEntity + { + public int Id { get; set; } + } + + [Test] + public void Criteria_IdMatches_ReturnsTrue() + { + // Arrange + int testId = 123; + TestEntity entity = new() { Id = testId }; + GetByIdSpecification specification = new(testId); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(entity); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_IdDoesNotMatch_ReturnsFalse() + { + // Arrange + int testId = 123; + TestEntity entity = new() { Id = testId }; + GetByIdSpecification specification = new(-1); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(entity); + + // Assert + Assert.That(result, Is.False); + } } diff --git a/AdvancedTodoList.UnitTests/Specifications/InvitationLinksSpecificationTests.cs b/AdvancedTodoList.UnitTests/Specifications/InvitationLinksSpecificationTests.cs index 115be84..73ddec5 100644 --- a/AdvancedTodoList.UnitTests/Specifications/InvitationLinksSpecificationTests.cs +++ b/AdvancedTodoList.UnitTests/Specifications/InvitationLinksSpecificationTests.cs @@ -1,40 +1,40 @@ using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications; namespace AdvancedTodoList.UnitTests.Specifications; [TestFixture] public class InvitationLinksSpecificationTests { - [Test] - public void Criteria_TodoListIdMatches_ReturnsTrue() - { - // Arrange - const string todoListId = "ID"; - InvitationLink link = new() { TodoListId = todoListId, Value = "link" }; - InvitationLinksSpecification specification = new(todoListId); - var function = specification.Criteria.Compile(); + [Test] + public void Criteria_TodoListIdMatches_ReturnsTrue() + { + // Arrange + const string todoListId = "ID"; + InvitationLink link = new() { TodoListId = todoListId, Value = "link" }; + InvitationLinksSpecification specification = new(todoListId); + var function = specification.Criteria.Compile(); - // Act - bool result = function(link); + // Act + bool result = function(link); - // Assert - Assert.That(result, Is.True); - } + // Assert + Assert.That(result, Is.True); + } - [Test] - public void Criteria_TodoListIdDoesNotMatch_ReturnsFalse() - { - // Arrange - const string todoListId = "ID"; - InvitationLink link = new() { TodoListId = todoListId, Value = "link" }; - InvitationLinksSpecification specification = new("Wrong ID"); - var function = specification.Criteria.Compile(); + [Test] + public void Criteria_TodoListIdDoesNotMatch_ReturnsFalse() + { + // Arrange + const string todoListId = "ID"; + InvitationLink link = new() { TodoListId = todoListId, Value = "link" }; + InvitationLinksSpecification specification = new("Wrong ID"); + var function = specification.Criteria.Compile(); - // Act - bool result = function(link); + // Act + bool result = function(link); - // Assert - Assert.That(result, Is.False); - } + // Assert + Assert.That(result, Is.False); + } } diff --git a/AdvancedTodoList.UnitTests/Specifications/MemberPermissionsSpecificationTests.cs b/AdvancedTodoList.UnitTests/Specifications/MemberPermissionsSpecificationTests.cs index c3cbf8f..9ec0be8 100644 --- a/AdvancedTodoList.UnitTests/Specifications/MemberPermissionsSpecificationTests.cs +++ b/AdvancedTodoList.UnitTests/Specifications/MemberPermissionsSpecificationTests.cs @@ -1,50 +1,50 @@ using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications; namespace AdvancedTodoList.UnitTests.Specifications; public class MemberPermissionsSpecificationTests { - [Test] - public void Criteria_ValidTodoListIdAndUserId_ReturnsTrue() - { - // Arrange - const string todoListId = "TodoId"; - const string userId = "UserId"; - TodoListMember member = new() - { - TodoListId = todoListId, - UserId = userId - }; - MemberPermissionsSpecification specification = new(todoListId, userId); - var criteria = specification.Criteria.Compile(); + [Test] + public void Criteria_ValidTodoListIdAndUserId_ReturnsTrue() + { + // Arrange + const string todoListId = "TodoId"; + const string userId = "UserId"; + TodoListMember member = new() + { + TodoListId = todoListId, + UserId = userId + }; + MemberPermissionsSpecification specification = new(todoListId, userId); + var criteria = specification.Criteria.Compile(); - // Act - bool result = criteria(member); + // Act + bool result = criteria(member); - // Arrange - Assert.That(result, Is.True); - } + // Arrange + Assert.That(result, Is.True); + } - [Test] - [TestCase("TodoListId", "invalid")] - [TestCase("invalid", "UserId")] - [TestCase("invalid", "invalid")] - public void Criteria_InvalidTodoListIdAndUserIdPairs_ReturnsFalse(string todoListId, string userId) - { - // Arrange - TodoListMember member = new() - { - TodoListId = "TodoListId", - UserId = "UserId" - }; - MemberPermissionsSpecification specification = new(todoListId, userId); - var criteria = specification.Criteria.Compile(); + [Test] + [TestCase("TodoListId", "invalid")] + [TestCase("invalid", "UserId")] + [TestCase("invalid", "invalid")] + public void Criteria_InvalidTodoListIdAndUserIdPairs_ReturnsFalse(string todoListId, string userId) + { + // Arrange + TodoListMember member = new() + { + TodoListId = "TodoListId", + UserId = "UserId" + }; + MemberPermissionsSpecification specification = new(todoListId, userId); + var criteria = specification.Criteria.Compile(); - // Act - bool result = criteria(member); + // Act + bool result = criteria(member); - // Arrange - Assert.That(result, Is.False); - } + // Arrange + Assert.That(result, Is.False); + } } diff --git a/AdvancedTodoList.UnitTests/Specifications/QueryableSpecificationExtensionsTests.cs b/AdvancedTodoList.UnitTests/Specifications/QueryableSpecificationExtensionsTests.cs index 756a43d..dddbbcd 100644 --- a/AdvancedTodoList.UnitTests/Specifications/QueryableSpecificationExtensionsTests.cs +++ b/AdvancedTodoList.UnitTests/Specifications/QueryableSpecificationExtensionsTests.cs @@ -1,5 +1,5 @@ using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Infrastructure.Extensions; using System.Linq.Expressions; namespace AdvancedTodoList.UnitTests.Specifications; @@ -7,32 +7,32 @@ namespace AdvancedTodoList.UnitTests.Specifications; [TestFixture] public class QueryableSpecificationExtensionsTests { - private class TestSpecification : ISpecification - where T : class - { - public Expression> Criteria { get; set; } = _ => true; + private class TestSpecification : ISpecification + where T : class + { + public Expression> Criteria { get; set; } = _ => true; - public List>> Includes { get; set; } = []; + public List>> Includes { get; set; } = []; - public List IncludeStrings { get; set; } = []; - } + public List IncludeStrings { get; set; } = []; + } - [Test] - public void ApplySpecification_AppliesCriteria() - { - // Arrange - TestSpecification specification = new() - { - Criteria = s => s[0] == 'a' - }; - string[] testArray = ["abc", "hab", "bcd", "aab"]; - string[] expectedResult = ["abc", "aab"]; - IQueryable testQuery = testArray.AsQueryable(); + [Test] + public void ApplySpecification_AppliesCriteria() + { + // Arrange + TestSpecification specification = new() + { + Criteria = s => s[0] == 'a' + }; + string[] testArray = ["abc", "hab", "bcd", "aab"]; + string[] expectedResult = ["abc", "aab"]; + IQueryable testQuery = testArray.AsQueryable(); - // Act - var result = testQuery.ApplySpecification(specification); + // Act + var result = testQuery.ApplySpecification(specification); - // Assert - Assert.That(result, Is.EquivalentTo(expectedResult)); - } + // Assert + Assert.That(result, Is.EquivalentTo(expectedResult)); + } } diff --git a/AdvancedTodoList.UnitTests/Specifications/TodoItemsSpecificationTests.cs b/AdvancedTodoList.UnitTests/Specifications/TodoItemsSpecificationTests.cs index a9df293..b14ad2a 100644 --- a/AdvancedTodoList.UnitTests/Specifications/TodoItemsSpecificationTests.cs +++ b/AdvancedTodoList.UnitTests/Specifications/TodoItemsSpecificationTests.cs @@ -1,317 +1,317 @@ using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.Core.Specifications.Todo; namespace AdvancedTodoList.UnitTests.Specifications; [TestFixture] public class TodoItemsSpecificationTests { - private TodoItem _testItem; - - [SetUp] - public void SetUp() - { - _testItem = new() - { - Id = 700, - Name = "Make the call", - Description = "Call someone special to fix your problems", - OwnerId = "TestOwnerId", - TodoListId = "TestListId", - Priority = 555, - CategoryId = 777, - DeadlineDate = new(2022, 8, 15), - State = TodoItemState.Completed - }; - } - - [Test] - public void Criteria_TodoListIdMatchesEmptyFilter_ReturnsTrue() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsSpecification specification = new(todoListId, new()); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_TodoListIdDoesNotMatch_ReturnsFalse() - { - // Arrange - TodoItemsSpecification specification = new("Wrong ID", new()); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public void Criteria_SubstringOfName_ReturnsTrue() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsFilter filter = new(Name: _testItem.Name[5..7]); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_NotSubstringOfName_ReturnsFalse() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsFilter filter = new(Name: "Not a substring"); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public void Criteria_OwnerId_ReturnsTrue() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsFilter filter = new(OwnerId: _testItem.OwnerId); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_NotSubstringOfOwnerId_ReturnsFalse() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsFilter filter = new(OwnerId: "WrongOwnerId"); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public void Criteria_SubsetOfState_ReturnsTrue() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemState[] states = [TodoItemState.Completed, TodoItemState.Active, TodoItemState.Skipped]; - TodoItemsFilter filter = new(State: states); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_NotSubsetOfState_ReturnsFalse() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemState[] states = [TodoItemState.Skipped]; - TodoItemsFilter filter = new(State: states); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public void Criteria_SubsetOfCategoryId_ReturnsTrue() - { - // Arrange - string todoListId = _testItem.TodoListId; - int?[] categoriesIds = [null, _testItem.CategoryId, 1, 10000]; - TodoItemsFilter filter = new(CategoryId: categoriesIds); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_NotSubsetOfCategoryId_ReturnsFalse() - { - // Arrange - string todoListId = _testItem.TodoListId; - int?[] categoriesIds = [null, 1, 100, 200]; - TodoItemsFilter filter = new(CategoryId: categoriesIds); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public void Criteria_PriorityIsGreaterThanMinPriotity_ReturnsTrue() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsFilter filter = new(MinPriority: _testItem.Priority - 1); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_PriorityIsLessThanMinPriority_ReturnsFalse() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsFilter filter = new(MinPriority: _testItem.Priority + 1); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public void Criteria_PriorityIsGreaterThanMaxPriotity_ReturnsTrue() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsFilter filter = new(MaxPriority: _testItem.Priority + 1); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_PriorityIsGreaterThanMaxPriority_ReturnsFalse() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsFilter filter = new(MaxPriority: _testItem.Priority - 1); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public void Criteria_DeadlineDateIsGreaterThanMinPriotity_ReturnsTrue() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsFilter filter = new(MinDeadlineDate: _testItem.DeadlineDate!.Value.AddDays(-1)); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_DeadlineDateIsLessThanMinDeadlineDate_ReturnsFalse() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsFilter filter = new(MinDeadlineDate: _testItem.DeadlineDate!.Value.AddDays(1)); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public void Criteria_DeadlineDateIsGreaterThanMaxPriotity_ReturnsTrue() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsFilter filter = new(MaxDeadlineDate: _testItem.DeadlineDate!.Value.AddDays(1)); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_DeadlineDateIsGreaterThanMaxDeadlineDate_ReturnsFalse() - { - // Arrange - string todoListId = _testItem.TodoListId; - TodoItemsFilter filter = new(MaxDeadlineDate: _testItem.DeadlineDate!.Value.AddDays(-1)); - TodoItemsSpecification specification = new(todoListId, filter); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testItem); - - // Assert - Assert.That(result, Is.False); - } + private TodoItem _testItem; + + [SetUp] + public void SetUp() + { + _testItem = new() + { + Id = 700, + Name = "Make the call", + Description = "Call someone special to fix your problems", + OwnerId = "TestOwnerId", + TodoListId = "TestListId", + Priority = 555, + CategoryId = 777, + DeadlineDate = new(2022, 8, 15), + State = TodoItemState.Completed + }; + } + + [Test] + public void Criteria_TodoListIdMatchesEmptyFilter_ReturnsTrue() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsSpecification specification = new(todoListId, new()); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_TodoListIdDoesNotMatch_ReturnsFalse() + { + // Arrange + TodoItemsSpecification specification = new("Wrong ID", new()); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void Criteria_SubstringOfName_ReturnsTrue() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsFilter filter = new(Name: _testItem.Name[5..7]); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_NotSubstringOfName_ReturnsFalse() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsFilter filter = new(Name: "Not a substring"); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void Criteria_OwnerId_ReturnsTrue() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsFilter filter = new(OwnerId: _testItem.OwnerId); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_NotSubstringOfOwnerId_ReturnsFalse() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsFilter filter = new(OwnerId: "WrongOwnerId"); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void Criteria_SubsetOfState_ReturnsTrue() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemState[] states = [TodoItemState.Completed, TodoItemState.Active, TodoItemState.Skipped]; + TodoItemsFilter filter = new(State: states); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_NotSubsetOfState_ReturnsFalse() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemState[] states = [TodoItemState.Skipped]; + TodoItemsFilter filter = new(State: states); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void Criteria_SubsetOfCategoryId_ReturnsTrue() + { + // Arrange + string todoListId = _testItem.TodoListId; + int?[] categoriesIds = [null, _testItem.CategoryId, 1, 10000]; + TodoItemsFilter filter = new(CategoryId: categoriesIds); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_NotSubsetOfCategoryId_ReturnsFalse() + { + // Arrange + string todoListId = _testItem.TodoListId; + int?[] categoriesIds = [null, 1, 100, 200]; + TodoItemsFilter filter = new(CategoryId: categoriesIds); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void Criteria_PriorityIsGreaterThanMinPriotity_ReturnsTrue() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsFilter filter = new(MinPriority: _testItem.Priority - 1); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_PriorityIsLessThanMinPriority_ReturnsFalse() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsFilter filter = new(MinPriority: _testItem.Priority + 1); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void Criteria_PriorityIsGreaterThanMaxPriotity_ReturnsTrue() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsFilter filter = new(MaxPriority: _testItem.Priority + 1); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_PriorityIsGreaterThanMaxPriority_ReturnsFalse() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsFilter filter = new(MaxPriority: _testItem.Priority - 1); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void Criteria_DeadlineDateIsGreaterThanMinPriotity_ReturnsTrue() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsFilter filter = new(MinDeadlineDate: _testItem.DeadlineDate!.Value.AddDays(-1)); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_DeadlineDateIsLessThanMinDeadlineDate_ReturnsFalse() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsFilter filter = new(MinDeadlineDate: _testItem.DeadlineDate!.Value.AddDays(1)); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void Criteria_DeadlineDateIsGreaterThanMaxPriotity_ReturnsTrue() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsFilter filter = new(MaxDeadlineDate: _testItem.DeadlineDate!.Value.AddDays(1)); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_DeadlineDateIsGreaterThanMaxDeadlineDate_ReturnsFalse() + { + // Arrange + string todoListId = _testItem.TodoListId; + TodoItemsFilter filter = new(MaxDeadlineDate: _testItem.DeadlineDate!.Value.AddDays(-1)); + TodoItemsSpecification specification = new(todoListId, filter); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testItem); + + // Assert + Assert.That(result, Is.False); + } } diff --git a/AdvancedTodoList.UnitTests/Specifications/TodoListDependantEntitiesSpecificationTests.cs b/AdvancedTodoList.UnitTests/Specifications/TodoListDependantEntitiesSpecificationTests.cs index af562bf..80f1681 100644 --- a/AdvancedTodoList.UnitTests/Specifications/TodoListDependantEntitiesSpecificationTests.cs +++ b/AdvancedTodoList.UnitTests/Specifications/TodoListDependantEntitiesSpecificationTests.cs @@ -1,78 +1,78 @@ using AdvancedTodoList.Core.Models; using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications.Todo; namespace AdvancedTodoList.UnitTests.Specifications; public class TodoListDependantEntitiesSpecificationTests { - private class TestEntity : ITodoListDependant, IHasName - { - public required string TodoListId { get; set; } - public required string Name { get; set; } - } + private class TestEntity : ITodoListDependant, IHasName + { + public required string TodoListId { get; set; } + public required string Name { get; set; } + } - [Test] - public void Criteria_TodoListIdMatches_ReturnsTrue() - { - // Arrange - const string todoListId = "ID"; - TestEntity entity = new() { TodoListId = todoListId, Name = "Name" }; - TodoListDependantEntitiesSpecification specification = new(todoListId); - var function = specification.Criteria.Compile(); + [Test] + public void Criteria_TodoListIdMatches_ReturnsTrue() + { + // Arrange + const string todoListId = "ID"; + TestEntity entity = new() { TodoListId = todoListId, Name = "Name" }; + TodoListDependantEntitiesSpecification specification = new(todoListId); + var function = specification.Criteria.Compile(); - // Act - bool result = function(entity); + // Act + bool result = function(entity); - // Assert - Assert.That(result, Is.True); - } + // Assert + Assert.That(result, Is.True); + } - [Test] - public void Criteria_TodoListIdDoesNotMatch_ReturnsFalse() - { - // Arrange - const string todoListId = "ID"; - TestEntity entity = new() { TodoListId = todoListId, Name = "Name" }; - TodoListDependantEntitiesSpecification specification = new("Wrong ID"); - var function = specification.Criteria.Compile(); + [Test] + public void Criteria_TodoListIdDoesNotMatch_ReturnsFalse() + { + // Arrange + const string todoListId = "ID"; + TestEntity entity = new() { TodoListId = todoListId, Name = "Name" }; + TodoListDependantEntitiesSpecification specification = new("Wrong ID"); + var function = specification.Criteria.Compile(); - // Act - bool result = function(entity); + // Act + bool result = function(entity); - // Assert - Assert.That(result, Is.False); - } + // Assert + Assert.That(result, Is.False); + } - [Test] - public void Criteria_NameMatches_ReturnsTrue() - { - // Arrange - TestEntity entity = new() { TodoListId = "ID", Name = "Name" }; - TodoListDependantEntitiesSpecification specification = new( - entity.TodoListId, entity.Name[1..3]); - var function = specification.Criteria.Compile(); + [Test] + public void Criteria_NameMatches_ReturnsTrue() + { + // Arrange + TestEntity entity = new() { TodoListId = "ID", Name = "Name" }; + TodoListDependantEntitiesSpecification specification = new( + entity.TodoListId, entity.Name[1..3]); + var function = specification.Criteria.Compile(); - // Act - bool result = function(entity); + // Act + bool result = function(entity); - // Assert - Assert.That(result, Is.True); - } + // Assert + Assert.That(result, Is.True); + } - [Test] - public void Criteria_NameDoesNotMatch_ReturnsFalse() - { - // Arrange - TestEntity entity = new() { TodoListId = "ID", Name = "Name" }; - TodoListDependantEntitiesSpecification specification = new( - entity.TodoListId, "Wrong"); - var function = specification.Criteria.Compile(); + [Test] + public void Criteria_NameDoesNotMatch_ReturnsFalse() + { + // Arrange + TestEntity entity = new() { TodoListId = "ID", Name = "Name" }; + TodoListDependantEntitiesSpecification specification = new( + entity.TodoListId, "Wrong"); + var function = specification.Criteria.Compile(); - // Act - bool result = function(entity); + // Act + bool result = function(entity); - // Assert - Assert.That(result, Is.False); - } + // Assert + Assert.That(result, Is.False); + } } diff --git a/AdvancedTodoList.UnitTests/Specifications/TodoListMembersSpecificationTests.cs b/AdvancedTodoList.UnitTests/Specifications/TodoListMembersSpecificationTests.cs index 75bbeb0..751b719 100644 --- a/AdvancedTodoList.UnitTests/Specifications/TodoListMembersSpecificationTests.cs +++ b/AdvancedTodoList.UnitTests/Specifications/TodoListMembersSpecificationTests.cs @@ -1,186 +1,186 @@ using AdvancedTodoList.Core.Models.Auth; using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications.Todo; namespace AdvancedTodoList.UnitTests.Specifications; [TestFixture] public class TodoListMembersSpecificationTests { - private TodoListMember _testMember; - - [SetUp] - public void SetUp() - { - ApplicationUser user = new() - { - Id = "TestUserId", - UserName = "TestUserName", - FirstName = "TestFirstName", - LastName = "TestLastName" - }; - _testMember = new() - { - Id = 123, - TodoListId = "TestTodoListId", - User = user, - UserId = user.Id, - RoleId = 777 - }; - } - - [Test] - public void Criteria_TodoListIdMatchesEmptyFilter_ReturnsTrue() - { - // Arrange - string todoListId = _testMember.TodoListId; - TodoListMembersSpecification specification = new(todoListId, new()); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testMember); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_TodoListIdDoesNotMatch_ReturnsFalse() - { - // Arrange - TodoListMembersSpecification specification = new("Wrong ID", new()); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testMember); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public void Criteria_SubsetOfRolesIds_ReturnsTrue() - { - // Arrange - string todoListId = _testMember.TodoListId; - int?[] roleIds = [null, 1, _testMember.RoleId, 2]; - TodoListMembersSpecification specification = new(todoListId, new(roleIds)); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testMember); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_NotSubsetOfRolesIds_ReturnsFalse() - { - // Arrange - string todoListId = _testMember.TodoListId; - int?[] roleIds = [null, 2]; - TodoListMembersSpecification specification = new(todoListId, new(roleIds)); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testMember); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public void Criteria_UserIdMatches_ReturnsTrue() - { - // Arrange - string todoListId = _testMember.TodoListId; - TodoListMembersSpecification specification = new(todoListId, new(UserId: _testMember.UserId)); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testMember); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_UserIdMatches_ReturnsFalse() - { - // Arrange - string todoListId = _testMember.TodoListId; - TodoListMembersSpecification specification = new(todoListId, new(UserId: "wrong id")); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testMember); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public void Criteria_SubstringOfUserName_ReturnsTrue() - { - // Arrange - string todoListId = _testMember.TodoListId; - TodoListMembersSpecification specification = new(todoListId, new( - UserName: _testMember.User.UserName![1..4])); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testMember); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_NotSubstringOfUserName_ReturnsFalse() - { - // Arrange - string todoListId = _testMember.TodoListId; - TodoListMembersSpecification specification = new(todoListId, new( - UserName: "wrong-username")); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testMember); - - // Assert - Assert.That(result, Is.False); - } - - [Test] - public void Criteria_SubstringOfFullName_ReturnsTrue() - { - // Arrange - string todoListId = _testMember.TodoListId; - TodoListMembersSpecification specification = new(todoListId, new( - FullName: _testMember.User.LastName![1..3])); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testMember); - - // Assert - Assert.That(result, Is.True); - } - - [Test] - public void Criteria_NotSubstringOfFullName_ReturnsFalse() - { - // Arrange - string todoListId = _testMember.TodoListId; - TodoListMembersSpecification specification = new(todoListId, new( - FullName: "wrong-username")); - var function = specification.Criteria.Compile(); - - // Act - bool result = function(_testMember); - - // Assert - Assert.That(result, Is.False); - } + private TodoListMember _testMember; + + [SetUp] + public void SetUp() + { + ApplicationUser user = new() + { + Id = "TestUserId", + UserName = "TestUserName", + FirstName = "TestFirstName", + LastName = "TestLastName" + }; + _testMember = new() + { + Id = 123, + TodoListId = "TestTodoListId", + User = user, + UserId = user.Id, + RoleId = 777 + }; + } + + [Test] + public void Criteria_TodoListIdMatchesEmptyFilter_ReturnsTrue() + { + // Arrange + string todoListId = _testMember.TodoListId; + TodoListMembersSpecification specification = new(todoListId, new()); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testMember); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_TodoListIdDoesNotMatch_ReturnsFalse() + { + // Arrange + TodoListMembersSpecification specification = new("Wrong ID", new()); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testMember); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void Criteria_SubsetOfRolesIds_ReturnsTrue() + { + // Arrange + string todoListId = _testMember.TodoListId; + int?[] roleIds = [null, 1, _testMember.RoleId, 2]; + TodoListMembersSpecification specification = new(todoListId, new(roleIds)); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testMember); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_NotSubsetOfRolesIds_ReturnsFalse() + { + // Arrange + string todoListId = _testMember.TodoListId; + int?[] roleIds = [null, 2]; + TodoListMembersSpecification specification = new(todoListId, new(roleIds)); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testMember); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void Criteria_UserIdMatches_ReturnsTrue() + { + // Arrange + string todoListId = _testMember.TodoListId; + TodoListMembersSpecification specification = new(todoListId, new(UserId: _testMember.UserId)); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testMember); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_UserIdMatches_ReturnsFalse() + { + // Arrange + string todoListId = _testMember.TodoListId; + TodoListMembersSpecification specification = new(todoListId, new(UserId: "wrong id")); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testMember); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void Criteria_SubstringOfUserName_ReturnsTrue() + { + // Arrange + string todoListId = _testMember.TodoListId; + TodoListMembersSpecification specification = new(todoListId, new( + UserName: _testMember.User.UserName![1..4])); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testMember); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_NotSubstringOfUserName_ReturnsFalse() + { + // Arrange + string todoListId = _testMember.TodoListId; + TodoListMembersSpecification specification = new(todoListId, new( + UserName: "wrong-username")); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testMember); + + // Assert + Assert.That(result, Is.False); + } + + [Test] + public void Criteria_SubstringOfFullName_ReturnsTrue() + { + // Arrange + string todoListId = _testMember.TodoListId; + TodoListMembersSpecification specification = new(todoListId, new( + FullName: _testMember.User.LastName![1..3])); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testMember); + + // Assert + Assert.That(result, Is.True); + } + + [Test] + public void Criteria_NotSubstringOfFullName_ReturnsFalse() + { + // Arrange + string todoListId = _testMember.TodoListId; + TodoListMembersSpecification specification = new(todoListId, new( + FullName: "wrong-username")); + var function = specification.Criteria.Compile(); + + // Act + bool result = function(_testMember); + + // Assert + Assert.That(result, Is.False); + } } diff --git a/AdvancedTodoList.UnitTests/Specifications/TodoListsSpecificationTests.cs b/AdvancedTodoList.UnitTests/Specifications/TodoListsSpecificationTests.cs index 03f55ae..31be8ed 100644 --- a/AdvancedTodoList.UnitTests/Specifications/TodoListsSpecificationTests.cs +++ b/AdvancedTodoList.UnitTests/Specifications/TodoListsSpecificationTests.cs @@ -1,92 +1,92 @@ using AdvancedTodoList.Core.Models.TodoLists; using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Infrastructure.Specifications; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.Core.Specifications.Todo; namespace AdvancedTodoList.UnitTests.Specifications; [TestFixture] public class TodoListsSpecificationTests { - private TodoList _testList; - private const string TestUserId = "TestUserId"; + private TodoList _testList; + private const string TestUserId = "TestUserId"; - [SetUp] - public void SetUp() - { - string id = "aajaogdsnbvoain"; - TodoListMember[] members = - { - new () {TodoListId = id, UserId = "123"}, - new () {TodoListId = id, UserId = TestUserId}, - new () {TodoListId = id, UserId = "321"}, - }; - _testList = new TodoList() - { - Name = "Test list", - Description = "Description", - TodoListMembers = members, - OwnerId = "1227" - }; - } + [SetUp] + public void SetUp() + { + string id = "aajaogdsnbvoain"; + TodoListMember[] members = + [ + new () {TodoListId = id, UserId = "123"}, + new () {TodoListId = id, UserId = TestUserId}, + new () {TodoListId = id, UserId = "321"}, + ]; + _testList = new TodoList() + { + Name = "Test list", + Description = "Description", + TodoListMembers = members, + OwnerId = "1227" + }; + } - [Test] - public void Criteria_UserIsMemberOfList_ReturnsTrue() - { - // Arrange - TodoListsFilter filter = new(); - TodoListsSpecification specification = new(TestUserId, filter); - var function = specification.Criteria.Compile(); + [Test] + public void Criteria_UserIsMemberOfList_ReturnsTrue() + { + // Arrange + TodoListsFilter filter = new(); + TodoListsSpecification specification = new(TestUserId, filter); + var function = specification.Criteria.Compile(); - // Act - bool result = function(_testList); + // Act + bool result = function(_testList); - // Assert - Assert.That(result, Is.True); - } + // Assert + Assert.That(result, Is.True); + } - [Test] - public void Criteria_UserIsMemberOfList_ReturnsFalse() - { - // Arrange - TodoListsFilter filter = new(); - TodoListsSpecification specification = new("Wrong user ID", filter); - var function = specification.Criteria.Compile(); + [Test] + public void Criteria_UserIsMemberOfList_ReturnsFalse() + { + // Arrange + TodoListsFilter filter = new(); + TodoListsSpecification specification = new("Wrong user ID", filter); + var function = specification.Criteria.Compile(); - // Act - bool result = function(_testList); + // Act + bool result = function(_testList); - // Assert - Assert.That(result, Is.False); - } + // Assert + Assert.That(result, Is.False); + } - [Test] - public void Criteria_NameMatches_ReturnsTrue() - { - // Arrange - TodoListsFilter filter = new(Name: _testList.Name[2..6]); - TodoListsSpecification specification = new(TestUserId, filter); - var function = specification.Criteria.Compile(); + [Test] + public void Criteria_NameMatches_ReturnsTrue() + { + // Arrange + TodoListsFilter filter = new(Name: _testList.Name[2..6]); + TodoListsSpecification specification = new(TestUserId, filter); + var function = specification.Criteria.Compile(); - // Act - bool result = function(_testList); + // Act + bool result = function(_testList); - // Assert - Assert.That(result, Is.True); - } + // Assert + Assert.That(result, Is.True); + } - [Test] - public void Criteria_NameDoesNotMatch_ReturnsFalse() - { - // Arrange - TodoListsFilter filter = new(Name: "Name that does not match"); - TodoListsSpecification specification = new(TestUserId, filter); - var function = specification.Criteria.Compile(); + [Test] + public void Criteria_NameDoesNotMatch_ReturnsFalse() + { + // Arrange + TodoListsFilter filter = new(Name: "Name that does not match"); + TodoListsSpecification specification = new(TestUserId, filter); + var function = specification.Criteria.Compile(); - // Act - bool result = function(_testList); + // Act + bool result = function(_testList); - // Assert - Assert.That(result, Is.False); - } + // Assert + Assert.That(result, Is.False); + } } diff --git a/AdvancedTodoList.UnitTests/Validation/Auth/LogInDtoValidatorTests.cs b/AdvancedTodoList.UnitTests/Validation/Auth/LogInDtoValidatorTests.cs index 69526a6..c4e7191 100644 --- a/AdvancedTodoList.UnitTests/Validation/Auth/LogInDtoValidatorTests.cs +++ b/AdvancedTodoList.UnitTests/Validation/Auth/LogInDtoValidatorTests.cs @@ -1,57 +1,57 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Validation; -using AdvancedTodoList.Core.Validation.Auth; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Validation; +using AdvancedTodoList.Application.Validation.Auth; namespace AdvancedTodoList.UnitTests.Validation.Auth; public class LogInDtoValidatorTests { - private const string ValidUserNameOrEmail = "username"; - private const string ValidPassword = "Pa$$w0rd"; - - [Test] - public void Dto_ValidData_Succeeds() - { - // Arrange - LogInDtoValidator validator = new(); - LogInDto dto = new(ValidUserNameOrEmail, ValidPassword); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldNotHaveAnyValidationErrors(); - } - - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] - public void UserNameOrEmail_Empty_ReturnsPropertyRequiredError(string testCase) - { - // Arrange - LogInDtoValidator validator = new(); - LogInDto dto = new(testCase, ValidPassword); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.UserNameOrEmail) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } - - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] - public void Password_Empty_ReturnsPropertyRequiredError(string testCase) - { - // Arrange - LogInDtoValidator validator = new(); - LogInDto dto = new(ValidUserNameOrEmail, testCase); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Password) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } + private const string ValidUserNameOrEmail = "username"; + private const string ValidPassword = "Pa$$w0rd"; + + [Test] + public void Dto_ValidData_Succeeds() + { + // Arrange + LogInDtoValidator validator = new(); + LogInDto dto = new(ValidUserNameOrEmail, ValidPassword); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } + + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] + public void UserNameOrEmail_Empty_ReturnsPropertyRequiredError(string testCase) + { + // Arrange + LogInDtoValidator validator = new(); + LogInDto dto = new(testCase, ValidPassword); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.UserNameOrEmail) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } + + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] + public void Password_Empty_ReturnsPropertyRequiredError(string testCase) + { + // Arrange + LogInDtoValidator validator = new(); + LogInDto dto = new(ValidUserNameOrEmail, testCase); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Password) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } } diff --git a/AdvancedTodoList.UnitTests/Validation/Auth/RegisterDtoValidatorTests.cs b/AdvancedTodoList.UnitTests/Validation/Auth/RegisterDtoValidatorTests.cs index 0440d4b..2413db5 100644 --- a/AdvancedTodoList.UnitTests/Validation/Auth/RegisterDtoValidatorTests.cs +++ b/AdvancedTodoList.UnitTests/Validation/Auth/RegisterDtoValidatorTests.cs @@ -1,128 +1,128 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Validation; -using AdvancedTodoList.Core.Validation.Auth; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Validation; +using AdvancedTodoList.Application.Validation.Auth; namespace AdvancedTodoList.UnitTests.Validation.Auth; [TestFixture] public class RegisterDtoValidatorTests { - private const string ValidEmail = "valid@example.com"; - private const string ValidFirstName = "Joe"; - private const string ValidLastName = "Doe"; - private const string ValidUserName = "joedoe123"; - private const string ValidPassword = "Pa$$w0rd"; - - [Test] - public void Dto_ValidDate_Succeeds() - { - // Arrange - RegisterDtoValidator validator = new(); - RegisterDto dto = new(ValidEmail, ValidUserName, ValidFirstName, ValidLastName, ValidPassword); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldNotHaveAnyValidationErrors(); - } - - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] - public void Email_Empty_ReturnsPropertyRequiredError(string testCase) - { - // Arrange - RegisterDtoValidator validator = new(); - RegisterDto dto = new(testCase, ValidUserName, ValidFirstName, ValidLastName, ValidPassword); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Email) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } - - [Test] - [TestCase("@")] - [TestCase("not@")] - [TestCase("@mail")] - [TestCase("mail.example.com")] - public void Email_IsNotValid_ReturnsInvalidEmailError(string testCase) - { - // Arrange - RegisterDtoValidator validator = new(); - RegisterDto dto = new(testCase, ValidUserName, ValidFirstName, ValidLastName, ValidPassword); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Email) - .WithErrorCode(ValidationErrorCodes.InvalidEmail); - } - - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] - public void UserName_Empty_ReturnsPropertyRequiredError(string testCase) - { - // Arrange - RegisterDtoValidator validator = new(); - RegisterDto dto = new(ValidEmail, testCase, ValidFirstName, ValidLastName, ValidPassword); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.UserName) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } - - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] - public void FirstName_Empty_ReturnsPropertyRequiredError(string testCase) - { - // Arrange - RegisterDtoValidator validator = new(); - RegisterDto dto = new(ValidEmail, ValidUserName, testCase, ValidLastName, ValidPassword); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.FirstName) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } - - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] - public void LastName_Empty_ReturnsPropertyRequiredError(string testCase) - { - // Arrange - RegisterDtoValidator validator = new(); - RegisterDto dto = new(ValidEmail, ValidUserName, ValidFirstName, testCase, ValidPassword); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.LastName) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } - - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] - public void Password_Empty_ReturnsPropertyRequiredError(string testCase) - { - // Arrange - RegisterDtoValidator validator = new(); - RegisterDto dto = new(ValidEmail, ValidUserName, ValidFirstName, ValidLastName, testCase); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Password) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } + private const string ValidEmail = "valid@example.com"; + private const string ValidFirstName = "Joe"; + private const string ValidLastName = "Doe"; + private const string ValidUserName = "joedoe123"; + private const string ValidPassword = "Pa$$w0rd"; + + [Test] + public void Dto_ValidDate_Succeeds() + { + // Arrange + RegisterDtoValidator validator = new(); + RegisterDto dto = new(ValidEmail, ValidUserName, ValidFirstName, ValidLastName, ValidPassword); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } + + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] + public void Email_Empty_ReturnsPropertyRequiredError(string testCase) + { + // Arrange + RegisterDtoValidator validator = new(); + RegisterDto dto = new(testCase, ValidUserName, ValidFirstName, ValidLastName, ValidPassword); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Email) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } + + [Test] + [TestCase("@")] + [TestCase("not@")] + [TestCase("@mail")] + [TestCase("mail.example.com")] + public void Email_IsNotValid_ReturnsInvalidEmailError(string testCase) + { + // Arrange + RegisterDtoValidator validator = new(); + RegisterDto dto = new(testCase, ValidUserName, ValidFirstName, ValidLastName, ValidPassword); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Email) + .WithErrorCode(ValidationErrorCodes.InvalidEmail); + } + + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] + public void UserName_Empty_ReturnsPropertyRequiredError(string testCase) + { + // Arrange + RegisterDtoValidator validator = new(); + RegisterDto dto = new(ValidEmail, testCase, ValidFirstName, ValidLastName, ValidPassword); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.UserName) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } + + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] + public void FirstName_Empty_ReturnsPropertyRequiredError(string testCase) + { + // Arrange + RegisterDtoValidator validator = new(); + RegisterDto dto = new(ValidEmail, ValidUserName, testCase, ValidLastName, ValidPassword); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.FirstName) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } + + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] + public void LastName_Empty_ReturnsPropertyRequiredError(string testCase) + { + // Arrange + RegisterDtoValidator validator = new(); + RegisterDto dto = new(ValidEmail, ValidUserName, ValidFirstName, testCase, ValidPassword); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.LastName) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } + + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] + public void Password_Empty_ReturnsPropertyRequiredError(string testCase) + { + // Arrange + RegisterDtoValidator validator = new(); + RegisterDto dto = new(ValidEmail, ValidUserName, ValidFirstName, ValidLastName, testCase); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Password) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } } diff --git a/AdvancedTodoList.UnitTests/Validation/PaginationParametersValidatorTests.cs b/AdvancedTodoList.UnitTests/Validation/PaginationParametersValidatorTests.cs index aff4adb..3552eae 100644 --- a/AdvancedTodoList.UnitTests/Validation/PaginationParametersValidatorTests.cs +++ b/AdvancedTodoList.UnitTests/Validation/PaginationParametersValidatorTests.cs @@ -1,62 +1,62 @@ -using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Validation; +using AdvancedTodoList.Application.Validation; +using AdvancedTodoList.Core.Pagination; namespace AdvancedTodoList.UnitTests.Validation; [TestFixture] public class PaginationParametersValidatorTests { - private const int ValidPage = 4; - private const int ValidPageSize = 20; - - [Test] - public void Parameters_Valid_Succeeds() - { - // Arrange - PaginationParametersValidator validator = new(); - PaginationParameters parameters = new(ValidPage, ValidPageSize); - - // Act - var result = validator.TestValidate(parameters); - - // Assert - result.ShouldNotHaveAnyValidationErrors(); - } - - [Test] - [TestCase(0)] - [TestCase(-1)] - public void Page_OutOfRange_ReturnsPropertyOutOfRangeError(int testCase) - { - // Arrange - PaginationParametersValidator validator = new(); - PaginationParameters parameters = new(testCase, ValidPageSize); - - // Act - var result = validator.TestValidate(parameters); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Page) - .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange); - } - - [Test] - [TestCase(0)] - [TestCase(-1)] - [TestCase(1_000_000)] - [TestCase(PaginationParameters.MinPageSize - 1)] - [TestCase(PaginationParameters.MaxPageSize + 1)] - public void PageSize_OutOfRange_ReturnsPropertyOutOfRangeError(int testCase) - { - // Arrange - PaginationParametersValidator validator = new(); - PaginationParameters parameters = new(ValidPage, testCase); - - // Act - var result = validator.TestValidate(parameters); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.PageSize) - .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange); - } + private const int ValidPage = 4; + private const int ValidPageSize = 20; + + [Test] + public void Parameters_Valid_Succeeds() + { + // Arrange + PaginationParametersValidator validator = new(); + PaginationParameters parameters = new(ValidPage, ValidPageSize); + + // Act + var result = validator.TestValidate(parameters); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } + + [Test] + [TestCase(0)] + [TestCase(-1)] + public void Page_OutOfRange_ReturnsPropertyOutOfRangeError(int testCase) + { + // Arrange + PaginationParametersValidator validator = new(); + PaginationParameters parameters = new(testCase, ValidPageSize); + + // Act + var result = validator.TestValidate(parameters); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Page) + .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange); + } + + [Test] + [TestCase(0)] + [TestCase(-1)] + [TestCase(1_000_000)] + [TestCase(PaginationParameters.MinPageSize - 1)] + [TestCase(PaginationParameters.MaxPageSize + 1)] + public void PageSize_OutOfRange_ReturnsPropertyOutOfRangeError(int testCase) + { + // Arrange + PaginationParametersValidator validator = new(); + PaginationParameters parameters = new(ValidPage, testCase); + + // Act + var result = validator.TestValidate(parameters); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.PageSize) + .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange); + } } diff --git a/AdvancedTodoList.UnitTests/Validation/TodoItemCreateDtoValidatorTests.cs b/AdvancedTodoList.UnitTests/Validation/TodoItemCreateDtoValidatorTests.cs index a0966c9..a43f53a 100644 --- a/AdvancedTodoList.UnitTests/Validation/TodoItemCreateDtoValidatorTests.cs +++ b/AdvancedTodoList.UnitTests/Validation/TodoItemCreateDtoValidatorTests.cs @@ -1,156 +1,156 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Validation; using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Validation; namespace AdvancedTodoList.UnitTests.Validation; [TestFixture] public class TodoItemCreateDtoValidatorTests { - private const string ValidName = "Valid"; - private const string ValidDescription = "Valid description"; - private const int ValidPriority = 4; - private const int ValidCategoryId = 3; - private static DateTime? ValidDeadline => DateTime.UtcNow + TimeSpan.FromDays(14); - - [Test] - public void Dto_ValidData_Succeeds() - { - // Arrange - TodoItemCreateDtoValidator validator = new(); - TodoItemCreateDto dto = new(ValidName, ValidDescription, ValidDeadline, ValidPriority, ValidCategoryId); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldNotHaveAnyValidationErrors(); - } - - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] - public void Name_Empty_ReturnsPropertyRequiredError(string testCase) - { - // Arrange - TodoItemCreateDtoValidator validator = new(); - TodoItemCreateDto dto = new(testCase, ValidDescription, ValidDeadline, ValidPriority, ValidCategoryId); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Name) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } - - [Test] - public void Name_TooLong_ReturnsPropertyTooLongError() - { - // Arrange - TodoItemCreateDtoValidator validator = new(); - string longName = new('X', TodoItem.NameMaxLength + 1); - TodoItemCreateDto dto = new(longName, ValidDescription, ValidDeadline, ValidPriority, ValidCategoryId); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Name) - .WithErrorCode(ValidationErrorCodes.PropertyTooLong); - } - - [Test] - public void Description_Null_ReturnsPropertyRequiredError() - { - // Arrange - TodoItemCreateDtoValidator validator = new(); - TodoItemCreateDto dto = new(ValidName, null!, ValidDeadline, ValidPriority, ValidCategoryId); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Description) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } - - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyNotNullStrings))] - public void Description_EmptyAndNotNull_IsAllowed(string testCase) - { - // Arrange - TodoItemCreateDtoValidator validator = new(); - TodoItemCreateDto dto = new(ValidName, testCase, ValidDeadline, ValidPriority, ValidCategoryId); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldNotHaveValidationErrorFor(x => x.Description); - } - - [Test] - public void Description_TooLong_ReturnsPropertyTooLongError() - { - // Arrange - TodoItemCreateDtoValidator validator = new(); - string longDescription = new('X', TodoItem.DescriptionMaxLength + 1); - TodoItemCreateDto dto = new(ValidName, longDescription, ValidDeadline, ValidPriority, ValidCategoryId); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Description) - .WithErrorCode(ValidationErrorCodes.PropertyTooLong); - } - - [Test] - public void DeadlineDate_BeforeCurrentDate_ReturnsPropertyOutOfRangeError() - { - // Arrange - TodoItemCreateDtoValidator validator = new(); - TodoItemCreateDto dto = new(ValidName, ValidDescription, - DateTime.UtcNow - TimeSpan.FromDays(1), ValidPriority, ValidCategoryId); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.DeadlineDate) - .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange); - } - - [Test] - [TestCase(-1)] - [TestCase(11)] - public void Priority_OutOfRange_ReturnsPropertyOutOfRangeError(int testCase) - { - // Arrange - TodoItemCreateDtoValidator validator = new(); - TodoItemCreateDto dto = new(ValidName, ValidDescription, - ValidDeadline, testCase, ValidCategoryId); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Priority) - .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange); - } - - [Test] - public void CategoryId_Null_Allowed() - { - // Arrange - TodoItemCreateDtoValidator validator = new(); - TodoItemCreateDto dto = new(ValidName, ValidDescription, - ValidDeadline, ValidPriority, null); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldNotHaveValidationErrorFor(x => x.CategoryId); - } + private const string ValidName = "Valid"; + private const string ValidDescription = "Valid description"; + private const int ValidPriority = 4; + private const int ValidCategoryId = 3; + private static DateTime? ValidDeadline => DateTime.UtcNow + TimeSpan.FromDays(14); + + [Test] + public void Dto_ValidData_Succeeds() + { + // Arrange + TodoItemCreateDtoValidator validator = new(); + TodoItemCreateDto dto = new(ValidName, ValidDescription, ValidDeadline, ValidPriority, ValidCategoryId); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } + + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] + public void Name_Empty_ReturnsPropertyRequiredError(string testCase) + { + // Arrange + TodoItemCreateDtoValidator validator = new(); + TodoItemCreateDto dto = new(testCase, ValidDescription, ValidDeadline, ValidPriority, ValidCategoryId); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Name) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } + + [Test] + public void Name_TooLong_ReturnsPropertyTooLongError() + { + // Arrange + TodoItemCreateDtoValidator validator = new(); + string longName = new('X', TodoItem.NameMaxLength + 1); + TodoItemCreateDto dto = new(longName, ValidDescription, ValidDeadline, ValidPriority, ValidCategoryId); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Name) + .WithErrorCode(ValidationErrorCodes.PropertyTooLong); + } + + [Test] + public void Description_Null_ReturnsPropertyRequiredError() + { + // Arrange + TodoItemCreateDtoValidator validator = new(); + TodoItemCreateDto dto = new(ValidName, null!, ValidDeadline, ValidPriority, ValidCategoryId); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Description) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } + + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyNotNullStrings))] + public void Description_EmptyAndNotNull_IsAllowed(string testCase) + { + // Arrange + TodoItemCreateDtoValidator validator = new(); + TodoItemCreateDto dto = new(ValidName, testCase, ValidDeadline, ValidPriority, ValidCategoryId); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldNotHaveValidationErrorFor(x => x.Description); + } + + [Test] + public void Description_TooLong_ReturnsPropertyTooLongError() + { + // Arrange + TodoItemCreateDtoValidator validator = new(); + string longDescription = new('X', TodoItem.DescriptionMaxLength + 1); + TodoItemCreateDto dto = new(ValidName, longDescription, ValidDeadline, ValidPriority, ValidCategoryId); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Description) + .WithErrorCode(ValidationErrorCodes.PropertyTooLong); + } + + [Test] + public void DeadlineDate_BeforeCurrentDate_ReturnsPropertyOutOfRangeError() + { + // Arrange + TodoItemCreateDtoValidator validator = new(); + TodoItemCreateDto dto = new(ValidName, ValidDescription, + DateTime.UtcNow - TimeSpan.FromDays(1), ValidPriority, ValidCategoryId); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.DeadlineDate) + .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange); + } + + [Test] + [TestCase(-1)] + [TestCase(11)] + public void Priority_OutOfRange_ReturnsPropertyOutOfRangeError(int testCase) + { + // Arrange + TodoItemCreateDtoValidator validator = new(); + TodoItemCreateDto dto = new(ValidName, ValidDescription, + ValidDeadline, testCase, ValidCategoryId); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Priority) + .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange); + } + + [Test] + public void CategoryId_Null_Allowed() + { + // Arrange + TodoItemCreateDtoValidator validator = new(); + TodoItemCreateDto dto = new(ValidName, ValidDescription, + ValidDeadline, ValidPriority, null); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldNotHaveValidationErrorFor(x => x.CategoryId); + } } diff --git a/AdvancedTodoList.UnitTests/Validation/TodoItemUpdateStateDtoValidatorTests.cs b/AdvancedTodoList.UnitTests/Validation/TodoItemUpdateStateDtoValidatorTests.cs index 41ccf03..dafe38e 100644 --- a/AdvancedTodoList.UnitTests/Validation/TodoItemUpdateStateDtoValidatorTests.cs +++ b/AdvancedTodoList.UnitTests/Validation/TodoItemUpdateStateDtoValidatorTests.cs @@ -1,44 +1,44 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Validation; using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Validation; namespace AdvancedTodoList.UnitTests.Validation; [TestFixture] public class TodoItemUpdateStateDtoValidatorTests { - [Test] - [TestCase(TodoItemState.Active)] - [TestCase(TodoItemState.Completed)] - [TestCase(TodoItemState.Skipped)] - public void State_Valid_Succeeds(TodoItemState testCase) - { - // Arrange - TodoItemUpdateStateDtoValidator validator = new(); - TodoItemUpdateStateDto dto = new(testCase); + [Test] + [TestCase(TodoItemState.Active)] + [TestCase(TodoItemState.Completed)] + [TestCase(TodoItemState.Skipped)] + public void State_Valid_Succeeds(TodoItemState testCase) + { + // Arrange + TodoItemUpdateStateDtoValidator validator = new(); + TodoItemUpdateStateDto dto = new(testCase); - // Act - var result = validator.TestValidate(dto); + // Act + var result = validator.TestValidate(dto); - // Assert - result.ShouldNotHaveAnyValidationErrors(); - } + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } - [Test] - [TestCase((TodoItemState)6)] - [TestCase((TodoItemState)124)] - [TestCase((TodoItemState)200)] - public void State_OutOfRange_ReturnsPropertyOutOfRangeError(TodoItemState testCase) - { - // Arrange - TodoItemUpdateStateDtoValidator validator = new(); - TodoItemUpdateStateDto dto = new(testCase); + [Test] + [TestCase((TodoItemState)6)] + [TestCase((TodoItemState)124)] + [TestCase((TodoItemState)200)] + public void State_OutOfRange_ReturnsPropertyOutOfRangeError(TodoItemState testCase) + { + // Arrange + TodoItemUpdateStateDtoValidator validator = new(); + TodoItemUpdateStateDto dto = new(testCase); - // Act - var result = validator.TestValidate(dto); + // Act + var result = validator.TestValidate(dto); - // Assert - result.ShouldHaveValidationErrorFor(x => x.State) - .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange); - } + // Assert + result.ShouldHaveValidationErrorFor(x => x.State) + .WithErrorCode(ValidationErrorCodes.PropertyOutOfRange); + } } diff --git a/AdvancedTodoList.UnitTests/Validation/TodoListCreateDtoValidatorTests.cs b/AdvancedTodoList.UnitTests/Validation/TodoListCreateDtoValidatorTests.cs index ae6654c..0024309 100644 --- a/AdvancedTodoList.UnitTests/Validation/TodoListCreateDtoValidatorTests.cs +++ b/AdvancedTodoList.UnitTests/Validation/TodoListCreateDtoValidatorTests.cs @@ -1,104 +1,104 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Validation; using AdvancedTodoList.Core.Models.TodoLists; -using AdvancedTodoList.Core.Validation; namespace AdvancedTodoList.UnitTests.Validation; [TestFixture] public class TodoListCreateDtoValidatorTests { - private const string ValidName = "Valid"; - private const string ValidDescription = "Valid description"; - - [Test] - public void Dto_ValidData_Succeeds() - { - // Arrange - TodoListCreateDtoValidator validator = new(); - TodoListCreateDto dto = new(ValidName, ValidDescription); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldNotHaveAnyValidationErrors(); - } - - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] - public void Name_Empty_ReturnsPropertyRequiredError(string testCase) - { - // Arrange - TodoListCreateDtoValidator validator = new(); - TodoListCreateDto dto = new(testCase, ValidDescription); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Name) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } - - [Test] - public void Name_TooLong_ReturnsPropertyTooLongError() - { - // Arrange - TodoListCreateDtoValidator validator = new(); - string longName = new('X', TodoList.NameMaxLength + 1); - TodoListCreateDto dto = new(longName, ValidDescription); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Name) - .WithErrorCode(ValidationErrorCodes.PropertyTooLong); - } - - [Test] - public void Description_Null_ReturnsPropertyRequiredError() - { - // Arrange - TodoListCreateDtoValidator validator = new(); - TodoListCreateDto dto = new(ValidName, null!); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Description) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } - - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyNotNullStrings))] - public void Description_EmptyAndNotNull_IsAllowed(string testCase) - { - // Arrange - TodoListCreateDtoValidator validator = new(); - TodoListCreateDto dto = new(ValidName, testCase); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldNotHaveValidationErrorFor(x => x.Description); - } - - [Test] - public void Description_TooLong_ReturnsPropertyTooLongError() - { - // Arrange - TodoListCreateDtoValidator validator = new(); - string longDescription = new('X', TodoList.DescriptionMaxLength + 1); - TodoListCreateDto dto = new(ValidName, longDescription); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Description) - .WithErrorCode(ValidationErrorCodes.PropertyTooLong); - } + private const string ValidName = "Valid"; + private const string ValidDescription = "Valid description"; + + [Test] + public void Dto_ValidData_Succeeds() + { + // Arrange + TodoListCreateDtoValidator validator = new(); + TodoListCreateDto dto = new(ValidName, ValidDescription); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } + + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] + public void Name_Empty_ReturnsPropertyRequiredError(string testCase) + { + // Arrange + TodoListCreateDtoValidator validator = new(); + TodoListCreateDto dto = new(testCase, ValidDescription); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Name) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } + + [Test] + public void Name_TooLong_ReturnsPropertyTooLongError() + { + // Arrange + TodoListCreateDtoValidator validator = new(); + string longName = new('X', TodoList.NameMaxLength + 1); + TodoListCreateDto dto = new(longName, ValidDescription); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Name) + .WithErrorCode(ValidationErrorCodes.PropertyTooLong); + } + + [Test] + public void Description_Null_ReturnsPropertyRequiredError() + { + // Arrange + TodoListCreateDtoValidator validator = new(); + TodoListCreateDto dto = new(ValidName, null!); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Description) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } + + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyNotNullStrings))] + public void Description_EmptyAndNotNull_IsAllowed(string testCase) + { + // Arrange + TodoListCreateDtoValidator validator = new(); + TodoListCreateDto dto = new(ValidName, testCase); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldNotHaveValidationErrorFor(x => x.Description); + } + + [Test] + public void Description_TooLong_ReturnsPropertyTooLongError() + { + // Arrange + TodoListCreateDtoValidator validator = new(); + string longDescription = new('X', TodoList.DescriptionMaxLength + 1); + TodoListCreateDto dto = new(ValidName, longDescription); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Description) + .WithErrorCode(ValidationErrorCodes.PropertyTooLong); + } } diff --git a/AdvancedTodoList.UnitTests/Validation/TodoListMemberAddDtoValidatorTests.cs b/AdvancedTodoList.UnitTests/Validation/TodoListMemberAddDtoValidatorTests.cs index 837b130..3a37945 100644 --- a/AdvancedTodoList.UnitTests/Validation/TodoListMemberAddDtoValidatorTests.cs +++ b/AdvancedTodoList.UnitTests/Validation/TodoListMemberAddDtoValidatorTests.cs @@ -1,68 +1,68 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Validation; using AdvancedTodoList.Core.Models.Auth; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.UnitTests.Validation; +using AdvancedTodoList.Core.Repositories; -namespace AdvancedTodoList.Core.Validation; +namespace AdvancedTodoList.UnitTests.Validation; [TestFixture] public class TodoListMemberAddDtoValidatorTests { - private TodoListMemberAddDtoValidator _validator; + private TodoListMemberAddDtoValidator _validator; - public const string ValidUserId = "ValidUserId"; - public const int ValidRoleId = 121; + public const string ValidUserId = "ValidUserId"; + public const int ValidRoleId = 121; - [SetUp] - public void SetUp() - { - IEntityExistenceChecker existenceChecker = Substitute.For(); - existenceChecker.ExistsAsync(Arg.Any()) - .Returns(false); - existenceChecker.ExistsAsync(ValidUserId) - .Returns(true); - _validator = new TodoListMemberAddDtoValidator(existenceChecker); - } + [SetUp] + public void SetUp() + { + IEntityExistenceChecker existenceChecker = Substitute.For(); + existenceChecker.ExistsAsync(Arg.Any()) + .Returns(false); + existenceChecker.ExistsAsync(ValidUserId) + .Returns(true); + _validator = new TodoListMemberAddDtoValidator(existenceChecker); + } - [Test] - public async Task ValidDto_Succeeds() - { - // Arrange - TodoListMemberAddDto dto = new(ValidUserId); + [Test] + public async Task ValidDto_Succeeds() + { + // Arrange + TodoListMemberAddDto dto = new(ValidUserId); - // Act - var result = await _validator.TestValidateAsync(dto); + // Act + var result = await _validator.TestValidateAsync(dto); - // Assert - result.ShouldNotHaveAnyValidationErrors(); - } + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] - public async Task UserId_Empty_ReturnsPropertyRequiresError(string testCase) - { - // Arrange - TodoListMemberAddDto dto = new(testCase); + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] + public async Task UserId_Empty_ReturnsPropertyRequiresError(string testCase) + { + // Arrange + TodoListMemberAddDto dto = new(testCase); - // Act - var result = await _validator.TestValidateAsync(dto); + // Act + var result = await _validator.TestValidateAsync(dto); - // Assert - result.ShouldHaveValidationErrorFor(x => x.UserId) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } + // Assert + result.ShouldHaveValidationErrorFor(x => x.UserId) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } - [Test] - public async Task UserId_Invalid_ReturnsInvalidForeignKeyError() - { - // Arrange - TodoListMemberAddDto dto = new("Invalid"); + [Test] + public async Task UserId_Invalid_ReturnsInvalidForeignKeyError() + { + // Arrange + TodoListMemberAddDto dto = new("Invalid"); - // Act - var result = await _validator.TestValidateAsync(dto); + // Act + var result = await _validator.TestValidateAsync(dto); - // Assert - result.ShouldHaveValidationErrorFor(x => x.UserId) - .WithErrorCode(ValidationErrorCodes.InvalidForeignKey); - } + // Assert + result.ShouldHaveValidationErrorFor(x => x.UserId) + .WithErrorCode(ValidationErrorCodes.InvalidForeignKey); + } } diff --git a/AdvancedTodoList.UnitTests/Validation/TodoListRoleCreateDtoValidatorTests.cs b/AdvancedTodoList.UnitTests/Validation/TodoListRoleCreateDtoValidatorTests.cs index e13d320..e305e36 100644 --- a/AdvancedTodoList.UnitTests/Validation/TodoListRoleCreateDtoValidatorTests.cs +++ b/AdvancedTodoList.UnitTests/Validation/TodoListRoleCreateDtoValidatorTests.cs @@ -1,58 +1,58 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Validation; using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Validation; namespace AdvancedTodoList.UnitTests.Validation; [TestFixture] public class TodoListRoleCreateDtoValidatorTests { - private const string ValidName = "Role"; - private readonly RolePermissions ValidPermissions = new(true, true, false, true); - - [Test] - public void Dto_ValidData_Succeeds() - { - // Arrange - TodoListRoleCreateDtoValidator validator = new(); - TodoListRoleCreateDto dto = new(ValidName, 5, ValidPermissions); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldNotHaveAnyValidationErrors(); - } - - [Test] - [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] - public void Name_Empty_ReturnsPropertyRequiredError(string testCase) - { - // Arrange - TodoListRoleCreateDtoValidator validator = new(); - TodoListRoleCreateDto dto = new(testCase, 5, ValidPermissions); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Name) - .WithErrorCode(ValidationErrorCodes.PropertyRequired); - } - - [Test] - public void Name_TooLong_ReturnsPropertyTooLongError() - { - // Arrange - TodoListRoleCreateDtoValidator validator = new(); - string longName = new('X', TodoListRole.NameMaxLength + 1); - TodoListRoleCreateDto dto = new(longName, 5, ValidPermissions); - - // Act - var result = validator.TestValidate(dto); - - // Assert - result.ShouldHaveValidationErrorFor(x => x.Name) - .WithErrorCode(ValidationErrorCodes.PropertyTooLong); - } + private const string ValidName = "Role"; + private readonly RolePermissions ValidPermissions = new(true, true, false, true); + + [Test] + public void Dto_ValidData_Succeeds() + { + // Arrange + TodoListRoleCreateDtoValidator validator = new(); + TodoListRoleCreateDto dto = new(ValidName, 5, ValidPermissions); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldNotHaveAnyValidationErrors(); + } + + [Test] + [TestCaseSource(typeof(ValidationConstants), nameof(ValidationConstants.EmptyStrings))] + public void Name_Empty_ReturnsPropertyRequiredError(string testCase) + { + // Arrange + TodoListRoleCreateDtoValidator validator = new(); + TodoListRoleCreateDto dto = new(testCase, 5, ValidPermissions); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Name) + .WithErrorCode(ValidationErrorCodes.PropertyRequired); + } + + [Test] + public void Name_TooLong_ReturnsPropertyTooLongError() + { + // Arrange + TodoListRoleCreateDtoValidator validator = new(); + string longName = new('X', TodoListRole.NameMaxLength + 1); + TodoListRoleCreateDto dto = new(longName, 5, ValidPermissions); + + // Act + var result = validator.TestValidate(dto); + + // Assert + result.ShouldHaveValidationErrorFor(x => x.Name) + .WithErrorCode(ValidationErrorCodes.PropertyTooLong); + } } diff --git a/AdvancedTodoList.UnitTests/Validation/ValidationConstants.cs b/AdvancedTodoList.UnitTests/Validation/ValidationConstants.cs index b8ab488..0299e8c 100644 --- a/AdvancedTodoList.UnitTests/Validation/ValidationConstants.cs +++ b/AdvancedTodoList.UnitTests/Validation/ValidationConstants.cs @@ -2,17 +2,17 @@ public static class ValidationConstants { - public static readonly string[] EmptyNotNullStrings = - [ - string.Empty, - " ", - "\r", - "\t", - " \r \t " - ]; - public static readonly string[] EmptyStrings = - [ - null!, - .. EmptyNotNullStrings - ]; + public static readonly string[] EmptyNotNullStrings = + [ + string.Empty, + " ", + "\r", + "\t", + " \r \t " + ]; + public static readonly string[] EmptyStrings = + [ + null!, + .. EmptyNotNullStrings + ]; } diff --git a/AdvancedTodoList.sln b/AdvancedTodoList.sln index 0c9a0a0..0464c0f 100644 --- a/AdvancedTodoList.sln +++ b/AdvancedTodoList.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdvancedTodoList", "AdvancedTodoList\AdvancedTodoList.csproj", "{5A6E74CD-0DD8-4EDB-B01E-13F7B0A29013}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdvancedTodoList.WebApp", "AdvancedTodoList\AdvancedTodoList.WebApp.csproj", "{5A6E74CD-0DD8-4EDB-B01E-13F7B0A29013}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdvancedTodoList.Core", "AdvancedTodoList.Core\AdvancedTodoList.Core.csproj", "{316DE5AB-84EC-46B3-82EE-A3F159750971}" EndProject @@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdvancedTodoList.UnitTests" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdvancedTodoList.IntegrationTests", "AdvancedTodoList.IntegrationTests\AdvancedTodoList.IntegrationTests.csproj", "{443E81D2-5B70-4CDD-86E9-8184CA004684}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AdvancedTodoList.Application", "AdvancedTodoList.Application\AdvancedTodoList.Application.csproj", "{0D300C31-2F24-4F4F-AABD-05857D75E657}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +41,10 @@ Global {443E81D2-5B70-4CDD-86E9-8184CA004684}.Debug|Any CPU.Build.0 = Debug|Any CPU {443E81D2-5B70-4CDD-86E9-8184CA004684}.Release|Any CPU.ActiveCfg = Release|Any CPU {443E81D2-5B70-4CDD-86E9-8184CA004684}.Release|Any CPU.Build.0 = Release|Any CPU + {0D300C31-2F24-4F4F-AABD-05857D75E657}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D300C31-2F24-4F4F-AABD-05857D75E657}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D300C31-2F24-4F4F-AABD-05857D75E657}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D300C31-2F24-4F4F-AABD-05857D75E657}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/AdvancedTodoList/AdvancedTodoList.csproj b/AdvancedTodoList/AdvancedTodoList.WebApp.csproj similarity index 76% rename from AdvancedTodoList/AdvancedTodoList.csproj rename to AdvancedTodoList/AdvancedTodoList.WebApp.csproj index 3e9c79d..2f39d62 100644 --- a/AdvancedTodoList/AdvancedTodoList.csproj +++ b/AdvancedTodoList/AdvancedTodoList.WebApp.csproj @@ -17,17 +17,20 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + diff --git a/AdvancedTodoList/Controllers/AuthController.cs b/AdvancedTodoList/Controllers/AuthController.cs index acd2a78..795a40d 100644 --- a/AdvancedTodoList/Controllers/AuthController.cs +++ b/AdvancedTodoList/Controllers/AuthController.cs @@ -1,94 +1,94 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Services.Auth; -using AdvancedTodoList.Extensions; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.WebApp.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace AdvancedTodoList.Controllers; +namespace AdvancedTodoList.WebApp.Controllers; [ApiController] [Route("api/auth")] public class AuthController(IAuthService authService, ILogger logger) : ControllerBase { - private readonly IAuthService _authService = authService; - private readonly ILogger _logger = logger; + private readonly IAuthService _authService = authService; + private readonly ILogger _logger = logger; - /// - /// Accepts user credentials and returns access and refresh tokens. - /// - /// Returns access and refresh tokens. - /// Validation failed. - /// Invalid credentials. - [HttpPost("logIn")] - [ProducesResponseType(typeof(LogInResponse), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task LogInAsync([FromBody] LogInDto logInDto) - { - var result = await _authService.LogInAsync(logInDto); - if (result == null) return Unauthorized(); - return Ok(result); - } + /// + /// Accepts user credentials and returns access and refresh tokens. + /// + /// Returns access and refresh tokens. + /// Validation failed. + /// Invalid credentials. + [HttpPost("logIn")] + [ProducesResponseType(typeof(LogInResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task LogInAsync([FromBody] LogInDto logInDto) + { + var result = await _authService.LogInAsync(logInDto); + if (result == null) return Unauthorized(); + return Ok(result); + } - /// - /// Registers a user. - /// - /// Success. - /// Validation failed. - [HttpPost("register")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task RegisterAsync([FromBody] RegisterDto registerDto) - { - var result = await _authService.RegisterAsync(registerDto); - if (result.IsSuccess) return NoContent(); + /// + /// Registers a user. + /// + /// Success. + /// Validation failed. + [HttpPost("register")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task RegisterAsync([FromBody] RegisterDto registerDto) + { + var result = await _authService.RegisterAsync(registerDto); + if (result.IsSuccess) return NoContent(); - // Add errors to the model state - foreach (var error in result.Errors) - { - ModelState.AddModelError(error.Property, error.Message); - } - return BadRequest(ModelState); - } + // Add errors to the model state + foreach (var error in result.Errors) + { + ModelState.AddModelError(error.Property, error.Message); + } + return BadRequest(ModelState); + } - /// - /// Accepts an old access token with a valid refresh token and returns a new access token. - /// - /// Returns access and refresh tokens. - /// Validation failed. - /// Invalid access/refresh token. - [HttpPost("refresh")] - [ProducesResponseType(typeof(LogInResponse), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task RefreshAsync([FromBody] RefreshDto refreshDto) - { - var result = await _authService.RefreshAsync(refreshDto); - if (result == null) return Unauthorized(); + /// + /// Accepts an old access token with a valid refresh token and returns a new access token. + /// + /// Returns access and refresh tokens. + /// Validation failed. + /// Invalid access/refresh token. + [HttpPost("refresh")] + [ProducesResponseType(typeof(LogInResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task RefreshAsync([FromBody] RefreshDto refreshDto) + { + var result = await _authService.RefreshAsync(refreshDto); + if (result == null) return Unauthorized(); - return Ok(result); - } + return Ok(result); + } - /// - /// Revokes specified refresh token for the current user. - /// - /// Success. - /// Validation failed. - /// Authorization failed/invalid refresh token. - [Authorize] - [HttpPost("logOut")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task LogOutAsync([FromBody] LogOutDto logOutDto) - { - string? userId = User.GetUserId(); - if (userId == null) - { - _logger.LogWarning("User ID could not be found in claims."); - return Unauthorized(); - } - return await _authService.LogOutAsync(userId, logOutDto) ? - NoContent() : Unauthorized(); - } + /// + /// Revokes specified refresh token for the current user. + /// + /// Success. + /// Validation failed. + /// Authorization failed/invalid refresh token. + [Authorize] + [HttpPost("logOut")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task LogOutAsync([FromBody] LogOutDto logOutDto) + { + string? userId = User.GetUserId(); + if (userId == null) + { + _logger.LogWarning("User ID could not be found in claims."); + return Unauthorized(); + } + return await _authService.LogOutAsync(userId, logOutDto) ? + NoContent() : Unauthorized(); + } } diff --git a/AdvancedTodoList/Controllers/InvitationLinksController.cs b/AdvancedTodoList/Controllers/InvitationLinksController.cs index 9e924b5..b417e83 100644 --- a/AdvancedTodoList/Controllers/InvitationLinksController.cs +++ b/AdvancedTodoList/Controllers/InvitationLinksController.cs @@ -1,80 +1,80 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Extensions; +using AdvancedTodoList.WebApp.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace AdvancedTodoList.Controllers; +namespace AdvancedTodoList.WebApp.Controllers; [Authorize] [ApiController] [Route("api/todo/{listId}/invitationLinks")] public class InvitationLinksController(IInvitationLinksService invitationLinksService) : ControllerBase { - private readonly IInvitationLinksService _invitationLinksService = invitationLinksService; + private readonly IInvitationLinksService _invitationLinksService = invitationLinksService; - /// - /// Gets a page with invitation links of the to-do list with the specified ID. - /// - /// ID of the to-do list. - /// Paginations parameters to apply. - /// Returns invitation links of the to-do list. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpGet(Name = nameof(GetInvitationLinksAsync))] - [ProducesResponseType(typeof(Page), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetInvitationLinksAsync( - [FromRoute] string listId, [FromQuery] PaginationParameters paginationParameters) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _invitationLinksService.GetInvitationLinksAsync(context, paginationParameters); - return result.ToActionResult(); - } + /// + /// Gets a page with invitation links of the to-do list with the specified ID. + /// + /// ID of the to-do list. + /// Paginations parameters to apply. + /// Returns invitation links of the to-do list. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpGet(Name = nameof(GetInvitationLinksAsync))] + [ProducesResponseType(typeof(Page), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetInvitationLinksAsync( + [FromRoute] string listId, [FromQuery] PaginationParameters paginationParameters) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _invitationLinksService.GetInvitationLinksAsync(context, paginationParameters); + return result.ToActionResult(); + } - /// - /// Creates a new to-do list invitation link. - /// - /// ID of the to-do list which will contain the invitation link. - /// Successfully created. - /// Validation failed. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpPost] - [ProducesResponseType(typeof(InvitationLinkDto), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PostInvitationLinkAsync([FromRoute] string listId) - { - TodoListContext context = new(listId, User.GetUserId()!); - var response = await _invitationLinksService.CreateAsync(context); - return response.ToActionResult(); - } + /// + /// Creates a new to-do list invitation link. + /// + /// ID of the to-do list which will contain the invitation link. + /// Successfully created. + /// Validation failed. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpPost] + [ProducesResponseType(typeof(InvitationLinkDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task PostInvitationLinkAsync([FromRoute] string listId) + { + TodoListContext context = new(listId, User.GetUserId()!); + var response = await _invitationLinksService.CreateAsync(context); + return response.ToActionResult(); + } - /// - /// Deletes a to-do list invitation link. - /// - /// Success. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list invitation link was not found. - [HttpDelete("{invitationLinkId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteInvitationLinkAsync( - [FromRoute] string listId, [FromRoute] int invitationLinkId) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _invitationLinksService.DeleteAsync(context, invitationLinkId); - return result.ToActionResult(); - } + /// + /// Deletes a to-do list invitation link. + /// + /// Success. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list invitation link was not found. + [HttpDelete("{invitationLinkId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task DeleteInvitationLinkAsync( + [FromRoute] string listId, [FromRoute] int invitationLinkId) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _invitationLinksService.DeleteAsync(context, invitationLinkId); + return result.ToActionResult(); + } } diff --git a/AdvancedTodoList/Controllers/JoinTodoListController.cs b/AdvancedTodoList/Controllers/JoinTodoListController.cs index 144a91e..45127af 100644 --- a/AdvancedTodoList/Controllers/JoinTodoListController.cs +++ b/AdvancedTodoList/Controllers/JoinTodoListController.cs @@ -1,46 +1,46 @@ -using AdvancedTodoList.Core.Dtos; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Extensions; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.WebApp.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace AdvancedTodoList.Controllers; +namespace AdvancedTodoList.WebApp.Controllers; [Authorize] [ApiController] [Route("api/todo/join")] public class JoinTodoListController(IInvitationLinksService invitationLinksService) : ControllerBase { - private readonly IInvitationLinksService _invitationLinksService = invitationLinksService; + private readonly IInvitationLinksService _invitationLinksService = invitationLinksService; - /// - /// Uses the invitation link to join the caller to a to-do list. - /// - /// Value of the invitation link. - /// Successfully joined. - /// Authentication failed. - /// Link was not found. - /// Link is expired. - /// Caller is already the member of the to-do list. - [HttpPost("{invitationLinkValue}")] - [ProducesResponseType(typeof(TodoListMemberMinimalViewDto), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - [ProducesResponseType(StatusCodes.Status410Gone)] - [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] - public async Task JoinByInvitationLinkAsync([FromRoute] string invitationLinkValue) - { - var response = await _invitationLinksService.JoinAsync(User.GetUserId()!, invitationLinkValue); + /// + /// Uses the invitation link to join the caller to a to-do list. + /// + /// Value of the invitation link. + /// Successfully joined. + /// Authentication failed. + /// Link was not found. + /// Link is expired. + /// Caller is already the member of the to-do list. + [HttpPost("{invitationLinkValue}")] + [ProducesResponseType(typeof(TodoListMemberMinimalViewDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status410Gone)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public async Task JoinByInvitationLinkAsync([FromRoute] string invitationLinkValue) + { + var response = await _invitationLinksService.JoinAsync(User.GetUserId()!, invitationLinkValue); - return response.Status switch - { - JoinByInvitationLinkStatus.Success => Ok(response.Dto), - JoinByInvitationLinkStatus.NotFound => NotFound(), - JoinByInvitationLinkStatus.Expired => Problem( - statusCode: StatusCodes.Status410Gone, detail: "Invitation link is expired."), - JoinByInvitationLinkStatus.UserIsAlreadyMember => Problem( - statusCode: StatusCodes.Status422UnprocessableEntity, detail: "Caller is already the member of the to-do list."), - _ => throw new InvalidOperationException("Invalid invitation links service response.") - }; - } + return response.Status switch + { + JoinByInvitationLinkStatus.Success => Ok(response.Dto), + JoinByInvitationLinkStatus.NotFound => NotFound(), + JoinByInvitationLinkStatus.Expired => Problem( + statusCode: StatusCodes.Status410Gone, detail: "Invitation link is expired."), + JoinByInvitationLinkStatus.UserIsAlreadyMember => Problem( + statusCode: StatusCodes.Status422UnprocessableEntity, detail: "Caller is already the member of the to-do list."), + _ => throw new InvalidOperationException("Invalid invitation links service response.") + }; + } } diff --git a/AdvancedTodoList/Controllers/TodoItemCategoriesController.cs b/AdvancedTodoList/Controllers/TodoItemCategoriesController.cs index 67c2398..71ec321 100644 --- a/AdvancedTodoList/Controllers/TodoItemCategoriesController.cs +++ b/AdvancedTodoList/Controllers/TodoItemCategoriesController.cs @@ -1,134 +1,134 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Extensions; +using AdvancedTodoList.WebApp.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace AdvancedTodoList.Controllers; +namespace AdvancedTodoList.WebApp.Controllers; [Authorize] [ApiController] [Route("api/todo/{listId}/categories")] public class TodoItemCategoriesController(ITodoItemCategoriesService categoriesService) : - ControllerBase + ControllerBase { - private readonly ITodoItemCategoriesService _categoriesService = categoriesService; + private readonly ITodoItemCategoriesService _categoriesService = categoriesService; - /// - /// Gets a page with categories of the to-do list with the specified ID. - /// - /// ID of the to-do list. - /// Paginations parameters to apply. - /// Optional name to filter roles by. - /// Returns categories of the to-do list. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpGet(Name = nameof(GetTodoListCategoriesAsync))] - [ProducesResponseType(typeof(Page), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetTodoListCategoriesAsync( - [FromRoute] string listId, [FromQuery] PaginationParameters paginationParameters, - [FromQuery] string? name) - { - TodoListContext context = new(listId, User.GetUserId()!); - var response = await _categoriesService.GetCategoriesOfListAsync(context, paginationParameters, name); - return response.ToActionResult(); - } + /// + /// Gets a page with categories of the to-do list with the specified ID. + /// + /// ID of the to-do list. + /// Paginations parameters to apply. + /// Optional name to filter roles by. + /// Returns categories of the to-do list. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpGet(Name = nameof(GetTodoListCategoriesAsync))] + [ProducesResponseType(typeof(Page), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetTodoListCategoriesAsync( + [FromRoute] string listId, [FromQuery] PaginationParameters paginationParameters, + [FromQuery] string? name) + { + TodoListContext context = new(listId, User.GetUserId()!); + var response = await _categoriesService.GetCategoriesOfListAsync(context, paginationParameters, name); + return response.ToActionResult(); + } - /// - /// Gets a to-do list category by its ID. - /// - /// ID of the to-do list which contans the category to obtain. - /// ID of the to-do list category to obtain. - /// Returns requested to-do list category. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list category was not found. - [HttpGet("{categoryId}", Name = nameof(GetTodoListCategoryByIdAsync))] - [ProducesResponseType(typeof(TodoListRoleViewDto), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetTodoListCategoryByIdAsync( - [FromRoute] string listId, [FromRoute] int categoryId) - { - TodoListContext context = new(listId, User.GetUserId()!); - var response = await _categoriesService.GetByIdAsync(context, categoryId); - return response.ToActionResult(); - } + /// + /// Gets a to-do list category by its ID. + /// + /// ID of the to-do list which contans the category to obtain. + /// ID of the to-do list category to obtain. + /// Returns requested to-do list category. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list category was not found. + [HttpGet("{categoryId}", Name = nameof(GetTodoListCategoryByIdAsync))] + [ProducesResponseType(typeof(TodoListRoleViewDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetTodoListCategoryByIdAsync( + [FromRoute] string listId, [FromRoute] int categoryId) + { + TodoListContext context = new(listId, User.GetUserId()!); + var response = await _categoriesService.GetByIdAsync(context, categoryId); + return response.ToActionResult(); + } - /// - /// Creates a new to-do list category. - /// - /// ID of the to-do list which will contain the category. - /// - /// Successfully created. - /// Validation failed. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpPost] - [ProducesResponseType(typeof(TodoListRoleViewDto), StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PostTodoItemCategoryAsync( - [FromRoute] string listId, [FromBody] TodoItemCategoryCreateDto dto) - { - TodoListContext context = new(listId, User.GetUserId()!); - var response = await _categoriesService.CreateAsync(context, dto); - if (response.Status != ServiceResponseStatus.Success || response.Result == null) - return response.ToActionResult(); + /// + /// Creates a new to-do list category. + /// + /// ID of the to-do list which will contain the category. + /// + /// Successfully created. + /// Validation failed. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpPost] + [ProducesResponseType(typeof(TodoListRoleViewDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task PostTodoItemCategoryAsync( + [FromRoute] string listId, [FromBody] TodoItemCategoryCreateDto dto) + { + TodoListContext context = new(listId, User.GetUserId()!); + var response = await _categoriesService.CreateAsync(context, dto); + if (response.Status != ServiceResponseStatus.Success || response.Result == null) + return response.ToActionResult(); - var routeValues = new { listId, categoryId = response.Result.Id }; - return CreatedAtRoute(nameof(GetTodoListCategoryByIdAsync), routeValues, response.Result); - } + var routeValues = new { listId, categoryId = response.Result.Id }; + return CreatedAtRoute(nameof(GetTodoListCategoryByIdAsync), routeValues, response.Result); + } - /// - /// Updates a to-do list category. - /// - /// Success. - /// Validation failed. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list category was not found. - [HttpPut("{categoryId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PutTodoItemCategoryAsync( - [FromRoute] string listId, [FromRoute] int categoryId, - [FromBody] TodoItemCategoryCreateDto dto) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _categoriesService.EditAsync(context, categoryId, dto); - return result.ToActionResult(); - } + /// + /// Updates a to-do list category. + /// + /// Success. + /// Validation failed. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list category was not found. + [HttpPut("{categoryId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task PutTodoItemCategoryAsync( + [FromRoute] string listId, [FromRoute] int categoryId, + [FromBody] TodoItemCategoryCreateDto dto) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _categoriesService.EditAsync(context, categoryId, dto); + return result.ToActionResult(); + } - /// - /// Deletes a to-do list category. - /// - /// Success. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list category was not found. - [HttpDelete("{categoryId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteTodoItemCategoryAsync( - [FromRoute] string listId, [FromRoute] int categoryId) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _categoriesService.DeleteAsync(context, categoryId); - return result.ToActionResult(); - } + /// + /// Deletes a to-do list category. + /// + /// Success. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list category was not found. + [HttpDelete("{categoryId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task DeleteTodoItemCategoryAsync( + [FromRoute] string listId, [FromRoute] int categoryId) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _categoriesService.DeleteAsync(context, categoryId); + return result.ToActionResult(); + } } diff --git a/AdvancedTodoList/Controllers/TodoItemsController.cs b/AdvancedTodoList/Controllers/TodoItemsController.cs index 97782a4..be8a0bb 100644 --- a/AdvancedTodoList/Controllers/TodoItemsController.cs +++ b/AdvancedTodoList/Controllers/TodoItemsController.cs @@ -1,159 +1,159 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Extensions; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.WebApp.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace AdvancedTodoList.Controllers; +namespace AdvancedTodoList.WebApp.Controllers; [Authorize] [ApiController] [Route("api/todo/{listId}/items")] public class TodoItemsController(ITodoItemsService todoItemsService) : ControllerBase { - private readonly ITodoItemsService _rolesService = todoItemsService; + private readonly ITodoItemsService _rolesService = todoItemsService; - /// - /// Gets a page with items of the to-do list with the specified ID. - /// - /// ID of the to-do list. - /// Paginations parameters to apply. - /// Filter parameters to apply. - /// Returns items of the to-do list. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpGet(Name = nameof(GetListItemsAsync))] - [ProducesResponseType(typeof(Page), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetListItemsAsync( - [FromRoute] string listId, [FromQuery] PaginationParameters paginationParameters, - [FromQuery] TodoItemsFilter filter) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _rolesService.GetItemsOfListAsync(context, paginationParameters, filter); - return result.ToActionResult(); - } + /// + /// Gets a page with items of the to-do list with the specified ID. + /// + /// ID of the to-do list. + /// Paginations parameters to apply. + /// Filter parameters to apply. + /// Returns items of the to-do list. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpGet(Name = nameof(GetListItemsAsync))] + [ProducesResponseType(typeof(Page), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetListItemsAsync( + [FromRoute] string listId, [FromQuery] PaginationParameters paginationParameters, + [FromQuery] TodoItemsFilter filter) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _rolesService.GetItemsOfListAsync(context, paginationParameters, filter); + return result.ToActionResult(); + } - /// - /// Gets a to-do list item by its ID. - /// - /// ID of the to-do list which contans the item to obtain. - /// ID of the to-do list item to obtain. - /// Returns requested to-do list item. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list item was not found. - [HttpGet("{itemId}", Name = nameof(GetTodoItemByIdAsync))] - [ProducesResponseType(typeof(TodoItemGetByIdDto), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetTodoItemByIdAsync( - [FromRoute] string listId, [FromRoute] int itemId) - { - TodoListContext context = new(listId, User.GetUserId()!); - var response = await _rolesService.GetByIdAsync(context, itemId); - return response.ToActionResult(); - } + /// + /// Gets a to-do list item by its ID. + /// + /// ID of the to-do list which contans the item to obtain. + /// ID of the to-do list item to obtain. + /// Returns requested to-do list item. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list item was not found. + [HttpGet("{itemId}", Name = nameof(GetTodoItemByIdAsync))] + [ProducesResponseType(typeof(TodoItemGetByIdDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetTodoItemByIdAsync( + [FromRoute] string listId, [FromRoute] int itemId) + { + TodoListContext context = new(listId, User.GetUserId()!); + var response = await _rolesService.GetByIdAsync(context, itemId); + return response.ToActionResult(); + } - /// - /// Creates a new to-do list item. - /// - /// ID of the to-do list which will contain the item. - /// - /// Successfully created. - /// Validation failed. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpPost] - [ProducesResponseType(typeof(TodoItemGetByIdDto), StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PostTodoItemAsync( - [FromRoute] string listId, [FromBody] TodoItemCreateDto dto) - { - TodoListContext context = new(listId, User.GetUserId()!); - var response = await _rolesService.CreateAsync(context, dto); - if (response.Status == TodoItemsServiceStatus.Success && response.Result != null) - { - var routeValues = new { listId, itemId = response.Result!.Id }; - return CreatedAtRoute(nameof(GetTodoItemByIdAsync), routeValues, response.Result); - } + /// + /// Creates a new to-do list item. + /// + /// ID of the to-do list which will contain the item. + /// + /// Successfully created. + /// Validation failed. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpPost] + [ProducesResponseType(typeof(TodoItemGetByIdDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task PostTodoItemAsync( + [FromRoute] string listId, [FromBody] TodoItemCreateDto dto) + { + TodoListContext context = new(listId, User.GetUserId()!); + var response = await _rolesService.CreateAsync(context, dto); + if (response.Status == TodoItemsServiceStatus.Success && response.Result != null) + { + var routeValues = new { listId, itemId = response.Result!.Id }; + return CreatedAtRoute(nameof(GetTodoItemByIdAsync), routeValues, response.Result); + } - return response.Status.ToActionResult(); - } + return response.Status.ToActionResult(); + } - /// - /// Updates a to-do list item. - /// - /// Success. - /// Validation failed. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list item was not found. - [HttpPut("{itemId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PutTodoItemAsync( - [FromRoute] string listId, [FromRoute] int itemId, - [FromBody] TodoItemCreateDto dto) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _rolesService.EditAsync(context, itemId, dto); - return result.ToActionResult(); - } + /// + /// Updates a to-do list item. + /// + /// Success. + /// Validation failed. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list item was not found. + [HttpPut("{itemId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task PutTodoItemAsync( + [FromRoute] string listId, [FromRoute] int itemId, + [FromBody] TodoItemCreateDto dto) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _rolesService.EditAsync(context, itemId, dto); + return result.ToActionResult(); + } - /// - /// Updates a to-do list item's state. - /// - /// Success. - /// Validation failed. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list item was not found. - [HttpPut("{itemId}/state")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PutTodoItemStateAsync( - [FromRoute] string listId, [FromRoute] int itemId, - [FromBody] TodoItemUpdateStateDto dto) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _rolesService.UpdateStateAsync(context, itemId, dto); - return result.ToActionResult(); - } + /// + /// Updates a to-do list item's state. + /// + /// Success. + /// Validation failed. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list item was not found. + [HttpPut("{itemId}/state")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task PutTodoItemStateAsync( + [FromRoute] string listId, [FromRoute] int itemId, + [FromBody] TodoItemUpdateStateDto dto) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _rolesService.UpdateStateAsync(context, itemId, dto); + return result.ToActionResult(); + } - /// - /// Deletes a to-do list item. - /// - /// Success. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list item was not found. - [HttpDelete("{itemId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteTodoListAsync( - [FromRoute] string listId, [FromRoute] int itemId) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _rolesService.DeleteAsync(context, itemId); - return result.ToActionResult(); - } + /// + /// Deletes a to-do list item. + /// + /// Success. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list item was not found. + [HttpDelete("{itemId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task DeleteTodoListAsync( + [FromRoute] string listId, [FromRoute] int itemId) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _rolesService.DeleteAsync(context, itemId); + return result.ToActionResult(); + } } diff --git a/AdvancedTodoList/Controllers/TodoListMembersController.cs b/AdvancedTodoList/Controllers/TodoListMembersController.cs index 7a03f0c..aa827d4 100644 --- a/AdvancedTodoList/Controllers/TodoListMembersController.cs +++ b/AdvancedTodoList/Controllers/TodoListMembersController.cs @@ -1,136 +1,136 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Extensions; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.WebApp.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace AdvancedTodoList.Controllers; +namespace AdvancedTodoList.WebApp.Controllers; [Authorize] [ApiController] [Route("api/todo/{listId}/members")] public class TodoListMembersController( - ITodoListMembersService membersService, - ILogger logger) : ControllerBase + ITodoListMembersService membersService, + ILogger logger) : ControllerBase { - private readonly ITodoListMembersService _membersService = membersService; - private readonly ILogger _logger = logger; + private readonly ITodoListMembersService _membersService = membersService; + private readonly ILogger _logger = logger; - /// - /// Gets a page with members of the to-do list with the specified ID. - /// - /// ID of the to-do list. - /// Paginations parameters to apply. - /// Filter parameters to use. - /// Returns members of the to-do list. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpGet(Name = nameof(GetTodoListMembersAsync))] - [ProducesResponseType(typeof(Page), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetTodoListMembersAsync( - [FromRoute] string listId, [FromQuery] PaginationParameters paginationParameters, - [FromQuery] TodoListMembersFilter filter) - { - TodoListContext context = new(listId, User.GetUserId()!); - var response = await _membersService.GetMembersAsync(context, paginationParameters, filter); - return response.ToActionResult(); - } + /// + /// Gets a page with members of the to-do list with the specified ID. + /// + /// ID of the to-do list. + /// Paginations parameters to apply. + /// Filter parameters to use. + /// Returns members of the to-do list. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpGet(Name = nameof(GetTodoListMembersAsync))] + [ProducesResponseType(typeof(Page), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetTodoListMembersAsync( + [FromRoute] string listId, [FromQuery] PaginationParameters paginationParameters, + [FromQuery] TodoListMembersFilter filter) + { + TodoListContext context = new(listId, User.GetUserId()!); + var response = await _membersService.GetMembersAsync(context, paginationParameters, filter); + return response.ToActionResult(); + } - /// - /// Adds a member to the to-do list. - /// - /// ID of the to-do list which will contain the member. - /// - /// Successfully created. - /// Validation failed. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpPost] - [ProducesResponseType(typeof(TodoListMemberMinimalViewDto), StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task AddMemberAsync( - [FromRoute] string listId, [FromBody] TodoListMemberAddDto dto) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _membersService.AddMemberAsync(context, dto); - switch (result.Status) - { - case TodoListMemberServiceResultStatus.Success: - return NoContent(); - case TodoListMemberServiceResultStatus.NotFound: - return NotFound(); - case TodoListMemberServiceResultStatus.UserAlreadyAdded: - ModelState.AddModelError("UserId", "The user is already a member of the to-do list."); - return BadRequest(ModelState); - case TodoListMemberServiceResultStatus.Forbidden: - return Forbid(); - } - _logger.LogError("Unexpected result status from to-do list members service when trying to add a member."); - return StatusCode(StatusCodes.Status500InternalServerError); - } + /// + /// Adds a member to the to-do list. + /// + /// ID of the to-do list which will contain the member. + /// + /// Successfully created. + /// Validation failed. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpPost] + [ProducesResponseType(typeof(TodoListMemberMinimalViewDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task AddMemberAsync( + [FromRoute] string listId, [FromBody] TodoListMemberAddDto dto) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _membersService.AddMemberAsync(context, dto); + switch (result.Status) + { + case TodoListMemberServiceResultStatus.Success: + return NoContent(); + case TodoListMemberServiceResultStatus.NotFound: + return NotFound(); + case TodoListMemberServiceResultStatus.UserAlreadyAdded: + ModelState.AddModelError("UserId", "The user is already a member of the to-do list."); + return BadRequest(ModelState); + case TodoListMemberServiceResultStatus.Forbidden: + return Forbid(); + } + _logger.LogError("Unexpected result status from to-do list members service when trying to add a member."); + return StatusCode(StatusCodes.Status500InternalServerError); + } - /// - /// Sets a role to the to-do list member. - /// - /// Success. - /// Validation failed. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list member was not found. - [HttpPut("{memberId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task UpdateMemberRoleAsync( - [FromRoute] string listId, [FromRoute] int memberId, - [FromBody] TodoListMemberUpdateRoleDto dto) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _membersService.UpdateMemberRoleAsync(context, memberId, dto); - switch (result) - { - case TodoListMemberServiceResultStatus.Success: - return NoContent(); - case TodoListMemberServiceResultStatus.NotFound: - return NotFound(); - case TodoListMemberServiceResultStatus.InvalidRoleId: - ModelState.AddModelError("RoleId", "The role ID is invalid for the current to-do list."); - return BadRequest(ModelState); - case TodoListMemberServiceResultStatus.Forbidden: - return Forbid(); - } - _logger.LogError("Unexpected result status from to-do list members service when trying to update member's role."); - return StatusCode(StatusCodes.Status500InternalServerError); - } + /// + /// Sets a role to the to-do list member. + /// + /// Success. + /// Validation failed. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list member was not found. + [HttpPut("{memberId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task UpdateMemberRoleAsync( + [FromRoute] string listId, [FromRoute] int memberId, + [FromBody] TodoListMemberUpdateRoleDto dto) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _membersService.UpdateMemberRoleAsync(context, memberId, dto); + switch (result) + { + case TodoListMemberServiceResultStatus.Success: + return NoContent(); + case TodoListMemberServiceResultStatus.NotFound: + return NotFound(); + case TodoListMemberServiceResultStatus.InvalidRoleId: + ModelState.AddModelError("RoleId", "The role ID is invalid for the current to-do list."); + return BadRequest(ModelState); + case TodoListMemberServiceResultStatus.Forbidden: + return Forbid(); + } + _logger.LogError("Unexpected result status from to-do list members service when trying to update member's role."); + return StatusCode(StatusCodes.Status500InternalServerError); + } - /// - /// Removes a member from the to-do list. - /// - /// Success. - /// Authentication failed. - /// To-do list member was not found. - [HttpDelete("{memberId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task RemoveMemberAsync( - [FromRoute] string listId, [FromRoute] int memberId) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _membersService.RemoveMemberAsync(context, memberId); - return result.ToActionResult(); - } + /// + /// Removes a member from the to-do list. + /// + /// Success. + /// Authentication failed. + /// To-do list member was not found. + [HttpDelete("{memberId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task RemoveMemberAsync( + [FromRoute] string listId, [FromRoute] int memberId) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _membersService.RemoveMemberAsync(context, memberId); + return result.ToActionResult(); + } } diff --git a/AdvancedTodoList/Controllers/TodoListRolesController.cs b/AdvancedTodoList/Controllers/TodoListRolesController.cs index aa3b271..62e3b5b 100644 --- a/AdvancedTodoList/Controllers/TodoListRolesController.cs +++ b/AdvancedTodoList/Controllers/TodoListRolesController.cs @@ -1,133 +1,133 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Extensions; +using AdvancedTodoList.WebApp.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace AdvancedTodoList.Controllers; +namespace AdvancedTodoList.WebApp.Controllers; [Authorize] [ApiController] [Route("api/todo/{listId}/roles")] public class TodoListRolesController(ITodoListRolesService rolesService) : ControllerBase { - private readonly ITodoListRolesService _rolesService = rolesService; + private readonly ITodoListRolesService _rolesService = rolesService; - /// - /// Gets a page with roles of the to-do list with the specified ID. - /// - /// ID of the to-do list. - /// Paginations parameters to apply. - /// Optional name to filter roles by. - /// Returns roles of the to-do list. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpGet(Name = nameof(GetTodoListRolesAsync))] - [ProducesResponseType(typeof(Page), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetTodoListRolesAsync( - [FromRoute] string listId, [FromQuery] PaginationParameters paginationParameters, - [FromQuery] string? name) - { - TodoListContext context = new(listId, User.GetUserId()!); - var response = await _rolesService.GetRolesOfListAsync(context, paginationParameters, name); - return response.ToActionResult(); - } + /// + /// Gets a page with roles of the to-do list with the specified ID. + /// + /// ID of the to-do list. + /// Paginations parameters to apply. + /// Optional name to filter roles by. + /// Returns roles of the to-do list. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpGet(Name = nameof(GetTodoListRolesAsync))] + [ProducesResponseType(typeof(Page), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetTodoListRolesAsync( + [FromRoute] string listId, [FromQuery] PaginationParameters paginationParameters, + [FromQuery] string? name) + { + TodoListContext context = new(listId, User.GetUserId()!); + var response = await _rolesService.GetRolesOfListAsync(context, paginationParameters, name); + return response.ToActionResult(); + } - /// - /// Gets a to-do list role by its ID. - /// - /// ID of the to-do list which contans the role to obtain. - /// ID of the to-do list role to obtain. - /// Returns requested to-do list role. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list role was not found. - [HttpGet("{roleId}", Name = nameof(GetTodoListRoleByIdAsync))] - [ProducesResponseType(typeof(TodoListRoleViewDto), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetTodoListRoleByIdAsync( - [FromRoute] string listId, [FromRoute] int roleId) - { - TodoListContext context = new(listId, User.GetUserId()!); - var response = await _rolesService.GetByIdAsync(context, roleId); - return response.ToActionResult(); - } + /// + /// Gets a to-do list role by its ID. + /// + /// ID of the to-do list which contans the role to obtain. + /// ID of the to-do list role to obtain. + /// Returns requested to-do list role. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list role was not found. + [HttpGet("{roleId}", Name = nameof(GetTodoListRoleByIdAsync))] + [ProducesResponseType(typeof(TodoListRoleViewDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetTodoListRoleByIdAsync( + [FromRoute] string listId, [FromRoute] int roleId) + { + TodoListContext context = new(listId, User.GetUserId()!); + var response = await _rolesService.GetByIdAsync(context, roleId); + return response.ToActionResult(); + } - /// - /// Creates a new to-do list role. - /// - /// ID of the to-do list which will contain the role. - /// - /// Successfully created. - /// Validation failed. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpPost] - [ProducesResponseType(typeof(TodoListRoleViewDto), StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PostTodoListRoleAsync( - [FromRoute] string listId, [FromBody] TodoListRoleCreateDto dto) - { - TodoListContext context = new(listId, User.GetUserId()!); - var response = await _rolesService.CreateAsync(context, dto); - if (response.Status != ServiceResponseStatus.Success || response.Result == null) - return response.ToActionResult(); + /// + /// Creates a new to-do list role. + /// + /// ID of the to-do list which will contain the role. + /// + /// Successfully created. + /// Validation failed. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpPost] + [ProducesResponseType(typeof(TodoListRoleViewDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task PostTodoListRoleAsync( + [FromRoute] string listId, [FromBody] TodoListRoleCreateDto dto) + { + TodoListContext context = new(listId, User.GetUserId()!); + var response = await _rolesService.CreateAsync(context, dto); + if (response.Status != ServiceResponseStatus.Success || response.Result == null) + return response.ToActionResult(); - var routeValues = new { listId, roleId = response.Result.Id }; - return CreatedAtRoute(nameof(GetTodoListRoleByIdAsync), routeValues, response.Result); - } + var routeValues = new { listId, roleId = response.Result.Id }; + return CreatedAtRoute(nameof(GetTodoListRoleByIdAsync), routeValues, response.Result); + } - /// - /// Updates a to-do list role. - /// - /// Success. - /// Validation failed. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list role was not found. - [HttpPut("{roleId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PutTodoListRoleAsync( - [FromRoute] string listId, [FromRoute] int roleId, - [FromBody] TodoListRoleCreateDto dto) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _rolesService.EditAsync(context, roleId, dto); - return result.ToActionResult(); - } + /// + /// Updates a to-do list role. + /// + /// Success. + /// Validation failed. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list role was not found. + [HttpPut("{roleId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task PutTodoListRoleAsync( + [FromRoute] string listId, [FromRoute] int roleId, + [FromBody] TodoListRoleCreateDto dto) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _rolesService.EditAsync(context, roleId, dto); + return result.ToActionResult(); + } - /// - /// Deletes a to-do list role. - /// - /// Success. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list role was not found. - [HttpDelete("{roleId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status403Forbidden)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteTodoListAsync( - [FromRoute] string listId, [FromRoute] int roleId) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _rolesService.DeleteAsync(context, roleId); - return result.ToActionResult(); - } + /// + /// Deletes a to-do list role. + /// + /// Success. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list role was not found. + [HttpDelete("{roleId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task DeleteTodoListAsync( + [FromRoute] string listId, [FromRoute] int roleId) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _rolesService.DeleteAsync(context, roleId); + return result.ToActionResult(); + } } diff --git a/AdvancedTodoList/Controllers/TodoListsController.cs b/AdvancedTodoList/Controllers/TodoListsController.cs index 1a0735f..6af45f0 100644 --- a/AdvancedTodoList/Controllers/TodoListsController.cs +++ b/AdvancedTodoList/Controllers/TodoListsController.cs @@ -1,114 +1,114 @@ -using AdvancedTodoList.Core.Dtos; +using AdvancedTodoList.Application.Dtos; +using AdvancedTodoList.Application.Services.Definitions; using AdvancedTodoList.Core.Pagination; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Specifications; -using AdvancedTodoList.Extensions; +using AdvancedTodoList.Core.Specifications.Filters; +using AdvancedTodoList.WebApp.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace AdvancedTodoList.Controllers; +namespace AdvancedTodoList.WebApp.Controllers; [Authorize] [ApiController] [Route("api/todo")] public class TodoListsController( - ITodoListsService todoListsService, - ILogger logger) : ControllerBase + ITodoListsService todoListsService, + ILogger logger) : ControllerBase { - private readonly ITodoListsService _todoListsService = todoListsService; - private readonly ILogger _logger = logger; + private readonly ITodoListsService _todoListsService = todoListsService; + private readonly ILogger _logger = logger; - /// - /// Gets a page with to-do lists of which the caller is a member. - /// - /// Paginations parameters to apply. - /// Filter parameters to apply. - /// Returns to-do lists of which the caller is a member. - /// Authentication failed. - [HttpGet(Name = nameof(GetTodoListsOfCallerAsync))] - [ProducesResponseType(typeof(Page), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task GetTodoListsOfCallerAsync( - [FromQuery] PaginationParameters paginationParameters, [FromQuery] TodoListsFilter filter) - { - var page = await _todoListsService.GetListsOfUserAsync(User.GetUserId()!, - paginationParameters, filter); - return Ok(page); - } + /// + /// Gets a page with to-do lists of which the caller is a member. + /// + /// Paginations parameters to apply. + /// Filter parameters to apply. + /// Returns to-do lists of which the caller is a member. + /// Authentication failed. + [HttpGet(Name = nameof(GetTodoListsOfCallerAsync))] + [ProducesResponseType(typeof(Page), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task GetTodoListsOfCallerAsync( + [FromQuery] PaginationParameters paginationParameters, [FromQuery] TodoListsFilter filter) + { + var page = await _todoListsService.GetListsOfUserAsync(User.GetUserId()!, + paginationParameters, filter); + return Ok(page); + } - /// - /// Gets a to-do list by its ID. - /// - /// ID of the to-do list to obtain. - /// Returns requested to-do list. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpGet("{listId}", Name = nameof(GetTodoListByIdAsync))] - [ProducesResponseType(typeof(TodoListGetByIdDto), StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetTodoListByIdAsync([FromRoute] string listId) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _todoListsService.GetByIdAsync(context); - return result.ToActionResult(); - } + /// + /// Gets a to-do list by its ID. + /// + /// ID of the to-do list to obtain. + /// Returns requested to-do list. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpGet("{listId}", Name = nameof(GetTodoListByIdAsync))] + [ProducesResponseType(typeof(TodoListGetByIdDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetTodoListByIdAsync([FromRoute] string listId) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _todoListsService.GetByIdAsync(context); + return result.ToActionResult(); + } - /// - /// Creates a new to-do list. - /// - /// Successfully created. - /// Validation failed. - /// Authentication failed. - /// User has no permission to perform this action. - [HttpPost] - [ProducesResponseType(typeof(TodoListGetByIdDto), StatusCodes.Status201Created)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task PostTodoListAsync([FromBody] TodoListCreateDto dto) - { - var list = await _todoListsService.CreateAsync(dto, User.GetUserId()!); - var routeValues = new { listId = list.Id }; - return CreatedAtRoute(nameof(GetTodoListByIdAsync), routeValues, list); - } + /// + /// Creates a new to-do list. + /// + /// Successfully created. + /// Validation failed. + /// Authentication failed. + /// User has no permission to perform this action. + [HttpPost] + [ProducesResponseType(typeof(TodoListGetByIdDto), StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task PostTodoListAsync([FromBody] TodoListCreateDto dto) + { + var list = await _todoListsService.CreateAsync(dto, User.GetUserId()!); + var routeValues = new { listId = list.Id }; + return CreatedAtRoute(nameof(GetTodoListByIdAsync), routeValues, list); + } - /// - /// Updates a to-do list. - /// - /// Success. - /// Validation failed. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpPut("{listId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task PutTodoListAsync( - [FromRoute] string listId, [FromBody] TodoListCreateDto dto) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _todoListsService.EditAsync(context, dto); - return result.ToActionResult(); - } + /// + /// Updates a to-do list. + /// + /// Success. + /// Validation failed. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpPut("{listId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task PutTodoListAsync( + [FromRoute] string listId, [FromBody] TodoListCreateDto dto) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _todoListsService.EditAsync(context, dto); + return result.ToActionResult(); + } - /// - /// Deletes a to-do list. - /// - /// Success. - /// Authentication failed. - /// User has no permission to perform this action. - /// To-do list was not found. - [HttpDelete("{listId}")] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteTodoListAsync([FromRoute] string listId) - { - TodoListContext context = new(listId, User.GetUserId()!); - var result = await _todoListsService.DeleteAsync(context); - return result.ToActionResult(); - } + /// + /// Deletes a to-do list. + /// + /// Success. + /// Authentication failed. + /// User has no permission to perform this action. + /// To-do list was not found. + [HttpDelete("{listId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task DeleteTodoListAsync([FromRoute] string listId) + { + TodoListContext context = new(listId, User.GetUserId()!); + var result = await _todoListsService.DeleteAsync(context); + return result.ToActionResult(); + } } diff --git a/AdvancedTodoList/Extensions/ClaimsPrincipalExtensions.cs b/AdvancedTodoList/Extensions/ClaimsPrincipalExtensions.cs index 67e37fb..4edfe37 100644 --- a/AdvancedTodoList/Extensions/ClaimsPrincipalExtensions.cs +++ b/AdvancedTodoList/Extensions/ClaimsPrincipalExtensions.cs @@ -1,19 +1,19 @@ using System.Security.Claims; -namespace AdvancedTodoList.Extensions; +namespace AdvancedTodoList.WebApp.Extensions; public static class ClaimsPrincipalExtensions { - /// - /// Gets a user ID or if it's not found. - /// - /// - /// User ID or if it's not found. - /// - public static string? GetUserId(this ClaimsPrincipal claimsPrincipal) - { - var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier); - if (idClaim == null) return null; - return idClaim.Value; - } + /// + /// Gets a user ID or if it's not found. + /// + /// + /// User ID or if it's not found. + /// + public static string? GetUserId(this ClaimsPrincipal claimsPrincipal) + { + var idClaim = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier); + if (idClaim == null) return null; + return idClaim.Value; + } } diff --git a/AdvancedTodoList/Extensions/ServiceResponseExtensions.cs b/AdvancedTodoList/Extensions/ServiceResponseExtensions.cs index 6e789b6..ea88354 100644 --- a/AdvancedTodoList/Extensions/ServiceResponseExtensions.cs +++ b/AdvancedTodoList/Extensions/ServiceResponseExtensions.cs @@ -1,29 +1,29 @@ -using AdvancedTodoList.Core.Services; +using AdvancedTodoList.Application.Services.Definitions; using Microsoft.AspNetCore.Mvc; -namespace AdvancedTodoList.Extensions; +namespace AdvancedTodoList.WebApp.Extensions; public static class ServiceResponseExtensions { - public static IActionResult ToActionResult(this ServiceResponse response) - { - return response.Status switch - { - ServiceResponseStatus.Success => - response.Result != null ? new OkObjectResult(response.Result) : new NoContentResult(), - ServiceResponseStatus.NotFound => new NotFoundResult(), - ServiceResponseStatus.Forbidden => new ForbidResult(), - _ => throw new ArgumentException("Invalid service response status", nameof(response)) - }; - } - public static IActionResult ToActionResult(this ServiceResponseStatus status) - { - return status switch - { - ServiceResponseStatus.Success => new NoContentResult(), - ServiceResponseStatus.NotFound => new NotFoundResult(), - ServiceResponseStatus.Forbidden => new ForbidResult(), - _ => throw new ArgumentException("Invalid service response status", nameof(status)) - }; - } + public static IActionResult ToActionResult(this ServiceResponse response) + { + return response.Status switch + { + ServiceResponseStatus.Success => + response.Result != null ? new OkObjectResult(response.Result) : new NoContentResult(), + ServiceResponseStatus.NotFound => new NotFoundResult(), + ServiceResponseStatus.Forbidden => new ForbidResult(), + _ => throw new ArgumentException("Invalid service response status", nameof(response)) + }; + } + public static IActionResult ToActionResult(this ServiceResponseStatus status) + { + return status switch + { + ServiceResponseStatus.Success => new NoContentResult(), + ServiceResponseStatus.NotFound => new NotFoundResult(), + ServiceResponseStatus.Forbidden => new ForbidResult(), + _ => throw new ArgumentException("Invalid service response status", nameof(status)) + }; + } } diff --git a/AdvancedTodoList/Extensions/TodoItemsServiceStatusExtensions.cs b/AdvancedTodoList/Extensions/TodoItemsServiceStatusExtensions.cs index 9bad533..2926134 100644 --- a/AdvancedTodoList/Extensions/TodoItemsServiceStatusExtensions.cs +++ b/AdvancedTodoList/Extensions/TodoItemsServiceStatusExtensions.cs @@ -1,29 +1,29 @@ -using AdvancedTodoList.Core.Services; +using AdvancedTodoList.Application.Services.Definitions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; -namespace AdvancedTodoList.Extensions; +namespace AdvancedTodoList.WebApp.Extensions; public static class TodoItemsServiceStatusExtensions { - public static IActionResult ToActionResult(this TodoItemsServiceStatus status) - { - switch (status) - { - case TodoItemsServiceStatus.Success: - return new NoContentResult(); + public static IActionResult ToActionResult(this TodoItemsServiceStatus status) + { + switch (status) + { + case TodoItemsServiceStatus.Success: + return new NoContentResult(); - case TodoItemsServiceStatus.NotFound: - return new NotFoundResult(); + case TodoItemsServiceStatus.NotFound: + return new NotFoundResult(); - case TodoItemsServiceStatus.Forbidden: - return new ForbidResult(); + case TodoItemsServiceStatus.Forbidden: + return new ForbidResult(); - case TodoItemsServiceStatus.InvalidCategoryId: - ModelStateDictionary modelState = new(); - modelState.AddModelError("CategoryId", "Category ID is invalid."); - return new BadRequestObjectResult(modelState); - } - throw new ArgumentException("Invalid to-do items service response status", nameof(status)); - } + case TodoItemsServiceStatus.InvalidCategoryId: + ModelStateDictionary modelState = new(); + modelState.AddModelError("CategoryId", "Category ID is invalid."); + return new BadRequestObjectResult(modelState); + } + throw new ArgumentException("Invalid to-do items service response status", nameof(status)); + } } diff --git a/AdvancedTodoList/Program.cs b/AdvancedTodoList/Program.cs index f71f1cf..c6bd762 100644 --- a/AdvancedTodoList/Program.cs +++ b/AdvancedTodoList/Program.cs @@ -1,16 +1,16 @@ -using AdvancedTodoList.Core.Mapping; +using AdvancedTodoList.Application.Mapping; +using AdvancedTodoList.Application.Options; +using AdvancedTodoList.Application.Services.Definitions; +using AdvancedTodoList.Application.Services.Definitions.Auth; +using AdvancedTodoList.Application.Services.Implementations; +using AdvancedTodoList.Application.Services.Implementations.Auth; +using AdvancedTodoList.Application.Validation; using AdvancedTodoList.Core.Models.Auth; using AdvancedTodoList.Core.Models.TodoLists; using AdvancedTodoList.Core.Models.TodoLists.Members; -using AdvancedTodoList.Core.Options; using AdvancedTodoList.Core.Repositories; -using AdvancedTodoList.Core.Services; -using AdvancedTodoList.Core.Services.Auth; -using AdvancedTodoList.Core.Validation; using AdvancedTodoList.Infrastructure.Data; using AdvancedTodoList.Infrastructure.Repositories; -using AdvancedTodoList.Infrastructure.Services; -using AdvancedTodoList.Infrastructure.Services.Auth; using FluentValidation; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; @@ -25,34 +25,34 @@ // Add services to the container. builder.Services.AddControllers() - .AddJsonOptions(options => - { - options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; - }); + .AddJsonOptions(options => + { + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { - options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme - { - In = ParameterLocation.Header, - Description = "Please enter access token (JWT)", - Name = "Authorization", - Type = SecuritySchemeType.ApiKey - }); - - var scheme = new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "Bearer" - } - }; - - options.AddSecurityRequirement(new OpenApiSecurityRequirement { { scheme, Array.Empty() } }); - - var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; - options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); + options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please enter access token (JWT)", + Name = "Authorization", + Type = SecuritySchemeType.ApiKey + }); + + var scheme = new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }; + + options.AddSecurityRequirement(new OpenApiSecurityRequirement { { scheme, Array.Empty() } }); + + var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); }); // Configure antiforgery @@ -60,46 +60,46 @@ // Configure auth string? jwtSecret = builder.Configuration["Auth:AccessToken:SecretKey"] ?? - throw new InvalidOperationException("JWT secret is not configured"); + throw new InvalidOperationException("JWT secret is not configured"); builder.Services.AddAuthentication() - .AddJwtBearer(options => - { - options.SaveToken = true; - options.TokenValidationParameters = new TokenValidationParameters - { - ClockSkew = TimeSpan.FromSeconds(5), - ValidIssuer = builder.Configuration["Auth:AccessToken:ValidIssuer"], - ValidAudience = builder.Configuration["Auth:AccessToken:ValidAudience"], - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret)) - }; - }); + .AddJwtBearer(options => + { + options.SaveToken = true; + options.TokenValidationParameters = new TokenValidationParameters + { + ClockSkew = TimeSpan.FromSeconds(5), + ValidIssuer = builder.Configuration["Auth:AccessToken:ValidIssuer"], + ValidAudience = builder.Configuration["Auth:AccessToken:ValidAudience"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSecret)) + }; + }); builder.Services.AddAuthorizationBuilder(); // Get connection string string? connectionString = - builder.Configuration.GetConnectionString("DefaultConnection") ?? - throw new InvalidOperationException("Connection string is not specified in 'ConnectionStrings:DefaultConnection'"); + builder.Configuration.GetConnectionString("DefaultConnection") ?? + throw new InvalidOperationException("Connection string is not specified in 'ConnectionStrings:DefaultConnection'"); // Configure DbContext builder.Services.AddDbContext( - o => o.UseSqlServer(connectionString, - b => b.MigrationsAssembly("AdvancedTodoList.Infrastructure")) - ); + o => o.UseSqlServer(connectionString, + b => b.MigrationsAssembly("AdvancedTodoList.Infrastructure")) + ); builder.Services.AddIdentityCore(options => - { - options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._"; - }) - .AddEntityFrameworkStores() - .AddApiEndpoints(); + { + options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._"; + }) + .AddEntityFrameworkStores() + .AddApiEndpoints(); // Bind options builder.Services.Configure( - builder.Configuration.GetSection("Auth:AccessToken")); + builder.Configuration.GetSection("Auth:AccessToken")); builder.Services.Configure( - builder.Configuration.GetSection("Auth:RefreshToken")); + builder.Configuration.GetSection("Auth:RefreshToken")); builder.Services.Configure( - builder.Configuration.GetSection("Todo:InvitationLink")); + builder.Configuration.GetSection("Todo:InvitationLink")); // Register application services builder.Services.AddScoped(); @@ -114,7 +114,7 @@ builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(typeof(ITodoListDependantEntitiesService<,>), - typeof(TodoListDependantEntitiesService<,>)); + typeof(TodoListDependantEntitiesService<,>)); // Register unit of work builder.Services.AddScoped(); // Register application repositories @@ -139,12 +139,12 @@ // Enable auto validation by SharpGrip builder.Services.AddFluentValidationAutoValidation(configuration => { - // Disable the built-in .NET model (data annotations) validation. - configuration.DisableBuiltInModelValidation = true; - // Enable validation for parameters bound from 'BindingSource.Body' binding sources. - configuration.EnableBodyBindingSourceAutomaticValidation = true; - // Enable validation for parameters bound from 'BindingSource.Query' binding sources. - configuration.EnableQueryBindingSourceAutomaticValidation = true; + // Disable the built-in .NET model (data annotations) validation. + configuration.DisableBuiltInModelValidation = true; + // Enable validation for parameters bound from 'BindingSource.Body' binding sources. + configuration.EnableBodyBindingSourceAutomaticValidation = true; + // Enable validation for parameters bound from 'BindingSource.Query' binding sources. + configuration.EnableQueryBindingSourceAutomaticValidation = true; }); @@ -153,8 +153,8 @@ // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { - app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwagger(); + app.UseSwaggerUI(); } app.UseHttpsRedirection(); diff --git a/README.md b/README.md index e5f5688..5250418 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # AdvancedTodoList -**AdvancedTodoList**, developed as my personal project, is a Web API for managing to-do lists in teams, where each member can have a role and different sets of permissions. It's based on ASP.NET Core and Entity Framework Core with SQL Server running on Docker. +**AdvancedTodoList**, developed as my personal project, is a Web API for managing tasks in team settings, where each member can have a role and different sets of permissions. It's based on ASP.NET Core and Entity Framework Core with SQL Server running on Docker. ## Technologies and tools used * .NET 8.0 @@ -11,6 +11,7 @@ * SharpGrip.FluentValidation.AutoValidation ## Features + * **Collaborative to-do list management**: The API allows users to add new tasks (`TodoListItem`) to their to-do lists, update task details, mark tasks as completed/skipped, or delete them when they're no longer needed. * **Invitation links**: Users can create invitation links for their to-do lists to allow other users to join them. * **Search and Filtering**: Most endpoints allow users to specify filtering criteria and search prompts. @@ -20,29 +21,48 @@ * **Integration testing**: Integration tests utilize `Testcontainers` for comprehensive testing of API functionality and interactions. ## Architecture Overview -The Advanced Todo List application follows a clean architecture. The main three layers are: +This project follows a pragmatic, clean-architecture inspired layout that balances separation of concerns with developer productivity. The solution preserves clear layer responsibilities and interfaces while choosing to use EF Core directly where it simplifies the codebase. ### Core Layer (`AdvancedTodoList.Core`) -* Contains services and repositories interfaces. + +A layer that defines the application's domain and data access contracts. + +* Contains data access services interfaces (e.g. repositories). * Defines the entities and value objects representing the domain model. -* Provides services for CRUD operations, task management, user authentication, authorization, etc. * Defines specifications to query and filter parameters. + +### Application Layer (`AdvancedTodoList.Application`) + +The business logic layer that depends on the Core. + +* Provides services for business logic such as CRUD operations, task management, user authentication, authorization, etc. * Implements validation logic using FluentValidation for ensuring data integrity and consistency. +* Contains data transfer objects and application options. ### Infrastructure Layer (`AdvancedTodoList.Infrastructure`) + +Handles the direct EF Core data access. Depends on the Core. + * Handles data access and infrastructure-related concerns. * Implements repositories and data access logic using Entity Framework Core. * Manages database context and configuration. -* Handles JWT and refresh tokens. -* Implements services and specifications. -### Application Layer (`AdvancedTodoList`) +### Presentation Layer (`AdvancedTodoList.WebApi`) + +A layer that enables HTTP methods. Directly depends on the Core (filter and pagination parameters) and Application (service interfaces) layers. Indirectly depends on the Infrastructure, but only for mapping services to the interfaces for Dependency Injection. + * Implements RESTful API endpoints using ASP.NET Core Web API. -* Handles incoming HTTP requests, validates input data, and delegates to services for business logic execution. +* Handles incoming HTTP requests, validates input data (using middleware), and delegates to services for business logic execution. * Uses authentication and authorization mechanisms to secure endpoints. * Facilitates error handling and response formatting. * Exposes Swagger documentation. +## Design Choices Justifications + +* Use of EF Core in the domain layer - despite being a clean-architecture violation, this choice significantly reduces mapping boilerplate and makes many queries simpler and more expressive by leveraging EF Core/LINQ directly against the model. It is justified here because the project commits to a single, stable data provider that is unlikely to change. +* Repository and Unit of Work implementations over `DbContext` - repository + UoW provide clear, testable contracts and centralize common data operations (pagination, specification application, common query helpers), which reduces duplication and keeps transactional boundaries explicit. +* Creating interfaces for services that have only one implementation - primarily improves testability (easy mocking/faking) and decouples callers from concrete implementations, which helps when composing dependencies in DI. + ## Testing Approach The `AdvancedTodoList` application uses two types of automated tests: unit tests and integration tests.