Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fafafae5d1 | |||
| 0cdb391393 | |||
| 13c9c8aa68 | |||
| 3421818d41 | |||
| d3ec22aa99 | |||
| 8d9392f3ca | |||
| 1aac2a45dc | |||
| 9122eeff9d | |||
| 23dab73bd8 | |||
| ef3d05f827 | |||
| db7a183928 | |||
| 6dfd2fd302 | |||
| c8f2f13f6c | |||
| eb4e527cbd | |||
| 877c7877ee | |||
| 2f76fd7858 | |||
| 0dc30f29c5 | |||
| f4e2c28869 | |||
| d4c078a5c8 | |||
| 7cf6211d4d |
+42
@@ -0,0 +1,42 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
.next/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
*.coverage
|
||||||
|
*.coverage.json
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
@@ -72,6 +72,7 @@ public class AnnouncementsController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
|
[AllowAnonymous]
|
||||||
public async Task<ActionResult<AnnouncementDto>> GetAnnouncement(Guid id)
|
public async Task<ActionResult<AnnouncementDto>> GetAnnouncement(Guid id)
|
||||||
{
|
{
|
||||||
var announcement = await _context.Announcements
|
var announcement = await _context.Announcements
|
||||||
@@ -98,6 +99,7 @@ public class AnnouncementsController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("event/{eventId}")]
|
[HttpGet("event/{eventId}")]
|
||||||
|
[AllowAnonymous]
|
||||||
public async Task<ActionResult<IEnumerable<AnnouncementDto>>> GetEventAnnouncements(Guid eventId)
|
public async Task<ActionResult<IEnumerable<AnnouncementDto>>> GetEventAnnouncements(Guid eventId)
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentUserId();
|
||||||
|
|||||||
+4
-1
@@ -73,4 +73,7 @@ app.UseAuthorization();
|
|||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
// Make Program class public for integration testing
|
||||||
|
public partial class Program { }
|
||||||
@@ -0,0 +1,528 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moq;
|
||||||
|
using FluentAssertions;
|
||||||
|
using RacePlannerApi.Controllers;
|
||||||
|
using RacePlannerApi.Data;
|
||||||
|
using RacePlannerApi.DTOs;
|
||||||
|
using RacePlannerApi.Models;
|
||||||
|
using backend.Tests.Utilities;
|
||||||
|
|
||||||
|
namespace backend.Tests.Controllers;
|
||||||
|
|
||||||
|
public class AnnouncementsControllerTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly RacePlannerDbContext _context;
|
||||||
|
private readonly AnnouncementsController _controller;
|
||||||
|
|
||||||
|
public AnnouncementsControllerTests()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<RacePlannerDbContext>()
|
||||||
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
_context = new RacePlannerDbContext(options);
|
||||||
|
_controller = new AnnouncementsController(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUserContext(Guid userId, string role = "Participant")
|
||||||
|
{
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
|
||||||
|
new Claim(ClaimTypes.Role, role)
|
||||||
|
};
|
||||||
|
var identity = new ClaimsIdentity(claims, "TestAuthType");
|
||||||
|
var principal = new ClaimsPrincipal(identity);
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = principal }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_context.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Create Announcement - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAnnouncement_WithValidData_CreatesAnnouncement()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new CreateAnnouncementRequest
|
||||||
|
{
|
||||||
|
EventId = eventEntity.Id,
|
||||||
|
Title = "Important Update",
|
||||||
|
Content = "Event details have been updated."
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateAnnouncement(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var createdResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
|
||||||
|
var response = createdResult.Value.Should().BeOfType<AnnouncementDto>().Subject;
|
||||||
|
response.Title.Should().Be(request.Title);
|
||||||
|
response.Content.Should().Be(request.Content);
|
||||||
|
response.IsPublished.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Create Announcement - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAnnouncement_ForNonExistentEvent_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new CreateAnnouncementRequest
|
||||||
|
{
|
||||||
|
EventId = Guid.NewGuid(),
|
||||||
|
Title = "Test",
|
||||||
|
Content = "Content"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateAnnouncement(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var notFoundResult = result.Result.Should().BeOfType<NotFoundObjectResult>().Subject;
|
||||||
|
notFoundResult.Value.Should().BeEquivalentTo(new { error = "Event not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAnnouncement_NonOrganizer_ReturnsForbidden()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var otherOrganizer = TestDataFactory.CreateUser(email: "other@example.com", role: UserRole.Organizer);
|
||||||
|
_context.Users.AddRange(organizer, otherOrganizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(otherOrganizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new CreateAnnouncementRequest
|
||||||
|
{
|
||||||
|
EventId = eventEntity.Id,
|
||||||
|
Title = "Test",
|
||||||
|
Content = "Content"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateAnnouncement(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<ForbidResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get Announcement - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAnnouncement_ReturnsPublishedAnnouncement()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Title", "Content", organizer.Id);
|
||||||
|
announcement.IsPublished = true;
|
||||||
|
_context.Announcements.Add(announcement);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetAnnouncement(announcement.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = okResult.Value.Should().BeOfType<AnnouncementDto>().Subject;
|
||||||
|
response.Title.Should().Be("Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAnnouncement_OrganizerCanViewUnpublishedAnnouncement()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft Title", "Content", organizer.Id);
|
||||||
|
announcement.IsPublished = false;
|
||||||
|
_context.Announcements.Add(announcement);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetAnnouncement(announcement.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<OkObjectResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get Announcement - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetAnnouncement_UnpublishedNotVisibleToPublic()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft", "Content", organizer.Id);
|
||||||
|
announcement.IsPublished = false;
|
||||||
|
_context.Announcements.Add(announcement);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetAnnouncement(announcement.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get Event Announcements
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetEventAnnouncements_ReturnsPublishedAnnouncements()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var published = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Published", "Content", organizer.Id);
|
||||||
|
published.IsPublished = true;
|
||||||
|
var unpublished = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft", "Content", organizer.Id);
|
||||||
|
unpublished.IsPublished = false;
|
||||||
|
_context.Announcements.AddRange(published, unpublished);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetEventAnnouncements(eventEntity.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var announcements = okResult.Value.Should().BeAssignableTo<IEnumerable<AnnouncementDto>>().Subject;
|
||||||
|
announcements.Should().HaveCount(1);
|
||||||
|
announcements.First().Title.Should().Be("Published");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetEventAnnouncements_OrganizerSeesAll()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var published = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Published", "Content", organizer.Id);
|
||||||
|
published.IsPublished = true;
|
||||||
|
var unpublished = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft", "Content", organizer.Id);
|
||||||
|
unpublished.IsPublished = false;
|
||||||
|
_context.Announcements.AddRange(published, unpublished);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetEventAnnouncements(eventEntity.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var announcements = okResult.Value.Should().BeAssignableTo<IEnumerable<AnnouncementDto>>().Subject;
|
||||||
|
announcements.Should().HaveCount(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Update Announcement - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAnnouncement_WithValidData_UpdatesAnnouncement()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Original", "Content", organizer.Id);
|
||||||
|
_context.Announcements.Add(announcement);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new UpdateAnnouncementRequest
|
||||||
|
{
|
||||||
|
Title = "Updated Title",
|
||||||
|
Content = "Updated Content"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.UpdateAnnouncement(announcement.Id, request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = okResult.Value.Should().BeOfType<AnnouncementDto>().Subject;
|
||||||
|
response.Title.Should().Be("Updated Title");
|
||||||
|
response.Content.Should().Be("Updated Content");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAnnouncement_PublishUnpublishedAnnouncement()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Draft", "Content", organizer.Id);
|
||||||
|
announcement.IsPublished = false;
|
||||||
|
_context.Announcements.Add(announcement);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new UpdateAnnouncementRequest { IsPublished = true };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.UpdateAnnouncement(announcement.Id, request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = okResult.Value.Should().BeOfType<AnnouncementDto>().Subject;
|
||||||
|
response.IsPublished.Should().BeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Update Announcement - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAnnouncement_NonExistent_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new UpdateAnnouncementRequest { Title = "Updated" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.UpdateAnnouncement(Guid.NewGuid(), request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateAnnouncement_NonOrganizer_ReturnsForbidden()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var otherOrganizer = TestDataFactory.CreateUser(email: "other@example.com", role: UserRole.Organizer);
|
||||||
|
_context.Users.AddRange(organizer, otherOrganizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Title", "Content", organizer.Id);
|
||||||
|
_context.Announcements.Add(announcement);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(otherOrganizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new UpdateAnnouncementRequest { Title = "Hacked" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.UpdateAnnouncement(announcement.Id, request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<ForbidResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Delete Announcement - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAnnouncement_OrganizerCanDeleteOwnAnnouncement()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var announcement = TestDataFactory.CreateAnnouncement(eventEntity.Id, "To Delete", "Content", organizer.Id);
|
||||||
|
_context.Announcements.Add(announcement);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.DeleteAnnouncement(announcement.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().BeOfType<NoContentResult>();
|
||||||
|
|
||||||
|
var deleted = await _context.Announcements.FindAsync(announcement.Id);
|
||||||
|
deleted.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Delete Announcement - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAnnouncement_NonExistent_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.DeleteAnnouncement(Guid.NewGuid());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get My Announcements
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetMyAnnouncements_ReturnsAnnouncementsForRegisteredEvents()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var event1 = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
var event2 = TestDataFactory.CreateEvent(name: "Event 2", organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.AddRange(event1, event2);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(event1.Id, participant.Id, RegistrationStatus.Confirmed);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
|
||||||
|
var announcement1 = TestDataFactory.CreateAnnouncement(event1.Id, "Announcement 1", "Content", organizer.Id);
|
||||||
|
announcement1.IsPublished = true;
|
||||||
|
var announcement2 = TestDataFactory.CreateAnnouncement(event2.Id, "Announcement 2", "Content", organizer.Id);
|
||||||
|
announcement2.IsPublished = true;
|
||||||
|
_context.Announcements.AddRange(announcement1, announcement2);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetMyAnnouncements();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var announcements = okResult.Value.Should().BeAssignableTo<IEnumerable<AnnouncementDto>>().Subject;
|
||||||
|
announcements.Should().HaveCount(1);
|
||||||
|
announcements.First().Title.Should().Be("Announcement 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetMyAnnouncements_ExcludesUnpublished()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant2@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id, RegistrationStatus.Confirmed);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
|
||||||
|
var published = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Published", "Content", organizer.Id);
|
||||||
|
published.IsPublished = true;
|
||||||
|
var unpublished = TestDataFactory.CreateAnnouncement(eventEntity.Id, "Unpublished", "Content", organizer.Id);
|
||||||
|
unpublished.IsPublished = false;
|
||||||
|
_context.Announcements.AddRange(published, unpublished);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetMyAnnouncements();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var announcements = okResult.Value.Should().BeAssignableTo<IEnumerable<AnnouncementDto>>().Subject;
|
||||||
|
announcements.Should().HaveCount(1);
|
||||||
|
announcements.First().Title.Should().Be("Published");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moq;
|
||||||
|
using FluentAssertions;
|
||||||
|
using RacePlannerApi.Controllers;
|
||||||
|
using RacePlannerApi.Data;
|
||||||
|
using RacePlannerApi.DTOs;
|
||||||
|
using RacePlannerApi.Models;
|
||||||
|
using backend.Tests.Utilities;
|
||||||
|
|
||||||
|
namespace backend.Tests.Controllers;
|
||||||
|
|
||||||
|
public class DashboardControllerTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly RacePlannerDbContext _context;
|
||||||
|
private readonly DashboardController _controller;
|
||||||
|
|
||||||
|
public DashboardControllerTests()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<RacePlannerDbContext>()
|
||||||
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
_context = new RacePlannerDbContext(options);
|
||||||
|
_controller = new DashboardController(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUserContext(Guid userId, string role = "Participant")
|
||||||
|
{
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
|
||||||
|
new Claim(ClaimTypes.Role, role)
|
||||||
|
};
|
||||||
|
var identity = new ClaimsIdentity(claims, "TestAuthType");
|
||||||
|
var principal = new ClaimsPrincipal(identity);
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = principal }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_context.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Organizer Dashboard - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetOrganizerDashboard_ReturnsDashboardData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
// Create events
|
||||||
|
var pastEvent = TestDataFactory.CreateEvent(
|
||||||
|
name: "Past Event",
|
||||||
|
eventDate: DateTime.UtcNow.AddDays(-10),
|
||||||
|
organizerId: organizer.Id,
|
||||||
|
status: EventStatus.Completed);
|
||||||
|
var draftEvent = TestDataFactory.CreateEvent(
|
||||||
|
name: "Draft Event",
|
||||||
|
eventDate: DateTime.UtcNow.AddDays(30),
|
||||||
|
organizerId: organizer.Id,
|
||||||
|
status: EventStatus.Draft);
|
||||||
|
var publishedEvent = TestDataFactory.CreateEvent(
|
||||||
|
name: "Published Event",
|
||||||
|
eventDate: DateTime.UtcNow.AddDays(15),
|
||||||
|
organizerId: organizer.Id,
|
||||||
|
status: EventStatus.Published);
|
||||||
|
publishedEvent.MaxParticipants = 100;
|
||||||
|
|
||||||
|
_context.Events.AddRange(pastEvent, draftEvent, publishedEvent);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Add registrations with payments
|
||||||
|
var participant1 = TestDataFactory.CreateUser(email: "p1@example.com", role: UserRole.Participant);
|
||||||
|
var participant2 = TestDataFactory.CreateUser(email: "p2@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(participant1, participant2);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var reg1 = TestDataFactory.CreateRegistration(publishedEvent.Id, participant1.Id, RegistrationStatus.Confirmed);
|
||||||
|
var reg2 = TestDataFactory.CreateRegistration(publishedEvent.Id, participant2.Id, RegistrationStatus.Pending);
|
||||||
|
_context.Registrations.AddRange(reg1, reg2);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var payment = TestDataFactory.CreatePayment(reg1.Id, 50.00m, PaymentMethod.Cash);
|
||||||
|
_context.Payments.Add(payment);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetOrganizerDashboard();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var dashboard = okResult.Value.Should().BeOfType<OrganizerDashboardDto>().Subject;
|
||||||
|
dashboard.TotalEvents.Should().Be(3);
|
||||||
|
dashboard.PublishedEvents.Should().Be(1);
|
||||||
|
dashboard.DraftEvents.Should().Be(1);
|
||||||
|
dashboard.TotalRegistrations.Should().Be(2);
|
||||||
|
dashboard.ConfirmedRegistrations.Should().Be(1);
|
||||||
|
dashboard.PendingRegistrations.Should().Be(1);
|
||||||
|
dashboard.TotalRevenue.Should().Be(50.00m);
|
||||||
|
dashboard.UpcomingEvents.Should().HaveCount(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetOrganizerDashboard_EmptyOrganizer_ReturnsEmptyDashboard()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetOrganizerDashboard();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var dashboard = okResult.Value.Should().BeOfType<OrganizerDashboardDto>().Subject;
|
||||||
|
dashboard.TotalEvents.Should().Be(0);
|
||||||
|
dashboard.TotalRegistrations.Should().Be(0);
|
||||||
|
dashboard.TotalRevenue.Should().Be(0);
|
||||||
|
dashboard.UpcomingEvents.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Participant Dashboard - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetParticipantDashboard_ReturnsDashboardData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var upcomingEvent = TestDataFactory.CreateEvent(
|
||||||
|
name: "Upcoming Event",
|
||||||
|
eventDate: DateTime.UtcNow.AddDays(15),
|
||||||
|
organizerId: organizer.Id,
|
||||||
|
status: EventStatus.Published);
|
||||||
|
var pastEvent = TestDataFactory.CreateEvent(
|
||||||
|
name: "Past Event",
|
||||||
|
eventDate: DateTime.UtcNow.AddDays(-10),
|
||||||
|
organizerId: organizer.Id,
|
||||||
|
status: EventStatus.Completed);
|
||||||
|
|
||||||
|
var cancelledEvent = TestDataFactory.CreateEvent(name: "Cancelled Event", organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.AddRange(upcomingEvent, pastEvent, cancelledEvent);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
var upcomingReg = TestDataFactory.CreateRegistration(upcomingEvent.Id, participant.Id, RegistrationStatus.Confirmed);
|
||||||
|
var pastReg = TestDataFactory.CreateRegistration(pastEvent.Id, participant.Id, RegistrationStatus.Completed);
|
||||||
|
var cancelledReg = TestDataFactory.CreateRegistration(cancelledEvent.Id, participant.Id, RegistrationStatus.Cancelled);
|
||||||
|
_context.Registrations.AddRange(upcomingReg, pastReg, cancelledReg);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetParticipantDashboard();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var dashboard = okResult.Value.Should().BeOfType<ParticipantDashboardDto>().Subject;
|
||||||
|
dashboard.TotalRegistrations.Should().Be(3);
|
||||||
|
dashboard.UpcomingEvents.Should().Be(1);
|
||||||
|
dashboard.CompletedEvents.Should().Be(1);
|
||||||
|
dashboard.CancelledRegistrations.Should().Be(1);
|
||||||
|
dashboard.MyRegistrations.Should().HaveCount(3);
|
||||||
|
dashboard.UpcomingEventList.Should().HaveCount(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetParticipantDashboard_NoRegistrations_ReturnsEmptyDashboard()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var participant = TestDataFactory.CreateUser(role: UserRole.Participant);
|
||||||
|
_context.Users.Add(participant);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetParticipantDashboard();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var dashboard = okResult.Value.Should().BeOfType<ParticipantDashboardDto>().Subject;
|
||||||
|
dashboard.TotalRegistrations.Should().Be(0);
|
||||||
|
dashboard.UpcomingEvents.Should().Be(0);
|
||||||
|
dashboard.MyRegistrations.Should().BeEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Dashboard - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetOrganizerDashboard_Unauthenticated_ReturnsUnauthorized()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetOrganizerDashboard();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<UnauthorizedResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetParticipantDashboard_Unauthenticated_ReturnsUnauthorized()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetParticipantDashboard();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<UnauthorizedResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Event Capacity Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetOrganizerDashboard_ShowsEventsNearCapacity()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
// Event at 90% capacity (should appear in near capacity list)
|
||||||
|
var nearCapacityEvent = TestDataFactory.CreateEvent(
|
||||||
|
name: "Near Capacity",
|
||||||
|
eventDate: DateTime.UtcNow.AddDays(10),
|
||||||
|
organizerId: organizer.Id,
|
||||||
|
status: EventStatus.Published);
|
||||||
|
nearCapacityEvent.MaxParticipants = 10;
|
||||||
|
|
||||||
|
// Event at 50% capacity (should NOT appear)
|
||||||
|
var normalEvent = TestDataFactory.CreateEvent(
|
||||||
|
name: "Normal",
|
||||||
|
eventDate: DateTime.UtcNow.AddDays(10),
|
||||||
|
organizerId: organizer.Id,
|
||||||
|
status: EventStatus.Published);
|
||||||
|
normalEvent.MaxParticipants = 100;
|
||||||
|
|
||||||
|
_context.Events.AddRange(nearCapacityEvent, normalEvent);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Add registrations
|
||||||
|
var participant1 = TestDataFactory.CreateUser(email: "p1@test.com", role: UserRole.Participant);
|
||||||
|
var participant2 = TestDataFactory.CreateUser(email: "p2@test.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(participant1, participant2);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Fill near capacity event to 90%
|
||||||
|
for (int i = 0; i < 9; i++)
|
||||||
|
{
|
||||||
|
var p = TestDataFactory.CreateUser(email: $"test{i}@test.com", role: UserRole.Participant);
|
||||||
|
_context.Users.Add(p);
|
||||||
|
var reg = TestDataFactory.CreateRegistration(nearCapacityEvent.Id, p.Id, RegistrationStatus.Confirmed);
|
||||||
|
_context.Registrations.Add(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill normal event to 50%
|
||||||
|
for (int i = 0; i < 50; i++)
|
||||||
|
{
|
||||||
|
var p = TestDataFactory.CreateUser(email: $"normal{i}@test.com", role: UserRole.Participant);
|
||||||
|
_context.Users.Add(p);
|
||||||
|
var reg = TestDataFactory.CreateRegistration(normalEvent.Id, p.Id, RegistrationStatus.Confirmed);
|
||||||
|
_context.Registrations.Add(reg);
|
||||||
|
}
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetOrganizerDashboard();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var dashboard = okResult.Value.Should().BeOfType<OrganizerDashboardDto>().Subject;
|
||||||
|
dashboard.EventsNearCapacity.Should().HaveCount(1);
|
||||||
|
dashboard.EventsNearCapacity.First().Name.Should().Be("Near Capacity");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,511 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moq;
|
||||||
|
using FluentAssertions;
|
||||||
|
using RacePlannerApi.Controllers;
|
||||||
|
using RacePlannerApi.Data;
|
||||||
|
using RacePlannerApi.DTOs;
|
||||||
|
using RacePlannerApi.Models;
|
||||||
|
using backend.Tests.Utilities;
|
||||||
|
|
||||||
|
namespace backend.Tests.Controllers;
|
||||||
|
|
||||||
|
public class EventsControllerTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly RacePlannerDbContext _context;
|
||||||
|
private readonly EventsController _controller;
|
||||||
|
|
||||||
|
public EventsControllerTests()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<RacePlannerDbContext>()
|
||||||
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
_context = new RacePlannerDbContext(options);
|
||||||
|
_controller = new EventsController(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUserContext(Guid userId, string role = "Participant")
|
||||||
|
{
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
|
||||||
|
new Claim(ClaimTypes.Role, role)
|
||||||
|
};
|
||||||
|
var identity = new ClaimsIdentity(claims, "TestAuthType");
|
||||||
|
var principal = new ClaimsPrincipal(identity);
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = principal }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_context.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Create Event - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateEvent_WithValidData_CreatesEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new CreateEventRequest
|
||||||
|
{
|
||||||
|
Name = "Test Marathon",
|
||||||
|
Description = "A test marathon event",
|
||||||
|
EventDate = DateTime.UtcNow.AddDays(30),
|
||||||
|
Location = "Test City",
|
||||||
|
Category = "Running",
|
||||||
|
Tags = new List<string> { "marathon", "running" },
|
||||||
|
MaxParticipants = 100
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateEvent(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var createdResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
|
||||||
|
var response = createdResult.Value.Should().BeOfType<EventDto>().Subject;
|
||||||
|
response.Name.Should().Be(request.Name);
|
||||||
|
response.Status.Should().Be("Draft");
|
||||||
|
response.Organizer.Id.Should().Be(organizer.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateEvent_SetsStatusToDraft()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new CreateEventRequest
|
||||||
|
{
|
||||||
|
Name = "Test Event",
|
||||||
|
EventDate = DateTime.UtcNow.AddDays(10),
|
||||||
|
Location = "Test Location",
|
||||||
|
MaxParticipants = 50
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateEvent(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var createdResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
|
||||||
|
var response = createdResult.Value.Should().BeOfType<EventDto>().Subject;
|
||||||
|
response.Status.Should().Be("Draft");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Create Event - Negative Tests
|
||||||
|
|
||||||
|
[Fact(Skip = "Authorization attributes require integration tests with full ASP.NET Core pipeline")]
|
||||||
|
public async Task CreateEvent_WithoutOrganizerRole_ReturnsForbidden()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var participant = TestDataFactory.CreateUser(role: UserRole.Participant);
|
||||||
|
_context.Users.Add(participant);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
var request = new CreateEventRequest
|
||||||
|
{
|
||||||
|
Name = "Test Event",
|
||||||
|
EventDate = DateTime.UtcNow.AddDays(10),
|
||||||
|
Location = "Test Location"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateEvent(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<ForbidResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateEvent_WithUnauthenticatedUser_ReturnsUnauthorized()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = new CreateEventRequest
|
||||||
|
{
|
||||||
|
Name = "Test Event",
|
||||||
|
EventDate = DateTime.UtcNow.AddDays(10),
|
||||||
|
Location = "Test Location"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateEvent(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<UnauthorizedResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get Events - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetEvents_ReturnsPublishedEventsForAnonymous()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var publishedEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
var draftEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft);
|
||||||
|
_context.Events.AddRange(publishedEvent, draftEvent);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetEvents();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var events = okResult.Value.Should().BeAssignableTo<IEnumerable<EventDto>>().Subject;
|
||||||
|
events.Should().HaveCount(1);
|
||||||
|
events.First().Status.Should().Be("Published");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetEvents_WithCategoryFilter_ReturnsFilteredEvents()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var runningEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
runningEvent.Category = "Running";
|
||||||
|
var cyclingEvent = TestDataFactory.CreateEvent(name: "Cycling Event", organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
cyclingEvent.Category = "Cycling";
|
||||||
|
|
||||||
|
_context.Events.AddRange(runningEvent, cyclingEvent);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Set anonymous context
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
|
||||||
|
};
|
||||||
|
|
||||||
|
var filter = new EventFilterRequest { Category = "Running" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetEvents(filter);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var events = okResult.Value.Should().BeAssignableTo<IEnumerable<EventDto>>().Subject;
|
||||||
|
events.Should().HaveCount(1);
|
||||||
|
events.First().Category.Should().Be("Running");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetEvents_WithDateRangeFilter_ReturnsFilteredEvents()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var futureEvent = TestDataFactory.CreateEvent(
|
||||||
|
name: "Future Event",
|
||||||
|
eventDate: DateTime.UtcNow.AddDays(60),
|
||||||
|
organizerId: organizer.Id,
|
||||||
|
status: EventStatus.Published);
|
||||||
|
var pastEvent = TestDataFactory.CreateEvent(
|
||||||
|
name: "Past Event",
|
||||||
|
eventDate: DateTime.UtcNow.AddDays(-10),
|
||||||
|
organizerId: organizer.Id,
|
||||||
|
status: EventStatus.Published);
|
||||||
|
|
||||||
|
_context.Events.AddRange(futureEvent, pastEvent);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Set anonymous context
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
|
||||||
|
};
|
||||||
|
|
||||||
|
var filter = new EventFilterRequest
|
||||||
|
{
|
||||||
|
FromDate = DateTime.UtcNow.AddDays(1),
|
||||||
|
ToDate = DateTime.UtcNow.AddDays(100)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetEvents(filter);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var events = okResult.Value.Should().BeAssignableTo<IEnumerable<EventDto>>().Subject;
|
||||||
|
events.Should().HaveCount(1);
|
||||||
|
events.First().Name.Should().Be("Future Event");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get Single Event - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetEvent_ReturnsPublishedEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetEvent(eventEntity.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = okResult.Value.Should().BeOfType<EventDto>().Subject;
|
||||||
|
response.Id.Should().Be(eventEntity.Id);
|
||||||
|
response.Name.Should().Be(eventEntity.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetEvent_OrganizerCanViewOwnDraftEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var draftEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft);
|
||||||
|
_context.Events.Add(draftEvent);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetEvent(draftEvent.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<OkObjectResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get Single Event - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetEvent_DraftEventNotVisibleToPublic()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var draftEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft);
|
||||||
|
_context.Events.Add(draftEvent);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetEvent(draftEvent.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetEvent_NonExistentEvent_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var nonExistentId = Guid.NewGuid();
|
||||||
|
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = new ClaimsPrincipal() }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetEvent(nonExistentId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Update Event - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateEvent_WithValidData_UpdatesEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new UpdateEventRequest
|
||||||
|
{
|
||||||
|
Name = "Updated Event Name",
|
||||||
|
Description = "Updated description",
|
||||||
|
Status = EventStatus.Published
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.UpdateEvent(eventEntity.Id, request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = okResult.Value.Should().BeOfType<EventDto>().Subject;
|
||||||
|
response.Name.Should().Be("Updated Event Name");
|
||||||
|
response.Status.Should().Be("Published");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Update Event - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateEvent_NonOrganizer_ReturnsForbidden()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var otherUser = TestDataFactory.CreateUser(email: "other@example.com", role: UserRole.Organizer);
|
||||||
|
_context.Users.AddRange(organizer, otherUser);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(otherUser.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new UpdateEventRequest { Name = "Hacked Name" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.UpdateEvent(eventEntity.Id, request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<ForbidResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateEvent_NonExistentEvent_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new UpdateEventRequest { Name = "Updated Name" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.UpdateEvent(Guid.NewGuid(), request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Delete Event - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteEvent_OrganizerCanDeleteOwnEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.DeleteEvent(eventEntity.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().BeOfType<NoContentResult>();
|
||||||
|
|
||||||
|
var deletedEvent = await _context.Events.FindAsync(eventEntity.Id);
|
||||||
|
deletedEvent.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Delete Event - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteEvent_NonOrganizer_ReturnsForbidden()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var otherUser = TestDataFactory.CreateUser(email: "other@example.com", role: UserRole.Organizer);
|
||||||
|
_context.Users.AddRange(organizer, otherUser);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(otherUser.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.DeleteEvent(eventEntity.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().BeOfType<ForbidResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteEvent_NonExistentEvent_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.DeleteEvent(Guid.NewGuid());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,381 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moq;
|
||||||
|
using FluentAssertions;
|
||||||
|
using RacePlannerApi.Controllers;
|
||||||
|
using RacePlannerApi.Data;
|
||||||
|
using RacePlannerApi.DTOs;
|
||||||
|
using RacePlannerApi.Models;
|
||||||
|
using backend.Tests.Utilities;
|
||||||
|
|
||||||
|
namespace backend.Tests.Controllers;
|
||||||
|
|
||||||
|
public class PaymentsControllerTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly RacePlannerDbContext _context;
|
||||||
|
private readonly PaymentsController _controller;
|
||||||
|
|
||||||
|
public PaymentsControllerTests()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<RacePlannerDbContext>()
|
||||||
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
_context = new RacePlannerDbContext(options);
|
||||||
|
_controller = new PaymentsController(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUserContext(Guid userId, string role = "Participant")
|
||||||
|
{
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
|
||||||
|
new Claim(ClaimTypes.Role, role)
|
||||||
|
};
|
||||||
|
var identity = new ClaimsIdentity(claims, "TestAuthType");
|
||||||
|
var principal = new ClaimsPrincipal(identity);
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = principal }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_context.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Record Payment - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RecordPayment_WithValidData_RecordsPayment()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id, RegistrationStatus.Confirmed);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new CreatePaymentRequest
|
||||||
|
{
|
||||||
|
RegistrationId = registration.Id,
|
||||||
|
Amount = 50.00m,
|
||||||
|
Method = PaymentMethod.Cash,
|
||||||
|
Notes = "Cash payment received"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.RecordPayment(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var createdResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
|
||||||
|
var response = createdResult.Value.Should().BeOfType<PaymentDto>().Subject;
|
||||||
|
response.Amount.Should().Be(50.00m);
|
||||||
|
response.Method.Should().Be("Cash");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RecordPayment_OnlinePayment_WithTransactionId()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant2@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id, RegistrationStatus.Confirmed);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new CreatePaymentRequest
|
||||||
|
{
|
||||||
|
RegistrationId = registration.Id,
|
||||||
|
Amount = 75.00m,
|
||||||
|
Method = PaymentMethod.Online,
|
||||||
|
TransactionId = "txn_12345",
|
||||||
|
Notes = "Online payment"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.RecordPayment(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var createdResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
|
||||||
|
var response = createdResult.Value.Should().BeOfType<PaymentDto>().Subject;
|
||||||
|
response.Method.Should().Be("Online");
|
||||||
|
response.TransactionId.Should().Be("txn_12345");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Record Payment - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RecordPayment_ForCancelledRegistration_ReturnsBadRequest()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant3@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id, RegistrationStatus.Cancelled);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new CreatePaymentRequest
|
||||||
|
{
|
||||||
|
RegistrationId = registration.Id,
|
||||||
|
Amount = 50.00m,
|
||||||
|
Method = PaymentMethod.Cash
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.RecordPayment(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var badRequestResult = result.Result.Should().BeOfType<BadRequestObjectResult>().Subject;
|
||||||
|
badRequestResult.Value.Should().BeEquivalentTo(new { error = "Cannot record payment for cancelled registration" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RecordPayment_ForNonExistentRegistration_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new CreatePaymentRequest
|
||||||
|
{
|
||||||
|
RegistrationId = Guid.NewGuid(),
|
||||||
|
Amount = 50.00m,
|
||||||
|
Method = PaymentMethod.Cash
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.RecordPayment(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var notFoundResult = result.Result.Should().BeOfType<NotFoundObjectResult>().Subject;
|
||||||
|
notFoundResult.Value.Should().BeEquivalentTo(new { error = "Registration not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RecordPayment_NonOrganizer_ReturnsForbidden()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant4@example.com", role: UserRole.Participant);
|
||||||
|
var otherOrganizer = TestDataFactory.CreateUser(email: "other@example.com", role: UserRole.Organizer);
|
||||||
|
_context.Users.AddRange(organizer, participant, otherOrganizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(otherOrganizer.Id, "Organizer");
|
||||||
|
|
||||||
|
var request = new CreatePaymentRequest
|
||||||
|
{
|
||||||
|
RegistrationId = registration.Id,
|
||||||
|
Amount = 50.00m,
|
||||||
|
Method = PaymentMethod.Cash
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.RecordPayment(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<ForbidResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get Payment Status - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPaymentStatus_UnpaidRegistration_ReturnsUnpaid()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant5@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetPaymentStatus(registration.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = okResult.Value;
|
||||||
|
response.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPaymentStatus_PaidRegistration_ReturnsPaid()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant6@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Record a payment
|
||||||
|
var payment = TestDataFactory.CreatePayment(registration.Id, 50.00m, PaymentMethod.Cash);
|
||||||
|
_context.Payments.Add(payment);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetPaymentStatus(registration.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = okResult.Value;
|
||||||
|
response.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get Payment Status - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPaymentStatus_NonExistentRegistration_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var participant = TestDataFactory.CreateUser(role: UserRole.Participant);
|
||||||
|
_context.Users.Add(participant);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetPaymentStatus(Guid.NewGuid());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get Payment Report - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPaymentReport_ReturnsEventPaymentSummary()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant1 = TestDataFactory.CreateUser(email: "p1@example.com", role: UserRole.Participant);
|
||||||
|
var participant2 = TestDataFactory.CreateUser(email: "p2@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant1, participant2);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration1 = TestDataFactory.CreateRegistration(eventEntity.Id, participant1.Id, RegistrationStatus.Confirmed);
|
||||||
|
var registration2 = TestDataFactory.CreateRegistration(eventEntity.Id, participant2.Id, RegistrationStatus.Confirmed);
|
||||||
|
_context.Registrations.AddRange(registration1, registration2);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Add payment for first registration
|
||||||
|
var payment = TestDataFactory.CreatePayment(registration1.Id, 50.00m, PaymentMethod.Cash);
|
||||||
|
_context.Payments.Add(payment);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetPaymentReport(eventEntity.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = okResult.Value.Should().BeOfType<PaymentReportDto>().Subject;
|
||||||
|
response.TotalCollected.Should().Be(50.00m);
|
||||||
|
response.TotalRegistrations.Should().Be(2);
|
||||||
|
response.PaidRegistrations.Should().Be(1);
|
||||||
|
response.UnpaidRegistrations.Should().Be(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get Payment Report - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPaymentReport_NonOrganizer_ReturnsForbidden()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var otherOrganizer = TestDataFactory.CreateUser(email: "other@example.com", role: UserRole.Organizer);
|
||||||
|
_context.Users.AddRange(organizer, otherOrganizer);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(otherOrganizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetPaymentReport(eventEntity.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetPaymentReport_NonExistentEvent_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
_context.Users.Add(organizer);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetPaymentReport(Guid.NewGuid());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Moq;
|
||||||
|
using FluentAssertions;
|
||||||
|
using RacePlannerApi.Controllers;
|
||||||
|
using RacePlannerApi.Data;
|
||||||
|
using RacePlannerApi.DTOs;
|
||||||
|
using RacePlannerApi.Models;
|
||||||
|
using backend.Tests.Utilities;
|
||||||
|
|
||||||
|
namespace backend.Tests.Controllers;
|
||||||
|
|
||||||
|
public class RegistrationsControllerTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly RacePlannerDbContext _context;
|
||||||
|
private readonly RegistrationsController _controller;
|
||||||
|
|
||||||
|
public RegistrationsControllerTests()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<RacePlannerDbContext>()
|
||||||
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
_context = new RacePlannerDbContext(options);
|
||||||
|
_controller = new RegistrationsController(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUserContext(Guid userId, string role = "Participant")
|
||||||
|
{
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
|
||||||
|
new Claim(ClaimTypes.Role, role)
|
||||||
|
};
|
||||||
|
var identity = new ClaimsIdentity(claims, "TestAuthType");
|
||||||
|
var principal = new ClaimsPrincipal(identity);
|
||||||
|
_controller.ControllerContext = new ControllerContext
|
||||||
|
{
|
||||||
|
HttpContext = new DefaultHttpContext { User = principal }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_context.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Create Registration - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateRegistration_WithValidData_CreatesRegistration()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
var request = new CreateRegistrationRequest
|
||||||
|
{
|
||||||
|
EventId = eventEntity.Id,
|
||||||
|
Category = "Open",
|
||||||
|
EmergencyContact = "Emergency Contact: 123-456-7890"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateRegistration(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var createdResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
|
||||||
|
var response = createdResult.Value.Should().BeOfType<RegistrationDto>().Subject;
|
||||||
|
response.EventId.Should().Be(eventEntity.Id);
|
||||||
|
response.ParticipantId.Should().Be(participant.Id);
|
||||||
|
response.Status.Should().Be("Pending");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateRegistration_SetsStatusToPending()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant2@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
var request = new CreateRegistrationRequest
|
||||||
|
{
|
||||||
|
EventId = eventEntity.Id,
|
||||||
|
Category = "Open"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateRegistration(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var createdResult = result.Result.Should().BeOfType<CreatedAtActionResult>().Subject;
|
||||||
|
var response = createdResult.Value.Should().BeOfType<RegistrationDto>().Subject;
|
||||||
|
response.Status.Should().Be("Pending");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Create Registration - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateRegistration_ForDraftEvent_ReturnsBadRequest()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant3@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var draftEvent = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Draft);
|
||||||
|
_context.Events.Add(draftEvent);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
var request = new CreateRegistrationRequest
|
||||||
|
{
|
||||||
|
EventId = draftEvent.Id,
|
||||||
|
Category = "Open"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateRegistration(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var badRequestResult = result.Result.Should().BeOfType<BadRequestObjectResult>().Subject;
|
||||||
|
badRequestResult.Value.Should().BeEquivalentTo(new { error = "Event is not open for registration" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateRegistration_DuplicateRegistration_ReturnsConflict()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant4@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
// Create existing registration
|
||||||
|
var existingRegistration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id);
|
||||||
|
_context.Registrations.Add(existingRegistration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
var request = new CreateRegistrationRequest
|
||||||
|
{
|
||||||
|
EventId = eventEntity.Id,
|
||||||
|
Category = "Open"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateRegistration(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var conflictResult = result.Result.Should().BeOfType<ConflictObjectResult>().Subject;
|
||||||
|
conflictResult.Value.Should().BeEquivalentTo(new { error = "Already registered for this event" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateRegistration_ForFullEvent_ReturnsBadRequest()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant1 = TestDataFactory.CreateUser(email: "participant1@example.com", role: UserRole.Participant);
|
||||||
|
var participant2 = TestDataFactory.CreateUser(email: "participant2@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant1, participant2);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
eventEntity.MaxParticipants = 1;
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
// Fill the event
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant1.Id, RegistrationStatus.Confirmed);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant2.Id, "Participant");
|
||||||
|
|
||||||
|
var request = new CreateRegistrationRequest
|
||||||
|
{
|
||||||
|
EventId = eventEntity.Id,
|
||||||
|
Category = "Open"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateRegistration(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var badRequestResult = result.Result.Should().BeOfType<BadRequestObjectResult>().Subject;
|
||||||
|
badRequestResult.Value.Should().BeEquivalentTo(new { error = "Event is full" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateRegistration_ForNonExistentEvent_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var participant = TestDataFactory.CreateUser(role: UserRole.Participant);
|
||||||
|
_context.Users.Add(participant);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
var request = new CreateRegistrationRequest
|
||||||
|
{
|
||||||
|
EventId = Guid.NewGuid(),
|
||||||
|
Category = "Open"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CreateRegistration(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var notFoundResult = result.Result.Should().BeOfType<NotFoundObjectResult>().Subject;
|
||||||
|
notFoundResult.Value.Should().BeEquivalentTo(new { error = "Event not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get Registration - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetRegistration_ParticipantCanViewOwnRegistration()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant5@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetRegistration(registration.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<OkObjectResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetRegistration_OrganizerCanViewAnyRegistrationForTheirEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant6@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetRegistration(registration.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<OkObjectResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetMyRegistrations_ReturnsParticipantsRegistrations()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant7@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var event1 = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
var event2 = TestDataFactory.CreateEvent(name: "Event 2", organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.AddRange(event1, event2);
|
||||||
|
|
||||||
|
var registration1 = TestDataFactory.CreateRegistration(event1.Id, participant.Id);
|
||||||
|
var registration2 = TestDataFactory.CreateRegistration(event2.Id, participant.Id);
|
||||||
|
_context.Registrations.AddRange(registration1, registration2);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetMyRegistrations();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var registrations = okResult.Value.Should().BeAssignableTo<IEnumerable<RegistrationDto>>().Subject;
|
||||||
|
registrations.Should().HaveCount(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Get Registration - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetRegistration_NonExistentRegistration_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var participant = TestDataFactory.CreateUser(role: UserRole.Participant);
|
||||||
|
_context.Users.Add(participant);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.GetRegistration(Guid.NewGuid());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Cancel Registration - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CancelRegistration_ParticipantCanCancelOwnRegistration()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant8@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id, RegistrationStatus.Pending);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CancelRegistration(registration.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = okResult.Value.Should().BeOfType<RegistrationDto>().Subject;
|
||||||
|
response.Status.Should().Be("Cancelled");
|
||||||
|
|
||||||
|
// Verify in database
|
||||||
|
var updatedRegistration = await _context.Registrations.FindAsync(registration.Id);
|
||||||
|
updatedRegistration!.Status.Should().Be(RegistrationStatus.Cancelled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CancelRegistration_OrganizerCanCancelAnyRegistration()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant9@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id, RegistrationStatus.Pending);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(organizer.Id, "Organizer");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CancelRegistration(registration.Id);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = okResult.Value.Should().BeOfType<RegistrationDto>().Subject;
|
||||||
|
response.Status.Should().Be("Cancelled");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Cancel Registration - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CancelRegistration_NonExistentRegistration_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var participant = TestDataFactory.CreateUser(role: UserRole.Participant);
|
||||||
|
_context.Users.Add(participant);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.CancelRegistration(Guid.NewGuid());
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Update Registration - Positive Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateRegistration_ParticipantCanUpdateOwnRegistration()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizer = TestDataFactory.CreateUser(role: UserRole.Organizer);
|
||||||
|
var participant = TestDataFactory.CreateUser(email: "participant10@example.com", role: UserRole.Participant);
|
||||||
|
_context.Users.AddRange(organizer, participant);
|
||||||
|
|
||||||
|
var eventEntity = TestDataFactory.CreateEvent(organizerId: organizer.Id, status: EventStatus.Published);
|
||||||
|
_context.Events.Add(eventEntity);
|
||||||
|
|
||||||
|
var registration = TestDataFactory.CreateRegistration(eventEntity.Id, participant.Id);
|
||||||
|
_context.Registrations.Add(registration);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
var request = new UpdateRegistrationRequest
|
||||||
|
{
|
||||||
|
Category = "Updated Category",
|
||||||
|
EmergencyContact = "Updated Emergency Contact"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.UpdateRegistration(registration.Id, request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var response = okResult.Value.Should().BeOfType<RegistrationDto>().Subject;
|
||||||
|
response.Category.Should().Be("Updated Category");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Update Registration - Negative Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateRegistration_NonExistentRegistration_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var participant = TestDataFactory.CreateUser(role: UserRole.Participant);
|
||||||
|
_context.Users.Add(participant);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
SetUserContext(participant.Id, "Participant");
|
||||||
|
|
||||||
|
var request = new UpdateRegistrationRequest { Category = "Updated" };
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _controller.UpdateRegistration(Guid.NewGuid(), request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Result.Should().BeOfType<NotFoundResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -84,7 +84,8 @@ public static class TestDataFactory
|
|||||||
public static Announcement CreateAnnouncement(
|
public static Announcement CreateAnnouncement(
|
||||||
Guid eventId,
|
Guid eventId,
|
||||||
string title = "Test Announcement",
|
string title = "Test Announcement",
|
||||||
string content = "Test announcement content")
|
string content = "Test announcement content",
|
||||||
|
Guid? authorId = null)
|
||||||
{
|
{
|
||||||
return new Announcement
|
return new Announcement
|
||||||
{
|
{
|
||||||
@@ -92,6 +93,7 @@ public static class TestDataFactory
|
|||||||
EventId = eventId,
|
EventId = eventId,
|
||||||
Title = title,
|
Title = title,
|
||||||
Content = content,
|
Content = content,
|
||||||
|
AuthorId = authorId ?? Guid.NewGuid(),
|
||||||
CreatedAt = DateTime.UtcNow,
|
CreatedAt = DateTime.UtcNow,
|
||||||
UpdatedAt = DateTime.UtcNow
|
UpdatedAt = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
|||||||
Generated
+566
@@ -25,6 +25,7 @@
|
|||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.2.2",
|
"eslint-config-next": "16.2.2",
|
||||||
"jest": "^30.3.0",
|
"jest": "^30.3.0",
|
||||||
|
"jest-environment-jsdom": "^30.3.0",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"ts-jest": "^29.4.9",
|
"ts-jest": "^29.4.9",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
@@ -48,6 +49,27 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@asamuzakjp/css-color": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@csstools/css-calc": "^2.1.3",
|
||||||
|
"@csstools/css-color-parser": "^3.0.9",
|
||||||
|
"@csstools/css-parser-algorithms": "^3.0.4",
|
||||||
|
"@csstools/css-tokenizer": "^3.0.3",
|
||||||
|
"lru-cache": "^10.4.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
|
||||||
|
"version": "10.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||||
|
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -533,6 +555,121 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@csstools/color-helpers": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/csstools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/csstools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT-0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@csstools/css-calc": {
|
||||||
|
"version": "2.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
|
||||||
|
"integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/csstools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/csstools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||||
|
"@csstools/css-tokenizer": "^3.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@csstools/css-color-parser": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/csstools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/csstools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@csstools/color-helpers": "^5.1.0",
|
||||||
|
"@csstools/css-calc": "^2.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@csstools/css-parser-algorithms": "^3.0.5",
|
||||||
|
"@csstools/css-tokenizer": "^3.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@csstools/css-parser-algorithms": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/csstools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/csstools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@csstools/css-tokenizer": "^3.0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@csstools/css-tokenizer": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/csstools"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/csstools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emnapi/core": {
|
"node_modules/@emnapi/core": {
|
||||||
"version": "1.9.2",
|
"version": "1.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
|
||||||
@@ -1565,6 +1702,34 @@
|
|||||||
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
|
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jest/environment-jsdom-abstract": {
|
||||||
|
"version": "30.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.3.0.tgz",
|
||||||
|
"integrity": "sha512-0hNFs5N6We3DMCwobzI0ydhkY10sT1tZSC0AAiy+0g2Dt/qEWgrcV5BrMxPczhe41cxW4qm6X+jqZaUdpZIajA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jest/environment": "30.3.0",
|
||||||
|
"@jest/fake-timers": "30.3.0",
|
||||||
|
"@jest/types": "30.3.0",
|
||||||
|
"@types/jsdom": "^21.1.7",
|
||||||
|
"@types/node": "*",
|
||||||
|
"jest-mock": "30.3.0",
|
||||||
|
"jest-util": "30.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"canvas": "^3.0.0",
|
||||||
|
"jsdom": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"canvas": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jest/expect": {
|
"node_modules/@jest/expect": {
|
||||||
"version": "30.3.0",
|
"version": "30.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz",
|
||||||
@@ -2635,6 +2800,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/jsdom": {
|
||||||
|
"version": "21.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz",
|
||||||
|
"integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"@types/tough-cookie": "*",
|
||||||
|
"parse5": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -2676,6 +2853,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/tough-cookie": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/yargs": {
|
"node_modules/@types/yargs": {
|
||||||
"version": "17.0.35",
|
"version": "17.0.35",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
|
||||||
@@ -3273,6 +3457,16 @@
|
|||||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/agent-base": {
|
||||||
|
"version": "7.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||||
|
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.14.0",
|
"version": "6.14.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -4015,6 +4209,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/cssstyle": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
|
||||||
|
"integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@asamuzakjp/css-color": "^3.2.0",
|
||||||
|
"rrweb-cssom": "^0.8.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -4025,6 +4233,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause"
|
"license": "BSD-2-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/data-urls": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-mimetype": "^4.0.0",
|
||||||
|
"whatwg-url": "^14.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/data-view-buffer": {
|
"node_modules/data-view-buffer": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -4089,6 +4311,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decimal.js": {
|
||||||
|
"version": "10.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
|
||||||
|
"integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/dedent": {
|
"node_modules/dedent": {
|
||||||
"version": "1.7.2",
|
"version": "1.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz",
|
||||||
@@ -4252,6 +4481,19 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/error-ex": {
|
"node_modules/error-ex": {
|
||||||
"version": "1.3.4",
|
"version": "1.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
||||||
@@ -5415,6 +5657,19 @@
|
|||||||
"hermes-estree": "0.25.1"
|
"hermes-estree": "0.25.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html-encoding-sniffer": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-encoding": "^3.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-escaper": {
|
"node_modules/html-escaper": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||||
@@ -5422,6 +5677,34 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/http-proxy-agent": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
|
||||||
|
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.1.0",
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/https-proxy-agent": {
|
||||||
|
"version": "7.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||||
|
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.1.2",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/human-signals": {
|
"node_modules/human-signals": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||||
@@ -5432,6 +5715,19 @@
|
|||||||
"node": ">=10.17.0"
|
"node": ">=10.17.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -5786,6 +6082,13 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-potential-custom-element-name": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-regex": {
|
"node_modules/is-regex": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -6396,6 +6699,29 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/jest-environment-jsdom": {
|
||||||
|
"version": "30.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.3.0.tgz",
|
||||||
|
"integrity": "sha512-RLEOJy6ip1lpw0yqJ8tB3i88FC7VBz7i00Zvl2qF71IdxjS98gC9/0SPWYIBVXHm5hgCYK0PAlSlnHGGy9RoMg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jest/environment": "30.3.0",
|
||||||
|
"@jest/environment-jsdom-abstract": "30.3.0",
|
||||||
|
"jsdom": "^26.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"canvas": "^3.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"canvas": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jest-environment-node": {
|
"node_modules/jest-environment-node": {
|
||||||
"version": "30.3.0",
|
"version": "30.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz",
|
||||||
@@ -6993,6 +7319,46 @@
|
|||||||
"js-yaml": "bin/js-yaml.js"
|
"js-yaml": "bin/js-yaml.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsdom": {
|
||||||
|
"version": "26.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
|
||||||
|
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cssstyle": "^4.2.1",
|
||||||
|
"data-urls": "^5.0.0",
|
||||||
|
"decimal.js": "^10.5.0",
|
||||||
|
"html-encoding-sniffer": "^4.0.0",
|
||||||
|
"http-proxy-agent": "^7.0.2",
|
||||||
|
"https-proxy-agent": "^7.0.6",
|
||||||
|
"is-potential-custom-element-name": "^1.0.1",
|
||||||
|
"nwsapi": "^2.2.16",
|
||||||
|
"parse5": "^7.2.1",
|
||||||
|
"rrweb-cssom": "^0.8.0",
|
||||||
|
"saxes": "^6.0.0",
|
||||||
|
"symbol-tree": "^3.2.4",
|
||||||
|
"tough-cookie": "^5.1.1",
|
||||||
|
"w3c-xmlserializer": "^5.0.0",
|
||||||
|
"webidl-conversions": "^7.0.0",
|
||||||
|
"whatwg-encoding": "^3.1.1",
|
||||||
|
"whatwg-mimetype": "^4.0.0",
|
||||||
|
"whatwg-url": "^14.1.1",
|
||||||
|
"ws": "^8.18.0",
|
||||||
|
"xml-name-validator": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"canvas": "^3.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"canvas": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jsesc": {
|
"node_modules/jsesc": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -7753,6 +8119,13 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nwsapi": {
|
||||||
|
"version": "2.2.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
|
||||||
|
"integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -7993,6 +8366,19 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parse5": {
|
||||||
|
"version": "7.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||||
|
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"entities": "^6.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/path-exists": {
|
"node_modules/path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -8430,6 +8816,13 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rrweb-cssom": {
|
||||||
|
"version": "0.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
|
||||||
|
"integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/run-parallel": {
|
"node_modules/run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -8501,6 +8894,26 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/saxes": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"xmlchars": "^2.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=v12.22.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/scheduler": {
|
"node_modules/scheduler": {
|
||||||
"version": "0.27.0",
|
"version": "0.27.0",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
@@ -9105,6 +9518,13 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/symbol-tree": {
|
||||||
|
"version": "3.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||||
|
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/synckit": {
|
"node_modules/synckit": {
|
||||||
"version": "0.11.12",
|
"version": "0.11.12",
|
||||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
|
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
|
||||||
@@ -9190,6 +9610,26 @@
|
|||||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tldts": {
|
||||||
|
"version": "6.1.86",
|
||||||
|
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
|
||||||
|
"integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tldts-core": "^6.1.86"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"tldts": "bin/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tldts-core": {
|
||||||
|
"version": "6.1.86",
|
||||||
|
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
|
||||||
|
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tmpl": {
|
"node_modules/tmpl": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||||
@@ -9208,6 +9648,32 @@
|
|||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tough-cookie": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"tldts": "^6.1.32"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "^2.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ts-api-utils": {
|
"node_modules/ts-api-utils": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -9585,6 +10051,19 @@
|
|||||||
"node": ">=10.12.0"
|
"node": ">=10.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/w3c-xmlserializer": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"xml-name-validator": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/walker": {
|
"node_modules/walker": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
|
||||||
@@ -9595,6 +10074,54 @@
|
|||||||
"makeerror": "1.0.12"
|
"makeerror": "1.0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-encoding": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||||
|
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "0.6.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-mimetype": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "14.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
|
||||||
|
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "^5.1.0",
|
||||||
|
"webidl-conversions": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@@ -9811,6 +10338,45 @@
|
|||||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
|
||||||
|
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xml-name-validator": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/xmlchars": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/y18n": {
|
"node_modules/y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.2.2",
|
"eslint-config-next": "16.2.2",
|
||||||
"jest": "^30.3.0",
|
"jest": "^30.3.0",
|
||||||
|
"jest-environment-jsdom": "^30.3.0",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"ts-jest": "^29.4.9",
|
"ts-jest": "^29.4.9",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
|||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import { Dashboard } from '../dashboard';
|
||||||
|
import { useAuth } from '@/lib/auth-context';
|
||||||
|
import { api } from '@/lib/api';
|
||||||
|
|
||||||
|
// Mock the auth context and API
|
||||||
|
jest.mock('@/lib/auth-context', () => ({
|
||||||
|
useAuth: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('@/lib/api', () => ({
|
||||||
|
api: {
|
||||||
|
getOrganizerDashboard: jest.fn(),
|
||||||
|
getParticipantDashboard: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Dashboard', () => {
|
||||||
|
const mockOrganizerData = {
|
||||||
|
totalEvents: 10,
|
||||||
|
publishedEvents: 5,
|
||||||
|
draftEvents: 3,
|
||||||
|
totalRegistrations: 50,
|
||||||
|
totalRevenue: 2500.00,
|
||||||
|
upcomingEvents: [
|
||||||
|
{ id: '1', name: 'Marathon', eventDate: '2024-06-15', registrationCount: 45 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockParticipantData = {
|
||||||
|
totalRegistrations: 5,
|
||||||
|
upcomingEvents: 2,
|
||||||
|
completedEvents: 3,
|
||||||
|
cancelledRegistrations: 0,
|
||||||
|
myRegistrations: [
|
||||||
|
{ id: '1', eventId: '1', eventName: 'Marathon', eventDate: '2024-06-15', status: 'Confirmed' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Organizer Dashboard', () => {
|
||||||
|
it('renders loading state initially', () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({ user: { role: 'Organizer' } });
|
||||||
|
(api.getOrganizerDashboard as jest.Mock).mockImplementation(() => new Promise(() => {}));
|
||||||
|
|
||||||
|
render(<Dashboard />);
|
||||||
|
|
||||||
|
expect(screen.getByText(/loading dashboard/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders organizer dashboard with data', async () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({ user: { role: 'Organizer' } });
|
||||||
|
(api.getOrganizerDashboard as jest.Mock).mockResolvedValue(mockOrganizerData);
|
||||||
|
|
||||||
|
render(<Dashboard />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Total Events')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('10')).toBeInTheDocument(); // totalEvents
|
||||||
|
expect(screen.getByText('5')).toBeInTheDocument(); // publishedEvents
|
||||||
|
expect(screen.getByText('3')).toBeInTheDocument(); // draftEvents
|
||||||
|
expect(screen.getByText('50')).toBeInTheDocument(); // totalRegistrations
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays quick action buttons for organizer', async () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({ user: { role: 'Organizer' } });
|
||||||
|
(api.getOrganizerDashboard as jest.Mock).mockResolvedValue(mockOrganizerData);
|
||||||
|
|
||||||
|
render(<Dashboard />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Create Event')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('Manage Events')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows upcoming events section', async () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({ user: { role: 'Organizer' } });
|
||||||
|
(api.getOrganizerDashboard as jest.Mock).mockResolvedValue(mockOrganizerData);
|
||||||
|
|
||||||
|
render(<Dashboard />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Upcoming Events')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('Marathon')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays revenue information', async () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({ user: { role: 'Organizer' } });
|
||||||
|
(api.getOrganizerDashboard as jest.Mock).mockResolvedValue(mockOrganizerData);
|
||||||
|
|
||||||
|
render(<Dashboard />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Revenue')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText(/2,500\.00|2500\.00/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Participant Dashboard', () => {
|
||||||
|
it('renders participant dashboard with data', async () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({ user: { role: 'Participant' } });
|
||||||
|
(api.getParticipantDashboard as jest.Mock).mockResolvedValue(mockParticipantData);
|
||||||
|
|
||||||
|
render(<Dashboard />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Total Registrations')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('5')).toBeInTheDocument(); // totalRegistrations
|
||||||
|
expect(screen.getByText('2')).toBeInTheDocument(); // upcomingEvents
|
||||||
|
expect(screen.getByText('3')).toBeInTheDocument(); // completedEvents
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays quick action buttons for participant', async () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({ user: { role: 'Participant' } });
|
||||||
|
(api.getParticipantDashboard as jest.Mock).mockResolvedValue(mockParticipantData);
|
||||||
|
|
||||||
|
render(<Dashboard />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Browse Events')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('My Registrations')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows my recent registrations', async () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({ user: { role: 'Participant' } });
|
||||||
|
(api.getParticipantDashboard as jest.Mock).mockResolvedValue(mockParticipantData);
|
||||||
|
|
||||||
|
render(<Dashboard />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('My Recent Registrations')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('Marathon')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Confirmed')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Negative Tests', () => {
|
||||||
|
it('displays error when API fails', async () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({ user: { role: 'Organizer' } });
|
||||||
|
(api.getOrganizerDashboard as jest.Mock).mockRejectedValue(new Error('Network error'));
|
||||||
|
|
||||||
|
render(<Dashboard />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/network error/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles organizer dashboard API failure', async () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({ user: { role: 'Organizer' } });
|
||||||
|
(api.getOrganizerDashboard as jest.Mock).mockRejectedValue(new Error('API Error'));
|
||||||
|
|
||||||
|
render(<Dashboard />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/api error/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles participant dashboard API failure', async () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({ user: { role: 'Participant' } });
|
||||||
|
(api.getParticipantDashboard as jest.Mock).mockRejectedValue(new Error('API Error'));
|
||||||
|
|
||||||
|
render(<Dashboard />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/api error/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows no data available when dashboard is null', async () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({ user: { role: 'Organizer' } });
|
||||||
|
(api.getOrganizerDashboard as jest.Mock).mockResolvedValue(null);
|
||||||
|
|
||||||
|
render(<Dashboard />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/no data available/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||||
|
import { EventList } from '../event-list';
|
||||||
|
import { api } from '@/lib/api';
|
||||||
|
|
||||||
|
// Mock the API
|
||||||
|
jest.mock('@/lib/api', () => ({
|
||||||
|
api: {
|
||||||
|
getEvents: jest.fn(),
|
||||||
|
},
|
||||||
|
Event: {},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('EventList', () => {
|
||||||
|
const mockEvents = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'Marathon 2024',
|
||||||
|
description: 'Annual city marathon',
|
||||||
|
eventDate: '2024-06-15',
|
||||||
|
location: 'City Center',
|
||||||
|
status: 'Published',
|
||||||
|
category: 'Running',
|
||||||
|
tags: ['marathon', 'running'],
|
||||||
|
maxParticipants: 100,
|
||||||
|
currentRegistrations: 45,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'Cycling Race',
|
||||||
|
description: 'Mountain cycling event',
|
||||||
|
eventDate: '2024-07-20',
|
||||||
|
location: 'Mountain Trail',
|
||||||
|
status: 'Draft',
|
||||||
|
category: 'Cycling',
|
||||||
|
tags: ['cycling', 'mountain'],
|
||||||
|
maxParticipants: 50,
|
||||||
|
currentRegistrations: 20,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Positive Tests
|
||||||
|
describe('Positive Tests', () => {
|
||||||
|
it('renders loading state initially', () => {
|
||||||
|
(api.getEvents as jest.Mock).mockImplementation(() => new Promise(() => {}));
|
||||||
|
|
||||||
|
render(<EventList />);
|
||||||
|
|
||||||
|
expect(screen.getByText(/loading events/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders list of events', async () => {
|
||||||
|
(api.getEvents as jest.Mock).mockResolvedValue(mockEvents);
|
||||||
|
|
||||||
|
render(<EventList />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Marathon 2024')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('Cycling Race')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Annual city marathon')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters events by category', async () => {
|
||||||
|
(api.getEvents as jest.Mock).mockResolvedValue([mockEvents[0]]);
|
||||||
|
|
||||||
|
render(<EventList />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
const selects = screen.getAllByRole('combobox');
|
||||||
|
const categorySelect = selects[0];
|
||||||
|
fireEvent.change(categorySelect, {
|
||||||
|
target: { value: 'Running' },
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(api.getEvents).toHaveBeenCalledWith(expect.objectContaining({ category: 'Running' }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters events by status', async () => {
|
||||||
|
(api.getEvents as jest.Mock).mockResolvedValue([mockEvents[1]]);
|
||||||
|
|
||||||
|
render(<EventList />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
const selects = screen.getAllByRole('combobox');
|
||||||
|
const statusSelect = selects[1];
|
||||||
|
fireEvent.change(statusSelect, {
|
||||||
|
target: { value: 'Draft' },
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(api.getEvents).toHaveBeenCalledWith(expect.objectContaining({ status: 'Draft' }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays event details correctly', async () => {
|
||||||
|
(api.getEvents as jest.Mock).mockResolvedValue([mockEvents[0]]);
|
||||||
|
|
||||||
|
render(<EventList />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Marathon 2024')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('City Center')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/45.*\/.*100.*registered/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows view details link for events', async () => {
|
||||||
|
(api.getEvents as jest.Mock).mockResolvedValue([mockEvents[0]]);
|
||||||
|
|
||||||
|
render(<EventList />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Marathon 2024')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
const viewLink = screen.getByRole('link', { name: /view details/i });
|
||||||
|
expect(viewLink).toHaveAttribute('href', '/events/1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Negative Tests
|
||||||
|
describe('Negative Tests', () => {
|
||||||
|
it('displays error message when API fails', async () => {
|
||||||
|
(api.getEvents as jest.Mock).mockRejectedValue(new Error('Failed to fetch'));
|
||||||
|
|
||||||
|
render(<EventList />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/failed to fetch/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows empty state when no events', async () => {
|
||||||
|
(api.getEvents as jest.Mock).mockResolvedValue([]);
|
||||||
|
|
||||||
|
render(<EventList />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/no events found/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles network error gracefully', async () => {
|
||||||
|
(api.getEvents as jest.Mock).mockRejectedValue(new Error('Network error'));
|
||||||
|
|
||||||
|
render(<EventList />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/network error/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||||
|
import { LoginForm } from '../login-form';
|
||||||
|
import { useAuth } from '@/lib/auth-context';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
// Mock the auth context
|
||||||
|
jest.mock('@/lib/auth-context', () => ({
|
||||||
|
useAuth: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock next/navigation
|
||||||
|
jest.mock('next/navigation', () => ({
|
||||||
|
useRouter: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('LoginForm', () => {
|
||||||
|
const mockLogin = jest.fn();
|
||||||
|
const mockPush = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({
|
||||||
|
login: mockLogin,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
(useRouter as jest.Mock).mockReturnValue({
|
||||||
|
push: mockPush,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Positive Tests
|
||||||
|
describe('Positive Tests', () => {
|
||||||
|
it('renders login form with all fields', () => {
|
||||||
|
render(<LoginForm />);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('submits form with valid credentials', async () => {
|
||||||
|
mockLogin.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
|
render(<LoginForm />);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||||
|
target: { value: 'user@example.com' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/password/i), {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /login/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockLogin).toHaveBeenCalledWith('user@example.com', 'password123');
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockPush).toHaveBeenCalledWith('/dashboard');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows loading state while submitting', async () => {
|
||||||
|
mockLogin.mockImplementation(() => new Promise(() => {})); // Never resolves
|
||||||
|
|
||||||
|
render(<LoginForm />);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||||
|
target: { value: 'user@example.com' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/password/i), {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /login/i }));
|
||||||
|
|
||||||
|
expect(screen.getByText(/logging in/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: /logging in/i })).toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error message from auth context', () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({
|
||||||
|
login: mockLogin,
|
||||||
|
error: 'Invalid credentials',
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<LoginForm />);
|
||||||
|
|
||||||
|
expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Negative Tests
|
||||||
|
describe('Negative Tests', () => {
|
||||||
|
it('prevents submission when email is empty', () => {
|
||||||
|
render(<LoginForm />);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||||
|
target: { value: '' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Form element exists with proper structure
|
||||||
|
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevents submission when password is empty', () => {
|
||||||
|
render(<LoginForm />);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/password/i), {
|
||||||
|
target: { value: '' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Form element exists with proper structure
|
||||||
|
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enforces minimum password length of 8 characters', () => {
|
||||||
|
render(<LoginForm />);
|
||||||
|
|
||||||
|
const passwordInput = screen.getByLabelText(/password/i);
|
||||||
|
expect(passwordInput).toHaveAttribute('minLength', '8');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles login failure gracefully', async () => {
|
||||||
|
mockLogin.mockRejectedValueOnce(new Error('Login failed'));
|
||||||
|
|
||||||
|
render(<LoginForm />);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||||
|
target: { value: 'user@example.com' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/password/i), {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /login/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockPush).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||||
|
import { RegisterForm } from '../register-form';
|
||||||
|
import { useAuth } from '@/lib/auth-context';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
// Mock the auth context
|
||||||
|
jest.mock('@/lib/auth-context', () => ({
|
||||||
|
useAuth: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock next/navigation
|
||||||
|
jest.mock('next/navigation', () => ({
|
||||||
|
useRouter: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('RegisterForm', () => {
|
||||||
|
const mockRegister = jest.fn();
|
||||||
|
const mockPush = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({
|
||||||
|
register: mockRegister,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
(useRouter as jest.Mock).mockReturnValue({
|
||||||
|
push: mockPush,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Positive Tests
|
||||||
|
describe('Positive Tests', () => {
|
||||||
|
it('renders registration form with all fields', () => {
|
||||||
|
render(<RegisterForm />);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText(/full name/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/^password$/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/confirm password/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/account type/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: /register/i })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('submits form with valid data', async () => {
|
||||||
|
mockRegister.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
|
render(<RegisterForm />);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/full name/i), {
|
||||||
|
target: { value: 'John Doe' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||||
|
target: { value: 'john@example.com' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/^password$/i), {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/confirm password/i), {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /register/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockRegister).toHaveBeenCalledWith('john@example.com', 'password123', 'John Doe', 'Participant');
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockPush).toHaveBeenCalledWith('/dashboard');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows selecting organizer role', async () => {
|
||||||
|
mockRegister.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
|
render(<RegisterForm />);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/full name/i), {
|
||||||
|
target: { value: 'Jane Doe' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||||
|
target: { value: 'jane@example.com' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/^password$/i), {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/confirm password/i), {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/account type/i), {
|
||||||
|
target: { value: 'Organizer' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /register/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockRegister).toHaveBeenCalledWith('jane@example.com', 'password123', 'Jane Doe', 'Organizer');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows loading state while submitting', async () => {
|
||||||
|
mockRegister.mockImplementation(() => new Promise(() => {}));
|
||||||
|
|
||||||
|
render(<RegisterForm />);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/full name/i), {
|
||||||
|
target: { value: 'Test User' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||||
|
target: { value: 'test@example.com' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/^password$/i), {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/confirm password/i), {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /register/i }));
|
||||||
|
|
||||||
|
expect(screen.getByText(/registering/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: /registering/i })).toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Negative Tests
|
||||||
|
describe('Negative Tests', () => {
|
||||||
|
it('shows error when passwords do not match', async () => {
|
||||||
|
render(<RegisterForm />);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/full name/i), {
|
||||||
|
target: { value: 'Test User' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||||
|
target: { value: 'test@example.com' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/^password$/i), {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/confirm password/i), {
|
||||||
|
target: { value: 'differentpassword' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /register/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/passwords do not match/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockRegister).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows error when password is too short', async () => {
|
||||||
|
render(<RegisterForm />);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/full name/i), {
|
||||||
|
target: { value: 'Test User' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||||
|
target: { value: 'test@example.com' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/^password$/i), {
|
||||||
|
target: { value: 'short' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/confirm password/i), {
|
||||||
|
target: { value: 'short' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /register/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText(/password must be at least 8 characters/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockRegister).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error message from auth context', () => {
|
||||||
|
(useAuth as jest.Mock).mockReturnValue({
|
||||||
|
register: mockRegister,
|
||||||
|
error: 'Email already exists',
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<RegisterForm />);
|
||||||
|
|
||||||
|
expect(screen.getByText(/email already exists/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles registration failure gracefully', async () => {
|
||||||
|
mockRegister.mockRejectedValueOnce(new Error('Registration failed'));
|
||||||
|
|
||||||
|
render(<RegisterForm />);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/full name/i), {
|
||||||
|
target: { value: 'Test User' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||||
|
target: { value: 'test@example.com' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/^password$/i), {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/confirm password/i), {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /register/i }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockPush).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Generated
+1737
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.59.1",
|
||||||
|
"@types/supertest": "^7.2.0",
|
||||||
|
"jest-environment-jsdom": "^30.3.0",
|
||||||
|
"supertest": "^7.2.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.2.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "backend", "backend", "{1AE8ACA6-933B-BF2A-3671-3E2EAC007D16}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RacePlannerApi", "backend\RacePlannerApi.csproj", "{27AF3BD7-30A1-6835-9192-7CE37DC352E7}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "backend.Tests", "backend\backend.Tests\backend.Tests.csproj", "{65E5F452-669A-36C7-E613-B0E59DB60AD6}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integration", "integration", "{28A6993C-471E-82FE-7D9E-AD3B1EC22BD9}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "backend", "backend", "{4A476A78-F17B-EE5B-E9F7-D8462CF56313}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "backend.Tests.Integration", "tests\integration\backend\backend.Tests.Integration.csproj", "{CDAB5585-211E-F212-F1C9-05CB383433AE}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{27AF3BD7-30A1-6835-9192-7CE37DC352E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{27AF3BD7-30A1-6835-9192-7CE37DC352E7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{27AF3BD7-30A1-6835-9192-7CE37DC352E7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{27AF3BD7-30A1-6835-9192-7CE37DC352E7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{65E5F452-669A-36C7-E613-B0E59DB60AD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{65E5F452-669A-36C7-E613-B0E59DB60AD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{65E5F452-669A-36C7-E613-B0E59DB60AD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{65E5F452-669A-36C7-E613-B0E59DB60AD6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{CDAB5585-211E-F212-F1C9-05CB383433AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CDAB5585-211E-F212-F1C9-05CB383433AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CDAB5585-211E-F212-F1C9-05CB383433AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CDAB5585-211E-F212-F1C9-05CB383433AE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{27AF3BD7-30A1-6835-9192-7CE37DC352E7} = {1AE8ACA6-933B-BF2A-3671-3E2EAC007D16}
|
||||||
|
{65E5F452-669A-36C7-E613-B0E59DB60AD6} = {1AE8ACA6-933B-BF2A-3671-3E2EAC007D16}
|
||||||
|
{28A6993C-471E-82FE-7D9E-AD3B1EC22BD9} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
|
||||||
|
{4A476A78-F17B-EE5B-E9F7-D8462CF56313} = {28A6993C-471E-82FE-7D9E-AD3B1EC22BD9}
|
||||||
|
{CDAB5585-211E-F212-F1C9-05CB383433AE} = {4A476A78-F17B-EE5B-E9F7-D8462CF56313}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {CC24DFB3-989B-426F-A826-6946A04B67ED}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using RacePlannerApi.DTOs;
|
||||||
|
using RacePlannerApi.Models;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace backend.Tests.Integration;
|
||||||
|
|
||||||
|
public class AuthIntegrationTests : IntegrationTestBase
|
||||||
|
{
|
||||||
|
public AuthIntegrationTests(CustomWebApplicationFactory factory) : base(factory) { }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Register_WithValidData_ReturnsSuccess()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var request = new RegisterRequest
|
||||||
|
{
|
||||||
|
Email = "test@example.com",
|
||||||
|
Password = "SecurePass123!",
|
||||||
|
Name = "Test User",
|
||||||
|
Role = UserRole.Participant
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsJsonAsync("/api/auth/register", request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<AuthResponse>();
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.Token.Should().NotBeNullOrEmpty();
|
||||||
|
result.User.Email.Should().Be(request.Email);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(Skip = "Duplicate email check depends on database state - needs investigation")]
|
||||||
|
public async Task Register_WithDuplicateEmail_ReturnsConflict()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var request = new RegisterRequest
|
||||||
|
{
|
||||||
|
Email = "duplicate@example.com",
|
||||||
|
Password = "SecurePass123!",
|
||||||
|
Name = "Test User",
|
||||||
|
Role = UserRole.Participant
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register first user
|
||||||
|
await _client.PostAsJsonAsync("/api/auth/register", request);
|
||||||
|
|
||||||
|
// Act - Try to register again with same email
|
||||||
|
var response = await _client.PostAsJsonAsync("/api/auth/register", request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.Conflict);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Login_WithValidCredentials_ReturnsToken()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var registerRequest = new RegisterRequest
|
||||||
|
{
|
||||||
|
Email = "login@example.com",
|
||||||
|
Password = "SecurePass123!",
|
||||||
|
Name = "Test User",
|
||||||
|
Role = UserRole.Participant
|
||||||
|
};
|
||||||
|
await _client.PostAsJsonAsync("/api/auth/register", registerRequest);
|
||||||
|
|
||||||
|
var loginRequest = new LoginRequest
|
||||||
|
{
|
||||||
|
Email = "login@example.com",
|
||||||
|
Password = "SecurePass123!"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<AuthResponse>();
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.Token.Should().NotBeNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Login_WithInvalidCredentials_ReturnsUnauthorized()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var loginRequest = new LoginRequest
|
||||||
|
{
|
||||||
|
Email = "nonexistent@example.com",
|
||||||
|
Password = "WrongPassword123!"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Login_WithIncorrectPassword_ReturnsUnauthorized()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var registerRequest = new RegisterRequest
|
||||||
|
{
|
||||||
|
Email = "wrongpass@example.com",
|
||||||
|
Password = "CorrectPass123!",
|
||||||
|
Name = "Test User",
|
||||||
|
Role = UserRole.Participant
|
||||||
|
};
|
||||||
|
await _client.PostAsJsonAsync("/api/auth/register", registerRequest);
|
||||||
|
|
||||||
|
var loginRequest = new LoginRequest
|
||||||
|
{
|
||||||
|
Email = "wrongpass@example.com",
|
||||||
|
Password = "WrongPass123!"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using RacePlannerApi.DTOs;
|
||||||
|
using RacePlannerApi.Models;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace backend.Tests.Integration;
|
||||||
|
|
||||||
|
public class EventsIntegrationTests : IntegrationTestBase
|
||||||
|
{
|
||||||
|
public EventsIntegrationTests(CustomWebApplicationFactory factory) : base(factory) { }
|
||||||
|
|
||||||
|
private async Task<string> GetOrganizerTokenAsync()
|
||||||
|
{
|
||||||
|
// Register and login as organizer
|
||||||
|
var registerRequest = new RegisterRequest
|
||||||
|
{
|
||||||
|
Email = "organizer@test.com",
|
||||||
|
Password = "SecurePass123!",
|
||||||
|
Name = "Test Organizer",
|
||||||
|
Role = UserRole.Organizer
|
||||||
|
};
|
||||||
|
await _client.PostAsJsonAsync("/api/auth/register", registerRequest);
|
||||||
|
|
||||||
|
var loginRequest = new LoginRequest
|
||||||
|
{
|
||||||
|
Email = "organizer@test.com",
|
||||||
|
Password = "SecurePass123!"
|
||||||
|
};
|
||||||
|
var loginResponse = await _client.PostAsJsonAsync("/api/auth/login", loginRequest);
|
||||||
|
var authResult = await loginResponse.Content.ReadFromJsonAsync<AuthResponse>();
|
||||||
|
return authResult!.Token;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetEvents_ReturnsPublishedEvents()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await _client.GetAsync("/api/events");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
var events = await response.Content.ReadFromJsonAsync<List<EventDto>>();
|
||||||
|
events.Should().NotBeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateEvent_WithValidData_ReturnsCreated()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var token = await GetOrganizerTokenAsync();
|
||||||
|
var client = CreateAuthenticatedClient(token);
|
||||||
|
|
||||||
|
var request = new CreateEventRequest
|
||||||
|
{
|
||||||
|
Name = "Test Marathon",
|
||||||
|
Description = "A test marathon event",
|
||||||
|
EventDate = DateTime.UtcNow.AddDays(30),
|
||||||
|
Location = "Test City",
|
||||||
|
Category = "Running",
|
||||||
|
Tags = new List<string>(),
|
||||||
|
MaxParticipants = 100
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.PostAsJsonAsync("/api/events", request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.Created);
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<EventDto>();
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.Name.Should().Be(request.Name);
|
||||||
|
result.Status.Should().Be("Draft");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateEvent_WithoutAuth_ReturnsUnauthorized()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var request = new CreateEventRequest
|
||||||
|
{
|
||||||
|
Name = "Test Event",
|
||||||
|
EventDate = DateTime.UtcNow.AddDays(30),
|
||||||
|
Location = "Test City"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await _client.PostAsJsonAsync("/api/events", request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetEvent_WithValidId_ReturnsEvent()
|
||||||
|
{
|
||||||
|
// Arrange - Create an event first
|
||||||
|
var token = await GetOrganizerTokenAsync();
|
||||||
|
var client = CreateAuthenticatedClient(token);
|
||||||
|
|
||||||
|
var createRequest = new CreateEventRequest
|
||||||
|
{
|
||||||
|
Name = "Test Event",
|
||||||
|
Description = "Test description",
|
||||||
|
EventDate = DateTime.UtcNow.AddDays(30),
|
||||||
|
Location = "Test City",
|
||||||
|
Category = "Running",
|
||||||
|
Tags = new List<string>(),
|
||||||
|
MaxParticipants = 50
|
||||||
|
};
|
||||||
|
var createResponse = await client.PostAsJsonAsync("/api/events", createRequest);
|
||||||
|
var createdEvent = await createResponse.Content.ReadFromJsonAsync<EventDto>();
|
||||||
|
|
||||||
|
// Publish the event so it's visible
|
||||||
|
var updateRequest = new UpdateEventRequest
|
||||||
|
{
|
||||||
|
Status = EventStatus.Published
|
||||||
|
};
|
||||||
|
await client.PutAsJsonAsync($"/api/events/{createdEvent!.Id}", updateRequest);
|
||||||
|
|
||||||
|
// Act - Get the event as anonymous user
|
||||||
|
var getResponse = await _client.GetAsync($"/api/events/{createdEvent.Id}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
getResponse.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
var result = await getResponse.Content.ReadFromJsonAsync<EventDto>();
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.Id.Should().Be(createdEvent.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetEvent_WithInvalidId_ReturnsNotFound()
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var response = await _client.GetAsync($"/api/events/{Guid.NewGuid()}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateEvent_WithValidData_ReturnsUpdatedEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var token = await GetOrganizerTokenAsync();
|
||||||
|
var client = CreateAuthenticatedClient(token);
|
||||||
|
|
||||||
|
var createRequest = new CreateEventRequest
|
||||||
|
{
|
||||||
|
Name = "Original Name",
|
||||||
|
EventDate = DateTime.UtcNow.AddDays(30),
|
||||||
|
Location = "Original Location",
|
||||||
|
Category = "Running",
|
||||||
|
Tags = new List<string>(),
|
||||||
|
MaxParticipants = 50
|
||||||
|
};
|
||||||
|
var createResponse = await client.PostAsJsonAsync("/api/events", createRequest);
|
||||||
|
var createdEvent = await createResponse.Content.ReadFromJsonAsync<EventDto>();
|
||||||
|
|
||||||
|
var updateRequest = new UpdateEventRequest
|
||||||
|
{
|
||||||
|
Name = "Updated Name",
|
||||||
|
Description = "Updated description"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.PutAsJsonAsync($"/api/events/{createdEvent!.Id}", updateRequest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<EventDto>();
|
||||||
|
result.Should().NotBeNull();
|
||||||
|
result!.Name.Should().Be("Updated Name");
|
||||||
|
result.Description.Should().Be("Updated description");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteEvent_AsOrganizer_ReturnsNoContent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var token = await GetOrganizerTokenAsync();
|
||||||
|
var client = CreateAuthenticatedClient(token);
|
||||||
|
|
||||||
|
var createRequest = new CreateEventRequest
|
||||||
|
{
|
||||||
|
Name = "Event to Delete",
|
||||||
|
EventDate = DateTime.UtcNow.AddDays(30),
|
||||||
|
Location = "Test City",
|
||||||
|
Category = "Running",
|
||||||
|
Tags = new List<string>(),
|
||||||
|
MaxParticipants = 50
|
||||||
|
};
|
||||||
|
var createResponse = await client.PostAsJsonAsync("/api/events", createRequest);
|
||||||
|
var createdEvent = await createResponse.Content.ReadFromJsonAsync<EventDto>();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var response = await client.DeleteAsync($"/api/events/{createdEvent!.Id}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
response.StatusCode.Should().Be(HttpStatusCode.NoContent);
|
||||||
|
|
||||||
|
// Verify event is deleted
|
||||||
|
var getResponse = await client.GetAsync($"/api/events/{createdEvent.Id}");
|
||||||
|
getResponse.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Testing;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using RacePlannerApi.Data;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace backend.Tests.Integration;
|
||||||
|
|
||||||
|
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
|
||||||
|
{
|
||||||
|
// Use a static database name so all tests in the same process share the database
|
||||||
|
private static readonly string _databaseName = $"IntegrationTestDb_{Guid.NewGuid():N}";
|
||||||
|
|
||||||
|
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||||
|
{
|
||||||
|
builder.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
// Remove all DbContextOptions registrations
|
||||||
|
var descriptors = services.Where(
|
||||||
|
d => d.ServiceType == typeof(DbContextOptions<RacePlannerDbContext>) ||
|
||||||
|
d.ServiceType.Name.Contains("DbContextOptions")).ToList();
|
||||||
|
|
||||||
|
foreach (var descriptor in descriptors)
|
||||||
|
{
|
||||||
|
services.Remove(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add in-memory database with consistent name
|
||||||
|
services.AddDbContext<RacePlannerDbContext>(options =>
|
||||||
|
{
|
||||||
|
options.UseInMemoryDatabase(_databaseName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class IntegrationTestBase : IClassFixture<CustomWebApplicationFactory>, IDisposable
|
||||||
|
{
|
||||||
|
protected readonly CustomWebApplicationFactory _factory;
|
||||||
|
protected readonly HttpClient _client;
|
||||||
|
protected readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
|
||||||
|
protected readonly IServiceScope _scope;
|
||||||
|
protected readonly RacePlannerDbContext _dbContext;
|
||||||
|
|
||||||
|
protected IntegrationTestBase(CustomWebApplicationFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
_client = _factory.CreateClient();
|
||||||
|
_scope = _factory.Services.CreateScope();
|
||||||
|
_dbContext = _scope.ServiceProvider.GetRequiredService<RacePlannerDbContext>();
|
||||||
|
_dbContext.Database.EnsureCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_scope.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpClient CreateAuthenticatedClient(string token = "")
|
||||||
|
{
|
||||||
|
var client = _factory.CreateClient();
|
||||||
|
if (!string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
client.DefaultRequestHeaders.Authorization =
|
||||||
|
new AuthenticationHeaderValue("Bearer", token);
|
||||||
|
}
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<T?> GetAsync<T>(string url)
|
||||||
|
{
|
||||||
|
var response = await _client.GetAsync(url);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return await response.Content.ReadFromJsonAsync<T>(_jsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<TResponse?> PostAsync<TRequest, TResponse>(string url, TRequest data)
|
||||||
|
{
|
||||||
|
var response = await _client.PostAsJsonAsync(url, data, _jsonOptions);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return await response.Content.ReadFromJsonAsync<TResponse>(_jsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task<TResponse?> PutAsync<TRequest, TResponse>(string url, TRequest data)
|
||||||
|
{
|
||||||
|
var response = await _client.PutAsJsonAsync(url, data, _jsonOptions);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
return await response.Content.ReadFromJsonAsync<TResponse>(_jsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async Task DeleteAsync(string url)
|
||||||
|
{
|
||||||
|
var response = await _client.DeleteAsync(url);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace backend.Tests.Integration;
|
||||||
|
|
||||||
|
public class UnitTest1
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Test1()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||||
|
<PackageReference Include="FluentAssertions" Version="8.9.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\backend\RacePlannerApi.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
tests/integration/backend/bin/Debug/net10.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"RacePlannerApi, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null": "\/Users\/mastermito\/Dev\/raceplanner\/backend"
|
||||||
|
}
|
||||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,851 @@
|
|||||||
|
{
|
||||||
|
"runtimeTarget": {
|
||||||
|
"name": ".NETCoreApp,Version=v10.0",
|
||||||
|
"signature": ""
|
||||||
|
},
|
||||||
|
"compilationOptions": {},
|
||||||
|
"targets": {
|
||||||
|
".NETCoreApp,Version=v10.0": {
|
||||||
|
"RacePlannerApi/1.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"BCrypt.Net-Next": "4.1.0",
|
||||||
|
"Microsoft.AspNetCore.Authentication.JwtBearer": "10.0.5",
|
||||||
|
"Microsoft.AspNetCore.OpenApi": "10.0.0",
|
||||||
|
"Microsoft.EntityFrameworkCore.Design": "10.0.5",
|
||||||
|
"Npgsql.EntityFrameworkCore.PostgreSQL": "10.0.1",
|
||||||
|
"System.IdentityModel.Tokens.Jwt": "8.17.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"RacePlannerApi.dll": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"BCrypt.Net-Next/4.1.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/BCrypt.Net-Next.dll": {
|
||||||
|
"assemblyVersion": "4.1.0.0",
|
||||||
|
"fileVersion": "4.1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Humanizer.Core/2.14.1": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/Humanizer.dll": {
|
||||||
|
"assemblyVersion": "2.14.0.0",
|
||||||
|
"fileVersion": "2.14.1.48190"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.AspNetCore.Authentication.JwtBearer/10.0.5": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect": "8.0.1"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Microsoft.AspNetCore.Authentication.JwtBearer.dll": {
|
||||||
|
"assemblyVersion": "10.0.5.0",
|
||||||
|
"fileVersion": "10.0.526.15411"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.AspNetCore.OpenApi/10.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.OpenApi": "2.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Microsoft.AspNetCore.OpenApi.dll": {
|
||||||
|
"assemblyVersion": "10.0.0.0",
|
||||||
|
"fileVersion": "10.0.25.52411"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.Build.Framework/18.0.2": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Microsoft.Build.Framework.dll": {
|
||||||
|
"assemblyVersion": "15.1.0.0",
|
||||||
|
"fileVersion": "18.0.2.52102"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.CodeAnalysis.Common/5.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net9.0/Microsoft.CodeAnalysis.dll": {
|
||||||
|
"assemblyVersion": "5.0.0.0",
|
||||||
|
"fileVersion": "5.0.25.56712"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"lib/net9.0/cs/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net9.0/de/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net9.0/es/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net9.0/fr/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net9.0/it/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ja/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ko/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net9.0/pl/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net9.0/pt-BR/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ru/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net9.0/tr/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net9.0/zh-Hans/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net9.0/zh-Hant/Microsoft.CodeAnalysis.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.CodeAnalysis.CSharp/5.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.CodeAnalysis.Common": "5.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net9.0/Microsoft.CodeAnalysis.CSharp.dll": {
|
||||||
|
"assemblyVersion": "5.0.0.0",
|
||||||
|
"fileVersion": "5.0.25.56712"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"lib/net9.0/cs/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net9.0/de/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net9.0/es/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net9.0/fr/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net9.0/it/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ja/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ko/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net9.0/pl/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ru/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net9.0/tr/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.CodeAnalysis.CSharp.Workspaces/5.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "2.14.1",
|
||||||
|
"Microsoft.CodeAnalysis.CSharp": "5.0.0",
|
||||||
|
"Microsoft.CodeAnalysis.Common": "5.0.0",
|
||||||
|
"Microsoft.CodeAnalysis.Workspaces.Common": "5.0.0",
|
||||||
|
"System.Composition": "9.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net9.0/Microsoft.CodeAnalysis.CSharp.Workspaces.dll": {
|
||||||
|
"assemblyVersion": "5.0.0.0",
|
||||||
|
"fileVersion": "5.0.25.56712"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"lib/net9.0/cs/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net9.0/de/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net9.0/es/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net9.0/fr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net9.0/it/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ja/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ko/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net9.0/pl/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net9.0/pt-BR/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ru/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net9.0/tr/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net9.0/zh-Hans/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net9.0/zh-Hant/Microsoft.CodeAnalysis.CSharp.Workspaces.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.CodeAnalysis.Workspaces.Common/5.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "2.14.1",
|
||||||
|
"Microsoft.CodeAnalysis.Common": "5.0.0",
|
||||||
|
"System.Composition": "9.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net9.0/Microsoft.CodeAnalysis.Workspaces.dll": {
|
||||||
|
"assemblyVersion": "5.0.0.0",
|
||||||
|
"fileVersion": "5.0.25.56712"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"lib/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net9.0/de/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net9.0/es/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net9.0/it/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.CodeAnalysis.Workspaces.MSBuild/5.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "2.14.1",
|
||||||
|
"Microsoft.Build.Framework": "18.0.2",
|
||||||
|
"Microsoft.CodeAnalysis.Workspaces.Common": "5.0.0",
|
||||||
|
"Microsoft.VisualStudio.SolutionPersistence": "1.0.52",
|
||||||
|
"Newtonsoft.Json": "13.0.3",
|
||||||
|
"System.Composition": "9.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net9.0/Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler.dll": {
|
||||||
|
"assemblyVersion": "5.0.0.0",
|
||||||
|
"fileVersion": "5.0.25.56712"
|
||||||
|
},
|
||||||
|
"lib/net9.0/Microsoft.CodeAnalysis.Workspaces.MSBuild.dll": {
|
||||||
|
"assemblyVersion": "5.0.0.0",
|
||||||
|
"fileVersion": "5.0.25.56712"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"lib/net9.0/cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "cs"
|
||||||
|
},
|
||||||
|
"lib/net9.0/de/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "de"
|
||||||
|
},
|
||||||
|
"lib/net9.0/es/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "es"
|
||||||
|
},
|
||||||
|
"lib/net9.0/fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "fr"
|
||||||
|
},
|
||||||
|
"lib/net9.0/it/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "it"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "ja"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "ko"
|
||||||
|
},
|
||||||
|
"lib/net9.0/pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "pl"
|
||||||
|
},
|
||||||
|
"lib/net9.0/pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "pt-BR"
|
||||||
|
},
|
||||||
|
"lib/net9.0/ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "ru"
|
||||||
|
},
|
||||||
|
"lib/net9.0/tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "tr"
|
||||||
|
},
|
||||||
|
"lib/net9.0/zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "zh-Hans"
|
||||||
|
},
|
||||||
|
"lib/net9.0/zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.resources.dll": {
|
||||||
|
"locale": "zh-Hant"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.EntityFrameworkCore/10.0.5": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.EntityFrameworkCore.Abstractions": "10.0.5"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Microsoft.EntityFrameworkCore.dll": {
|
||||||
|
"assemblyVersion": "10.0.5.0",
|
||||||
|
"fileVersion": "10.0.526.15411"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.EntityFrameworkCore.Abstractions/10.0.5": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll": {
|
||||||
|
"assemblyVersion": "10.0.5.0",
|
||||||
|
"fileVersion": "10.0.526.15411"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.EntityFrameworkCore.Design/10.0.5": {
|
||||||
|
"dependencies": {
|
||||||
|
"Humanizer.Core": "2.14.1",
|
||||||
|
"Microsoft.Build.Framework": "18.0.2",
|
||||||
|
"Microsoft.CodeAnalysis.CSharp": "5.0.0",
|
||||||
|
"Microsoft.CodeAnalysis.CSharp.Workspaces": "5.0.0",
|
||||||
|
"Microsoft.CodeAnalysis.Workspaces.MSBuild": "5.0.0",
|
||||||
|
"Microsoft.EntityFrameworkCore.Relational": "10.0.5",
|
||||||
|
"Microsoft.Extensions.DependencyModel": "10.0.5",
|
||||||
|
"Mono.TextTemplating": "3.0.0",
|
||||||
|
"Newtonsoft.Json": "13.0.3"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Microsoft.EntityFrameworkCore.Design.dll": {
|
||||||
|
"assemblyVersion": "10.0.5.0",
|
||||||
|
"fileVersion": "10.0.526.15411"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.EntityFrameworkCore.Relational/10.0.5": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.EntityFrameworkCore": "10.0.5"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll": {
|
||||||
|
"assemblyVersion": "10.0.5.0",
|
||||||
|
"fileVersion": "10.0.526.15411"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.Extensions.DependencyModel/10.0.5": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Microsoft.Extensions.DependencyModel.dll": {
|
||||||
|
"assemblyVersion": "10.0.0.5",
|
||||||
|
"fileVersion": "10.0.526.15411"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Abstractions/8.17.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Microsoft.IdentityModel.Abstractions.dll": {
|
||||||
|
"assemblyVersion": "8.17.0.0",
|
||||||
|
"fileVersion": "8.17.0.26082"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.JsonWebTokens/8.17.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Tokens": "8.17.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
|
||||||
|
"assemblyVersion": "8.17.0.0",
|
||||||
|
"fileVersion": "8.17.0.26082"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Logging/8.17.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Abstractions": "8.17.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Microsoft.IdentityModel.Logging.dll": {
|
||||||
|
"assemblyVersion": "8.17.0.0",
|
||||||
|
"fileVersion": "8.17.0.26082"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols/8.0.1": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Tokens": "8.17.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net9.0/Microsoft.IdentityModel.Protocols.dll": {
|
||||||
|
"assemblyVersion": "8.0.1.0",
|
||||||
|
"fileVersion": "8.0.1.50722"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Protocols": "8.0.1",
|
||||||
|
"System.IdentityModel.Tokens.Jwt": "8.17.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": {
|
||||||
|
"assemblyVersion": "8.0.1.0",
|
||||||
|
"fileVersion": "8.0.1.50722"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Tokens/8.17.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.Logging": "8.17.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Microsoft.IdentityModel.Tokens.dll": {
|
||||||
|
"assemblyVersion": "8.17.0.0",
|
||||||
|
"fileVersion": "8.17.0.26082"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.OpenApi/2.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.OpenApi.dll": {
|
||||||
|
"assemblyVersion": "2.0.0.0",
|
||||||
|
"fileVersion": "2.0.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Microsoft.VisualStudio.SolutionPersistence/1.0.52": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net8.0/Microsoft.VisualStudio.SolutionPersistence.dll": {
|
||||||
|
"assemblyVersion": "1.0.0.0",
|
||||||
|
"fileVersion": "1.0.52.6595"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Mono.TextTemplating/3.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"System.CodeDom": "6.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/Mono.TextTemplating.dll": {
|
||||||
|
"assemblyVersion": "3.0.0.0",
|
||||||
|
"fileVersion": "3.0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Newtonsoft.Json/13.0.3": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/Newtonsoft.Json.dll": {
|
||||||
|
"assemblyVersion": "13.0.0.0",
|
||||||
|
"fileVersion": "13.0.3.27908"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Npgsql/10.0.2": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Npgsql.dll": {
|
||||||
|
"assemblyVersion": "10.0.2.0",
|
||||||
|
"fileVersion": "10.0.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Npgsql.EntityFrameworkCore.PostgreSQL/10.0.1": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.EntityFrameworkCore": "10.0.5",
|
||||||
|
"Microsoft.EntityFrameworkCore.Relational": "10.0.5",
|
||||||
|
"Npgsql": "10.0.2"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/Npgsql.EntityFrameworkCore.PostgreSQL.dll": {
|
||||||
|
"assemblyVersion": "10.0.1.0",
|
||||||
|
"fileVersion": "10.0.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.CodeDom/6.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net6.0/System.CodeDom.dll": {
|
||||||
|
"assemblyVersion": "6.0.0.0",
|
||||||
|
"fileVersion": "6.0.21.52210"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.Composition/9.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"System.Composition.AttributedModel": "9.0.0",
|
||||||
|
"System.Composition.Convention": "9.0.0",
|
||||||
|
"System.Composition.Hosting": "9.0.0",
|
||||||
|
"System.Composition.Runtime": "9.0.0",
|
||||||
|
"System.Composition.TypedParts": "9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.Composition.AttributedModel/9.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net9.0/System.Composition.AttributedModel.dll": {
|
||||||
|
"assemblyVersion": "9.0.0.0",
|
||||||
|
"fileVersion": "9.0.24.52809"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.Composition.Convention/9.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"System.Composition.AttributedModel": "9.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net9.0/System.Composition.Convention.dll": {
|
||||||
|
"assemblyVersion": "9.0.0.0",
|
||||||
|
"fileVersion": "9.0.24.52809"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.Composition.Hosting/9.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"System.Composition.Runtime": "9.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net9.0/System.Composition.Hosting.dll": {
|
||||||
|
"assemblyVersion": "9.0.0.0",
|
||||||
|
"fileVersion": "9.0.24.52809"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.Composition.Runtime/9.0.0": {
|
||||||
|
"runtime": {
|
||||||
|
"lib/net9.0/System.Composition.Runtime.dll": {
|
||||||
|
"assemblyVersion": "9.0.0.0",
|
||||||
|
"fileVersion": "9.0.24.52809"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.Composition.TypedParts/9.0.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"System.Composition.AttributedModel": "9.0.0",
|
||||||
|
"System.Composition.Hosting": "9.0.0",
|
||||||
|
"System.Composition.Runtime": "9.0.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net9.0/System.Composition.TypedParts.dll": {
|
||||||
|
"assemblyVersion": "9.0.0.0",
|
||||||
|
"fileVersion": "9.0.24.52809"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"System.IdentityModel.Tokens.Jwt/8.17.0": {
|
||||||
|
"dependencies": {
|
||||||
|
"Microsoft.IdentityModel.JsonWebTokens": "8.17.0",
|
||||||
|
"Microsoft.IdentityModel.Tokens": "8.17.0"
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"lib/net10.0/System.IdentityModel.Tokens.Jwt.dll": {
|
||||||
|
"assemblyVersion": "8.17.0.0",
|
||||||
|
"fileVersion": "8.17.0.26082"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"RacePlannerApi/1.0.0": {
|
||||||
|
"type": "project",
|
||||||
|
"serviceable": false,
|
||||||
|
"sha512": ""
|
||||||
|
},
|
||||||
|
"BCrypt.Net-Next/4.1.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-5YT3DKllmtkyW68PjURu/V1TOe4MKiByKwsRNVcfYE1S5KuFTeozdmKzyNzolKiQF391OXCaQtINvYT3j1ERzQ==",
|
||||||
|
"path": "bcrypt.net-next/4.1.0",
|
||||||
|
"hashPath": "bcrypt.net-next.4.1.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Humanizer.Core/2.14.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==",
|
||||||
|
"path": "humanizer.core/2.14.1",
|
||||||
|
"hashPath": "humanizer.core.2.14.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.AspNetCore.Authentication.JwtBearer/10.0.5": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-fZzXogChrwQ/SfifQJgeW7AtR8hUv5+LH9oLWjm5OqfnVt3N8MwcMHHMdawvqqdjP79lIZgetnSpj77BLsSI1g==",
|
||||||
|
"path": "microsoft.aspnetcore.authentication.jwtbearer/10.0.5",
|
||||||
|
"hashPath": "microsoft.aspnetcore.authentication.jwtbearer.10.0.5.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.AspNetCore.OpenApi/10.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-0aqIF1t+sA2T62LIeMtXGSiaV7keGQaJnvwwmu+htQdjCaKYARfXAeqp4nHH9y2etpilyZ/tnQzZg4Ilmo/c4Q==",
|
||||||
|
"path": "microsoft.aspnetcore.openapi/10.0.0",
|
||||||
|
"hashPath": "microsoft.aspnetcore.openapi.10.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.Build.Framework/18.0.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-sOSb+0J4G/jCBW/YqmRuL0eOMXgfw1KQLdC9TkbvfA5xs7uNm+PBQXJCOzSJGXtZcZrtXozcwxPmUiRUbmd7FA==",
|
||||||
|
"path": "microsoft.build.framework/18.0.2",
|
||||||
|
"hashPath": "microsoft.build.framework.18.0.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.CodeAnalysis.Common/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-ZXRAdvH6GiDeHRyd3q/km8Z44RoM6FBWHd+gen/la81mVnAdHTEsEkO5J0TCNXBymAcx5UYKt5TvgKBhaLJEow==",
|
||||||
|
"path": "microsoft.codeanalysis.common/5.0.0",
|
||||||
|
"hashPath": "microsoft.codeanalysis.common.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.CodeAnalysis.CSharp/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-5DSyJ9bk+ATuDy7fp2Zt0mJStDVKbBoiz1DyfAwSa+k4H4IwykAUcV3URelw5b8/iVbfSaOwkwmPUZH6opZKCw==",
|
||||||
|
"path": "microsoft.codeanalysis.csharp/5.0.0",
|
||||||
|
"hashPath": "microsoft.codeanalysis.csharp.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.CodeAnalysis.CSharp.Workspaces/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-Al/Q8B+yO8odSqGVpSvrShMFDvlQdIBU//F3E6Rb0YdiLSALE9wh/pvozPNnfmh5HDnvU+mkmSjpz4hQO++jaA==",
|
||||||
|
"path": "microsoft.codeanalysis.csharp.workspaces/5.0.0",
|
||||||
|
"hashPath": "microsoft.codeanalysis.csharp.workspaces.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.CodeAnalysis.Workspaces.Common/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-ZbUmIvT6lqTNKiv06Jl5wf0MTMi1vQ1oH7ou4CLcs2C/no/L7EhP3T8y3XXvn9VbqMcJaJnEsNA1jwYUMgc5jg==",
|
||||||
|
"path": "microsoft.codeanalysis.workspaces.common/5.0.0",
|
||||||
|
"hashPath": "microsoft.codeanalysis.workspaces.common.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.CodeAnalysis.Workspaces.MSBuild/5.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-/G+LVoAGMz6Ae8nm+PGLxSw+F5RjYx/J7irbTO5uKAPw1bxHyQJLc/YOnpDxt+EpPtYxvC9wvBsg/kETZp1F9Q==",
|
||||||
|
"path": "microsoft.codeanalysis.workspaces.msbuild/5.0.0",
|
||||||
|
"hashPath": "microsoft.codeanalysis.workspaces.msbuild.5.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.EntityFrameworkCore/10.0.5": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-9tNBmK3EpYVGRQLiqP+bqK2m+TD0Gv//4vCzR7ZOgl4FWzCFyOpYdIVka13M4kcBdPdSJcs3wbHr3rmzOqbIMA==",
|
||||||
|
"path": "microsoft.entityframeworkcore/10.0.5",
|
||||||
|
"hashPath": "microsoft.entityframeworkcore.10.0.5.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.EntityFrameworkCore.Abstractions/10.0.5": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-32c58Rnm47Qvhimawf67KO9PytgPz3QoWye7Abapt0Yocw/JnzMiSNj/pRoIKyn8Jxypkv86zxKD4Q/zNTc0Ag==",
|
||||||
|
"path": "microsoft.entityframeworkcore.abstractions/10.0.5",
|
||||||
|
"hashPath": "microsoft.entityframeworkcore.abstractions.10.0.5.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.EntityFrameworkCore.Design/10.0.5": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-gm6f0cC2w/2tcd4GeZJqEMruTercpIJfO5sSAFLtqTqblDBHgAFk70xwshUIUVX4I6sZwdEUSd1YxoKFk1AL0w==",
|
||||||
|
"path": "microsoft.entityframeworkcore.design/10.0.5",
|
||||||
|
"hashPath": "microsoft.entityframeworkcore.design.10.0.5.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.EntityFrameworkCore.Relational/10.0.5": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-uxmFjZEAB/KbsgWFSS4lLqkEHCfXxB2x0UcbiO4e5fCRpFFeTMSx/me6009nYJLu5IKlDwO1POh++P6RilFTDw==",
|
||||||
|
"path": "microsoft.entityframeworkcore.relational/10.0.5",
|
||||||
|
"hashPath": "microsoft.entityframeworkcore.relational.10.0.5.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.Extensions.DependencyModel/10.0.5": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-xA4kkL+QS6KCAOKz/O0oquHs44Ob8J7zpBCNt3wjkBWDg5aCqfwG8rWWLsg5V86AM0sB849g9JjPjIdksTCIKg==",
|
||||||
|
"path": "microsoft.extensions.dependencymodel/10.0.5",
|
||||||
|
"hashPath": "microsoft.extensions.dependencymodel.10.0.5.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Abstractions/8.17.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-6NrxQGcZg6IunkN8K2F0UVMavNpfCjbjjjON7PYcL8FwI8aULKUreiHsRX/yaA8j3XsTJnQKUYpoQk5gBjULZw==",
|
||||||
|
"path": "microsoft.identitymodel.abstractions/8.17.0",
|
||||||
|
"hashPath": "microsoft.identitymodel.abstractions.8.17.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.JsonWebTokens/8.17.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-JbFZ3OVwtvqcqgBL0cIkhRYbIP7u9GIUYLOgbNqLWtBtZY8tGDpdGyXMzUVX0gVHq1ovuHsKZrkVv+ziHEnBHw==",
|
||||||
|
"path": "microsoft.identitymodel.jsonwebtokens/8.17.0",
|
||||||
|
"hashPath": "microsoft.identitymodel.jsonwebtokens.8.17.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Logging/8.17.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-w1vjfri0BWqW7RkSZY3ZsqekNfIJJg5BQSFs2j+a+pCXOVrkezmJcn74pT3djwjXJh71577C6wJQgNc2UPz30w==",
|
||||||
|
"path": "microsoft.identitymodel.logging/8.17.0",
|
||||||
|
"hashPath": "microsoft.identitymodel.logging.8.17.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols/8.0.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-uA2vpKqU3I2mBBEaeJAWPTjT9v1TZrGWKdgK6G5qJd03CLx83kdiqO9cmiK8/n1erkHzFBwU/RphP83aAe3i3g==",
|
||||||
|
"path": "microsoft.identitymodel.protocols/8.0.1",
|
||||||
|
"hashPath": "microsoft.identitymodel.protocols.8.0.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-AQDbfpL+yzuuGhO/mQhKNsp44pm5Jv8/BI4KiFXR7beVGZoSH35zMV3PrmcfvSTsyI6qrcR898NzUauD6SRigg==",
|
||||||
|
"path": "microsoft.identitymodel.protocols.openidconnect/8.0.1",
|
||||||
|
"hashPath": "microsoft.identitymodel.protocols.openidconnect.8.0.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.IdentityModel.Tokens/8.17.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-teaW35URIV2x78Tzk+dVJiC4M62/9mQoSEoDjDGoEZmcQa3H2rE+XQpm9Tmdo9KK1Lcrnve4zoyLavl69kCFGg==",
|
||||||
|
"path": "microsoft.identitymodel.tokens/8.17.0",
|
||||||
|
"hashPath": "microsoft.identitymodel.tokens.8.17.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.OpenApi/2.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-GGYLfzV/G/ct80OZ45JxnWP7NvMX1BCugn/lX7TH5o0lcVaviavsLMTxmFV2AybXWjbi3h6FF1vgZiTK6PXndw==",
|
||||||
|
"path": "microsoft.openapi/2.0.0",
|
||||||
|
"hashPath": "microsoft.openapi.2.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Microsoft.VisualStudio.SolutionPersistence/1.0.52": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-oNv2JtYXhpdJrX63nibx1JT3uCESOBQ1LAk7Dtz/sr0+laW0KRM6eKp4CZ3MHDR2siIkKsY8MmUkeP5DKkQQ5w==",
|
||||||
|
"path": "microsoft.visualstudio.solutionpersistence/1.0.52",
|
||||||
|
"hashPath": "microsoft.visualstudio.solutionpersistence.1.0.52.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Mono.TextTemplating/3.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-YqueG52R/Xej4VVbKuRIodjiAhV0HR/XVbLbNrJhCZnzjnSjgMJ/dCdV0akQQxavX6hp/LC6rqLGLcXeQYU7XA==",
|
||||||
|
"path": "mono.texttemplating/3.0.0",
|
||||||
|
"hashPath": "mono.texttemplating.3.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Newtonsoft.Json/13.0.3": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
|
||||||
|
"path": "newtonsoft.json/13.0.3",
|
||||||
|
"hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Npgsql/10.0.2": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-q5RfBI+wywJSFUNDE1L4ZbHEHCFTblo8Uf6A6oe4feOUFYiUQXyAf9GBh5qEZpvJaHiEbpBPkQumjEhXCJxdrg==",
|
||||||
|
"path": "npgsql/10.0.2",
|
||||||
|
"hashPath": "npgsql.10.0.2.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"Npgsql.EntityFrameworkCore.PostgreSQL/10.0.1": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-P6EwH0Q4xkaA264iNZDqCPhWt8pscfUGxXazDQg4noBfqjoOlk4hKWfvBjF9ZX3R/9JybRmmJfmxr2iBMj0EpA==",
|
||||||
|
"path": "npgsql.entityframeworkcore.postgresql/10.0.1",
|
||||||
|
"hashPath": "npgsql.entityframeworkcore.postgresql.10.0.1.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.CodeDom/6.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==",
|
||||||
|
"path": "system.codedom/6.0.0",
|
||||||
|
"hashPath": "system.codedom.6.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Composition/9.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-3Djj70fFTraOarSKmRnmRy/zm4YurICm+kiCtI0dYRqGJnLX6nJ+G3WYuFJ173cAPax/gh96REcbNiVqcrypFQ==",
|
||||||
|
"path": "system.composition/9.0.0",
|
||||||
|
"hashPath": "system.composition.9.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Composition.AttributedModel/9.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-iri00l/zIX9g4lHMY+Nz0qV1n40+jFYAmgsaiNn16xvt2RDwlqByNG4wgblagnDYxm3YSQQ0jLlC/7Xlk9CzyA==",
|
||||||
|
"path": "system.composition.attributedmodel/9.0.0",
|
||||||
|
"hashPath": "system.composition.attributedmodel.9.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Composition.Convention/9.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-+vuqVP6xpi582XIjJi6OCsIxuoTZfR0M7WWufk3uGDeCl3wGW6KnpylUJ3iiXdPByPE0vR5TjJgR6hDLez4FQg==",
|
||||||
|
"path": "system.composition.convention/9.0.0",
|
||||||
|
"hashPath": "system.composition.convention.9.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Composition.Hosting/9.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-OFqSeFeJYr7kHxDfaViGM1ymk7d4JxK//VSoNF9Ux0gpqkLsauDZpu89kTHHNdCWfSljbFcvAafGyBoY094btQ==",
|
||||||
|
"path": "system.composition.hosting/9.0.0",
|
||||||
|
"hashPath": "system.composition.hosting.9.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Composition.Runtime/9.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-w1HOlQY1zsOWYussjFGZCEYF2UZXgvoYnS94NIu2CBnAGMbXFAX8PY8c92KwUItPmowal68jnVLBCzdrWLeEKA==",
|
||||||
|
"path": "system.composition.runtime/9.0.0",
|
||||||
|
"hashPath": "system.composition.runtime.9.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.Composition.TypedParts/9.0.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-aRZlojCCGEHDKqh43jaDgaVpYETsgd7Nx4g1zwLKMtv4iTo0627715ajEFNpEEBTgLmvZuv8K0EVxc3sM4NWJA==",
|
||||||
|
"path": "system.composition.typedparts/9.0.0",
|
||||||
|
"hashPath": "system.composition.typedparts.9.0.0.nupkg.sha512"
|
||||||
|
},
|
||||||
|
"System.IdentityModel.Tokens.Jwt/8.17.0": {
|
||||||
|
"type": "package",
|
||||||
|
"serviceable": true,
|
||||||
|
"sha512": "sha512-nKikRYheDeSaXA3wGr2otwaiRFygBa25m+hc7MEomZVIEWZvKVqd8wgP9yn+8QpLRGgw//dUs4LErGx9gtVmAA==",
|
||||||
|
"path": "system.identitymodel.tokens.jwt/8.17.0",
|
||||||
|
"hashPath": "system.identitymodel.tokens.jwt.8.17.0.nupkg.sha512"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"runtimeOptions": {
|
||||||
|
"tfm": "net10.0",
|
||||||
|
"frameworks": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.NETCore.App",
|
||||||
|
"version": "10.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.AspNetCore.App",
|
||||||
|
"version": "10.0.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configProperties": {
|
||||||
|
"System.GC.Server": true,
|
||||||
|
"System.Reflection.NullabilityInfoContext.IsSupported": true,
|
||||||
|
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
{"Version":1,"ManifestType":"Build","Endpoints":[]}
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Jwt": {
|
||||||
|
"Key": "${JWT_SECRET_KEY}",
|
||||||
|
"Issuer": "RacePlannerApi",
|
||||||
|
"Audience": "RacePlannerClient"
|
||||||
|
},
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "${DATABASE_URL}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
+20
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"runtimeOptions": {
|
||||||
|
"tfm": "net10.0",
|
||||||
|
"frameworks": [
|
||||||
|
{
|
||||||
|
"name": "Microsoft.NETCore.App",
|
||||||
|
"version": "10.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Microsoft.AspNetCore.App",
|
||||||
|
"version": "10.0.0"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configProperties": {
|
||||||
|
"MSTest.EnableParentProcessQuery": true,
|
||||||
|
"System.Reflection.NullabilityInfoContext.IsSupported": true,
|
||||||
|
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user