feat(signalr): Add real-time notifications for Epic/Story/Task operations
Extends SignalR notification system to cover all ProjectManagement CRUD operations:
Domain Events Created:
- EpicUpdatedEvent, EpicDeletedEvent
- StoryCreatedEvent, StoryUpdatedEvent, StoryDeletedEvent
- TaskCreatedEvent, TaskUpdatedEvent, TaskDeletedEvent, TaskAssignedEvent
Event Handlers Added (10 handlers):
- EpicCreatedEventHandler, EpicUpdatedEventHandler, EpicDeletedEventHandler
- StoryCreatedEventHandler, StoryUpdatedEventHandler, StoryDeletedEventHandler
- TaskCreatedEventHandler, TaskUpdatedEventHandler, TaskDeletedEventHandler
- TaskAssignedEventHandler
Infrastructure Extensions:
- Extended IProjectNotificationService with Epic/Story/Task methods
- Extended IRealtimeNotificationService with Epic/Story/Task methods
- Extended RealtimeNotificationService with implementations
- Extended ProjectNotificationServiceAdapter for delegation
Domain Changes:
- Updated EpicCreatedEvent to include TenantId (consistency with other events)
- Added Epic/Story/Task CRUD methods to Project aggregate root
- All operations raise appropriate domain events
Broadcasting Strategy:
- Created events: Broadcast to both project-{projectId} and tenant-{tenantId} groups
- Updated events: Broadcast to project-{projectId} group only
- Deleted events: Broadcast to project-{projectId} group only
- Assigned events: Broadcast to project-{projectId} group with assignment details
Test Results:
- All 192 domain tests passing
- Domain and Application layers compile successfully
- Event handlers auto-registered by MediatR
Files Changed:
- 9 new domain event files
- 10 new event handler files
- 3 service interfaces extended
- 2 service implementations extended
- 1 aggregate updated with event raising logic
- 1 test file updated for new event signature
Status: Complete real-time collaboration for ProjectManagement module
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,22 @@ public interface IRealtimeNotificationService
|
||||
Task NotifyProjectArchived(Guid tenantId, Guid projectId);
|
||||
Task NotifyProjectUpdate(Guid tenantId, Guid projectId, object data);
|
||||
|
||||
// Epic notifications
|
||||
Task NotifyEpicCreated(Guid tenantId, Guid projectId, Guid epicId, object epic);
|
||||
Task NotifyEpicUpdated(Guid tenantId, Guid projectId, Guid epicId, object epic);
|
||||
Task NotifyEpicDeleted(Guid tenantId, Guid projectId, Guid epicId);
|
||||
|
||||
// Story notifications
|
||||
Task NotifyStoryCreated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story);
|
||||
Task NotifyStoryUpdated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story);
|
||||
Task NotifyStoryDeleted(Guid tenantId, Guid projectId, Guid epicId, Guid storyId);
|
||||
|
||||
// Task notifications
|
||||
Task NotifyTaskCreated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task);
|
||||
Task NotifyTaskUpdated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task);
|
||||
Task NotifyTaskDeleted(Guid tenantId, Guid projectId, Guid storyId, Guid taskId);
|
||||
Task NotifyTaskAssigned(Guid tenantId, Guid projectId, Guid taskId, Guid assigneeId);
|
||||
|
||||
// Issue notifications
|
||||
Task NotifyIssueCreated(Guid tenantId, Guid projectId, object issue);
|
||||
Task NotifyIssueUpdated(Guid tenantId, Guid projectId, object issue);
|
||||
|
||||
@@ -15,6 +15,7 @@ public class ProjectNotificationServiceAdapter : IProjectNotificationService
|
||||
_realtimeService = realtimeService;
|
||||
}
|
||||
|
||||
// Project notifications
|
||||
public Task NotifyProjectCreated(Guid tenantId, Guid projectId, object project)
|
||||
{
|
||||
return _realtimeService.NotifyProjectCreated(tenantId, projectId, project);
|
||||
@@ -29,4 +30,57 @@ public class ProjectNotificationServiceAdapter : IProjectNotificationService
|
||||
{
|
||||
return _realtimeService.NotifyProjectArchived(tenantId, projectId);
|
||||
}
|
||||
|
||||
// Epic notifications
|
||||
public Task NotifyEpicCreated(Guid tenantId, Guid projectId, Guid epicId, object epic)
|
||||
{
|
||||
return _realtimeService.NotifyEpicCreated(tenantId, projectId, epicId, epic);
|
||||
}
|
||||
|
||||
public Task NotifyEpicUpdated(Guid tenantId, Guid projectId, Guid epicId, object epic)
|
||||
{
|
||||
return _realtimeService.NotifyEpicUpdated(tenantId, projectId, epicId, epic);
|
||||
}
|
||||
|
||||
public Task NotifyEpicDeleted(Guid tenantId, Guid projectId, Guid epicId)
|
||||
{
|
||||
return _realtimeService.NotifyEpicDeleted(tenantId, projectId, epicId);
|
||||
}
|
||||
|
||||
// Story notifications
|
||||
public Task NotifyStoryCreated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story)
|
||||
{
|
||||
return _realtimeService.NotifyStoryCreated(tenantId, projectId, epicId, storyId, story);
|
||||
}
|
||||
|
||||
public Task NotifyStoryUpdated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story)
|
||||
{
|
||||
return _realtimeService.NotifyStoryUpdated(tenantId, projectId, epicId, storyId, story);
|
||||
}
|
||||
|
||||
public Task NotifyStoryDeleted(Guid tenantId, Guid projectId, Guid epicId, Guid storyId)
|
||||
{
|
||||
return _realtimeService.NotifyStoryDeleted(tenantId, projectId, epicId, storyId);
|
||||
}
|
||||
|
||||
// Task notifications
|
||||
public Task NotifyTaskCreated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task)
|
||||
{
|
||||
return _realtimeService.NotifyTaskCreated(tenantId, projectId, storyId, taskId, task);
|
||||
}
|
||||
|
||||
public Task NotifyTaskUpdated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task)
|
||||
{
|
||||
return _realtimeService.NotifyTaskUpdated(tenantId, projectId, storyId, taskId, task);
|
||||
}
|
||||
|
||||
public Task NotifyTaskDeleted(Guid tenantId, Guid projectId, Guid storyId, Guid taskId)
|
||||
{
|
||||
return _realtimeService.NotifyTaskDeleted(tenantId, projectId, storyId, taskId);
|
||||
}
|
||||
|
||||
public Task NotifyTaskAssigned(Guid tenantId, Guid projectId, Guid taskId, Guid assigneeId)
|
||||
{
|
||||
return _realtimeService.NotifyTaskAssigned(tenantId, projectId, taskId, assigneeId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,110 @@ public class RealtimeNotificationService : IRealtimeNotificationService
|
||||
await _projectHubContext.Clients.Group(groupName).SendAsync("ProjectUpdated", data);
|
||||
}
|
||||
|
||||
// Epic notifications
|
||||
public async Task NotifyEpicCreated(Guid tenantId, Guid projectId, Guid epicId, object epic)
|
||||
{
|
||||
var projectGroupName = $"project-{projectId}";
|
||||
var tenantGroupName = $"tenant-{tenantId}";
|
||||
|
||||
_logger.LogInformation("Notifying epic {EpicId} created in project {ProjectId}", epicId, projectId);
|
||||
|
||||
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("EpicCreated", epic);
|
||||
await _projectHubContext.Clients.Group(tenantGroupName).SendAsync("EpicCreated", epic);
|
||||
}
|
||||
|
||||
public async Task NotifyEpicUpdated(Guid tenantId, Guid projectId, Guid epicId, object epic)
|
||||
{
|
||||
var projectGroupName = $"project-{projectId}";
|
||||
|
||||
_logger.LogInformation("Notifying epic {EpicId} updated", epicId);
|
||||
|
||||
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("EpicUpdated", epic);
|
||||
}
|
||||
|
||||
public async Task NotifyEpicDeleted(Guid tenantId, Guid projectId, Guid epicId)
|
||||
{
|
||||
var projectGroupName = $"project-{projectId}";
|
||||
|
||||
_logger.LogInformation("Notifying epic {EpicId} deleted", epicId);
|
||||
|
||||
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("EpicDeleted", new { EpicId = epicId });
|
||||
}
|
||||
|
||||
// Story notifications
|
||||
public async Task NotifyStoryCreated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story)
|
||||
{
|
||||
var projectGroupName = $"project-{projectId}";
|
||||
var tenantGroupName = $"tenant-{tenantId}";
|
||||
|
||||
_logger.LogInformation("Notifying story {StoryId} created in epic {EpicId}", storyId, epicId);
|
||||
|
||||
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("StoryCreated", story);
|
||||
await _projectHubContext.Clients.Group(tenantGroupName).SendAsync("StoryCreated", story);
|
||||
}
|
||||
|
||||
public async Task NotifyStoryUpdated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story)
|
||||
{
|
||||
var projectGroupName = $"project-{projectId}";
|
||||
|
||||
_logger.LogInformation("Notifying story {StoryId} updated", storyId);
|
||||
|
||||
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("StoryUpdated", story);
|
||||
}
|
||||
|
||||
public async Task NotifyStoryDeleted(Guid tenantId, Guid projectId, Guid epicId, Guid storyId)
|
||||
{
|
||||
var projectGroupName = $"project-{projectId}";
|
||||
|
||||
_logger.LogInformation("Notifying story {StoryId} deleted", storyId);
|
||||
|
||||
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("StoryDeleted", new { StoryId = storyId });
|
||||
}
|
||||
|
||||
// Task notifications
|
||||
public async Task NotifyTaskCreated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task)
|
||||
{
|
||||
var projectGroupName = $"project-{projectId}";
|
||||
var tenantGroupName = $"tenant-{tenantId}";
|
||||
|
||||
_logger.LogInformation("Notifying task {TaskId} created in story {StoryId}", taskId, storyId);
|
||||
|
||||
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("TaskCreated", task);
|
||||
await _projectHubContext.Clients.Group(tenantGroupName).SendAsync("TaskCreated", task);
|
||||
}
|
||||
|
||||
public async Task NotifyTaskUpdated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task)
|
||||
{
|
||||
var projectGroupName = $"project-{projectId}";
|
||||
|
||||
_logger.LogInformation("Notifying task {TaskId} updated", taskId);
|
||||
|
||||
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("TaskUpdated", task);
|
||||
}
|
||||
|
||||
public async Task NotifyTaskDeleted(Guid tenantId, Guid projectId, Guid storyId, Guid taskId)
|
||||
{
|
||||
var projectGroupName = $"project-{projectId}";
|
||||
|
||||
_logger.LogInformation("Notifying task {TaskId} deleted", taskId);
|
||||
|
||||
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("TaskDeleted", new { TaskId = taskId });
|
||||
}
|
||||
|
||||
public async Task NotifyTaskAssigned(Guid tenantId, Guid projectId, Guid taskId, Guid assigneeId)
|
||||
{
|
||||
var projectGroupName = $"project-{projectId}";
|
||||
|
||||
_logger.LogInformation("Notifying task {TaskId} assigned to {AssigneeId}", taskId, assigneeId);
|
||||
|
||||
await _projectHubContext.Clients.Group(projectGroupName).SendAsync("TaskAssigned", new
|
||||
{
|
||||
TaskId = taskId,
|
||||
AssigneeId = assigneeId,
|
||||
AssignedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
public async Task NotifyIssueCreated(Guid tenantId, Guid projectId, object issue)
|
||||
{
|
||||
var groupName = $"project-{projectId}";
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.Services;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for EpicCreatedEvent - sends SignalR notification
|
||||
/// </summary>
|
||||
public class EpicCreatedEventHandler : INotificationHandler<EpicCreatedEvent>
|
||||
{
|
||||
private readonly IProjectNotificationService _notificationService;
|
||||
private readonly ILogger<EpicCreatedEventHandler> _logger;
|
||||
|
||||
public EpicCreatedEventHandler(
|
||||
IProjectNotificationService notificationService,
|
||||
ILogger<EpicCreatedEventHandler> logger)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Handle(EpicCreatedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Handling EpicCreatedEvent for epic {EpicId}", notification.EpicId);
|
||||
|
||||
var epicData = new
|
||||
{
|
||||
Id = notification.EpicId.Value,
|
||||
ProjectId = notification.ProjectId.Value,
|
||||
Name = notification.EpicName,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _notificationService.NotifyEpicCreated(
|
||||
notification.TenantId.Value,
|
||||
notification.ProjectId.Value,
|
||||
notification.EpicId.Value,
|
||||
epicData);
|
||||
|
||||
_logger.LogInformation("SignalR notification sent for epic {EpicId}", notification.EpicId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.Services;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for EpicDeletedEvent - sends SignalR notification
|
||||
/// </summary>
|
||||
public class EpicDeletedEventHandler : INotificationHandler<EpicDeletedEvent>
|
||||
{
|
||||
private readonly IProjectNotificationService _notificationService;
|
||||
private readonly ILogger<EpicDeletedEventHandler> _logger;
|
||||
|
||||
public EpicDeletedEventHandler(
|
||||
IProjectNotificationService notificationService,
|
||||
ILogger<EpicDeletedEventHandler> logger)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Handle(EpicDeletedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Handling EpicDeletedEvent for epic {EpicId}", notification.EpicId);
|
||||
|
||||
await _notificationService.NotifyEpicDeleted(
|
||||
notification.TenantId.Value,
|
||||
notification.ProjectId.Value,
|
||||
notification.EpicId.Value);
|
||||
|
||||
_logger.LogInformation("SignalR notification sent for epic {EpicId}", notification.EpicId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.Services;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for EpicUpdatedEvent - sends SignalR notification
|
||||
/// </summary>
|
||||
public class EpicUpdatedEventHandler : INotificationHandler<EpicUpdatedEvent>
|
||||
{
|
||||
private readonly IProjectNotificationService _notificationService;
|
||||
private readonly ILogger<EpicUpdatedEventHandler> _logger;
|
||||
|
||||
public EpicUpdatedEventHandler(
|
||||
IProjectNotificationService notificationService,
|
||||
ILogger<EpicUpdatedEventHandler> logger)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Handle(EpicUpdatedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Handling EpicUpdatedEvent for epic {EpicId}", notification.EpicId);
|
||||
|
||||
var epicData = new
|
||||
{
|
||||
Id = notification.EpicId.Value,
|
||||
ProjectId = notification.ProjectId.Value,
|
||||
Name = notification.EpicName,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _notificationService.NotifyEpicUpdated(
|
||||
notification.TenantId.Value,
|
||||
notification.ProjectId.Value,
|
||||
notification.EpicId.Value,
|
||||
epicData);
|
||||
|
||||
_logger.LogInformation("SignalR notification sent for epic {EpicId}", notification.EpicId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.Services;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for StoryCreatedEvent - sends SignalR notification
|
||||
/// </summary>
|
||||
public class StoryCreatedEventHandler : INotificationHandler<StoryCreatedEvent>
|
||||
{
|
||||
private readonly IProjectNotificationService _notificationService;
|
||||
private readonly ILogger<StoryCreatedEventHandler> _logger;
|
||||
|
||||
public StoryCreatedEventHandler(
|
||||
IProjectNotificationService notificationService,
|
||||
ILogger<StoryCreatedEventHandler> logger)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Handle(StoryCreatedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Handling StoryCreatedEvent for story {StoryId}", notification.StoryId);
|
||||
|
||||
var storyData = new
|
||||
{
|
||||
Id = notification.StoryId.Value,
|
||||
ProjectId = notification.ProjectId.Value,
|
||||
EpicId = notification.EpicId.Value,
|
||||
Title = notification.StoryTitle,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _notificationService.NotifyStoryCreated(
|
||||
notification.TenantId.Value,
|
||||
notification.ProjectId.Value,
|
||||
notification.EpicId.Value,
|
||||
notification.StoryId.Value,
|
||||
storyData);
|
||||
|
||||
_logger.LogInformation("SignalR notification sent for story {StoryId}", notification.StoryId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.Services;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for StoryDeletedEvent - sends SignalR notification
|
||||
/// </summary>
|
||||
public class StoryDeletedEventHandler : INotificationHandler<StoryDeletedEvent>
|
||||
{
|
||||
private readonly IProjectNotificationService _notificationService;
|
||||
private readonly ILogger<StoryDeletedEventHandler> _logger;
|
||||
|
||||
public StoryDeletedEventHandler(
|
||||
IProjectNotificationService notificationService,
|
||||
ILogger<StoryDeletedEventHandler> logger)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Handle(StoryDeletedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Handling StoryDeletedEvent for story {StoryId}", notification.StoryId);
|
||||
|
||||
await _notificationService.NotifyStoryDeleted(
|
||||
notification.TenantId.Value,
|
||||
notification.ProjectId.Value,
|
||||
notification.EpicId.Value,
|
||||
notification.StoryId.Value);
|
||||
|
||||
_logger.LogInformation("SignalR notification sent for story {StoryId}", notification.StoryId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.Services;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for StoryUpdatedEvent - sends SignalR notification
|
||||
/// </summary>
|
||||
public class StoryUpdatedEventHandler : INotificationHandler<StoryUpdatedEvent>
|
||||
{
|
||||
private readonly IProjectNotificationService _notificationService;
|
||||
private readonly ILogger<StoryUpdatedEventHandler> _logger;
|
||||
|
||||
public StoryUpdatedEventHandler(
|
||||
IProjectNotificationService notificationService,
|
||||
ILogger<StoryUpdatedEventHandler> logger)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Handle(StoryUpdatedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Handling StoryUpdatedEvent for story {StoryId}", notification.StoryId);
|
||||
|
||||
var storyData = new
|
||||
{
|
||||
Id = notification.StoryId.Value,
|
||||
ProjectId = notification.ProjectId.Value,
|
||||
EpicId = notification.EpicId.Value,
|
||||
Title = notification.StoryTitle,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _notificationService.NotifyStoryUpdated(
|
||||
notification.TenantId.Value,
|
||||
notification.ProjectId.Value,
|
||||
notification.EpicId.Value,
|
||||
notification.StoryId.Value,
|
||||
storyData);
|
||||
|
||||
_logger.LogInformation("SignalR notification sent for story {StoryId}", notification.StoryId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.Services;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for TaskAssignedEvent - sends SignalR notification
|
||||
/// </summary>
|
||||
public class TaskAssignedEventHandler : INotificationHandler<TaskAssignedEvent>
|
||||
{
|
||||
private readonly IProjectNotificationService _notificationService;
|
||||
private readonly ILogger<TaskAssignedEventHandler> _logger;
|
||||
|
||||
public TaskAssignedEventHandler(
|
||||
IProjectNotificationService notificationService,
|
||||
ILogger<TaskAssignedEventHandler> logger)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Handle(TaskAssignedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Handling TaskAssignedEvent for task {TaskId}", notification.TaskId);
|
||||
|
||||
await _notificationService.NotifyTaskAssigned(
|
||||
notification.TenantId.Value,
|
||||
notification.ProjectId.Value,
|
||||
notification.TaskId.Value,
|
||||
notification.AssigneeId.Value);
|
||||
|
||||
_logger.LogInformation("SignalR notification sent for task {TaskId} assigned to {AssigneeId}",
|
||||
notification.TaskId, notification.AssigneeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.Services;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for TaskCreatedEvent - sends SignalR notification
|
||||
/// </summary>
|
||||
public class TaskCreatedEventHandler : INotificationHandler<TaskCreatedEvent>
|
||||
{
|
||||
private readonly IProjectNotificationService _notificationService;
|
||||
private readonly ILogger<TaskCreatedEventHandler> _logger;
|
||||
|
||||
public TaskCreatedEventHandler(
|
||||
IProjectNotificationService notificationService,
|
||||
ILogger<TaskCreatedEventHandler> logger)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Handle(TaskCreatedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Handling TaskCreatedEvent for task {TaskId}", notification.TaskId);
|
||||
|
||||
var taskData = new
|
||||
{
|
||||
Id = notification.TaskId.Value,
|
||||
ProjectId = notification.ProjectId.Value,
|
||||
StoryId = notification.StoryId.Value,
|
||||
Title = notification.TaskTitle,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _notificationService.NotifyTaskCreated(
|
||||
notification.TenantId.Value,
|
||||
notification.ProjectId.Value,
|
||||
notification.StoryId.Value,
|
||||
notification.TaskId.Value,
|
||||
taskData);
|
||||
|
||||
_logger.LogInformation("SignalR notification sent for task {TaskId}", notification.TaskId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.Services;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for TaskDeletedEvent - sends SignalR notification
|
||||
/// </summary>
|
||||
public class TaskDeletedEventHandler : INotificationHandler<TaskDeletedEvent>
|
||||
{
|
||||
private readonly IProjectNotificationService _notificationService;
|
||||
private readonly ILogger<TaskDeletedEventHandler> _logger;
|
||||
|
||||
public TaskDeletedEventHandler(
|
||||
IProjectNotificationService notificationService,
|
||||
ILogger<TaskDeletedEventHandler> logger)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Handle(TaskDeletedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Handling TaskDeletedEvent for task {TaskId}", notification.TaskId);
|
||||
|
||||
await _notificationService.NotifyTaskDeleted(
|
||||
notification.TenantId.Value,
|
||||
notification.ProjectId.Value,
|
||||
notification.StoryId.Value,
|
||||
notification.TaskId.Value);
|
||||
|
||||
_logger.LogInformation("SignalR notification sent for task {TaskId}", notification.TaskId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Application.Services;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Application.EventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for TaskUpdatedEvent - sends SignalR notification
|
||||
/// </summary>
|
||||
public class TaskUpdatedEventHandler : INotificationHandler<TaskUpdatedEvent>
|
||||
{
|
||||
private readonly IProjectNotificationService _notificationService;
|
||||
private readonly ILogger<TaskUpdatedEventHandler> _logger;
|
||||
|
||||
public TaskUpdatedEventHandler(
|
||||
IProjectNotificationService notificationService,
|
||||
ILogger<TaskUpdatedEventHandler> logger)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Handle(TaskUpdatedEvent notification, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("Handling TaskUpdatedEvent for task {TaskId}", notification.TaskId);
|
||||
|
||||
var taskData = new
|
||||
{
|
||||
Id = notification.TaskId.Value,
|
||||
ProjectId = notification.ProjectId.Value,
|
||||
StoryId = notification.StoryId.Value,
|
||||
Title = notification.TaskTitle,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _notificationService.NotifyTaskUpdated(
|
||||
notification.TenantId.Value,
|
||||
notification.ProjectId.Value,
|
||||
notification.StoryId.Value,
|
||||
notification.TaskId.Value,
|
||||
taskData);
|
||||
|
||||
_logger.LogInformation("SignalR notification sent for task {TaskId}", notification.TaskId);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,24 @@ namespace ColaFlow.Modules.ProjectManagement.Application.Services;
|
||||
/// </summary>
|
||||
public interface IProjectNotificationService
|
||||
{
|
||||
// Project notifications
|
||||
Task NotifyProjectCreated(Guid tenantId, Guid projectId, object project);
|
||||
Task NotifyProjectUpdated(Guid tenantId, Guid projectId, object project);
|
||||
Task NotifyProjectArchived(Guid tenantId, Guid projectId);
|
||||
|
||||
// Epic notifications
|
||||
Task NotifyEpicCreated(Guid tenantId, Guid projectId, Guid epicId, object epic);
|
||||
Task NotifyEpicUpdated(Guid tenantId, Guid projectId, Guid epicId, object epic);
|
||||
Task NotifyEpicDeleted(Guid tenantId, Guid projectId, Guid epicId);
|
||||
|
||||
// Story notifications
|
||||
Task NotifyStoryCreated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story);
|
||||
Task NotifyStoryUpdated(Guid tenantId, Guid projectId, Guid epicId, Guid storyId, object story);
|
||||
Task NotifyStoryDeleted(Guid tenantId, Guid projectId, Guid epicId, Guid storyId);
|
||||
|
||||
// Task notifications
|
||||
Task NotifyTaskCreated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task);
|
||||
Task NotifyTaskUpdated(Guid tenantId, Guid projectId, Guid storyId, Guid taskId, object task);
|
||||
Task NotifyTaskDeleted(Guid tenantId, Guid projectId, Guid storyId, Guid taskId);
|
||||
Task NotifyTaskAssigned(Guid tenantId, Guid projectId, Guid taskId, Guid assigneeId);
|
||||
}
|
||||
|
||||
@@ -90,11 +90,154 @@ public class Project : AggregateRoot
|
||||
var epic = Epic.Create(this.TenantId, name, description, this.Id, createdBy);
|
||||
_epics.Add(epic);
|
||||
|
||||
AddDomainEvent(new EpicCreatedEvent(epic.Id, epic.Name, this.Id));
|
||||
AddDomainEvent(new EpicCreatedEvent(epic.Id, this.TenantId, this.Id, epic.Name));
|
||||
|
||||
return epic;
|
||||
}
|
||||
|
||||
public void UpdateEpic(EpicId epicId, string name, string description)
|
||||
{
|
||||
var epic = _epics.FirstOrDefault(e => e.Id == epicId);
|
||||
if (epic == null)
|
||||
throw new DomainException($"Epic with ID {epicId.Value} not found");
|
||||
|
||||
epic.UpdateDetails(name, description);
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
AddDomainEvent(new EpicUpdatedEvent(epic.Id, this.TenantId, this.Id, epic.Name));
|
||||
}
|
||||
|
||||
public void DeleteEpic(EpicId epicId)
|
||||
{
|
||||
var epic = _epics.FirstOrDefault(e => e.Id == epicId);
|
||||
if (epic == null)
|
||||
throw new DomainException($"Epic with ID {epicId.Value} not found");
|
||||
|
||||
if (epic.Stories.Any())
|
||||
throw new DomainException($"Cannot delete epic with ID {epicId.Value}. The epic has {epic.Stories.Count} associated story/stories. Please delete or reassign the stories first.");
|
||||
|
||||
_epics.Remove(epic);
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
AddDomainEvent(new EpicDeletedEvent(epicId, this.TenantId, this.Id));
|
||||
}
|
||||
|
||||
public Story CreateStory(EpicId epicId, string title, string description, TaskPriority priority, UserId createdBy)
|
||||
{
|
||||
var epic = _epics.FirstOrDefault(e => e.Id == epicId);
|
||||
if (epic == null)
|
||||
throw new DomainException($"Epic with ID {epicId.Value} not found");
|
||||
|
||||
var story = epic.CreateStory(title, description, priority, createdBy);
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
AddDomainEvent(new StoryCreatedEvent(story.Id, this.TenantId, this.Id, epicId, story.Title));
|
||||
|
||||
return story;
|
||||
}
|
||||
|
||||
public void UpdateStory(EpicId epicId, StoryId storyId, string title, string description)
|
||||
{
|
||||
var epic = _epics.FirstOrDefault(e => e.Id == epicId);
|
||||
if (epic == null)
|
||||
throw new DomainException($"Epic with ID {epicId.Value} not found");
|
||||
|
||||
var story = epic.Stories.FirstOrDefault(s => s.Id == storyId);
|
||||
if (story == null)
|
||||
throw new DomainException($"Story with ID {storyId.Value} not found in epic");
|
||||
|
||||
story.UpdateDetails(title, description);
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
AddDomainEvent(new StoryUpdatedEvent(storyId, this.TenantId, this.Id, epicId, story.Title));
|
||||
}
|
||||
|
||||
public void DeleteStory(EpicId epicId, StoryId storyId)
|
||||
{
|
||||
var epic = _epics.FirstOrDefault(e => e.Id == epicId);
|
||||
if (epic == null)
|
||||
throw new DomainException($"Epic with ID {epicId.Value} not found");
|
||||
|
||||
epic.RemoveStory(storyId);
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
AddDomainEvent(new StoryDeletedEvent(storyId, this.TenantId, this.Id, epicId));
|
||||
}
|
||||
|
||||
public WorkTask CreateTask(EpicId epicId, StoryId storyId, string title, string description, TaskPriority priority, UserId createdBy)
|
||||
{
|
||||
var epic = _epics.FirstOrDefault(e => e.Id == epicId);
|
||||
if (epic == null)
|
||||
throw new DomainException($"Epic with ID {epicId.Value} not found");
|
||||
|
||||
var story = epic.Stories.FirstOrDefault(s => s.Id == storyId);
|
||||
if (story == null)
|
||||
throw new DomainException($"Story with ID {storyId.Value} not found in epic");
|
||||
|
||||
var task = story.CreateTask(title, description, priority, createdBy);
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
AddDomainEvent(new TaskCreatedEvent(task.Id, this.TenantId, this.Id, storyId, task.Title));
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
public void UpdateTask(EpicId epicId, StoryId storyId, TaskId taskId, string title, string description)
|
||||
{
|
||||
var epic = _epics.FirstOrDefault(e => e.Id == epicId);
|
||||
if (epic == null)
|
||||
throw new DomainException($"Epic with ID {epicId.Value} not found");
|
||||
|
||||
var story = epic.Stories.FirstOrDefault(s => s.Id == storyId);
|
||||
if (story == null)
|
||||
throw new DomainException($"Story with ID {storyId.Value} not found in epic");
|
||||
|
||||
var task = story.Tasks.FirstOrDefault(t => t.Id == taskId);
|
||||
if (task == null)
|
||||
throw new DomainException($"Task with ID {taskId.Value} not found in story");
|
||||
|
||||
task.UpdateDetails(title, description);
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
AddDomainEvent(new TaskUpdatedEvent(taskId, this.TenantId, this.Id, storyId, task.Title));
|
||||
}
|
||||
|
||||
public void DeleteTask(EpicId epicId, StoryId storyId, TaskId taskId)
|
||||
{
|
||||
var epic = _epics.FirstOrDefault(e => e.Id == epicId);
|
||||
if (epic == null)
|
||||
throw new DomainException($"Epic with ID {epicId.Value} not found");
|
||||
|
||||
var story = epic.Stories.FirstOrDefault(s => s.Id == storyId);
|
||||
if (story == null)
|
||||
throw new DomainException($"Story with ID {storyId.Value} not found in epic");
|
||||
|
||||
story.RemoveTask(taskId);
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
AddDomainEvent(new TaskDeletedEvent(taskId, this.TenantId, this.Id, storyId));
|
||||
}
|
||||
|
||||
public void AssignTask(EpicId epicId, StoryId storyId, TaskId taskId, UserId assigneeId)
|
||||
{
|
||||
var epic = _epics.FirstOrDefault(e => e.Id == epicId);
|
||||
if (epic == null)
|
||||
throw new DomainException($"Epic with ID {epicId.Value} not found");
|
||||
|
||||
var story = epic.Stories.FirstOrDefault(s => s.Id == storyId);
|
||||
if (story == null)
|
||||
throw new DomainException($"Story with ID {storyId.Value} not found in epic");
|
||||
|
||||
var task = story.Tasks.FirstOrDefault(t => t.Id == taskId);
|
||||
if (task == null)
|
||||
throw new DomainException($"Task with ID {taskId.Value} not found in story");
|
||||
|
||||
task.AssignTo(assigneeId);
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
AddDomainEvent(new TaskAssignedEvent(taskId, this.TenantId, this.Id, storyId, assigneeId));
|
||||
}
|
||||
|
||||
public void Archive()
|
||||
{
|
||||
if (Status == ProjectStatus.Archived)
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
/// </summary>
|
||||
public sealed record EpicCreatedEvent(
|
||||
EpicId EpicId,
|
||||
string EpicName,
|
||||
ProjectId ProjectId
|
||||
TenantId TenantId,
|
||||
ProjectId ProjectId,
|
||||
string EpicName
|
||||
) : DomainEvent;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when an epic is deleted
|
||||
/// </summary>
|
||||
public sealed record EpicDeletedEvent(
|
||||
EpicId EpicId,
|
||||
TenantId TenantId,
|
||||
ProjectId ProjectId
|
||||
) : DomainEvent;
|
||||
@@ -0,0 +1,14 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when an epic is updated
|
||||
/// </summary>
|
||||
public sealed record EpicUpdatedEvent(
|
||||
EpicId EpicId,
|
||||
TenantId TenantId,
|
||||
ProjectId ProjectId,
|
||||
string EpicName
|
||||
) : DomainEvent;
|
||||
@@ -0,0 +1,15 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a story is created
|
||||
/// </summary>
|
||||
public sealed record StoryCreatedEvent(
|
||||
StoryId StoryId,
|
||||
TenantId TenantId,
|
||||
ProjectId ProjectId,
|
||||
EpicId EpicId,
|
||||
string StoryTitle
|
||||
) : DomainEvent;
|
||||
@@ -0,0 +1,14 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a story is deleted
|
||||
/// </summary>
|
||||
public sealed record StoryDeletedEvent(
|
||||
StoryId StoryId,
|
||||
TenantId TenantId,
|
||||
ProjectId ProjectId,
|
||||
EpicId EpicId
|
||||
) : DomainEvent;
|
||||
@@ -0,0 +1,15 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a story is updated
|
||||
/// </summary>
|
||||
public sealed record StoryUpdatedEvent(
|
||||
StoryId StoryId,
|
||||
TenantId TenantId,
|
||||
ProjectId ProjectId,
|
||||
EpicId EpicId,
|
||||
string StoryTitle
|
||||
) : DomainEvent;
|
||||
@@ -0,0 +1,15 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a task is assigned to a user
|
||||
/// </summary>
|
||||
public sealed record TaskAssignedEvent(
|
||||
TaskId TaskId,
|
||||
TenantId TenantId,
|
||||
ProjectId ProjectId,
|
||||
StoryId StoryId,
|
||||
UserId AssigneeId
|
||||
) : DomainEvent;
|
||||
@@ -0,0 +1,15 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a task is created
|
||||
/// </summary>
|
||||
public sealed record TaskCreatedEvent(
|
||||
TaskId TaskId,
|
||||
TenantId TenantId,
|
||||
ProjectId ProjectId,
|
||||
StoryId StoryId,
|
||||
string TaskTitle
|
||||
) : DomainEvent;
|
||||
@@ -0,0 +1,14 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a task is deleted
|
||||
/// </summary>
|
||||
public sealed record TaskDeletedEvent(
|
||||
TaskId TaskId,
|
||||
TenantId TenantId,
|
||||
ProjectId ProjectId,
|
||||
StoryId StoryId
|
||||
) : DomainEvent;
|
||||
@@ -0,0 +1,15 @@
|
||||
using ColaFlow.Shared.Kernel.Events;
|
||||
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
|
||||
|
||||
namespace ColaFlow.Modules.ProjectManagement.Domain.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a task is updated
|
||||
/// </summary>
|
||||
public sealed record TaskUpdatedEvent(
|
||||
TaskId TaskId,
|
||||
TenantId TenantId,
|
||||
ProjectId ProjectId,
|
||||
StoryId StoryId,
|
||||
string TaskTitle
|
||||
) : DomainEvent;
|
||||
@@ -125,16 +125,18 @@ public class DomainEventsTests
|
||||
{
|
||||
// Arrange
|
||||
var epicId = EpicId.Create();
|
||||
var epicName = "Epic 1";
|
||||
var tenantId = TenantId.Create(Guid.NewGuid());
|
||||
var projectId = ProjectId.Create();
|
||||
var epicName = "Epic 1";
|
||||
|
||||
// Act
|
||||
var @event = new EpicCreatedEvent(epicId, epicName, projectId);
|
||||
var @event = new EpicCreatedEvent(epicId, tenantId, projectId, epicName);
|
||||
|
||||
// Assert
|
||||
@event.EpicId.Should().Be(epicId);
|
||||
@event.EpicName.Should().Be(epicName);
|
||||
@event.TenantId.Should().Be(tenantId);
|
||||
@event.ProjectId.Should().Be(projectId);
|
||||
@event.EpicName.Should().Be(epicName);
|
||||
@event.OccurredOn.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
@@ -143,17 +145,19 @@ public class DomainEventsTests
|
||||
{
|
||||
// Arrange
|
||||
var epicId = EpicId.Create();
|
||||
var epicName = "Epic 1";
|
||||
var tenantId = TenantId.Create(Guid.NewGuid());
|
||||
var projectId = ProjectId.Create();
|
||||
var epicName = "Epic 1";
|
||||
|
||||
// Act
|
||||
var event1 = new EpicCreatedEvent(epicId, epicName, projectId);
|
||||
var event2 = new EpicCreatedEvent(epicId, epicName, projectId);
|
||||
var event1 = new EpicCreatedEvent(epicId, tenantId, projectId, epicName);
|
||||
var event2 = new EpicCreatedEvent(epicId, tenantId, projectId, epicName);
|
||||
|
||||
// Assert - Records with same values should be equal
|
||||
event1.EpicId.Should().Be(event2.EpicId);
|
||||
event1.EpicName.Should().Be(event2.EpicName);
|
||||
event1.TenantId.Should().Be(event2.TenantId);
|
||||
event1.ProjectId.Should().Be(event2.ProjectId);
|
||||
event1.EpicName.Should().Be(event2.EpicName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -167,7 +171,7 @@ public class DomainEventsTests
|
||||
var projectCreatedEvent = new ProjectCreatedEvent(ProjectId.Create(), TenantId.Create(Guid.NewGuid()), "Test", UserId.Create());
|
||||
var projectUpdatedEvent = new ProjectUpdatedEvent(ProjectId.Create(), "Test", "Desc");
|
||||
var projectArchivedEvent = new ProjectArchivedEvent(ProjectId.Create());
|
||||
var epicCreatedEvent = new EpicCreatedEvent(EpicId.Create(), "Epic", ProjectId.Create());
|
||||
var epicCreatedEvent = new EpicCreatedEvent(EpicId.Create(), TenantId.Create(Guid.NewGuid()), ProjectId.Create(), "Epic");
|
||||
|
||||
// Assert
|
||||
projectCreatedEvent.OccurredOn.Kind.Should().Be(DateTimeKind.Utc);
|
||||
|
||||
Reference in New Issue
Block a user