Add unit tests for Environments, Audit, and Webhooks namespaces #6
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using MicCheck.Api.Audit;
|
||||
using MicCheck.Api.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -46,6 +47,7 @@ public class OrganizationService(IMicCheckDbContext db, AuditService auditServic
|
||||
return org;
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage(Justification = "ExecuteUpdateAsync requires a real EF Core relational query provider; our Mock<DbSet<T>> LINQ-to-Objects provider can't execute it, and CLAUDE.md disallows the EF InMemory provider as a substitute. The not-a-member early-return branch is covered by OrganizationServiceTests.")]
|
||||
public async Task SetPrimaryAsync(int organizationId, int userId, CancellationToken ct = default)
|
||||
{
|
||||
var member = await db.OrganizationUsers
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
using MicCheck.Api.Organizations;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace MicCheck.Api.Tests.Unit.Organizations;
|
||||
|
||||
[TestFixture]
|
||||
public class CreateOrganizationRequestValidatorTests
|
||||
{
|
||||
private CreateOrganizationRequestValidator _validator = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => _validator = new CreateOrganizationRequestValidator();
|
||||
|
||||
[Test]
|
||||
public void WhenTheRequestIsValid_ThenValidationSucceeds()
|
||||
{
|
||||
var result = _validator.Validate(new CreateOrganizationRequest("My Org"));
|
||||
|
||||
Assert.That(result.IsValid, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenNameIsEmpty_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new CreateOrganizationRequest(""));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenNameExceedsMaximumLength_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new CreateOrganizationRequest(new string('a', 201)));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture]
|
||||
public class UpdateOrganizationRequestValidatorTests
|
||||
{
|
||||
private UpdateOrganizationRequestValidator _validator = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => _validator = new UpdateOrganizationRequestValidator();
|
||||
|
||||
[Test]
|
||||
public void WhenTheRequestIsValid_ThenValidationSucceeds()
|
||||
{
|
||||
var result = _validator.Validate(new UpdateOrganizationRequest("Renamed"));
|
||||
|
||||
Assert.That(result.IsValid, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenNameIsEmpty_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new UpdateOrganizationRequest(""));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture]
|
||||
public class InviteUserRequestValidatorTests
|
||||
{
|
||||
private InviteUserRequestValidator _validator = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => _validator = new InviteUserRequestValidator();
|
||||
|
||||
[Test]
|
||||
public void WhenTheRequestIsValid_ThenValidationSucceeds()
|
||||
{
|
||||
var result = _validator.Validate(new InviteUserRequest(1, "Admin"));
|
||||
|
||||
Assert.That(result.IsValid, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenUserIdIsZeroOrLess_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new InviteUserRequest(0, "User"));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenRoleIsEmpty_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new InviteUserRequest(1, ""));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenRoleIsNotAValidOrganizationRole_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new InviteUserRequest(1, "SuperAdmin"));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using MicCheck.Api.Organizations;
|
||||
using MicCheck.Api.Users;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace MicCheck.Api.Tests.Unit.Organizations;
|
||||
|
||||
[TestFixture]
|
||||
public class OrganizationResponseTests
|
||||
{
|
||||
[Test]
|
||||
public void WhenMappingAnOrganizationWithoutSpecifyingPrimary_ThenIsPrimaryDefaultsToFalse()
|
||||
{
|
||||
var org = new Organization { Id = 1, Name = "My Org", CreatedAt = DateTimeOffset.UtcNow };
|
||||
|
||||
var response = OrganizationResponse.From(org);
|
||||
|
||||
Assert.That(response.Id, Is.EqualTo(1));
|
||||
Assert.That(response.Name, Is.EqualTo("My Org"));
|
||||
Assert.That(response.IsPrimary, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenMappingAnOrganizationAsPrimary_ThenIsPrimaryIsTrue()
|
||||
{
|
||||
var org = new Organization { Id = 1, Name = "My Org", CreatedAt = DateTimeOffset.UtcNow };
|
||||
|
||||
var response = OrganizationResponse.From(org, isPrimary: true);
|
||||
|
||||
Assert.That(response.IsPrimary, Is.True);
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture]
|
||||
public class OrganizationMemberResponseTests
|
||||
{
|
||||
[Test]
|
||||
public void WhenMappingAMember_ThenFieldsAreSourcedFromTheAssociatedUser()
|
||||
{
|
||||
var user = new User
|
||||
{
|
||||
Id = 1,
|
||||
Email = "alice@example.com",
|
||||
PasswordHash = "hashed",
|
||||
FirstName = "Alice",
|
||||
LastName = "Smith",
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
var member = new OrganizationUser { OrganizationId = 1, UserId = 1, Role = OrganizationRole.Admin, User = user };
|
||||
|
||||
var response = OrganizationMemberResponse.From(member);
|
||||
|
||||
Assert.That(response.UserId, Is.EqualTo(1));
|
||||
Assert.That(response.FirstName, Is.EqualTo("Alice"));
|
||||
Assert.That(response.LastName, Is.EqualTo("Smith"));
|
||||
Assert.That(response.Email, Is.EqualTo("alice@example.com"));
|
||||
Assert.That(response.Role, Is.EqualTo("Admin"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
using MicCheck.Api.Audit;
|
||||
using MicCheck.Api.Data;
|
||||
using MicCheck.Api.Organizations;
|
||||
using MicCheck.Api.Tests.Unit.TestSupport;
|
||||
using MicCheck.Api.Users;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace MicCheck.Api.Tests.Unit.Organizations;
|
||||
|
||||
[TestFixture]
|
||||
public class OrganizationServiceTests
|
||||
{
|
||||
private Mock<IMicCheckDbContext> _db = null!;
|
||||
private List<Organization> _organizations = null!;
|
||||
private List<OrganizationUser> _members = null!;
|
||||
private List<User> _users = null!;
|
||||
private OrganizationService _service = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_db = new Mock<IMicCheckDbContext>();
|
||||
_organizations = [];
|
||||
_members = [];
|
||||
var organizationsSet = MockDbSetFactory.Create(_organizations);
|
||||
organizationsSet.Setup(m => m.Add(It.IsAny<Organization>())).Callback<Organization>(org =>
|
||||
{
|
||||
typeof(Organization).GetProperty(nameof(Organization.Id))!.SetValue(org, _organizations.Count + 1);
|
||||
_organizations.Add(org);
|
||||
// Mimics EF Core's cascade-insert and FK fixup of a loaded, mapped collection navigation on SaveChanges.
|
||||
foreach (var member in org.Members)
|
||||
{
|
||||
typeof(OrganizationUser).GetProperty(nameof(OrganizationUser.OrganizationId))!.SetValue(member, org.Id);
|
||||
_members.Add(member);
|
||||
}
|
||||
});
|
||||
_db.Setup(c => c.Organizations).Returns(organizationsSet.Object);
|
||||
_db.SetupDbSet(c => c.OrganizationUsers, _members);
|
||||
_users = [];
|
||||
_db.SetupDbSet(c => c.Users, _users);
|
||||
|
||||
var webhookQueue = new MicCheck.Api.Webhooks.WebhookQueue();
|
||||
var auditService = new Mock<AuditService>(_db.Object, null!, webhookQueue);
|
||||
auditService.Setup(a => a.LogAsync(
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
|
||||
It.IsAny<int>(), It.IsAny<int?>(), It.IsAny<int?>(),
|
||||
It.IsAny<string?>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
_service = new OrganizationService(_db.Object, auditService.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenCreatingAnOrganization_ThenItIsPersistedWithTheCreatorAsAdmin()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
|
||||
Assert.That(org.Name, Is.EqualTo("My Org"));
|
||||
Assert.That(_members.Single().UserId, Is.EqualTo(1));
|
||||
Assert.That(_members.Single().Role, Is.EqualTo(OrganizationRole.Admin));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenCreatingAUsersFirstOrganization_ThenItIsMarkedAsPrimary()
|
||||
{
|
||||
var org = await _service.CreateAsync("First Org", creatorUserId: 1);
|
||||
|
||||
Assert.That(_members.Single(m => m.OrganizationId == org.Id).IsPrimary, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenCreatingASecondOrganizationForAUser_ThenItIsNotMarkedAsPrimary()
|
||||
{
|
||||
await _service.CreateAsync("First Org", creatorUserId: 1);
|
||||
|
||||
var second = await _service.CreateAsync("Second Org", creatorUserId: 1);
|
||||
|
||||
Assert.That(_members.Single(m => m.OrganizationId == second.Id).IsPrimary, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingOrganizationsForAUser_ThenOnlyThatUsersOrganizationsAreReturnedWithPrimaryFlag()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
await _service.CreateAsync("Other User Org", creatorUserId: 2);
|
||||
|
||||
var results = await _service.ListForUserAsync(1);
|
||||
|
||||
Assert.That(results, Has.Count.EqualTo(1));
|
||||
Assert.That(results[0].Org.Id, Is.EqualTo(org.Id));
|
||||
Assert.That(results[0].IsPrimary, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenFindingAnOrganizationByIdThatExists_ThenItIsReturned()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
|
||||
var found = await _service.FindByIdAsync(org.Id);
|
||||
|
||||
Assert.That(found, Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenFindingAnOrganizationByIdThatDoesNotExist_ThenNullIsReturned()
|
||||
{
|
||||
var found = await _service.FindByIdAsync(999);
|
||||
|
||||
Assert.That(found, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenSettingPrimaryForAUserThatIsNotAMember_ThenKeyNotFoundExceptionIsThrown()
|
||||
{
|
||||
Assert.ThrowsAsync<KeyNotFoundException>(() => _service.SetPrimaryAsync(999, 1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenUpdatingAnOrganizationThatDoesNotExist_ThenKeyNotFoundExceptionIsThrown()
|
||||
{
|
||||
Assert.ThrowsAsync<KeyNotFoundException>(() => _service.UpdateAsync(999, "renamed"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenUpdatingAnOrganization_ThenTheNameIsChanged()
|
||||
{
|
||||
var org = await _service.CreateAsync("Old Name", creatorUserId: 1);
|
||||
|
||||
var updated = await _service.UpdateAsync(org.Id, "New Name");
|
||||
|
||||
Assert.That(updated.Name, Is.EqualTo("New Name"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenDeletingAnOrganizationThatDoesNotExist_ThenNoExceptionIsThrown()
|
||||
{
|
||||
Assert.DoesNotThrowAsync(() => _service.DeleteAsync(999));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenDeletingAnOrganization_ThenItIsRemoved()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
|
||||
await _service.DeleteAsync(org.Id);
|
||||
|
||||
Assert.That(_organizations.Any(o => o.Id == org.Id), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingMembers_ThenOnlyThatOrganizationsMembersAreReturned()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
await _service.CreateAsync("Other Org", creatorUserId: 2);
|
||||
|
||||
var members = await _service.ListMembersAsync(org.Id);
|
||||
|
||||
Assert.That(members, Has.Count.EqualTo(1));
|
||||
Assert.That(members[0].UserId, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenInvitingAUserWhoIsNotYetAMember_ThenTheyAreAddedWithTheGivenRole()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
|
||||
await _service.InviteUserAsync(org.Id, userId: 2, OrganizationRole.User);
|
||||
|
||||
Assert.That(_members.Single(m => m.UserId == 2).Role, Is.EqualTo(OrganizationRole.User));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenInvitingAUserWhoIsAlreadyAMember_ThenTheirRoleIsUpdatedWithoutDuplication()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
await _service.InviteUserAsync(org.Id, userId: 2, OrganizationRole.User);
|
||||
|
||||
await _service.InviteUserAsync(org.Id, userId: 2, OrganizationRole.Admin);
|
||||
|
||||
Assert.That(_members.Count(m => m.UserId == 2), Is.EqualTo(1));
|
||||
Assert.That(_members.Single(m => m.UserId == 2).Role, Is.EqualTo(OrganizationRole.Admin));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenRemovingAMemberThatDoesNotExist_ThenNoExceptionIsThrown()
|
||||
{
|
||||
Assert.DoesNotThrowAsync(() => _service.RemoveMemberAsync(1, 999));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenRemovingAMember_ThenTheyAreNoLongerListed()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
await _service.InviteUserAsync(org.Id, userId: 2, OrganizationRole.User);
|
||||
|
||||
await _service.RemoveMemberAsync(org.Id, 2);
|
||||
|
||||
Assert.That(_members.Any(m => m.UserId == 2), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenGettingAnInviteTokenForAnOrganizationThatDoesNotExist_ThenKeyNotFoundExceptionIsThrown()
|
||||
{
|
||||
Assert.ThrowsAsync<KeyNotFoundException>(() => _service.GetOrCreateInviteTokenAsync(999));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenGettingAnInviteTokenForTheFirstTime_ThenANewTokenIsGeneratedAndPersisted()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
|
||||
var token = await _service.GetOrCreateInviteTokenAsync(org.Id);
|
||||
|
||||
Assert.That(token, Is.Not.Null.And.Not.Empty);
|
||||
Assert.That(_organizations.Single(o => o.Id == org.Id).InviteToken, Is.EqualTo(token));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenGettingAnInviteTokenASecondTime_ThenTheSameTokenIsReturned()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
var first = await _service.GetOrCreateInviteTokenAsync(org.Id);
|
||||
|
||||
var second = await _service.GetOrCreateInviteTokenAsync(org.Id);
|
||||
|
||||
Assert.That(second, Is.EqualTo(first));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenRegeneratingAnInviteTokenForAnOrganizationThatDoesNotExist_ThenKeyNotFoundExceptionIsThrown()
|
||||
{
|
||||
Assert.ThrowsAsync<KeyNotFoundException>(() => _service.RegenerateInviteTokenAsync(999));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenRegeneratingAnInviteToken_ThenANewTokenReplacesTheOld()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
var first = await _service.GetOrCreateInviteTokenAsync(org.Id);
|
||||
|
||||
var regenerated = await _service.RegenerateInviteTokenAsync(org.Id);
|
||||
|
||||
Assert.That(regenerated, Is.Not.EqualTo(first));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenFindingAnOrganizationByAValidInviteToken_ThenItIsReturned()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
var token = await _service.GetOrCreateInviteTokenAsync(org.Id);
|
||||
|
||||
var found = await _service.FindByInviteTokenAsync(token);
|
||||
|
||||
Assert.That(found, Is.Not.Null);
|
||||
Assert.That(found!.Id, Is.EqualTo(org.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenFindingAnOrganizationByAnInvalidInviteToken_ThenNullIsReturned()
|
||||
{
|
||||
var found = await _service.FindByInviteTokenAsync("not-a-real-token");
|
||||
|
||||
Assert.That(found, Is.Null);
|
||||
}
|
||||
|
||||
private User AddUser(int id, string email, bool isActive = true) => new()
|
||||
{
|
||||
Id = id,
|
||||
Email = email,
|
||||
PasswordHash = "hashed",
|
||||
FirstName = "Test",
|
||||
LastName = "User",
|
||||
IsActive = isActive,
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
[Test]
|
||||
public async Task WhenInvitingByEmailForAUserThatDoesNotExist_ThenTheResultIndicatesFailure()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
|
||||
var results = await _service.InviteUsersByEmailAsync(org.Id, [new InviteByEmailEntry("nobody@example.com", "User")]);
|
||||
|
||||
Assert.That(results[0].Success, Is.False);
|
||||
Assert.That(results[0].Error, Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenInvitingByEmailWithAnInvalidRole_ThenTheResultIndicatesFailure()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
_users.Add(AddUser(2, "bob@example.com"));
|
||||
|
||||
var results = await _service.InviteUsersByEmailAsync(org.Id, [new InviteByEmailEntry("bob@example.com", "NotARole")]);
|
||||
|
||||
Assert.That(results[0].Success, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenInvitingByEmailForAnInactiveUser_ThenTheResultIndicatesFailure()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
_users.Add(AddUser(2, "bob@example.com", isActive: false));
|
||||
|
||||
var results = await _service.InviteUsersByEmailAsync(org.Id, [new InviteByEmailEntry("bob@example.com", "User")]);
|
||||
|
||||
Assert.That(results[0].Success, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenInvitingByEmailForAValidActiveUser_ThenTheyAreAddedAsAMemberAndSuccessIsReturned()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
_users.Add(AddUser(2, "bob@example.com"));
|
||||
|
||||
var results = await _service.InviteUsersByEmailAsync(org.Id, [new InviteByEmailEntry(" BOB@Example.com ", "Admin")]);
|
||||
|
||||
Assert.That(results[0].Success, Is.True);
|
||||
Assert.That(_members.Single(m => m.UserId == 2).Role, Is.EqualTo(OrganizationRole.Admin));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenAcceptingAnInviteWithAnInvalidToken_ThenKeyNotFoundExceptionIsThrown()
|
||||
{
|
||||
Assert.ThrowsAsync<KeyNotFoundException>(() => _service.AcceptInviteAsync("not-a-real-token", 2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenAcceptingAValidInvite_ThenTheUserIsAddedAsAMemberWithUserRole()
|
||||
{
|
||||
var org = await _service.CreateAsync("My Org", creatorUserId: 1);
|
||||
var token = await _service.GetOrCreateInviteTokenAsync(org.Id);
|
||||
|
||||
await _service.AcceptInviteAsync(token, userId: 2);
|
||||
|
||||
Assert.That(_members.Single(m => m.UserId == 2).Role, Is.EqualTo(OrganizationRole.User));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
using System.Security.Claims;
|
||||
using MicCheck.Api.Audit;
|
||||
using MicCheck.Api.Common;
|
||||
using MicCheck.Api.Data;
|
||||
using MicCheck.Api.Organizations;
|
||||
using MicCheck.Api.Tests.Unit.TestSupport;
|
||||
using MicCheck.Api.Users;
|
||||
using MicCheck.Api.Webhooks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace MicCheck.Api.Tests.Unit.Organizations;
|
||||
|
||||
[TestFixture]
|
||||
public class OrganizationsControllerTests
|
||||
{
|
||||
private Mock<IMicCheckDbContext> _db = null!;
|
||||
private List<Organization> _organizations = null!;
|
||||
private List<OrganizationUser> _members = null!;
|
||||
private List<User> _users = null!;
|
||||
private List<Webhook> _webhooks = null!;
|
||||
private OrganizationsController _controller = null!;
|
||||
private const int UserId = 1;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_db = new Mock<IMicCheckDbContext>();
|
||||
_organizations = [];
|
||||
_members = [];
|
||||
var organizationsSet = MockDbSetFactory.Create(_organizations);
|
||||
organizationsSet.Setup(m => m.Add(It.IsAny<Organization>())).Callback<Organization>(org =>
|
||||
{
|
||||
typeof(Organization).GetProperty(nameof(Organization.Id))!.SetValue(org, _organizations.Count + 1);
|
||||
_organizations.Add(org);
|
||||
foreach (var member in org.Members)
|
||||
{
|
||||
typeof(OrganizationUser).GetProperty(nameof(OrganizationUser.OrganizationId))!.SetValue(member, org.Id);
|
||||
_members.Add(member);
|
||||
}
|
||||
});
|
||||
_db.Setup(c => c.Organizations).Returns(organizationsSet.Object);
|
||||
_db.SetupDbSet(c => c.OrganizationUsers, _members);
|
||||
_users = [];
|
||||
_db.SetupDbSet(c => c.Users, _users);
|
||||
_webhooks = [];
|
||||
_db.SetupDbSetWithGeneratedIds(c => c.Webhooks, _webhooks);
|
||||
|
||||
var webhookQueue = new WebhookQueue();
|
||||
var auditService = new Mock<AuditService>(_db.Object, null!, webhookQueue);
|
||||
auditService.Setup(a => a.LogAsync(
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
|
||||
It.IsAny<int>(), It.IsAny<int?>(), It.IsAny<int?>(),
|
||||
It.IsAny<string?>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
_controller = new OrganizationsController(new OrganizationService(_db.Object, auditService.Object), new WebhookService(_db.Object));
|
||||
|
||||
var identity = new ClaimsIdentity([new Claim(ClaimTypes.NameIdentifier, UserId.ToString())], "TestAuth");
|
||||
_controller.ControllerContext = new ControllerContext
|
||||
{
|
||||
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal(identity) }
|
||||
};
|
||||
}
|
||||
|
||||
private void Unauthenticate() =>
|
||||
_controller.ControllerContext.HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingWithoutAuthentication_ThenUnauthorizedIsReturned()
|
||||
{
|
||||
Unauthenticate();
|
||||
|
||||
var result = await _controller.List();
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<UnauthorizedResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingOrganizationsForTheCurrentUser_ThenTheyAreReturnedInAPage()
|
||||
{
|
||||
await _controller.Create(new CreateOrganizationRequest("Org A"), CancellationToken.None);
|
||||
await _controller.Create(new CreateOrganizationRequest("Org B"), CancellationToken.None);
|
||||
|
||||
var result = await _controller.List();
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
var page = (PaginatedResponse<OrganizationResponse>)ok!.Value!;
|
||||
Assert.That(page.Count, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenCreatingWithoutAuthentication_ThenUnauthorizedIsReturned()
|
||||
{
|
||||
Unauthenticate();
|
||||
|
||||
var result = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<UnauthorizedResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenCreatingAnOrganization_ThenACreatedResultWithTheOrganizationIsReturned()
|
||||
{
|
||||
var result = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
|
||||
var created = result.Result as CreatedAtActionResult;
|
||||
Assert.That(created, Is.Not.Null);
|
||||
Assert.That(((OrganizationResponse)created!.Value!).Name, Is.EqualTo("My Org"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenGettingAnOrganizationByIdThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.GetById(999, CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenGettingAnOrganizationByIdThatExists_ThenItIsReturned()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.GetById(orgId, CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<OkObjectResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenUpdatingAnOrganizationThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.Update(999, new UpdateOrganizationRequest("renamed"), CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenUpdatingAnOrganization_ThenTheUpdatedOrganizationIsReturned()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("Old Name"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.Update(orgId, new UpdateOrganizationRequest("New Name"), CancellationToken.None);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
Assert.That(((OrganizationResponse)ok!.Value!).Name, Is.EqualTo("New Name"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenSettingPrimaryWithoutAuthentication_ThenUnauthorizedIsReturned()
|
||||
{
|
||||
Unauthenticate();
|
||||
|
||||
var result = await _controller.SetPrimary(1, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<UnauthorizedResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenSettingPrimaryForAUserThatIsNotAMember_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.SetPrimary(999, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenDeletingAnOrganizationThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.Delete(999, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenDeletingAnOrganization_ThenNoContentIsReturnedAndItIsRemoved()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.Delete(orgId, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NoContentResult>());
|
||||
Assert.That(_organizations.Any(o => o.Id == orgId), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingUsersForAnOrganizationThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.ListUsers(999, CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingUsersForAnOrganization_ThenMembersAreReturned()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
var user = new User { Id = UserId, Email = "alice@example.com", PasswordHash = "hashed", FirstName = "Alice", LastName = "Smith", CreatedAt = DateTimeOffset.UtcNow };
|
||||
_users.Add(user);
|
||||
var member = _members.Single(m => m.OrganizationId == orgId);
|
||||
typeof(OrganizationUser).GetProperty(nameof(OrganizationUser.User))!.SetValue(member, user);
|
||||
|
||||
var result = await _controller.ListUsers(orgId, CancellationToken.None);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
var members = (IReadOnlyList<OrganizationMemberResponse>)ok!.Value!;
|
||||
Assert.That(members, Has.Count.EqualTo(1));
|
||||
Assert.That(members[0].Email, Is.EqualTo("alice@example.com"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenInvitingAUserToAnOrganizationThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.InviteUser(999, new InviteUserRequest(2, "User"), CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenInvitingAUser_ThenOkIsReturnedAndTheyBecomeAMember()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.InviteUser(orgId, new InviteUserRequest(2, "Admin"), CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<OkResult>());
|
||||
Assert.That(_members.Single(m => m.UserId == 2).Role, Is.EqualTo(OrganizationRole.Admin));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenInvitingUsersByEmailForAnOrganizationThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.InviteUsersByEmail(999, new InviteUsersByEmailRequest([]), CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenInvitingUsersByEmail_ThenResultsAreReturnedForEachEntry()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.InviteUsersByEmail(
|
||||
orgId, new InviteUsersByEmailRequest([new InviteByEmailEntry("nobody@example.com", "User")]), CancellationToken.None);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
var results = (IReadOnlyList<InviteByEmailResult>)ok!.Value!;
|
||||
Assert.That(results, Has.Count.EqualTo(1));
|
||||
Assert.That(results[0].Success, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenGettingInviteLinkForAnOrganizationThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.GetInviteLink(999, CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenGettingInviteLink_ThenATokenIsReturned()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.GetInviteLink(orgId, CancellationToken.None);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
Assert.That(((InviteTokenResponse)ok!.Value!).Token, Is.Not.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenRegeneratingInviteLinkForAnOrganizationThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.RegenerateInviteLink(999, CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenRegeneratingInviteLink_ThenANewTokenIsReturned()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
var first = await _controller.GetInviteLink(orgId, CancellationToken.None);
|
||||
var firstToken = ((InviteTokenResponse)((OkObjectResult)first.Result!).Value!).Token;
|
||||
|
||||
var result = await _controller.RegenerateInviteLink(orgId, CancellationToken.None);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
Assert.That(((InviteTokenResponse)ok!.Value!).Token, Is.Not.EqualTo(firstToken));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenAcceptingInviteWithoutAuthentication_ThenUnauthorizedIsReturned()
|
||||
{
|
||||
Unauthenticate();
|
||||
|
||||
var result = await _controller.AcceptInvite("some-token", CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<UnauthorizedResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenAcceptingInviteWithAnInvalidToken_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.AcceptInvite("not-a-real-token", CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NotFoundObjectResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenAcceptingAValidInvite_ThenOkIsReturnedAndTheUserBecomesAMember()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
var link = await _controller.GetInviteLink(orgId, CancellationToken.None);
|
||||
var token = ((InviteTokenResponse)((OkObjectResult)link.Result!).Value!).Token;
|
||||
|
||||
Unauthenticate();
|
||||
var identity = new ClaimsIdentity([new Claim(ClaimTypes.NameIdentifier, "2")], "TestAuth");
|
||||
_controller.ControllerContext.HttpContext.User = new ClaimsPrincipal(identity);
|
||||
|
||||
var result = await _controller.AcceptInvite(token, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<OkResult>());
|
||||
Assert.That(_members.Any(m => m.UserId == 2), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenRemovingUserFromAnOrganizationThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.RemoveUser(999, 2, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenRemovingAUser_ThenNoContentIsReturnedAndTheyAreNoLongerAMember()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
await _controller.InviteUser(orgId, new InviteUserRequest(2, "User"), CancellationToken.None);
|
||||
|
||||
var result = await _controller.RemoveUser(orgId, 2, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NoContentResult>());
|
||||
Assert.That(_members.Any(m => m.UserId == 2), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingWebhooksForAnOrganizationThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.ListWebhooks(999, CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenCreatingAWebhookForAnOrganizationThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.CreateWebhook(999, new CreateWebhookRequest("https://example.com", null, true), CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenCreatingAWebhook_ThenACreatedResultWithTheWebhookIsReturned()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.CreateWebhook(orgId, new CreateWebhookRequest("https://example.com", "secret", true), CancellationToken.None);
|
||||
|
||||
var webhookCreated = result.Result as CreatedAtActionResult;
|
||||
Assert.That(webhookCreated, Is.Not.Null);
|
||||
Assert.That(((WebhookResponse)webhookCreated!.Value!).Url, Is.EqualTo("https://example.com"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingWebhooksForAnOrganization_ThenTheyAreReturned()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
await _controller.CreateWebhook(orgId, new CreateWebhookRequest("https://example.com", null, true), CancellationToken.None);
|
||||
|
||||
var result = await _controller.ListWebhooks(orgId, CancellationToken.None);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
var webhooks = (IReadOnlyList<WebhookResponse>)ok!.Value!;
|
||||
Assert.That(webhooks, Has.Count.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenUpdatingAWebhookForAnOrganizationThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.UpdateWebhook(999, 1, new CreateWebhookRequest("https://example.com", null, true), CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenUpdatingAWebhookThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.UpdateWebhook(orgId, 999, new CreateWebhookRequest("https://example.com", null, true), CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenUpdatingAWebhookThatBelongsToADifferentOrganization_ThenNotFoundIsReturned()
|
||||
{
|
||||
var org1 = await _controller.Create(new CreateOrganizationRequest("Org 1"), CancellationToken.None);
|
||||
var org1Id = ((OrganizationResponse)((CreatedAtActionResult)org1.Result!).Value!).Id;
|
||||
var org2 = await _controller.Create(new CreateOrganizationRequest("Org 2"), CancellationToken.None);
|
||||
var org2Id = ((OrganizationResponse)((CreatedAtActionResult)org2.Result!).Value!).Id;
|
||||
var webhook = await _controller.CreateWebhook(org1Id, new CreateWebhookRequest("https://example.com", null, true), CancellationToken.None);
|
||||
var webhookId = ((WebhookResponse)((CreatedAtActionResult)webhook.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.UpdateWebhook(org2Id, webhookId, new CreateWebhookRequest("https://other.com", null, false), CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenUpdatingAWebhook_ThenItIsChanged()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
var webhook = await _controller.CreateWebhook(orgId, new CreateWebhookRequest("https://example.com", null, true), CancellationToken.None);
|
||||
var webhookId = ((WebhookResponse)((CreatedAtActionResult)webhook.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.UpdateWebhook(orgId, webhookId, new CreateWebhookRequest("https://updated.com", null, false), CancellationToken.None);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
Assert.That(((WebhookResponse)ok!.Value!).Url, Is.EqualTo("https://updated.com"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenDeletingAWebhookForAnOrganizationThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.DeleteWebhook(999, 1, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenDeletingAWebhookThatBelongsToADifferentOrganization_ThenNotFoundIsReturned()
|
||||
{
|
||||
var org1 = await _controller.Create(new CreateOrganizationRequest("Org 1"), CancellationToken.None);
|
||||
var org1Id = ((OrganizationResponse)((CreatedAtActionResult)org1.Result!).Value!).Id;
|
||||
var org2 = await _controller.Create(new CreateOrganizationRequest("Org 2"), CancellationToken.None);
|
||||
var org2Id = ((OrganizationResponse)((CreatedAtActionResult)org2.Result!).Value!).Id;
|
||||
var webhook = await _controller.CreateWebhook(org1Id, new CreateWebhookRequest("https://example.com", null, true), CancellationToken.None);
|
||||
var webhookId = ((WebhookResponse)((CreatedAtActionResult)webhook.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.DeleteWebhook(org2Id, webhookId, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenDeletingAWebhook_ThenItIsRemoved()
|
||||
{
|
||||
var created = await _controller.Create(new CreateOrganizationRequest("My Org"), CancellationToken.None);
|
||||
var orgId = ((OrganizationResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
var webhook = await _controller.CreateWebhook(orgId, new CreateWebhookRequest("https://example.com", null, true), CancellationToken.None);
|
||||
var webhookId = ((WebhookResponse)((CreatedAtActionResult)webhook.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.DeleteWebhook(orgId, webhookId, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NoContentResult>());
|
||||
Assert.That(_webhooks.Any(w => w.Id == webhookId), Is.False);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using MicCheck.Api.Projects;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace MicCheck.Api.Tests.Unit.Projects;
|
||||
|
||||
[TestFixture]
|
||||
public class CreateProjectRequestValidatorTests
|
||||
{
|
||||
private CreateProjectRequestValidator _validator = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => _validator = new CreateProjectRequestValidator();
|
||||
|
||||
[Test]
|
||||
public void WhenTheRequestIsValid_ThenValidationSucceeds()
|
||||
{
|
||||
var result = _validator.Validate(new CreateProjectRequest("My Project", 1));
|
||||
|
||||
Assert.That(result.IsValid, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenNameIsEmpty_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new CreateProjectRequest("", 1));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenNameExceedsMaximumLength_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new CreateProjectRequest(new string('a', 201), 1));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenOrganizationIdIsZeroOrLess_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new CreateProjectRequest("My Project", 0));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using MicCheck.Api.Common.Security.Authorization;
|
||||
using MicCheck.Api.Projects;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace MicCheck.Api.Tests.Unit.Projects;
|
||||
|
||||
[TestFixture]
|
||||
public class ProjectResponseTests
|
||||
{
|
||||
[Test]
|
||||
public void WhenMappingAProject_ThenAllFieldsAreCopied()
|
||||
{
|
||||
var project = new Project { Id = 1, Name = "My Project", OrganizationId = 5, HideDisabledFlags = true, CreatedAt = DateTimeOffset.UtcNow };
|
||||
|
||||
var response = ProjectResponse.From(project);
|
||||
|
||||
Assert.That(response.Id, Is.EqualTo(1));
|
||||
Assert.That(response.Name, Is.EqualTo("My Project"));
|
||||
Assert.That(response.OrganizationId, Is.EqualTo(5));
|
||||
Assert.That(response.HideDisabledFlags, Is.True);
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture]
|
||||
public class UserPermissionResponseTests
|
||||
{
|
||||
[Test]
|
||||
public void WhenMappingAUserProjectPermission_ThenPermissionsAreMappedToStrings()
|
||||
{
|
||||
var permission = new UserProjectPermission
|
||||
{
|
||||
UserId = 1,
|
||||
ProjectId = 2,
|
||||
IsAdmin = true,
|
||||
Permissions = [ProjectPermission.ViewProject, ProjectPermission.EditFeature]
|
||||
};
|
||||
|
||||
var response = UserPermissionResponse.From(permission);
|
||||
|
||||
Assert.That(response.UserId, Is.EqualTo(1));
|
||||
Assert.That(response.ProjectId, Is.EqualTo(2));
|
||||
Assert.That(response.IsAdmin, Is.True);
|
||||
Assert.That(response.Permissions, Is.EqualTo(new[] { "ViewProject", "EditFeature" }));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using MicCheck.Api.Audit;
|
||||
using MicCheck.Api.Common.Security.Authorization;
|
||||
using MicCheck.Api.Data;
|
||||
using MicCheck.Api.Organizations;
|
||||
using MicCheck.Api.Projects;
|
||||
using MicCheck.Api.Tests.Unit.TestSupport;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace MicCheck.Api.Tests.Unit.Projects;
|
||||
|
||||
[TestFixture]
|
||||
public class ProjectServiceTests
|
||||
{
|
||||
private Mock<IMicCheckDbContext> _db = null!;
|
||||
private List<Project> _projects = null!;
|
||||
private List<UserProjectPermission> _permissions = null!;
|
||||
private ProjectService _service = null!;
|
||||
private const int OrganizationId = 1;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_db = new Mock<IMicCheckDbContext>();
|
||||
|
||||
_db.SetupDbSet(c => c.Organizations, [
|
||||
new Organization { Id = OrganizationId, Name = "Test Org", CreatedAt = DateTimeOffset.UtcNow }
|
||||
]);
|
||||
_projects = [];
|
||||
_db.SetupDbSetWithGeneratedIds(c => c.Projects, _projects);
|
||||
_permissions = [];
|
||||
_db.SetupDbSet(c => c.UserProjectPermissions, _permissions);
|
||||
|
||||
var webhookQueue = new MicCheck.Api.Webhooks.WebhookQueue();
|
||||
var auditService = new Mock<AuditService>(_db.Object, null!, webhookQueue);
|
||||
auditService.Setup(a => a.LogAsync(
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
|
||||
It.IsAny<int>(), It.IsAny<int?>(), It.IsAny<int?>(),
|
||||
It.IsAny<string?>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
_service = new ProjectService(_db.Object, auditService.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenCreatingAProject_ThenItIsPersistedWithTheGivenOrganization()
|
||||
{
|
||||
var project = await _service.CreateAsync(OrganizationId, "My Project");
|
||||
|
||||
Assert.That(project.Name, Is.EqualTo("My Project"));
|
||||
Assert.That(project.OrganizationId, Is.EqualTo(OrganizationId));
|
||||
Assert.That(_projects, Has.Count.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingProjectsForAnOrganization_ThenOnlyThatOrganizationsProjectsAreReturned()
|
||||
{
|
||||
await _service.CreateAsync(OrganizationId, "Project A");
|
||||
await _service.CreateAsync(999, "Other Org Project");
|
||||
|
||||
var projects = await _service.ListByOrganizationAsync(OrganizationId);
|
||||
|
||||
Assert.That(projects, Has.Count.EqualTo(1));
|
||||
Assert.That(projects[0].Name, Is.EqualTo("Project A"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenFindingAProjectByIdThatExists_ThenItIsReturned()
|
||||
{
|
||||
var project = await _service.CreateAsync(OrganizationId, "My Project");
|
||||
|
||||
var found = await _service.FindByIdAsync(project.Id);
|
||||
|
||||
Assert.That(found, Is.Not.Null);
|
||||
Assert.That(found!.Id, Is.EqualTo(project.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenFindingAProjectByIdThatDoesNotExist_ThenNullIsReturned()
|
||||
{
|
||||
var found = await _service.FindByIdAsync(999);
|
||||
|
||||
Assert.That(found, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenUpdatingAProjectThatDoesNotExist_ThenKeyNotFoundExceptionIsThrown()
|
||||
{
|
||||
Assert.ThrowsAsync<KeyNotFoundException>(() => _service.UpdateAsync(999, "renamed", true));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenUpdatingAProject_ThenNameAndHideDisabledFlagsAreChanged()
|
||||
{
|
||||
var project = await _service.CreateAsync(OrganizationId, "Old Name");
|
||||
|
||||
var updated = await _service.UpdateAsync(project.Id, "New Name", true);
|
||||
|
||||
Assert.That(updated.Name, Is.EqualTo("New Name"));
|
||||
Assert.That(updated.HideDisabledFlags, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenDeletingAProjectThatDoesNotExist_ThenNoExceptionIsThrown()
|
||||
{
|
||||
Assert.DoesNotThrowAsync(() => _service.DeleteAsync(999));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenDeletingAProject_ThenItIsRemovedFromTheDatabase()
|
||||
{
|
||||
var project = await _service.CreateAsync(OrganizationId, "My Project");
|
||||
|
||||
await _service.DeleteAsync(project.Id);
|
||||
|
||||
Assert.That(_projects.Any(p => p.Id == project.Id), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingUserPermissionsForAProject_ThenOnlyThatProjectsPermissionsAreReturned()
|
||||
{
|
||||
_permissions.Add(new UserProjectPermission { ProjectId = 1, UserId = 1 });
|
||||
_permissions.Add(new UserProjectPermission { ProjectId = 999, UserId = 2 });
|
||||
|
||||
var perms = await _service.ListUserPermissionsAsync(1);
|
||||
|
||||
Assert.That(perms, Has.Count.EqualTo(1));
|
||||
Assert.That(perms[0].UserId, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenSettingUserPermissionsForAUserWithNoExistingPermissions_ThenANewRecordIsCreated()
|
||||
{
|
||||
var perm = await _service.SetUserPermissionsAsync(1, userId: 5, isAdmin: true, [ProjectPermission.ViewProject]);
|
||||
|
||||
Assert.That(perm.IsAdmin, Is.True);
|
||||
Assert.That(perm.Permissions, Does.Contain(ProjectPermission.ViewProject));
|
||||
Assert.That(_permissions, Has.Count.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenSettingUserPermissionsForAUserThatAlreadyHasPermissions_ThenTheExistingRecordIsUpdated()
|
||||
{
|
||||
await _service.SetUserPermissionsAsync(1, userId: 5, isAdmin: false, [ProjectPermission.ViewProject]);
|
||||
|
||||
var updated = await _service.SetUserPermissionsAsync(1, userId: 5, isAdmin: true, [ProjectPermission.DeleteFeature]);
|
||||
|
||||
Assert.That(_permissions, Has.Count.EqualTo(1));
|
||||
Assert.That(updated.IsAdmin, Is.True);
|
||||
Assert.That(updated.Permissions, Is.EqualTo(new[] { ProjectPermission.DeleteFeature }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenRemovingUserPermissionsThatDoNotExist_ThenNoExceptionIsThrown()
|
||||
{
|
||||
Assert.DoesNotThrowAsync(() => _service.RemoveUserPermissionsAsync(1, 5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenRemovingUserPermissions_ThenTheRecordIsRemoved()
|
||||
{
|
||||
await _service.SetUserPermissionsAsync(1, userId: 5, isAdmin: true, [ProjectPermission.ViewProject]);
|
||||
|
||||
await _service.RemoveUserPermissionsAsync(1, 5);
|
||||
|
||||
Assert.That(_permissions, Is.Empty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
using MicCheck.Api.Audit;
|
||||
using MicCheck.Api.Common;
|
||||
using MicCheck.Api.Common.Security.Authorization;
|
||||
using MicCheck.Api.Data;
|
||||
using MicCheck.Api.Organizations;
|
||||
using MicCheck.Api.Projects;
|
||||
using MicCheck.Api.Tests.Unit.TestSupport;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace MicCheck.Api.Tests.Unit.Projects;
|
||||
|
||||
[TestFixture]
|
||||
public class ProjectsControllerTests
|
||||
{
|
||||
private Mock<IMicCheckDbContext> _db = null!;
|
||||
private List<Project> _projects = null!;
|
||||
private List<UserProjectPermission> _permissions = null!;
|
||||
private ProjectsController _controller = null!;
|
||||
private const int OrganizationId = 1;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_db = new Mock<IMicCheckDbContext>();
|
||||
|
||||
_db.SetupDbSet(c => c.Organizations, [
|
||||
new Organization { Id = OrganizationId, Name = "Test Org", CreatedAt = DateTimeOffset.UtcNow }
|
||||
]);
|
||||
_projects = [];
|
||||
_db.SetupDbSetWithGeneratedIds(c => c.Projects, _projects);
|
||||
_permissions = [];
|
||||
_db.SetupDbSet(c => c.UserProjectPermissions, _permissions);
|
||||
|
||||
var webhookQueue = new MicCheck.Api.Webhooks.WebhookQueue();
|
||||
var auditService = new Mock<AuditService>(_db.Object, null!, webhookQueue);
|
||||
auditService.Setup(a => a.LogAsync(
|
||||
It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
|
||||
It.IsAny<int>(), It.IsAny<int?>(), It.IsAny<int?>(),
|
||||
It.IsAny<string?>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
_controller = new ProjectsController(new ProjectService(_db.Object, auditService.Object));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingProjectsForAnOrganization_ThenTheyAreReturnedInAPage()
|
||||
{
|
||||
await _controller.Create(new CreateProjectRequest("Project A", OrganizationId), CancellationToken.None);
|
||||
await _controller.Create(new CreateProjectRequest("Project B", OrganizationId), CancellationToken.None);
|
||||
|
||||
var result = await _controller.List(OrganizationId);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
var page = (PaginatedResponse<ProjectResponse>)ok!.Value!;
|
||||
Assert.That(page.Count, Is.EqualTo(2));
|
||||
Assert.That(page.Results, Has.Count.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingProjectsWithAPageSizeAboveTheMaximum_ThenItIsClampedTo100()
|
||||
{
|
||||
for (var i = 0; i < 3; i++)
|
||||
await _controller.Create(new CreateProjectRequest($"Project {i}", OrganizationId), CancellationToken.None);
|
||||
|
||||
var result = await _controller.List(OrganizationId, page: 1, pageSize: 1000);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
var page = (PaginatedResponse<ProjectResponse>)ok!.Value!;
|
||||
Assert.That(page.Results, Has.Count.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenCreatingAProject_ThenACreatedResultWithTheProjectIsReturned()
|
||||
{
|
||||
var result = await _controller.Create(new CreateProjectRequest("My Project", OrganizationId), CancellationToken.None);
|
||||
|
||||
var created = result.Result as CreatedAtActionResult;
|
||||
Assert.That(created, Is.Not.Null);
|
||||
Assert.That(((ProjectResponse)created!.Value!).Name, Is.EqualTo("My Project"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenGettingAProjectByIdThatExists_ThenItIsReturned()
|
||||
{
|
||||
var created = await _controller.Create(new CreateProjectRequest("My Project", OrganizationId), CancellationToken.None);
|
||||
var projectId = ((ProjectResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.GetById(projectId, CancellationToken.None);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
Assert.That(ok, Is.Not.Null);
|
||||
Assert.That(((ProjectResponse)ok!.Value!).Id, Is.EqualTo(projectId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenGettingAProjectByIdThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.GetById(999, CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenUpdatingAProjectThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.Update(999, new UpdateProjectRequest("renamed", false), CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenUpdatingAProject_ThenTheUpdatedProjectIsReturned()
|
||||
{
|
||||
var created = await _controller.Create(new CreateProjectRequest("Old Name", OrganizationId), CancellationToken.None);
|
||||
var projectId = ((ProjectResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.Update(projectId, new UpdateProjectRequest("New Name", true), CancellationToken.None);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
var response = (ProjectResponse)ok!.Value!;
|
||||
Assert.That(response.Name, Is.EqualTo("New Name"));
|
||||
Assert.That(response.HideDisabledFlags, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenDeletingAProjectThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.Delete(999, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenDeletingAProject_ThenNoContentIsReturnedAndItIsRemoved()
|
||||
{
|
||||
var created = await _controller.Create(new CreateProjectRequest("My Project", OrganizationId), CancellationToken.None);
|
||||
var projectId = ((ProjectResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.Delete(projectId, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NoContentResult>());
|
||||
Assert.That(_projects.Any(p => p.Id == projectId), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingUserPermissionsForAProjectThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.ListUserPermissions(999, CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenListingUserPermissionsForAProject_ThenTheyAreReturned()
|
||||
{
|
||||
var created = await _controller.Create(new CreateProjectRequest("My Project", OrganizationId), CancellationToken.None);
|
||||
var projectId = ((ProjectResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
await _controller.CreateUserPermissions(projectId, new SetUserPermissionsRequest(5, true, ["ViewProject"]), CancellationToken.None);
|
||||
|
||||
var result = await _controller.ListUserPermissions(projectId, CancellationToken.None);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
var perms = (IReadOnlyList<UserPermissionResponse>)ok!.Value!;
|
||||
Assert.That(perms, Has.Count.EqualTo(1));
|
||||
Assert.That(perms[0].UserId, Is.EqualTo(5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenCreatingUserPermissionsForAProjectThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.CreateUserPermissions(999, new SetUserPermissionsRequest(5, true, []), CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenCreatingUserPermissions_ThenThePermissionsAreParsedAndPersisted()
|
||||
{
|
||||
var created = await _controller.Create(new CreateProjectRequest("My Project", OrganizationId), CancellationToken.None);
|
||||
var projectId = ((ProjectResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
|
||||
var result = await _controller.CreateUserPermissions(
|
||||
projectId, new SetUserPermissionsRequest(5, true, ["ViewProject", "EditFeature"]), CancellationToken.None);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
var response = (UserPermissionResponse)ok!.Value!;
|
||||
Assert.That(response.Permissions, Is.EqualTo(new[] { "ViewProject", "EditFeature" }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenUpdatingUserPermissionsForAProjectThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.UpdateUserPermissions(999, 5, new SetUserPermissionsRequest(5, true, []), CancellationToken.None);
|
||||
|
||||
Assert.That(result.Result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenUpdatingUserPermissions_ThenTheExistingRecordIsChanged()
|
||||
{
|
||||
var created = await _controller.Create(new CreateProjectRequest("My Project", OrganizationId), CancellationToken.None);
|
||||
var projectId = ((ProjectResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
await _controller.CreateUserPermissions(projectId, new SetUserPermissionsRequest(5, false, ["ViewProject"]), CancellationToken.None);
|
||||
|
||||
var result = await _controller.UpdateUserPermissions(projectId, 5, new SetUserPermissionsRequest(5, true, ["DeleteFeature"]), CancellationToken.None);
|
||||
|
||||
var ok = result.Result as OkObjectResult;
|
||||
var response = (UserPermissionResponse)ok!.Value!;
|
||||
Assert.That(response.IsAdmin, Is.True);
|
||||
Assert.That(response.Permissions, Is.EqualTo(new[] { "DeleteFeature" }));
|
||||
Assert.That(_permissions, Has.Count.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenDeletingUserPermissionsForAProjectThatDoesNotExist_ThenNotFoundIsReturned()
|
||||
{
|
||||
var result = await _controller.DeleteUserPermissions(999, 5, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NotFoundResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task WhenDeletingUserPermissions_ThenTheRecordIsRemoved()
|
||||
{
|
||||
var created = await _controller.Create(new CreateProjectRequest("My Project", OrganizationId), CancellationToken.None);
|
||||
var projectId = ((ProjectResponse)((CreatedAtActionResult)created.Result!).Value!).Id;
|
||||
await _controller.CreateUserPermissions(projectId, new SetUserPermissionsRequest(5, true, ["ViewProject"]), CancellationToken.None);
|
||||
|
||||
var result = await _controller.DeleteUserPermissions(projectId, 5, CancellationToken.None);
|
||||
|
||||
Assert.That(result, Is.InstanceOf<NoContentResult>());
|
||||
Assert.That(_permissions, Is.Empty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using MicCheck.Api.Projects;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace MicCheck.Api.Tests.Unit.Projects;
|
||||
|
||||
[TestFixture]
|
||||
public class SetUserPermissionsRequestValidatorTests
|
||||
{
|
||||
private SetUserPermissionsRequestValidator _validator = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => _validator = new SetUserPermissionsRequestValidator();
|
||||
|
||||
[Test]
|
||||
public void WhenTheRequestIsValid_ThenValidationSucceeds()
|
||||
{
|
||||
var result = _validator.Validate(new SetUserPermissionsRequest(1, true, ["ViewProject", "EditFeature"]));
|
||||
|
||||
Assert.That(result.IsValid, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenUserIdIsZeroOrLess_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new SetUserPermissionsRequest(0, true, []));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenAPermissionIsInvalid_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new SetUserPermissionsRequest(1, false, ["NotAPermission"]));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenPermissionsListIsEmpty_ThenValidationSucceeds()
|
||||
{
|
||||
var result = _validator.Validate(new SetUserPermissionsRequest(1, true, []));
|
||||
|
||||
Assert.That(result.IsValid, Is.True);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using MicCheck.Api.Projects;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace MicCheck.Api.Tests.Unit.Projects;
|
||||
|
||||
[TestFixture]
|
||||
public class UpdateProjectRequestValidatorTests
|
||||
{
|
||||
private UpdateProjectRequestValidator _validator = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => _validator = new UpdateProjectRequestValidator();
|
||||
|
||||
[Test]
|
||||
public void WhenTheRequestIsValid_ThenValidationSucceeds()
|
||||
{
|
||||
var result = _validator.Validate(new UpdateProjectRequest("Renamed", true));
|
||||
|
||||
Assert.That(result.IsValid, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenNameIsEmpty_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new UpdateProjectRequest("", false));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WhenNameExceedsMaximumLength_ThenValidationFails()
|
||||
{
|
||||
var result = _validator.Validate(new UpdateProjectRequest(new string('a', 201), false));
|
||||
|
||||
Assert.That(result.IsValid, Is.False);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user