diff --git a/API.Tests/API.Tests.csproj b/API.Tests/API.Tests.csproj new file mode 100644 index 0000000..100ed06 --- /dev/null +++ b/API.Tests/API.Tests.csproj @@ -0,0 +1,29 @@ + + + + net9.0 + enable + enable + false + true + + + + + + + + + + + + + + + + + + + + + diff --git a/API.Tests/Controllers/AgeGroupControllerTests.cs b/API.Tests/Controllers/AgeGroupControllerTests.cs new file mode 100644 index 0000000..1bd444b --- /dev/null +++ b/API.Tests/Controllers/AgeGroupControllerTests.cs @@ -0,0 +1,184 @@ +using API.Controllers; +using API.Models.Internal.Altersgruppen; +using API.Repository.AgeGroupRepo; +using Microsoft.AspNetCore.Mvc; +using Moq; + +namespace API.Tests.Controllers; + +public class AgeGroupControllerTests +{ + private readonly Mock _mockService; + private readonly AgeGroupController _controller; + + public AgeGroupControllerTests() + { + _mockService = new Mock(); + _controller = new AgeGroupController(_mockService.Object); + } + + [Fact] + public async Task GetAll_ReturnsOkWithAllGroups() + { + // Arrange + var groups = new List + { + new AltersGruppe { Id = "1", Name = "U10", StartingAge = 8, EndingAge = 10 }, + new AltersGruppe { Id = "2", Name = "U12", StartingAge = 10, EndingAge = 12 } + }; + _mockService.Setup(s => s.GetAllAsync()).ReturnsAsync(groups); + + // Act + var result = await _controller.GetAll(); + + // Assert + var okResult = Assert.IsType(result); + var returnedGroups = Assert.IsType>(okResult.Value); + Assert.Equal(2, returnedGroups.Count); + } + + [Fact] + public async Task GetAll_EmptyList_ReturnsOkWithEmptyList() + { + // Arrange + _mockService.Setup(s => s.GetAllAsync()).ReturnsAsync(new List()); + + // Act + var result = await _controller.GetAll(); + + // Assert + var okResult = Assert.IsType(result); + var returnedGroups = Assert.IsType>(okResult.Value); + Assert.Empty(returnedGroups); + } + + [Fact] + public async Task GetOne_ExistingId_ReturnsOkWithGroup() + { + // Arrange + var group = new AltersGruppe { Id = "123", Name = "U14", StartingAge = 12, EndingAge = 14 }; + _mockService.Setup(s => s.GetAsync("123")).ReturnsAsync(group); + + // Act + var result = await _controller.GetOne("123"); + + // Assert + var okResult = Assert.IsType(result); + var returnedGroup = Assert.IsType(okResult.Value); + Assert.Equal("U14", returnedGroup.Name); + } + + [Fact] + public async Task GetOne_NonExistingId_ReturnsNotFound() + { + // Arrange + _mockService.Setup(s => s.GetAsync("nonexistent")).ReturnsAsync((AltersGruppe?)null); + + // Act + var result = await _controller.GetOne("nonexistent"); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task Create_ValidGroup_ReturnsCreatedAtAction() + { + // Arrange + var ingoingGroup = new AltersGruppeIngoing { Name = "U16", StartingAge = 14, EndingAge = 16 }; + var createdGroup = new AltersGruppe { Id = "new-id", Name = "U16", StartingAge = 14, EndingAge = 16 }; + + _mockService.Setup(s => s.CreateAsync(It.IsAny())).ReturnsAsync(createdGroup); + + // Act + var result = await _controller.Create(ingoingGroup); + + // Assert + var createdResult = Assert.IsType(result); + Assert.Equal(nameof(AgeGroupController.GetOne), createdResult.ActionName); + + var returnedGroup = Assert.IsType(createdResult.Value); + Assert.Equal("U16", returnedGroup.Name); + Assert.Equal("new-id", createdResult.RouteValues?["Id"]); + } + + [Fact] + public async Task Update_ExistingGroup_ReturnsOkWithUpdatedGroup() + { + // Arrange + var ingoingGroup = new AltersGruppeIngoing { Name = "U16 Updated", StartingAge = 15, EndingAge = 16 }; + var updatedGroup = new AltersGruppe { Id = "123", Name = "U16 Updated", StartingAge = 15, EndingAge = 16 }; + + _mockService.Setup(s => s.UpdateAsync("123", It.IsAny())).ReturnsAsync(updatedGroup); + + // Act + var result = await _controller.Update("123", ingoingGroup); + + // Assert + var okResult = Assert.IsType(result); + var returnedGroup = Assert.IsType(okResult.Value); + Assert.Equal("U16 Updated", returnedGroup.Name); + Assert.Equal(15, returnedGroup.StartingAge); + } + + [Fact] + public async Task Update_NonExistingGroup_ReturnsNotFound() + { + // Arrange + var ingoingGroup = new AltersGruppeIngoing { Name = "U16 Updated", StartingAge = 15, EndingAge = 16 }; + _mockService.Setup(s => s.UpdateAsync("nonexistent", It.IsAny())).ReturnsAsync((AltersGruppe?)null); + + // Act + var result = await _controller.Update("nonexistent", ingoingGroup); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task Delete_ExistingGroup_ReturnsNoContent() + { + // Arrange + var group = new AltersGruppe { Id = "123", Name = "U18", StartingAge = 16, EndingAge = 18 }; + _mockService.Setup(s => s.DeleteAsync("123")).ReturnsAsync(group); + + // Act + var result = await _controller.Delete("123"); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task Delete_NonExistingGroup_ReturnsNotFound() + { + // Arrange + _mockService.Setup(s => s.DeleteAsync("nonexistent")).ReturnsAsync((AltersGruppe?)null); + + // Act + var result = await _controller.Delete("nonexistent"); + + // Assert + Assert.IsType(result); + } + + [Fact] + public async Task Create_ServiceIsCalled_WithCorrectParameters() + { + // Arrange + var ingoingGroup = new AltersGruppeIngoing { Name = "U20", StartingAge = 18, EndingAge = 20 }; + var createdGroup = new AltersGruppe { Id = "new-id", Name = "U20", StartingAge = 18, EndingAge = 20 }; + + _mockService.Setup(s => s.CreateAsync(It.IsAny())).ReturnsAsync(createdGroup); + + // Act + await _controller.Create(ingoingGroup); + + // Assert + _mockService.Verify(s => s.CreateAsync(It.Is(g => + g.Name == "U20" && + g.StartingAge == 18 && + g.EndingAge == 20 + )), Times.Once); + } +} diff --git a/API.Tests/Mappers/AltersgruppeMapperTests.cs b/API.Tests/Mappers/AltersgruppeMapperTests.cs new file mode 100644 index 0000000..2bfdff7 --- /dev/null +++ b/API.Tests/Mappers/AltersgruppeMapperTests.cs @@ -0,0 +1,118 @@ +using API.Models.Internal.Altersgruppen; + +namespace API.Tests.Mappers; + +public class AltersgruppeMapperTests +{ + [Fact] + public void ToInternalFromIngoing_MapsAllProperties() + { + // Arrange + var ingoing = new AltersGruppeIngoing + { + Name = "U14", + StartingAge = 12, + EndingAge = 14 + }; + + // Act + var result = ingoing.ToInternalFromIngoing(); + + // Assert + Assert.NotNull(result); + Assert.Equal("U14", result.Name); + Assert.Equal(12, result.StartingAge); + Assert.Equal(14, result.EndingAge); + } + + [Fact] + public void ToInternalFromIngoing_GeneratesNewId() + { + // Arrange + var ingoing = new AltersGruppeIngoing + { + Name = "U16", + StartingAge = 14, + EndingAge = 16 + }; + + // Act + var result = ingoing.ToInternalFromIngoing(); + + // Assert + Assert.NotNull(result.Id); + Assert.NotEmpty(result.Id); + } + + [Fact] + public void ToInternalFromIngoing_GeneratesUniqueIds() + { + // Arrange + var ingoing1 = new AltersGruppeIngoing { Name = "U10", StartingAge = 8, EndingAge = 10 }; + var ingoing2 = new AltersGruppeIngoing { Name = "U12", StartingAge = 10, EndingAge = 12 }; + + // Act + var result1 = ingoing1.ToInternalFromIngoing(); + var result2 = ingoing2.ToInternalFromIngoing(); + + // Assert + Assert.NotEqual(result1.Id, result2.Id); + } + + [Fact] + public void ToInternalFromIngoing_WithZeroAges_MapsCorrectly() + { + // Arrange + var ingoing = new AltersGruppeIngoing + { + Name = "Beginners", + StartingAge = 0, + EndingAge = 0 + }; + + // Act + var result = ingoing.ToInternalFromIngoing(); + + // Assert + Assert.Equal(0, result.StartingAge); + Assert.Equal(0, result.EndingAge); + } + + [Fact] + public void ToInternalFromIngoing_WithNegativeAges_MapsCorrectly() + { + // Arrange - testing edge case, though business logic might not allow this + var ingoing = new AltersGruppeIngoing + { + Name = "Invalid", + StartingAge = -1, + EndingAge = -5 + }; + + // Act + var result = ingoing.ToInternalFromIngoing(); + + // Assert + Assert.Equal(-1, result.StartingAge); + Assert.Equal(-5, result.EndingAge); + } + + [Fact] + public void ToInternalFromIngoing_WithLargeAges_MapsCorrectly() + { + // Arrange + var ingoing = new AltersGruppeIngoing + { + Name = "Seniors", + StartingAge = 60, + EndingAge = 100 + }; + + // Act + var result = ingoing.ToInternalFromIngoing(); + + // Assert + Assert.Equal(60, result.StartingAge); + Assert.Equal(100, result.EndingAge); + } +} diff --git a/API.Tests/Mappers/RegistrationKeyMapperTests.cs b/API.Tests/Mappers/RegistrationKeyMapperTests.cs new file mode 100644 index 0000000..da0a915 --- /dev/null +++ b/API.Tests/Mappers/RegistrationKeyMapperTests.cs @@ -0,0 +1,123 @@ +using API.Models.Internal.User; + +namespace API.Tests.Mappers; + +public class RegistrationKeyMapperTests +{ + [Fact] + public void ToInternalFromIngoing_MapsLinkedRole() + { + // Arrange + var ingoing = new RegistrationKeyIngoing + { + LinkedRole = "Admin" + }; + + // Act + var result = ingoing.ToInternalFromIngoing(); + + // Assert + Assert.NotNull(result); + Assert.Equal("Admin", result.LinkedRole); + } + + [Fact] + public void ToInternalFromIngoing_NullLinkedRole_MapsCorrectly() + { + // Arrange + var ingoing = new RegistrationKeyIngoing + { + LinkedRole = null + }; + + // Act + var result = ingoing.ToInternalFromIngoing(); + + // Assert + Assert.NotNull(result); + Assert.Null(result.LinkedRole); + } + + [Fact] + public void ToInternalFromIngoing_GeneratesNewId() + { + // Arrange + var ingoing = new RegistrationKeyIngoing + { + LinkedRole = "User" + }; + + // Act + var result = ingoing.ToInternalFromIngoing(); + + // Assert + Assert.NotNull(result.Id); + Assert.NotEmpty(result.Id); + } + + [Fact] + public void ToInternalFromIngoing_GeneratesUniqueIds() + { + // Arrange + var ingoing1 = new RegistrationKeyIngoing { LinkedRole = "Admin" }; + var ingoing2 = new RegistrationKeyIngoing { LinkedRole = "User" }; + + // Act + var result1 = ingoing1.ToInternalFromIngoing(); + var result2 = ingoing2.ToInternalFromIngoing(); + + // Assert + Assert.NotEqual(result1.Id, result2.Id); + } + + [Fact] + public void ToInternalFromIngoing_SetsCreatedDate() + { + // Arrange + var ingoing = new RegistrationKeyIngoing + { + LinkedRole = "Moderator" + }; + var beforeCreation = DateTime.UtcNow; + + // Act + var result = ingoing.ToInternalFromIngoing(); + + // Assert + var afterCreation = DateTime.UtcNow; + Assert.True(result.Created >= beforeCreation.AddSeconds(-1)); + Assert.True(result.Created <= afterCreation.AddSeconds(1)); + } + + [Fact] + public void ToInternalFromIngoing_EmptyLinkedRole_MapsCorrectly() + { + // Arrange + var ingoing = new RegistrationKeyIngoing + { + LinkedRole = "" + }; + + // Act + var result = ingoing.ToInternalFromIngoing(); + + // Assert + Assert.Equal("", result.LinkedRole); + } + + [Fact] + public void ToInternalFromIngoing_WhitespaceLinkedRole_MapsCorrectly() + { + // Arrange + var ingoing = new RegistrationKeyIngoing + { + LinkedRole = " " + }; + + // Act + var result = ingoing.ToInternalFromIngoing(); + + // Assert + Assert.Equal(" ", result.LinkedRole); + } +} diff --git a/API.Tests/Services/AgeGroupServiceTests.cs b/API.Tests/Services/AgeGroupServiceTests.cs new file mode 100644 index 0000000..b0015dd --- /dev/null +++ b/API.Tests/Services/AgeGroupServiceTests.cs @@ -0,0 +1,169 @@ +using API.Database; +using API.Models.Internal.Altersgruppen; +using API.Repository.AgeGroupRepo; +using Microsoft.EntityFrameworkCore; + +namespace API.Tests.Services; + +public class AgeGroupServiceTests : IDisposable +{ + private readonly ApplicationDbContext _context; + private readonly AgeGroupService _service; + + public AgeGroupServiceTests() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + _context = new ApplicationDbContext(options); + _service = new AgeGroupService(_context); + } + + public void Dispose() + { + _context.Database.EnsureDeleted(); + _context.Dispose(); + } + + [Fact] + public async Task GetAllAsync_EmptyDatabase_ReturnsEmptyList() + { + // Act + var result = await _service.GetAllAsync(); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public async Task GetAllAsync_WithData_ReturnsAllGroups() + { + // Arrange + var group1 = new AltersGruppe { Name = "U10", StartingAge = 8, EndingAge = 10 }; + var group2 = new AltersGruppe { Name = "U12", StartingAge = 10, EndingAge = 12 }; + await _context.Altersgruppen.AddRangeAsync(group1, group2); + await _context.SaveChangesAsync(); + + // Act + var result = await _service.GetAllAsync(); + + // Assert + Assert.Equal(2, result.Count); + Assert.Contains(result, g => g.Name == "U10"); + Assert.Contains(result, g => g.Name == "U12"); + } + + [Fact] + public async Task GetAsync_ExistingId_ReturnsGroup() + { + // Arrange + var group = new AltersGruppe { Name = "U14", StartingAge = 12, EndingAge = 14 }; + await _context.Altersgruppen.AddAsync(group); + await _context.SaveChangesAsync(); + + // Act + var result = await _service.GetAsync(group.Id); + + // Assert + Assert.NotNull(result); + Assert.Equal("U14", result.Name); + Assert.Equal(12, result.StartingAge); + Assert.Equal(14, result.EndingAge); + } + + [Fact] + public async Task GetAsync_NonExistingId_ReturnsNull() + { + // Act + var result = await _service.GetAsync("nonexistent-id"); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task CreateAsync_ValidGroup_AddsToDatabase() + { + // Arrange + var newGroup = new AltersGruppe { Name = "U16", StartingAge = 14, EndingAge = 16 }; + + // Act + var result = await _service.CreateAsync(newGroup); + + // Assert + Assert.NotNull(result); + Assert.Equal("U16", result.Name); + + var dbGroup = await _context.Altersgruppen.FindAsync(result.Id); + Assert.NotNull(dbGroup); + Assert.Equal("U16", dbGroup.Name); + } + + [Fact] + public async Task UpdateAsync_ExistingGroup_UpdatesProperties() + { + // Arrange + var group = new AltersGruppe { Name = "U18", StartingAge = 16, EndingAge = 18 }; + await _context.Altersgruppen.AddAsync(group); + await _context.SaveChangesAsync(); + + var updatedGroup = new AltersGruppe { Name = "U18 Updated", StartingAge = 15, EndingAge = 18 }; + + // Act + var result = await _service.UpdateAsync(group.Id, updatedGroup); + + // Assert + Assert.NotNull(result); + Assert.Equal("U18 Updated", result.Name); + Assert.Equal(15, result.StartingAge); + Assert.Equal(18, result.EndingAge); + + var dbGroup = await _context.Altersgruppen.FindAsync(group.Id); + Assert.NotNull(dbGroup); + Assert.Equal("U18 Updated", dbGroup.Name); + } + + [Fact] + public async Task UpdateAsync_NonExistingGroup_ReturnsNull() + { + // Arrange + var updatedGroup = new AltersGruppe { Name = "U18 Updated", StartingAge = 15, EndingAge = 18 }; + + // Act + var result = await _service.UpdateAsync("nonexistent-id", updatedGroup); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task DeleteAsync_ExistingGroup_RemovesFromDatabase() + { + // Arrange + var group = new AltersGruppe { Name = "U20", StartingAge = 18, EndingAge = 20 }; + await _context.Altersgruppen.AddAsync(group); + await _context.SaveChangesAsync(); + + // Act + var result = await _service.DeleteAsync(group.Id); + + // Assert + Assert.NotNull(result); + Assert.Equal("U20", result.Name); + + var dbGroup = await _context.Altersgruppen.FindAsync(group.Id); + Assert.Null(dbGroup); + } + + [Fact] + public async Task DeleteAsync_NonExistingGroup_ReturnsNull() + { + // Act + var result = await _service.DeleteAsync("nonexistent-id"); + + // Assert + Assert.Null(result); + } +} diff --git a/API.Tests/Services/PlunkEmailSenderTests.cs b/API.Tests/Services/PlunkEmailSenderTests.cs new file mode 100644 index 0000000..02b478d --- /dev/null +++ b/API.Tests/Services/PlunkEmailSenderTests.cs @@ -0,0 +1,214 @@ +using System.Net; +using System.Text.Json; +using API.Models.Outgoing; +using API.Services; +using Moq; +using Moq.Protected; + +namespace API.Tests.Services; + +public class PlunkEmailSenderTests +{ + private const string TestSecretKey = "test-secret-key"; + private const string PlunkApiUrl = "https://api.useplunk.com/v1/send"; + + private static HttpClient CreateMockHttpClient(HttpStatusCode statusCode, string responseContent = "{}") + { + var mockHandler = new Mock(); + + mockHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = statusCode, + Content = new StringContent(responseContent) + }); + + return new HttpClient(mockHandler.Object); + } + + private static (HttpClient client, Mock handler) CreateMockHttpClientWithHandler( + HttpStatusCode statusCode, string responseContent = "{}") + { + var mockHandler = new Mock(); + + mockHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = statusCode, + Content = new StringContent(responseContent) + }); + + return (new HttpClient(mockHandler.Object), mockHandler); + } + + [Fact] + public async Task SendEmailAsync_SuccessfulSend_DoesNotThrow() + { + // Arrange + var httpClient = CreateMockHttpClient(HttpStatusCode.OK); + var emailSender = new PlunkEmailSender(httpClient, TestSecretKey); + + // Act & Assert - should not throw + await emailSender.SendEmailAsync("test@example.com", "Test Subject", "

Test Body

"); + } + + [Fact] + public async Task SendEmailAsync_ApiReturnsError_ThrowsException() + { + // Arrange + var errorMessage = "Invalid API key"; + var httpClient = CreateMockHttpClient(HttpStatusCode.Unauthorized, errorMessage); + var emailSender = new PlunkEmailSender(httpClient, TestSecretKey); + + // Act & Assert + var exception = await Assert.ThrowsAsync(() => + emailSender.SendEmailAsync("test@example.com", "Test Subject", "

Test Body

")); + + Assert.Contains("Fehler beim Senden der E-Mail über Plunk", exception.Message); + Assert.Contains(errorMessage, exception.Message); + } + + [Fact] + public async Task SendEmailAsync_ServerError_ThrowsException() + { + // Arrange + var httpClient = CreateMockHttpClient(HttpStatusCode.InternalServerError, "Server error"); + var emailSender = new PlunkEmailSender(httpClient, TestSecretKey); + + // Act & Assert + await Assert.ThrowsAsync(() => + emailSender.SendEmailAsync("test@example.com", "Test Subject", "

Test Body

")); + } + + [Fact] + public async Task SendEmailAsync_BadRequest_ThrowsException() + { + // Arrange + var httpClient = CreateMockHttpClient(HttpStatusCode.BadRequest, "Invalid request"); + var emailSender = new PlunkEmailSender(httpClient, TestSecretKey); + + // Act & Assert + await Assert.ThrowsAsync(() => + emailSender.SendEmailAsync("test@example.com", "Test Subject", "

Test Body

")); + } + + [Fact] + public async Task SendEmailAsync_SendsCorrectPayload() + { + // Arrange + var mockHandler = new Mock(); + HttpRequestMessage? capturedRequest = null; + + mockHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .Callback((request, _) => capturedRequest = request) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("{}") + }); + + var httpClient = new HttpClient(mockHandler.Object); + var emailSender = new PlunkEmailSender(httpClient, TestSecretKey); + + // Act + await emailSender.SendEmailAsync("recipient@example.com", "My Subject", "

Hello

"); + + // Assert + Assert.NotNull(capturedRequest); + Assert.Equal(HttpMethod.Post, capturedRequest.Method); + Assert.Equal(PlunkApiUrl, capturedRequest.RequestUri?.ToString()); + + var requestContent = await capturedRequest.Content!.ReadAsStringAsync(); + var payload = JsonSerializer.Deserialize(requestContent, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + + Assert.NotNull(payload); + Assert.Equal("recipient@example.com", payload.To); + Assert.Equal("My Subject", payload.Subject); + Assert.Equal("

Hello

", payload.Body); + } + + [Fact] + public async Task SendEmailAsync_SendsCorrectAuthorizationHeader() + { + // Arrange + var mockHandler = new Mock(); + HttpRequestMessage? capturedRequest = null; + + mockHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .Callback((request, _) => capturedRequest = request) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("{}") + }); + + var httpClient = new HttpClient(mockHandler.Object); + var emailSender = new PlunkEmailSender(httpClient, "my-secret-api-key"); + + // Act + await emailSender.SendEmailAsync("test@example.com", "Subject", "Body"); + + // Assert + Assert.NotNull(capturedRequest); + var authHeader = httpClient.DefaultRequestHeaders.Authorization; + Assert.NotNull(authHeader); + Assert.Equal("Bearer", authHeader.Scheme); + Assert.Equal("my-secret-api-key", authHeader.Parameter); + } + + [Fact] + public async Task SendEmailAsync_SendsCorrectContentType() + { + // Arrange + var mockHandler = new Mock(); + HttpRequestMessage? capturedRequest = null; + + mockHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny()) + .Callback((request, _) => capturedRequest = request) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("{}") + }); + + var httpClient = new HttpClient(mockHandler.Object); + var emailSender = new PlunkEmailSender(httpClient, TestSecretKey); + + // Act + await emailSender.SendEmailAsync("test@example.com", "Subject", "Body"); + + // Assert + Assert.NotNull(capturedRequest); + Assert.NotNull(capturedRequest.Content); + Assert.Equal("application/json", capturedRequest.Content.Headers.ContentType?.MediaType); + Assert.Equal("utf-8", capturedRequest.Content.Headers.ContentType?.CharSet); + } +} diff --git a/API.Tests/Services/RegistrationKeyServiceTests.cs b/API.Tests/Services/RegistrationKeyServiceTests.cs new file mode 100644 index 0000000..ec9fd2e --- /dev/null +++ b/API.Tests/Services/RegistrationKeyServiceTests.cs @@ -0,0 +1,244 @@ +using API.Database; +using API.Models.Internal.User; +using API.Repository.RegistrationKeyRepo; +using Microsoft.EntityFrameworkCore; + +namespace API.Tests.Services; + +public class RegistrationKeyServiceTests : IDisposable +{ + private readonly ApplicationDbContext _context; + private readonly RegistrationKeyService _service; + + public RegistrationKeyServiceTests() + { + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) + .Options; + + _context = new ApplicationDbContext(options); + _service = new RegistrationKeyService(_context); + } + + public void Dispose() + { + _context.Database.EnsureDeleted(); + _context.Dispose(); + } + + [Fact] + public async Task GetAllAsync_EmptyDatabase_ReturnsEmptyList() + { + // Act + var result = await _service.GetAllAsync(); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public async Task GetAllAsync_WithData_ReturnsAllKeys() + { + // Arrange + var key1 = new RegistrationKey { LinkedRole = "Admin" }; + var key2 = new RegistrationKey { LinkedRole = "User" }; + await _context.RegistrationKeys.AddRangeAsync(key1, key2); + await _context.SaveChangesAsync(); + + // Act + var result = await _service.GetAllAsync(); + + // Assert + Assert.Equal(2, result.Count); + Assert.Contains(result, k => k.LinkedRole == "Admin"); + Assert.Contains(result, k => k.LinkedRole == "User"); + } + + [Fact] + public async Task GetAsync_ExistingId_ReturnsKey() + { + // Arrange + var key = new RegistrationKey { LinkedRole = "Moderator" }; + await _context.RegistrationKeys.AddAsync(key); + await _context.SaveChangesAsync(); + + // Act + var result = await _service.GetAsync(key.Id); + + // Assert + Assert.NotNull(result); + Assert.Equal("Moderator", result.LinkedRole); + } + + [Fact] + public async Task GetAsync_NonExistingId_ReturnsNull() + { + // Act + var result = await _service.GetAsync("nonexistent-id"); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task CreateAsync_ValidKey_AddsToDatabase() + { + // Arrange + var keyIngoing = new RegistrationKeyIngoing { LinkedRole = "Admin" }; + + // Act + var result = await _service.CreateAsync(keyIngoing); + + // Assert + Assert.NotNull(result); + Assert.Equal("Admin", result.LinkedRole); + Assert.NotNull(result.Id); + + var dbKey = await _context.RegistrationKeys.FindAsync(result.Id); + Assert.NotNull(dbKey); + Assert.Equal("Admin", dbKey.LinkedRole); + } + + [Fact] + public async Task CreateAsync_NullRole_AddsToDatabase() + { + // Arrange + var keyIngoing = new RegistrationKeyIngoing { LinkedRole = null }; + + // Act + var result = await _service.CreateAsync(keyIngoing); + + // Assert + Assert.NotNull(result); + Assert.Null(result.LinkedRole); + + var dbKey = await _context.RegistrationKeys.FindAsync(result.Id); + Assert.NotNull(dbKey); + } + + [Fact] + public async Task DeleteAsync_ExistingKey_RemovesFromDatabase() + { + // Arrange + var key = new RegistrationKey { LinkedRole = "ToDelete" }; + await _context.RegistrationKeys.AddAsync(key); + await _context.SaveChangesAsync(); + + // Act + var result = await _service.DeleteAsync(key.Id); + + // Assert + Assert.NotNull(result); + Assert.Equal("ToDelete", result.LinkedRole); + + var dbKey = await _context.RegistrationKeys.FindAsync(key.Id); + Assert.Null(dbKey); + } + + [Fact] + public async Task DeleteAsync_NonExistingKey_ReturnsNull() + { + // Act + var result = await _service.DeleteAsync("nonexistent-id"); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task DeleteOldRegistrationKeysAsync_ZeroDays_ReturnsZero() + { + // Arrange + var key = new RegistrationKey { LinkedRole = "Old", Created = DateTime.UtcNow.AddDays(-10) }; + await _context.RegistrationKeys.AddAsync(key); + await _context.SaveChangesAsync(); + + // Act + var result = await _service.DeleteOldRegistrationKeysAsync(0); + + // Assert + Assert.Equal(0, result); + Assert.Single(await _context.RegistrationKeys.ToListAsync()); + } + + [Fact] + public async Task DeleteOldRegistrationKeysAsync_NegativeDays_ReturnsZero() + { + // Arrange + var key = new RegistrationKey { LinkedRole = "Old", Created = DateTime.UtcNow.AddDays(-10) }; + await _context.RegistrationKeys.AddAsync(key); + await _context.SaveChangesAsync(); + + // Act + var result = await _service.DeleteOldRegistrationKeysAsync(-5); + + // Assert + Assert.Equal(0, result); + Assert.Single(await _context.RegistrationKeys.ToListAsync()); + } + + [Fact] + public async Task DeleteOldRegistrationKeysAsync_NoOldKeys_ReturnsZero() + { + // Arrange + var key = new RegistrationKey { LinkedRole = "New", Created = DateTime.UtcNow }; + await _context.RegistrationKeys.AddAsync(key); + await _context.SaveChangesAsync(); + + // Act + var result = await _service.DeleteOldRegistrationKeysAsync(7); + + // Assert + Assert.Equal(0, result); + Assert.Single(await _context.RegistrationKeys.ToListAsync()); + } + + [Fact] + public async Task DeleteOldRegistrationKeysAsync_WithOldKeys_DeletesOldKeysOnly() + { + // Arrange + var oldKey1 = new RegistrationKey { LinkedRole = "Old1", Created = DateTime.UtcNow.AddDays(-10) }; + var oldKey2 = new RegistrationKey { LinkedRole = "Old2", Created = DateTime.UtcNow.AddDays(-15) }; + var newKey = new RegistrationKey { LinkedRole = "New", Created = DateTime.UtcNow.AddDays(-2) }; + + await _context.RegistrationKeys.AddRangeAsync(oldKey1, oldKey2, newKey); + await _context.SaveChangesAsync(); + + // Act + var result = await _service.DeleteOldRegistrationKeysAsync(7); + + // Assert + Assert.Equal(2, result); + + var remainingKeys = await _context.RegistrationKeys.ToListAsync(); + Assert.Single(remainingKeys); + Assert.Equal("New", remainingKeys[0].LinkedRole); + } + + [Fact] + public async Task DeleteOldRegistrationKeysAsync_ExactCutoffDate_KeyNotDeleted() + { + // Arrange - key created exactly at cutoff should NOT be deleted + var keyAtCutoff = new RegistrationKey { LinkedRole = "AtCutoff", Created = DateTime.UtcNow.AddDays(-7) }; + await _context.RegistrationKeys.AddAsync(keyAtCutoff); + await _context.SaveChangesAsync(); + + // Act + var result = await _service.DeleteOldRegistrationKeysAsync(7); + + // Assert - cutoff is < not <=, so exactly 7 days old should not be deleted + Assert.Equal(0, result); + Assert.Single(await _context.RegistrationKeys.ToListAsync()); + } + + [Fact] + public async Task DeleteOldRegistrationKeysAsync_EmptyDatabase_ReturnsZero() + { + // Act + var result = await _service.DeleteOldRegistrationKeysAsync(7); + + // Assert + Assert.Equal(0, result); + } +} diff --git a/JudoWeb.sln b/JudoWeb.sln index 0cbf2db..4140b6d 100644 --- a/JudoWeb.sln +++ b/JudoWeb.sln @@ -1,10 +1,12 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.14.36518.9 d17.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{98166726-DC3A-4D5B-889A-8B4428E28656}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API.Tests", "API.Tests\API.Tests.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {98166726-DC3A-4D5B-889A-8B4428E28656}.Debug|Any CPU.Build.0 = Debug|Any CPU {98166726-DC3A-4D5B-889A-8B4428E28656}.Release|Any CPU.ActiveCfg = Release|Any CPU {98166726-DC3A-4D5B-889A-8B4428E28656}.Release|Any CPU.Build.0 = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE