diff --git a/HopFrame.sln.DotSettings.user b/HopFrame.sln.DotSettings.user index e5935b1..d71bdae 100644 --- a/HopFrame.sln.DotSettings.user +++ b/HopFrame.sln.DotSettings.user @@ -84,6 +84,16 @@ + + + + + + + + + + diff --git a/docs/api/endpoints/group.md b/docs/api/endpoints/group.md new file mode 100644 index 0000000..c2cebca --- /dev/null +++ b/docs/api/endpoints/group.md @@ -0,0 +1,237 @@ +# Group Endpoints + +## Used Models +- [Group](../../models.md#permissiongroup) + +## API Endpoint: GetGroups + +**Endpoint:** `GET /api/v1/groups` + +**Description:** Retrieves a list of all groups. + +**Authorization Required:** Yes + +**Required Permission:** `hopframe.admin.groups.read` + +**Parameters:** None + +**Response:** + +- **200 OK:** Returns a list of groups. + ```json + [ + { + "Name": "string", + "IsDefaultGroup": "boolean", + "Description": "string", + "CreatedAt": "2023-12-23T00:00:00Z", + "Permissions": [ + { + "Id": 1, + "PermissionName": "string", + "GrantedAt": "2023-12-23T00:00:00Z" + } + ] + } + ] + ``` +- **401 Unauthorized:** User is not authorized to access this endpoint. + +## API Endpoint: GetDefaultGroups + +**Endpoint:** `GET /api/v1/groups/default` + +**Description:** Retrieves a list of default groups. + +**Authorization Required:** Yes + +**Required Permission:** `hopframe.admin.groups.read` + +**Parameters:** None + +**Response:** + +- **200 OK:** Returns a list of default groups. + ```json + [ + { + "Name": "string", + "IsDefaultGroup": "boolean", + "Description": "string", + "CreatedAt": "2023-12-23T00:00:00Z", + "Permissions": [ + { + "Id": 1, + "PermissionName": "string", + "GrantedAt": "2023-12-23T00:00:00Z" + } + ] + } + ] + ``` +- **401 Unauthorized:** User is not authorized to access this endpoint. + +## API Endpoint: GetUserGroups + +**Endpoint:** `GET /api/v1/groups/user/{userId}` + +**Description:** Retrieves a list of groups for a specific user. + +**Authorization Required:** Yes + +**Required Permission:** `hopframe.admin.groups.read` + +**Parameters:** + +- **userId:** `string` (path) - The ID of the user. + +**Response:** + +- **200 OK:** Returns a list of groups for the user. + ```json + [ + { + "Name": "string", + "IsDefaultGroup": "boolean", + "Description": "string", + "CreatedAt": "2023-12-23T00:00:00Z", + "Permissions": [ + { + "Id": 1, + "PermissionName": "string", + "GrantedAt": "2023-12-23T00:00:00Z" + } + ] + } + ] + ``` +- **400 Bad Request:** Invalid user ID. +- **401 Unauthorized:** User is not authorized to access this endpoint. +- **404 Not Found:** User does not exist. + +## API Endpoint: GetGroup + +**Endpoint:** `GET /api/v1/groups/{name}` + +**Description:** Retrieves details of a specific group. + +**Authorization Required:** Yes + +**Required Permission:** `hopframe.admin.groups.read` + +**Parameters:** + +- **name:** `string` (path) - The name of the group. + +**Response:** + +- **200 OK:** Returns the details of the group. + ```json + { + "Name": "string", + "IsDefaultGroup": "boolean", + "Description": "string", + "CreatedAt": "2023-12-23T00:00:00Z", + "Permissions": [ + { + "Id": 1, + "PermissionName": "string", + "GrantedAt": "2023-12-23T00:00:00Z" + } + ] + } + ``` +- **401 Unauthorized:** User is not authorized to access this endpoint. +- **404 Not Found:** Group does not exist. + +## API Endpoint: CreateGroup + +**Endpoint:** `POST /api/v1/groups` + +**Description:** Creates a new group. + +**Authorization Required:** Yes + +**Required Permission:** `hopframe.admin.groups.create` + +**Parameters:** + +- **group:** `PermissionGroup` (body) - The group to be created. + +**Response:** + +- **200 OK:** Returns the created group. + ```json + { + "Name": "string", + "IsDefaultGroup": "boolean", + "Description": "string", + "CreatedAt": "2023-12-23T00:00:00Z", + "Permissions": [ + { + "Id": 1, + "PermissionName": "string", + "GrantedAt": "2023-12-23T00:00:00Z" + } + ] + } + ``` +- **400 Bad Request:** Provide a group. +- **400 Bad Request:** Group names must start with 'group.'. +- **401 Unauthorized:** User is not authorized to access this endpoint. +- **409 Conflict:** Group already exists. + +## API Endpoint: UpdateGroup + +**Endpoint:** `PUT /api/v1/groups` + +**Description:** Updates an existing group. + +**Authorization Required:** Yes + +**Required Permission:** `hopframe.admin.groups.update` + +**Parameters:** + +- **group:** `PermissionGroup` (body) - The group to be updated. + +**Response:** + +- **200 OK:** Returns the updated group. + ```json + { + "Name": "string", + "IsDefaultGroup": "boolean", + "Description": "string", + "CreatedAt": "2023-12-23T00:00:00Z", + "Permissions": [ + { + "Id": 1, + "PermissionName": "string", + "GrantedAt": "2023-12-23T00:00:00Z" + } + ] + } + ``` +- **401 Unauthorized:** User is not authorized to access this endpoint. +- **404 Not Found:** Group does not exist. + +## API Endpoint: DeleteGroup + +**Endpoint:** `DELETE /api/v1/groups/{name}` + +**Description:** Deletes a specific group. + +**Authorization Required:** Yes + +**Required Permission:** `hopframe.admin.groups.delete` + +**Parameters:** + +- **name:** `string` (path) - The name of the group to be deleted. + +**Response:** + +- **200 OK:** Group deleted successfully. +- **401 Unauthorized:** User is not authorized to access this endpoint. +- **404 Not Found:** Group does not exist. diff --git a/tests/HopFrame.Tests.Api/Controllers/GroupControllerTests.cs b/tests/HopFrame.Tests.Api/Controllers/GroupControllerTests.cs new file mode 100644 index 0000000..6f27961 --- /dev/null +++ b/tests/HopFrame.Tests.Api/Controllers/GroupControllerTests.cs @@ -0,0 +1,369 @@ +using System.Net; +using HopFrame.Api.Controller; +using HopFrame.Api.Logic; +using HopFrame.Database.Models; +using HopFrame.Database.Repositories; +using HopFrame.Security.Authorization; +using HopFrame.Security.Claims; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Moq; + +namespace HopFrame.Tests.Api.Controllers; + +public class GroupControllerTests { + private (Mock, Mock>, Mock, Mock + , GroupController) SetupEnvironment() { + var mockGroups = new Mock(); + var mockPermissions = new Mock>(); + var mockPerms = new Mock(); + var mockContext = new Mock(); + + var options = new AdminPermissionOptions(); + + mockPermissions.Setup(o => o.Value).Returns(options); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(true); + + var controller = new GroupController(mockPermissions.Object, mockPerms.Object, mockContext.Object, + mockGroups.Object); + + return (mockGroups, mockPermissions, mockPerms, mockContext, controller); + } + + [Fact] + public async Task GetGroups_ShouldReturnUnauthorized_WhenUserIsNotAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(false); + + // Act + var result = await controller.GetGroups(); + + // Assert + Assert.IsType(result.Result); + } + + [Fact] + public async Task GetGroups_ShouldReturnGroups_WhenUserIsAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + var groups = new List { new PermissionGroup { Name = "testgroup" } }; + mockGroups.Setup(g => g.GetGroups()).ReturnsAsync(LogicResult>.Ok(groups)); + + // Act + var result = await controller.GetGroups(); + + // Assert + var okResult = Assert.IsType(result.Result); + var returnedGroups = Assert.IsAssignableFrom>(okResult.Value); + Assert.Single(returnedGroups); + Assert.Equal("testgroup", returnedGroups.First().Name); + } + + [Fact] + public async Task GetDefaultGroups_ShouldReturnUnauthorized_WhenUserIsNotAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(false); + + // Act + var result = await controller.GetDefaultGroups(); + + // Assert + Assert.IsType(result.Result); + } + + [Fact] + public async Task GetDefaultGroups_ShouldReturnGroups_WhenUserIsAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + var groups = new List { new PermissionGroup { Name = "defaultgroup" } }; + mockGroups.Setup(g => g.GetDefaultGroups()).ReturnsAsync(LogicResult>.Ok(groups)); + + // Act + var result = await controller.GetDefaultGroups(); + + // Assert + var okResult = Assert.IsType(result.Result); + var returnedGroups = Assert.IsAssignableFrom>(okResult.Value); + Assert.Single(returnedGroups); + Assert.Equal("defaultgroup", returnedGroups.First().Name); + } + + [Fact] + public async Task GetUserGroups_ShouldReturnUnauthorized_WhenUserIsNotAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(false); + + // Act + var result = await controller.GetUserGroups("valid-user-id"); + + // Assert + Assert.IsType(result.Result); + } + + [Fact] + public async Task GetUserGroups_ShouldReturnGroups_WhenUserIsAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(true); + var groups = new List { new PermissionGroup { Name = "usergroup" } }; + mockGroups.Setup(g => g.GetUserGroups(It.IsAny())) + .ReturnsAsync(LogicResult>.Ok(groups)); + + // Act + var result = await controller.GetUserGroups("valid-user-id"); + + // Assert + var okResult = Assert.IsType(result.Result); + var returnedGroups = Assert.IsAssignableFrom>(okResult.Value); + Assert.Single(returnedGroups); + Assert.Equal("usergroup", returnedGroups.First().Name); + } + + [Fact] + public async Task GetGroup_ShouldReturnUnauthorized_WhenUserIsNotAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(false); + + // Act + var result = await controller.GetGroup("group-name"); + + // Assert + Assert.IsType(result.Result); + } + + [Fact] + public async Task GetGroup_ShouldReturnGroup_WhenUserIsAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(true); + var group = new PermissionGroup { Name = "group-name" }; + mockGroups.Setup(g => g.GetGroup(It.IsAny())).ReturnsAsync(LogicResult.Ok(group)); + + // Act + var result = await controller.GetGroup("group-name"); + + // Assert + var okResult = Assert.IsType(result.Result); + var returnedGroup = Assert.IsType(okResult.Value); + Assert.Equal("group-name", returnedGroup.Name); + } + + [Fact] + public async Task CreateGroup_ShouldReturnUnauthorized_WhenUserIsNotAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(false); + + // Act + var result = await controller.CreateGroup(new PermissionGroup { Name = "newgroup" }); + + // Assert + Assert.IsType(result.Result); + } + + [Fact] + public async Task CreateGroup_ShouldReturnGroup_WhenUserIsAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(true); + var group = new PermissionGroup { Name = "newgroup" }; + mockGroups.Setup(g => g.CreateGroup(It.IsAny())) + .ReturnsAsync(LogicResult.Ok(group)); + + // Act + var result = await controller.CreateGroup(new PermissionGroup { Name = "newgroup" }); + + // Assert + var okResult = Assert.IsType(result.Result); + var createdGroup = Assert.IsType(okResult.Value); + Assert.Equal("newgroup", createdGroup.Name); + } + + [Fact] + public async Task UpdateGroup_ShouldReturnUnauthorized_WhenUserIsNotAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(false); + + // Act + var result = await controller.UpdateGroup(new PermissionGroup { Name = "updategroup" }); + + // Assert + Assert.IsType(result.Result); + } + + [Fact] + public async Task UpdateGroup_ShouldReturnGroup_WhenUserIsAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(true); + var group = new PermissionGroup { Name = "updategroup" }; + mockGroups.Setup(g => g.UpdateGroup(It.IsAny())) + .ReturnsAsync(LogicResult.Ok(group)); + + // Act + var result = await controller.UpdateGroup(new PermissionGroup { Name = "updategroup" }); + + // Assert + var okResult = Assert.IsType(result.Result); + var updatedGroup = Assert.IsType(okResult.Value); + Assert.Equal("updategroup", updatedGroup.Name); + } + + [Fact] + public async Task DeleteGroup_ShouldReturnUnauthorized_WhenUserIsNotAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(false); + + // Act + var result = await controller.DeleteGroup("group-name"); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task DeleteGroup_ShouldReturnOk_WhenUserIsAuthorized() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockPerms.Setup(p => p.HasPermission(It.IsAny(), It.IsAny())).ReturnsAsync(true); + mockGroups.Setup(g => g.DeleteGroup(It.IsAny())).ReturnsAsync(LogicResult.Ok()); + + // Act + var result = await controller.DeleteGroup("group-name"); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task GetUserGroups_ShouldReturnBadRequest_WhenUserIdIsInvalid() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockGroups.Setup(g => g.GetUserGroups(It.IsAny())) + .ReturnsAsync(LogicResult>.BadRequest("Invalid user id")); + + // Act + var result = await controller.GetUserGroups("invalid-user-id"); + + // Assert + var badRequestResult = Assert.IsType(result.Result); + Assert.Equal((int)HttpStatusCode.BadRequest, badRequestResult.StatusCode); + Assert.Equal("Invalid user id", badRequestResult.Value); + } + + [Fact] + public async Task GetUserGroups_ShouldReturnNotFound_WhenUserDoesNotExist() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockGroups.Setup(g => g.GetUserGroups(It.IsAny())) + .ReturnsAsync(LogicResult>.NotFound("That user does not exist")); + + // Act + var result = await controller.GetUserGroups("nonexistent-user-id"); + + // Assert + var notFoundResult = Assert.IsType(result.Result); + Assert.Equal((int)HttpStatusCode.NotFound, notFoundResult.StatusCode); + Assert.Equal("That user does not exist", notFoundResult.Value); + } + + [Fact] + public async Task GetGroup_ShouldReturnNotFound_WhenGroupDoesNotExist() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockGroups.Setup(g => g.GetGroup(It.IsAny())) + .ReturnsAsync(LogicResult.NotFound("That group does not exist")); + + // Act + var result = await controller.GetGroup("nonexistent-group-name"); + + // Assert + var notFoundResult = Assert.IsType(result.Result); + Assert.Equal((int)HttpStatusCode.NotFound, notFoundResult.StatusCode); + Assert.Equal("That group does not exist", notFoundResult.Value); + } + + [Fact] + public async Task CreateGroup_ShouldReturnBadRequest_WhenGroupIsNull() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockGroups.Setup(g => g.CreateGroup(It.IsAny())) + .ReturnsAsync(LogicResult.BadRequest("Provide a group")); + + // Act + var result = await controller.CreateGroup(null); + + // Assert + var badRequestResult = Assert.IsType(result.Result); + Assert.Equal((int)HttpStatusCode.BadRequest, badRequestResult.StatusCode); + Assert.Equal("Provide a group", badRequestResult.Value); + } + + [Fact] + public async Task CreateGroup_ShouldReturnBadRequest_WhenGroupNameIsInvalid() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockGroups.Setup(g => g.CreateGroup(It.IsAny())) + .ReturnsAsync(LogicResult.BadRequest("Group names must start with 'group.'")); + + // Act + var result = await controller.CreateGroup(new PermissionGroup { Name = "invalidgroupname" }); + + // Assert + var badRequestResult = Assert.IsType(result.Result); + Assert.Equal((int)HttpStatusCode.BadRequest, badRequestResult.StatusCode); + Assert.Equal("Group names must start with 'group.'", badRequestResult.Value); + } + + [Fact] + public async Task CreateGroup_ShouldReturnConflict_WhenGroupAlreadyExists() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockGroups.Setup(g => g.CreateGroup(It.IsAny())) + .ReturnsAsync(LogicResult.Conflict("That group already exists")); + + // Act + var result = await controller.CreateGroup(new PermissionGroup { Name = "group.exists" }); + + // Assert + var conflictResult = Assert.IsType(result.Result); + Assert.Equal((int)HttpStatusCode.Conflict, conflictResult.StatusCode); + Assert.Equal("That group already exists", conflictResult.Value); + } + + [Fact] + public async Task UpdateGroup_ShouldReturnNotFound_WhenGroupDoesNotExist() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockGroups.Setup(g => g.UpdateGroup(It.IsAny())).ReturnsAsync(LogicResult.NotFound("That group does not exist")); + + // Act + var result = await controller.UpdateGroup(new PermissionGroup { Name = "nonexistent-group-name" }); + + // Assert + var notFoundResult = Assert.IsType(result.Result); + Assert.Equal((int)HttpStatusCode.NotFound, notFoundResult.StatusCode); + Assert.Equal("That group does not exist", notFoundResult.Value); + } + + [Fact] + public async Task DeleteGroup_ShouldReturnNotFound_WhenGroupDoesNotExist() { + // Arrange + var (mockGroups, mockPermissions, mockPerms, mockContext, controller) = SetupEnvironment(); + mockGroups.Setup(g => g.DeleteGroup(It.IsAny())).ReturnsAsync(LogicResult.NotFound("That group does not exist")); + + // Act + var result = await controller.DeleteGroup("nonexistent-group-name"); + + // Assert + var notFoundResult = Assert.IsType(result); + Assert.Equal((int)HttpStatusCode.NotFound, notFoundResult.StatusCode); + Assert.Equal("That group does not exist", notFoundResult.Value); + } +} \ No newline at end of file