From 9d382e83b23484b85aa80f556cf595df7446f23c Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 14 Apr 2026 15:46:25 +0300 Subject: [PATCH 1/7] chore: update dependency and change log for next release --- Directory.Packages.props | 6 +++--- DxWorks.ScriptBee.Plugin.Api/CHANGELOG.md | 4 ++++ Plugins/CHANGELOG.md | 4 ++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 808f3e69..30017475 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -27,12 +27,12 @@ - + - + - + diff --git a/DxWorks.ScriptBee.Plugin.Api/CHANGELOG.md b/DxWorks.ScriptBee.Plugin.Api/CHANGELOG.md index 9d84895f..6d8d041c 100644 --- a/DxWorks.ScriptBee.Plugin.Api/CHANGELOG.md +++ b/DxWorks.ScriptBee.Plugin.Api/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- update Westwind.Utilities to 5.3.0 + ## [2.0.0] ### Changed diff --git a/Plugins/CHANGELOG.md b/Plugins/CHANGELOG.md index ddfed96f..74b17754 100644 --- a/Plugins/CHANGELOG.md +++ b/Plugins/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- update Jint to 4.8.0 + ## [2.0.0] ### Changed From 8bb81cdbd3585a54ffacf43152d8d0cb7d1c7bac Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 14 Apr 2026 15:46:45 +0300 Subject: [PATCH 2/7] refactor: removed unnecessary files --- .../Extensions/FileWatcherExtensions.cs | 15 ---- .../PluginRegistrationExtensions.cs | 64 ---------------- ScriptBeeWebApp/Hubs/FileWatcherHub.cs | 24 ------ ScriptBeeWebApp/Hubs/IFileWatcherClient.cs | 9 --- .../Services/GenerateScriptService.cs | 75 ------------------- .../Services/IFileWatcherHubService.cs | 8 -- .../Services/IGenerateScriptService.cs | 16 ---- ScriptBeeWebApp/Services/IScriptsService.cs | 49 ------------ scripts/create_plugin_api_nuget.sh | 4 - 9 files changed, 264 deletions(-) delete mode 100644 ScriptBeeWebApp/Extensions/FileWatcherExtensions.cs delete mode 100644 ScriptBeeWebApp/Extensions/PluginRegistrationExtensions.cs delete mode 100644 ScriptBeeWebApp/Hubs/FileWatcherHub.cs delete mode 100644 ScriptBeeWebApp/Hubs/IFileWatcherClient.cs delete mode 100644 ScriptBeeWebApp/Services/GenerateScriptService.cs delete mode 100644 ScriptBeeWebApp/Services/IFileWatcherHubService.cs delete mode 100644 ScriptBeeWebApp/Services/IGenerateScriptService.cs delete mode 100644 ScriptBeeWebApp/Services/IScriptsService.cs delete mode 100644 scripts/create_plugin_api_nuget.sh diff --git a/ScriptBeeWebApp/Extensions/FileWatcherExtensions.cs b/ScriptBeeWebApp/Extensions/FileWatcherExtensions.cs deleted file mode 100644 index 52aa8b45..00000000 --- a/ScriptBeeWebApp/Extensions/FileWatcherExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using ScriptBee.ProjectContext; -using ScriptBeeWebApp.Services; - -namespace ScriptBeeWebApp.Extensions; - -public static class FileWatcherExtensions -{ - public static IServiceCollection AddFileWatcherServices(this IServiceCollection services) - { - services.AddSingleton(); - services.AddSingleton(); - return services; - } -} diff --git a/ScriptBeeWebApp/Extensions/PluginRegistrationExtensions.cs b/ScriptBeeWebApp/Extensions/PluginRegistrationExtensions.cs deleted file mode 100644 index 79dc9e13..00000000 --- a/ScriptBeeWebApp/Extensions/PluginRegistrationExtensions.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using DxWorks.ScriptBee.Plugin.Api; -using Microsoft.Extensions.DependencyInjection; -using ScriptBee.FileManagement; -using ScriptBee.Plugin; -using ScriptBee.Plugin.Installer; -using ScriptBee.Plugin.Manifest; -using ScriptBeeWebApp.Services; - -namespace ScriptBeeWebApp.Extensions; - -public static class PluginRegistrationExtensions -{ - public static IServiceCollection AddPluginServices(this IServiceCollection services) - { - return services - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton(_ => RegisterPluginTypes()); - } - - private static IPluginRegistrationService RegisterPluginTypes() - { - var pluginRegistrationService = new PluginRegistrationService(); - - pluginRegistrationService.Add( - PluginKind.Loader, - new HashSet { typeof(IModelLoader) } - ); - pluginRegistrationService.Add( - PluginKind.Linker, - new HashSet { typeof(IModelLinker) } - ); - pluginRegistrationService.Add( - PluginKind.ScriptGenerator, - new HashSet { typeof(IScriptGeneratorStrategy) } - ); - pluginRegistrationService.Add( - PluginKind.ScriptRunner, - new HashSet { typeof(IScriptRunner) } - ); - pluginRegistrationService.Add( - PluginKind.HelperFunctions, - new HashSet { typeof(IHelperFunctions) } - ); - - // todo see how to start the plugin via node or http-server or something like that if not already started - pluginRegistrationService.Add(PluginKind.Ui, new HashSet()); - - return pluginRegistrationService; - } -} diff --git a/ScriptBeeWebApp/Hubs/FileWatcherHub.cs b/ScriptBeeWebApp/Hubs/FileWatcherHub.cs deleted file mode 100644 index f88ad8ed..00000000 --- a/ScriptBeeWebApp/Hubs/FileWatcherHub.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.SignalR; -using ScriptBeeWebApp.Data; - -namespace ScriptBeeWebApp.Hubs; - -public class FileWatcherHub : Hub -{ - public override Task OnConnectedAsync() - { - return Task.CompletedTask; - } - - public override Task OnDisconnectedAsync(Exception? exception) - { - return Task.CompletedTask; - } - - public Task SendFileWatch(WatchedFile watchedFile) - { - return Clients.All.ReceiveFileWatch(watchedFile); - } -} diff --git a/ScriptBeeWebApp/Hubs/IFileWatcherClient.cs b/ScriptBeeWebApp/Hubs/IFileWatcherClient.cs deleted file mode 100644 index b3844432..00000000 --- a/ScriptBeeWebApp/Hubs/IFileWatcherClient.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; -using ScriptBeeWebApp.Data; - -namespace ScriptBeeWebApp.Hubs; - -public interface IFileWatcherClient -{ - Task ReceiveFileWatch(WatchedFile watchedFile); -} diff --git a/ScriptBeeWebApp/Services/GenerateScriptService.cs b/ScriptBeeWebApp/Services/GenerateScriptService.cs deleted file mode 100644 index fa54d857..00000000 --- a/ScriptBeeWebApp/Services/GenerateScriptService.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.IO.Compression; -using DxWorks.ScriptBee.Plugin.Api; -using ScriptBee.Plugin; -using ScriptBee.Plugin.Manifest; -using ScriptBee.Scripts.ScriptSampleGenerators; -using ScriptBeeWebApp.EndpointDefinitions.DTO; - -namespace ScriptBeeWebApp.Services; - -// todo add tests -public class GenerateScriptService : IGenerateScriptService -{ - private readonly ILoadersService _loadersService; - private readonly IPluginRepository _pluginRepository; - - public GenerateScriptService(ILoadersService loadersService, IPluginRepository pluginRepository) - { - _loadersService = loadersService; - _pluginRepository = pluginRepository; - } - - public IEnumerable GetSupportedLanguages() - { - return _pluginRepository - .GetLoadedPluginExtensionPoints() - .Select(extensionPoint => new ScriptLanguage( - extensionPoint.Language, - extensionPoint.Extension - )); - } - - public IScriptGeneratorStrategy? GetGenerationStrategy(string scriptType) - { - return _pluginRepository.GetPlugin(strategy => - strategy.Language == scriptType - ); - } - - public async Task GenerateClassesZip( - IEnumerable classes, - IScriptGeneratorStrategy scriptGeneratorStrategy, - CancellationToken cancellationToken = default - ) - { - var acceptedModules = _loadersService.GetAcceptedModules(); - - var sampleCode = await new SampleCodeGenerator( - scriptGeneratorStrategy, - acceptedModules - ).GetSampleCode(classes, cancellationToken); - - return CreateFileZipStream(sampleCode, scriptGeneratorStrategy.Extension); - } - - private static Stream CreateFileZipStream( - IEnumerable sampleCode, - string extension - ) - { - var zipStream = new MemoryStream(); - using (var zip = new ZipArchive(zipStream, ZipArchiveMode.Create, true)) - { - foreach (var sampleCodeFile in sampleCode) - { - var zipArchiveEntry = zip.CreateEntry(sampleCodeFile.Name + extension); - - using var writer = new StreamWriter(zipArchiveEntry.Open()); - writer.Write(sampleCodeFile.Content); - } - } - - zipStream.Position = 0; - return zipStream; - } -} diff --git a/ScriptBeeWebApp/Services/IFileWatcherHubService.cs b/ScriptBeeWebApp/Services/IFileWatcherHubService.cs deleted file mode 100644 index 644c1a78..00000000 --- a/ScriptBeeWebApp/Services/IFileWatcherHubService.cs +++ /dev/null @@ -1,8 +0,0 @@ -using ScriptBeeWebApp.Data; - -namespace ScriptBeeWebApp.Services; - -public interface IFileWatcherHubService -{ - Task SendFileWatch(WatchedFile watchedFile); -} diff --git a/ScriptBeeWebApp/Services/IGenerateScriptService.cs b/ScriptBeeWebApp/Services/IGenerateScriptService.cs deleted file mode 100644 index 4c45eaaa..00000000 --- a/ScriptBeeWebApp/Services/IGenerateScriptService.cs +++ /dev/null @@ -1,16 +0,0 @@ -using DxWorks.ScriptBee.Plugin.Api; -using ScriptBeeWebApp.EndpointDefinitions.DTO; - -namespace ScriptBeeWebApp.Services; - -public interface IGenerateScriptService -{ - IEnumerable GetSupportedLanguages(); - IScriptGeneratorStrategy? GetGenerationStrategy(string scriptType); - - Task GenerateClassesZip( - IEnumerable classes, - IScriptGeneratorStrategy scriptGeneratorStrategy, - CancellationToken cancellationToken = default - ); -} diff --git a/ScriptBeeWebApp/Services/IScriptsService.cs b/ScriptBeeWebApp/Services/IScriptsService.cs deleted file mode 100644 index 21099302..00000000 --- a/ScriptBeeWebApp/Services/IScriptsService.cs +++ /dev/null @@ -1,49 +0,0 @@ -using OneOf; -using ScriptBeeWebApp.Data; -using ScriptBeeWebApp.EndpointDefinitions.Arguments; -using ScriptBeeWebApp.EndpointDefinitions.DTO; - -namespace ScriptBeeWebApp.Services; - -public interface IScriptsService -{ - IEnumerable GetSupportedLanguages(); - - Task, ProjectMissing>> GetScriptsStructureAsync( - string projectId, - CancellationToken cancellationToken = default - ); - - Task> GetScriptByFilePathAsync( - string filepath, - string projectId, - CancellationToken cancellationToken = default - ); - - Task> GetScriptByIdAsync( - string scriptId, - string projectId, - CancellationToken cancellationToken = default - ); - - Task> GetScriptContentAsync( - string scriptId, - string projectId, - CancellationToken cancellationToken = default - ); - - Task< - OneOf - > CreateScriptAsync(CreateScript createScript, CancellationToken cancellationToken = default); - - Task> UpdateScriptAsync( - UpdateScript updateScript, - CancellationToken cancellationToken = default - ); - - Task> DeleteScriptAsync( - string scriptId, - string projectId, - CancellationToken cancellationToken = default - ); -} diff --git a/scripts/create_plugin_api_nuget.sh b/scripts/create_plugin_api_nuget.sh deleted file mode 100644 index c7afafb3..00000000 --- a/scripts/create_plugin_api_nuget.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -cd ./DxWorks.ScriptBee.Plugin.Api || exit - -dotnet pack -c Release -o ./pack From 28e22ac9e982a651f168facbc3a289cffd4e656b Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 14 Apr 2026 16:27:29 +0300 Subject: [PATCH 3/7] feat(backend): add signalr project notifications --- .../Hubs/ProjectLiveUpdatesHub.cs | 18 ++++ .../NotificationExtensions.cs | 15 +++ .../Notifications.SignalR.csproj | 15 +++ .../Services/ProjectNotificationsService.cs | 39 ++++++++ .../Events/ScriptCreatedEvent.cs | 6 ++ .../Events/ScriptDeletedEvent.cs | 6 ++ .../Events/ScriptUpdatedEvent.cs | 6 ++ .../IProjectNotificationsService.cs | 12 +++ .../Ports.Notifications.csproj | 9 ++ .../Notifications.SignalR.Tests.csproj | 8 ++ .../ProjectNotificationsServiceTests.cs | 94 +++++++++++++++++++ 11 files changed, 228 insertions(+) create mode 100644 ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Hubs/ProjectLiveUpdatesHub.cs create mode 100644 ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/NotificationExtensions.cs create mode 100644 ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Notifications.SignalR.csproj create mode 100644 ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Services/ProjectNotificationsService.cs create mode 100644 ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Events/ScriptCreatedEvent.cs create mode 100644 ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Events/ScriptDeletedEvent.cs create mode 100644 ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Events/ScriptUpdatedEvent.cs create mode 100644 ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/IProjectNotificationsService.cs create mode 100644 ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Ports.Notifications.csproj create mode 100644 ScriptBeeWebApp/test/Gateway/Adapters/Notifications.SignalR.Tests/Notifications.SignalR.Tests.csproj create mode 100644 ScriptBeeWebApp/test/Gateway/Adapters/Notifications.SignalR.Tests/Services/ProjectNotificationsServiceTests.cs diff --git a/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Hubs/ProjectLiveUpdatesHub.cs b/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Hubs/ProjectLiveUpdatesHub.cs new file mode 100644 index 00000000..2292e18a --- /dev/null +++ b/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Hubs/ProjectLiveUpdatesHub.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.SignalR; + +namespace ScriptBee.Adapters.Notifications.SignalR.Hubs; + +public class ProjectLiveUpdatesHub : Hub +{ + public Task JoinChannel(string projectId, string channelName) + { + var groupName = $"{projectId}_{channelName}"; + return Groups.AddToGroupAsync(Context.ConnectionId, groupName); + } + + public Task LeaveChannel(string projectId, string channelName) + { + var groupName = $"{projectId}_{channelName}"; + return Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); + } +} diff --git a/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/NotificationExtensions.cs b/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/NotificationExtensions.cs new file mode 100644 index 00000000..92385073 --- /dev/null +++ b/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/NotificationExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using ScriptBee.Adapters.Notifications.SignalR.Services; +using ScriptBee.Ports.Notifications; + +namespace ScriptBee.Adapters.Notifications.SignalR; + +public static class NotificationExtensions +{ + public static IServiceCollection AddProjectLiveUpdates(this IServiceCollection services) + { + services.AddSignalR(); + services.AddSingleton(); + return services; + } +} diff --git a/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Notifications.SignalR.csproj b/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Notifications.SignalR.csproj new file mode 100644 index 00000000..093b5c48 --- /dev/null +++ b/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Notifications.SignalR.csproj @@ -0,0 +1,15 @@ + + + ScriptBee.Adapters.Notifications.SignalR + + + + + + + + + + + + diff --git a/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Services/ProjectNotificationsService.cs b/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Services/ProjectNotificationsService.cs new file mode 100644 index 00000000..bd4e0ecd --- /dev/null +++ b/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Services/ProjectNotificationsService.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.SignalR; +using ScriptBee.Adapters.Notifications.SignalR.Hubs; +using ScriptBee.Application.Model.Services; +using ScriptBee.Domain.Model.Project; +using ScriptBee.Ports.Notifications; +using ScriptBee.Ports.Notifications.Events; + +namespace ScriptBee.Adapters.Notifications.SignalR.Services; + +public class ProjectNotificationsService( + IHubContext hubContext, + IClientIdProvider clientIdProvider +) : IProjectNotificationsService +{ + private const string ScriptsChannel = "scripts"; + + public Task NotifyScriptCreated(ScriptCreatedEvent scriptCreatedEvent, CancellationToken cancellationToken) + { + var groupName = GetGroupName(scriptCreatedEvent.ProjectId, ScriptsChannel); + var message = scriptCreatedEvent with { ClientId = clientIdProvider.ClientId ?? "" }; + return hubContext.Clients.Group(groupName).SendAsync("ScriptCreated", message, cancellationToken); + } + + public Task NotifyScriptUpdated(ScriptUpdatedEvent scriptUpdatedEvent, CancellationToken cancellationToken) + { + var groupName = GetGroupName(scriptUpdatedEvent.ProjectId, ScriptsChannel); + var message = scriptUpdatedEvent with { ClientId = clientIdProvider.ClientId ?? "" }; + return hubContext.Clients.Group(groupName).SendAsync("ScriptUpdated", message, cancellationToken); + } + + public Task NotifyScriptDeleted(ScriptDeletedEvent scriptDeletedEvent, CancellationToken cancellationToken) + { + var groupName = GetGroupName(scriptDeletedEvent.ProjectId, ScriptsChannel); + var message = scriptDeletedEvent with { ClientId = clientIdProvider.ClientId ?? "" }; + return hubContext.Clients.Group(groupName).SendAsync("ScriptDeleted", message, cancellationToken); + } + + private static string GetGroupName(ProjectId projectId, string channelName) => $"{projectId.Value}_{channelName}"; +} diff --git a/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Events/ScriptCreatedEvent.cs b/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Events/ScriptCreatedEvent.cs new file mode 100644 index 00000000..dc710852 --- /dev/null +++ b/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Events/ScriptCreatedEvent.cs @@ -0,0 +1,6 @@ +using ScriptBee.Domain.Model.Project; +using ScriptBee.Domain.Model.ProjectStructure; + +namespace ScriptBee.Ports.Notifications.Events; + +public record ScriptCreatedEvent(ProjectId ProjectId, ScriptId ScriptId, string ClientId = ""); diff --git a/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Events/ScriptDeletedEvent.cs b/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Events/ScriptDeletedEvent.cs new file mode 100644 index 00000000..79e02ece --- /dev/null +++ b/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Events/ScriptDeletedEvent.cs @@ -0,0 +1,6 @@ +using ScriptBee.Domain.Model.Project; +using ScriptBee.Domain.Model.ProjectStructure; + +namespace ScriptBee.Ports.Notifications.Events; + +public record ScriptDeletedEvent(ProjectId ProjectId, ScriptId ScriptId, string ClientId = ""); diff --git a/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Events/ScriptUpdatedEvent.cs b/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Events/ScriptUpdatedEvent.cs new file mode 100644 index 00000000..14888520 --- /dev/null +++ b/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Events/ScriptUpdatedEvent.cs @@ -0,0 +1,6 @@ +using ScriptBee.Domain.Model.Project; +using ScriptBee.Domain.Model.ProjectStructure; + +namespace ScriptBee.Ports.Notifications.Events; + +public record ScriptUpdatedEvent(ProjectId ProjectId, ScriptId ScriptId, string ClientId = ""); diff --git a/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/IProjectNotificationsService.cs b/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/IProjectNotificationsService.cs new file mode 100644 index 00000000..e7c6aaee --- /dev/null +++ b/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/IProjectNotificationsService.cs @@ -0,0 +1,12 @@ +using ScriptBee.Ports.Notifications.Events; + +namespace ScriptBee.Ports.Notifications; + +public interface IProjectNotificationsService +{ + Task NotifyScriptCreated(ScriptCreatedEvent scriptCreatedEvent, CancellationToken cancellationToken); + + Task NotifyScriptUpdated(ScriptUpdatedEvent scriptUpdatedEvent, CancellationToken cancellationToken); + + Task NotifyScriptDeleted(ScriptDeletedEvent scriptDeletedEvent, CancellationToken cancellationToken); +} diff --git a/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Ports.Notifications.csproj b/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Ports.Notifications.csproj new file mode 100644 index 00000000..0578e42b --- /dev/null +++ b/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/Ports.Notifications.csproj @@ -0,0 +1,9 @@ + + + ScriptBee.Ports.Notifications + + + + + + diff --git a/ScriptBeeWebApp/test/Gateway/Adapters/Notifications.SignalR.Tests/Notifications.SignalR.Tests.csproj b/ScriptBeeWebApp/test/Gateway/Adapters/Notifications.SignalR.Tests/Notifications.SignalR.Tests.csproj new file mode 100644 index 00000000..9c3244ea --- /dev/null +++ b/ScriptBeeWebApp/test/Gateway/Adapters/Notifications.SignalR.Tests/Notifications.SignalR.Tests.csproj @@ -0,0 +1,8 @@ + + + ScriptBee.Adapters.Notifications.SignalR.Tests + + + + + diff --git a/ScriptBeeWebApp/test/Gateway/Adapters/Notifications.SignalR.Tests/Services/ProjectNotificationsServiceTests.cs b/ScriptBeeWebApp/test/Gateway/Adapters/Notifications.SignalR.Tests/Services/ProjectNotificationsServiceTests.cs new file mode 100644 index 00000000..6741a698 --- /dev/null +++ b/ScriptBeeWebApp/test/Gateway/Adapters/Notifications.SignalR.Tests/Services/ProjectNotificationsServiceTests.cs @@ -0,0 +1,94 @@ +using Microsoft.AspNetCore.SignalR; +using NSubstitute; +using ScriptBee.Adapters.Notifications.SignalR.Hubs; +using ScriptBee.Adapters.Notifications.SignalR.Services; +using ScriptBee.Application.Model.Services; +using ScriptBee.Domain.Model.Project; +using ScriptBee.Domain.Model.ProjectStructure; +using ScriptBee.Ports.Notifications.Events; + +namespace ScriptBee.Adapters.Notifications.SignalR.Tests.Services; + +public class ProjectNotificationsServiceTests +{ + private readonly IClientIdProvider _clientIdProvider = Substitute.For(); + private readonly IHubClients _hubClients = Substitute.For(); + private readonly IClientProxy _clientProxy = Substitute.For(); + private readonly ProjectNotificationsService _service; + + public ProjectNotificationsServiceTests() + { + var hubContext = Substitute.For>(); + + hubContext.Clients.Returns(_hubClients); + _hubClients.Group(Arg.Any()).Returns(_clientProxy); + + _service = new ProjectNotificationsService(hubContext, _clientIdProvider); + } + + [Fact] + public async Task NotifyScriptCreated_SendsMessageToCorrectGroup() + { + // Arrange + var projectId = ProjectId.FromValue("test-project"); + var scriptId = new ScriptId(Guid.NewGuid()); + var ev = new ScriptCreatedEvent(projectId, scriptId); + + // Act + await _service.NotifyScriptCreated(ev, CancellationToken.None); + + // Assert + _hubClients.Received(1).Group("test-project_scripts"); + await _clientProxy + .Received(1) + .SendCoreAsync( + "ScriptCreated", + Arg.Is(args => (ScriptCreatedEvent)args[0] == ev), + CancellationToken.None + ); + } + + [Fact] + public async Task NotifyScriptUpdated_SendsMessageToCorrectGroup() + { + // Arrange + var projectId = ProjectId.FromValue("test-project"); + var scriptId = new ScriptId(Guid.NewGuid()); + var ev = new ScriptUpdatedEvent(projectId, scriptId); + + // Act + await _service.NotifyScriptUpdated(ev, CancellationToken.None); + + // Assert + _hubClients.Received(1).Group("test-project_scripts"); + await _clientProxy + .Received(1) + .SendCoreAsync( + "ScriptUpdated", + Arg.Is(args => (ScriptUpdatedEvent)args[0] == ev), + CancellationToken.None + ); + } + + [Fact] + public async Task NotifyScriptDeleted_SendsMessageToCorrectGroup() + { + // Arrange + var projectId = ProjectId.FromValue("test-project"); + var scriptId = new ScriptId(Guid.NewGuid()); + var ev = new ScriptDeletedEvent(projectId, scriptId); + + // Act + await _service.NotifyScriptDeleted(ev, CancellationToken.None); + + // Assert + _hubClients.Received(1).Group("test-project_scripts"); + await _clientProxy + .Received(1) + .SendCoreAsync( + "ScriptDeleted", + Arg.Is(args => (ScriptDeletedEvent)args[0] == ev), + CancellationToken.None + ); + } +} From 3e825d1893bf2c873e64f91a6db669552e582c29 Mon Sep 17 00:00:00 2001 From: Andrei Timar Date: Tue, 14 Apr 2026 16:29:53 +0300 Subject: [PATCH 4/7] feat(backend): send script changes notifications --- ScriptBee.slnx | 3 ++ .../Services/ClientIdProvider.cs | 6 +++ .../Services/IClientIdProvider.cs | 6 +++ .../src/Common/Common.Web/Common.Web.csproj | 3 +- .../Common.Web/Services/ClientIdMiddleware.cs | 19 ++++++++ .../Services/ProjectNotificationsService.cs | 46 ++++++++++++++----- .../Extensions/CommonServicesExtensions.cs | 4 +- .../src/Gateway/Adapters/Web/Program.cs | 12 +++-- .../src/Gateway/Adapters/Web/Web.csproj | 1 + .../IProjectNotificationsService.cs | 15 ++++-- .../ProjectStructure/CreateScriptService.cs | 10 +++- .../DeleteProjectFilesService.cs | 10 +++- .../ProjectStructure/UpdateScriptService.cs | 22 +++++++-- .../Service.Project/Service.Project.csproj | 1 + .../ProjectStructure/CreateScriptCommand.cs | 2 +- .../FileBundlerTests.cs | 9 ++-- .../ProjectNotificationsServiceTests.cs | 41 +++++++++++++---- .../CreateScriptServiceTest.cs | 22 ++++++++- .../DeleteProjectFilesServiceTest.cs | 19 +++++++- .../UpdateScriptServiceTest.cs | 30 +++++++++++- 20 files changed, 235 insertions(+), 46 deletions(-) create mode 100644 ScriptBeeWebApp/src/Common/Application.Model/Services/ClientIdProvider.cs create mode 100644 ScriptBeeWebApp/src/Common/Application.Model/Services/IClientIdProvider.cs create mode 100644 ScriptBeeWebApp/src/Common/Common.Web/Services/ClientIdMiddleware.cs diff --git a/ScriptBee.slnx b/ScriptBee.slnx index d2d31729..d845a801 100644 --- a/ScriptBee.slnx +++ b/ScriptBee.slnx @@ -44,10 +44,12 @@ + + @@ -76,5 +78,6 @@ + diff --git a/ScriptBeeWebApp/src/Common/Application.Model/Services/ClientIdProvider.cs b/ScriptBeeWebApp/src/Common/Application.Model/Services/ClientIdProvider.cs new file mode 100644 index 00000000..65027152 --- /dev/null +++ b/ScriptBeeWebApp/src/Common/Application.Model/Services/ClientIdProvider.cs @@ -0,0 +1,6 @@ +namespace ScriptBee.Application.Model.Services; + +public class ClientIdProvider : IClientIdProvider +{ + public string? ClientId { get; set; } +} diff --git a/ScriptBeeWebApp/src/Common/Application.Model/Services/IClientIdProvider.cs b/ScriptBeeWebApp/src/Common/Application.Model/Services/IClientIdProvider.cs new file mode 100644 index 00000000..8c9f9f9c --- /dev/null +++ b/ScriptBeeWebApp/src/Common/Application.Model/Services/IClientIdProvider.cs @@ -0,0 +1,6 @@ +namespace ScriptBee.Application.Model.Services; + +public interface IClientIdProvider +{ + string? ClientId { get; set; } +} diff --git a/ScriptBeeWebApp/src/Common/Common.Web/Common.Web.csproj b/ScriptBeeWebApp/src/Common/Common.Web/Common.Web.csproj index 07bf4165..e457c8f1 100644 --- a/ScriptBeeWebApp/src/Common/Common.Web/Common.Web.csproj +++ b/ScriptBeeWebApp/src/Common/Common.Web/Common.Web.csproj @@ -1,4 +1,4 @@ - + ScriptBee.Common.Web @@ -16,5 +16,6 @@ + diff --git a/ScriptBeeWebApp/src/Common/Common.Web/Services/ClientIdMiddleware.cs b/ScriptBeeWebApp/src/Common/Common.Web/Services/ClientIdMiddleware.cs new file mode 100644 index 00000000..f41e783d --- /dev/null +++ b/ScriptBeeWebApp/src/Common/Common.Web/Services/ClientIdMiddleware.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Http; +using ScriptBee.Application.Model.Services; + +namespace ScriptBee.Common.Web.Services; + +public class ClientIdMiddleware(RequestDelegate next) +{ + private const string ClientIdHeaderName = "X-Client-Id"; + + public async Task InvokeAsync(HttpContext context, IClientIdProvider clientIdProvider) + { + if (context.Request.Headers.TryGetValue(ClientIdHeaderName, out var clientId)) + { + clientIdProvider.ClientId = clientId; + } + + await next(context); + } +} diff --git a/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Services/ProjectNotificationsService.cs b/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Services/ProjectNotificationsService.cs index bd4e0ecd..bb6b0183 100644 --- a/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Services/ProjectNotificationsService.cs +++ b/ScriptBeeWebApp/src/Gateway/Adapters/Notifications.SignalR/Services/ProjectNotificationsService.cs @@ -1,4 +1,6 @@ +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.DependencyInjection; using ScriptBee.Adapters.Notifications.SignalR.Hubs; using ScriptBee.Application.Model.Services; using ScriptBee.Domain.Model.Project; @@ -9,31 +11,53 @@ namespace ScriptBee.Adapters.Notifications.SignalR.Services; public class ProjectNotificationsService( IHubContext hubContext, - IClientIdProvider clientIdProvider + IHttpContextAccessor httpContextAccessor ) : IProjectNotificationsService { private const string ScriptsChannel = "scripts"; - public Task NotifyScriptCreated(ScriptCreatedEvent scriptCreatedEvent, CancellationToken cancellationToken) + public Task NotifyScriptCreated( + ScriptCreatedEvent scriptCreatedEvent, + CancellationToken cancellationToken + ) { var groupName = GetGroupName(scriptCreatedEvent.ProjectId, ScriptsChannel); - var message = scriptCreatedEvent with { ClientId = clientIdProvider.ClientId ?? "" }; - return hubContext.Clients.Group(groupName).SendAsync("ScriptCreated", message, cancellationToken); + var message = scriptCreatedEvent with { ClientId = GetClientId() }; + return hubContext + .Clients.Group(groupName) + .SendAsync("ScriptCreated", message, cancellationToken); } - public Task NotifyScriptUpdated(ScriptUpdatedEvent scriptUpdatedEvent, CancellationToken cancellationToken) + public Task NotifyScriptUpdated( + ScriptUpdatedEvent scriptUpdatedEvent, + CancellationToken cancellationToken + ) { var groupName = GetGroupName(scriptUpdatedEvent.ProjectId, ScriptsChannel); - var message = scriptUpdatedEvent with { ClientId = clientIdProvider.ClientId ?? "" }; - return hubContext.Clients.Group(groupName).SendAsync("ScriptUpdated", message, cancellationToken); + var message = scriptUpdatedEvent with { ClientId = GetClientId() }; + return hubContext + .Clients.Group(groupName) + .SendAsync("ScriptUpdated", message, cancellationToken); } - public Task NotifyScriptDeleted(ScriptDeletedEvent scriptDeletedEvent, CancellationToken cancellationToken) + public Task NotifyScriptDeleted( + ScriptDeletedEvent scriptDeletedEvent, + CancellationToken cancellationToken + ) { var groupName = GetGroupName(scriptDeletedEvent.ProjectId, ScriptsChannel); - var message = scriptDeletedEvent with { ClientId = clientIdProvider.ClientId ?? "" }; - return hubContext.Clients.Group(groupName).SendAsync("ScriptDeleted", message, cancellationToken); + var message = scriptDeletedEvent with { ClientId = GetClientId() }; + return hubContext + .Clients.Group(groupName) + .SendAsync("ScriptDeleted", message, cancellationToken); } - private static string GetGroupName(ProjectId projectId, string channelName) => $"{projectId.Value}_{channelName}"; + private static string GetGroupName(ProjectId projectId, string channelName) => + $"{projectId.Value}_{channelName}"; + + private string GetClientId() + { + var clientIdProvider = httpContextAccessor.HttpContext?.RequestServices.GetService(); + return clientIdProvider?.ClientId ?? ""; + } } diff --git a/ScriptBeeWebApp/src/Gateway/Adapters/Web/Extensions/CommonServicesExtensions.cs b/ScriptBeeWebApp/src/Gateway/Adapters/Web/Extensions/CommonServicesExtensions.cs index 5b87a2a5..0101ebbd 100644 --- a/ScriptBeeWebApp/src/Gateway/Adapters/Web/Extensions/CommonServicesExtensions.cs +++ b/ScriptBeeWebApp/src/Gateway/Adapters/Web/Extensions/CommonServicesExtensions.cs @@ -1,4 +1,5 @@ -using ScriptBee.Common; +using ScriptBee.Application.Model.Services; +using ScriptBee.Common; using ScriptBee.Service.Project.Plugin; using ScriptBee.Service.Project.ProjectStructure; using ScriptBee.UseCases.Project.ProjectStructure; @@ -12,6 +13,7 @@ public static IServiceCollection AddCommonServices(this IServiceCollection servi return services .AddSingleton() .AddSingleton() + .AddScoped() .AddSingleton() .AddSingleton(); } diff --git a/ScriptBeeWebApp/src/Gateway/Adapters/Web/Program.cs b/ScriptBeeWebApp/src/Gateway/Adapters/Web/Program.cs index 9a672770..69d365ff 100644 --- a/ScriptBeeWebApp/src/Gateway/Adapters/Web/Program.cs +++ b/ScriptBeeWebApp/src/Gateway/Adapters/Web/Program.cs @@ -1,9 +1,12 @@ using FluentValidation; using Microsoft.AspNetCore.Server.Kestrel.Core; +using ScriptBee.Adapters.Notifications.SignalR; +using ScriptBee.Adapters.Notifications.SignalR.Hubs; using ScriptBee.Artifacts.Extensions; using ScriptBee.Common.Web; using ScriptBee.Common.Web.EndpointDefinition; using ScriptBee.Common.Web.Extensions; +using ScriptBee.Common.Web.Services; using ScriptBee.Marketplace.Client.Extensions; using ScriptBee.Rest.Extensions; using ScriptBee.Web.EndpointDefinitions; @@ -17,6 +20,7 @@ builder .Services.AddConfiguredHealthChecks() + .AddHttpContextAccessor() .AddSerilog() .AddOpenApi() .AddValidatorsFromAssemblyContaining() @@ -28,8 +32,7 @@ .AddAnalysisConfig(builder.Configuration) .AddScriptBeeMarketplaceClient() .AddInstallPluginsForAllocatedInstancesServices() - // .AddFileWatcherServices() - .AddSignalR(); + .AddProjectLiveUpdates(); builder.Services.AddEndpointDefinitions( typeof(IEndpointDefinition), @@ -54,6 +57,7 @@ app.UseDefaultFiles(); app.UseStaticFiles(); app.UseRouting(); +app.UseMiddleware(); app.MapHealthChecksEndpoint(); @@ -63,10 +67,10 @@ app.UseEndpoints(_ => { }); +app.MapHub("/api/projectLiveUpdates"); + app.UseEndpointDefinitions(); app.MapFallbackToFile("index.html"); app.Run(); - -public partial class Program; diff --git a/ScriptBeeWebApp/src/Gateway/Adapters/Web/Web.csproj b/ScriptBeeWebApp/src/Gateway/Adapters/Web/Web.csproj index 60e0282e..43386acd 100644 --- a/ScriptBeeWebApp/src/Gateway/Adapters/Web/Web.csproj +++ b/ScriptBeeWebApp/src/Gateway/Adapters/Web/Web.csproj @@ -21,5 +21,6 @@ + diff --git a/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/IProjectNotificationsService.cs b/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/IProjectNotificationsService.cs index e7c6aaee..c11591af 100644 --- a/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/IProjectNotificationsService.cs +++ b/ScriptBeeWebApp/src/Gateway/Application/Ports.Notifications/IProjectNotificationsService.cs @@ -4,9 +4,18 @@ namespace ScriptBee.Ports.Notifications; public interface IProjectNotificationsService { - Task NotifyScriptCreated(ScriptCreatedEvent scriptCreatedEvent, CancellationToken cancellationToken); + Task NotifyScriptCreated( + ScriptCreatedEvent scriptCreatedEvent, + CancellationToken cancellationToken + ); - Task NotifyScriptUpdated(ScriptUpdatedEvent scriptUpdatedEvent, CancellationToken cancellationToken); + Task NotifyScriptUpdated( + ScriptUpdatedEvent scriptUpdatedEvent, + CancellationToken cancellationToken + ); - Task NotifyScriptDeleted(ScriptDeletedEvent scriptDeletedEvent, CancellationToken cancellationToken); + Task NotifyScriptDeleted( + ScriptDeletedEvent scriptDeletedEvent, + CancellationToken cancellationToken + ); } diff --git a/ScriptBeeWebApp/src/Gateway/Application/Service.Project/ProjectStructure/CreateScriptService.cs b/ScriptBeeWebApp/src/Gateway/Application/Service.Project/ProjectStructure/CreateScriptService.cs index 93b04910..679e134e 100644 --- a/ScriptBeeWebApp/src/Gateway/Application/Service.Project/ProjectStructure/CreateScriptService.cs +++ b/ScriptBeeWebApp/src/Gateway/Application/Service.Project/ProjectStructure/CreateScriptService.cs @@ -1,4 +1,4 @@ -using DxWorks.ScriptBee.Plugin.Api; +using DxWorks.ScriptBee.Plugin.Api; using OneOf; using ScriptBee.Artifacts; using ScriptBee.Common; @@ -6,6 +6,8 @@ using ScriptBee.Domain.Model.Errors; using ScriptBee.Domain.Model.Project; using ScriptBee.Domain.Model.ProjectStructure; +using ScriptBee.Ports.Notifications; +using ScriptBee.Ports.Notifications.Events; using ScriptBee.Ports.Project; using ScriptBee.Service.Project.Plugin; using ScriptBee.UseCases.Project.ProjectStructure; @@ -24,6 +26,7 @@ public sealed class CreateScriptService( ICreateFile createFile, IGuidProvider guidProvider, ICreateScript createScript, + IProjectNotificationsService projectNotificationsService, ScriptGeneratorStrategyFactory scriptGeneratorStrategyFactory ) : ICreateScriptUseCase { @@ -106,6 +109,11 @@ CancellationToken cancellationToken await createScript.Create(script, cancellationToken); + await projectNotificationsService.NotifyScriptCreated( + new ScriptCreatedEvent(projectDetails.Id, script.Id), + cancellationToken + ); + return script; } } diff --git a/ScriptBeeWebApp/src/Gateway/Application/Service.Project/ProjectStructure/DeleteProjectFilesService.cs b/ScriptBeeWebApp/src/Gateway/Application/Service.Project/ProjectStructure/DeleteProjectFilesService.cs index cb5a3044..94d5e7de 100644 --- a/ScriptBeeWebApp/src/Gateway/Application/Service.Project/ProjectStructure/DeleteProjectFilesService.cs +++ b/ScriptBeeWebApp/src/Gateway/Application/Service.Project/ProjectStructure/DeleteProjectFilesService.cs @@ -2,6 +2,8 @@ using OneOf.Types; using ScriptBee.Artifacts; using ScriptBee.Domain.Model.Errors; +using ScriptBee.Ports.Notifications; +using ScriptBee.Ports.Notifications.Events; using ScriptBee.Ports.Project; using ScriptBee.UseCases.Project.ProjectStructure; @@ -12,7 +14,8 @@ namespace ScriptBee.Service.Project.ProjectStructure; public sealed class DeleteProjectFilesService( IGetProject getProject, IDeleteScript deleteScript, - IDeleteFileOrFolder deleteFileOrFolder + IDeleteFileOrFolder deleteFileOrFolder, + IProjectNotificationsService projectNotificationsService ) : IDeleteProjectFilesUseCase { public async Task Delete( @@ -38,6 +41,11 @@ CancellationToken cancellationToken if (projectStructureEntry is not null) { deleteFileOrFolder.Delete(command.ProjectId, projectStructureEntry.File.Path); + + await projectNotificationsService.NotifyScriptDeleted( + new ScriptDeletedEvent(command.ProjectId, command.Id), + cancellationToken + ); } return new Success(); diff --git a/ScriptBeeWebApp/src/Gateway/Application/Service.Project/ProjectStructure/UpdateScriptService.cs b/ScriptBeeWebApp/src/Gateway/Application/Service.Project/ProjectStructure/UpdateScriptService.cs index 69740d5f..cef6731a 100644 --- a/ScriptBeeWebApp/src/Gateway/Application/Service.Project/ProjectStructure/UpdateScriptService.cs +++ b/ScriptBeeWebApp/src/Gateway/Application/Service.Project/ProjectStructure/UpdateScriptService.cs @@ -1,8 +1,10 @@ -using OneOf; +using OneOf; using OneOf.Types; using ScriptBee.Artifacts; using ScriptBee.Domain.Model.Errors; using ScriptBee.Domain.Model.ProjectStructure; +using ScriptBee.Ports.Notifications; +using ScriptBee.Ports.Notifications.Events; using ScriptBee.Ports.Project; using ScriptBee.UseCases.Project.ProjectStructure; @@ -15,7 +17,8 @@ public class UpdateScriptService( IGetProject getProject, IGetScripts getScripts, IUpdateScript updateScript, - IUpdateFile updateFile + IUpdateFile updateFile, + IProjectNotificationsService projectNotificationsService ) : IUpdateScriptUseCase { public async Task Update( @@ -88,7 +91,14 @@ CancellationToken cancellationToken updateFile.RenameFile(command.ProjectId, script.File.Path, updatedScript.File.Path); } - return await updateScript.Update(updatedScript, cancellationToken); + var result = await updateScript.Update(updatedScript, cancellationToken); + + await projectNotificationsService.NotifyScriptUpdated( + new ScriptUpdatedEvent(command.ProjectId, script.Id), + cancellationToken + ); + + return result; } private async Task UpdateScriptContent( @@ -107,6 +117,12 @@ await updateFile.UpdateContent( command.Content, cancellationToken ); + + await projectNotificationsService.NotifyScriptUpdated( + new ScriptUpdatedEvent(command.ProjectId, script.Id), + cancellationToken + ); + return new Success(); }, error => Task.FromResult(error) diff --git a/ScriptBeeWebApp/src/Gateway/Application/Service.Project/Service.Project.csproj b/ScriptBeeWebApp/src/Gateway/Application/Service.Project/Service.Project.csproj index ca597d52..6bac8996 100644 --- a/ScriptBeeWebApp/src/Gateway/Application/Service.Project/Service.Project.csproj +++ b/ScriptBeeWebApp/src/Gateway/Application/Service.Project/Service.Project.csproj @@ -14,6 +14,7 @@ + diff --git a/ScriptBeeWebApp/src/Gateway/Application/UseCases.Project/ProjectStructure/CreateScriptCommand.cs b/ScriptBeeWebApp/src/Gateway/Application/UseCases.Project/ProjectStructure/CreateScriptCommand.cs index 43618272..893ec309 100644 --- a/ScriptBeeWebApp/src/Gateway/Application/UseCases.Project/ProjectStructure/CreateScriptCommand.cs +++ b/ScriptBeeWebApp/src/Gateway/Application/UseCases.Project/ProjectStructure/CreateScriptCommand.cs @@ -1,4 +1,4 @@ -using DxWorks.ScriptBee.Plugin.Api.Model; +using DxWorks.ScriptBee.Plugin.Api.Model; using ScriptBee.Domain.Model.Project; namespace ScriptBee.UseCases.Project.ProjectStructure; diff --git a/ScriptBeeWebApp/test/Analysis/Application/Service.Analysis.Tests/FileBundlerTests.cs b/ScriptBeeWebApp/test/Analysis/Application/Service.Analysis.Tests/FileBundlerTests.cs index 82435e8b..93785df2 100644 --- a/ScriptBeeWebApp/test/Analysis/Application/Service.Analysis.Tests/FileBundlerTests.cs +++ b/ScriptBeeWebApp/test/Analysis/Application/Service.Analysis.Tests/FileBundlerTests.cs @@ -21,7 +21,7 @@ public async Task WriteToStream_ShouldEncodeFilesCorrectly() using var stream = new MemoryStream(); // Act - await _fileBundler.WriteToStream(files, stream, CancellationToken.None); + await _fileBundler.WriteToStream(files, stream, TestContext.Current.CancellationToken); stream.Position = 0; // Assert @@ -51,16 +51,15 @@ public async Task WriteToStream_ShouldEncodeFilesCorrectly() public async Task WriteToStream_EmptyList_ShouldWriteOnlyEndMarker() { // Arrange - var files = new List(); using var stream = new MemoryStream(); // Act - await _fileBundler.WriteToStream(files, stream, CancellationToken.None); + await _fileBundler.WriteToStream([], stream, TestContext.Current.CancellationToken); stream.Position = 0; // Assert - byte[] buffer = new byte[4]; - var read = await stream.ReadAsync(buffer); + var buffer = new byte[4]; + var read = await stream.ReadAsync(buffer, TestContext.Current.CancellationToken); Assert.Equal(4, read); Assert.Equal(0, BinaryPrimitives.ReadInt32BigEndian(buffer)); Assert.Equal(stream.Length, stream.Position); diff --git a/ScriptBeeWebApp/test/Gateway/Adapters/Notifications.SignalR.Tests/Services/ProjectNotificationsServiceTests.cs b/ScriptBeeWebApp/test/Gateway/Adapters/Notifications.SignalR.Tests/Services/ProjectNotificationsServiceTests.cs index 6741a698..bc09cb05 100644 --- a/ScriptBeeWebApp/test/Gateway/Adapters/Notifications.SignalR.Tests/Services/ProjectNotificationsServiceTests.cs +++ b/ScriptBeeWebApp/test/Gateway/Adapters/Notifications.SignalR.Tests/Services/ProjectNotificationsServiceTests.cs @@ -1,3 +1,4 @@ +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.SignalR; using NSubstitute; using ScriptBee.Adapters.Notifications.SignalR.Hubs; @@ -19,11 +20,19 @@ public class ProjectNotificationsServiceTests public ProjectNotificationsServiceTests() { var hubContext = Substitute.For>(); + var httpContextAccessor = Substitute.For(); + var httpContext = Substitute.For(); + var serviceProvider = Substitute.For(); hubContext.Clients.Returns(_hubClients); _hubClients.Group(Arg.Any()).Returns(_clientProxy); + _clientIdProvider.ClientId.Returns("test-client"); - _service = new ProjectNotificationsService(hubContext, _clientIdProvider); + httpContextAccessor.HttpContext.Returns(httpContext); + httpContext.RequestServices.Returns(serviceProvider); + serviceProvider.GetService(typeof(IClientIdProvider)).Returns(_clientIdProvider); + + _service = new ProjectNotificationsService(hubContext, httpContextAccessor); } [Fact] @@ -35,7 +44,7 @@ public async Task NotifyScriptCreated_SendsMessageToCorrectGroup() var ev = new ScriptCreatedEvent(projectId, scriptId); // Act - await _service.NotifyScriptCreated(ev, CancellationToken.None); + await _service.NotifyScriptCreated(ev, TestContext.Current.CancellationToken); // Assert _hubClients.Received(1).Group("test-project_scripts"); @@ -43,8 +52,12 @@ await _clientProxy .Received(1) .SendCoreAsync( "ScriptCreated", - Arg.Is(args => (ScriptCreatedEvent)args[0] == ev), - CancellationToken.None + Arg.Is(args => + ((ScriptCreatedEvent)args[0]).ProjectId == ev.ProjectId + && ((ScriptCreatedEvent)args[0]).ScriptId == ev.ScriptId + && ((ScriptCreatedEvent)args[0]).ClientId == "test-client" + ), + TestContext.Current.CancellationToken ); } @@ -57,7 +70,7 @@ public async Task NotifyScriptUpdated_SendsMessageToCorrectGroup() var ev = new ScriptUpdatedEvent(projectId, scriptId); // Act - await _service.NotifyScriptUpdated(ev, CancellationToken.None); + await _service.NotifyScriptUpdated(ev, TestContext.Current.CancellationToken); // Assert _hubClients.Received(1).Group("test-project_scripts"); @@ -65,8 +78,12 @@ await _clientProxy .Received(1) .SendCoreAsync( "ScriptUpdated", - Arg.Is(args => (ScriptUpdatedEvent)args[0] == ev), - CancellationToken.None + Arg.Is(args => + ((ScriptUpdatedEvent)args[0]).ProjectId == ev.ProjectId + && ((ScriptUpdatedEvent)args[0]).ScriptId == ev.ScriptId + && ((ScriptUpdatedEvent)args[0]).ClientId == "test-client" + ), + TestContext.Current.CancellationToken ); } @@ -79,7 +96,7 @@ public async Task NotifyScriptDeleted_SendsMessageToCorrectGroup() var ev = new ScriptDeletedEvent(projectId, scriptId); // Act - await _service.NotifyScriptDeleted(ev, CancellationToken.None); + await _service.NotifyScriptDeleted(ev, TestContext.Current.CancellationToken); // Assert _hubClients.Received(1).Group("test-project_scripts"); @@ -87,8 +104,12 @@ await _clientProxy .Received(1) .SendCoreAsync( "ScriptDeleted", - Arg.Is(args => (ScriptDeletedEvent)args[0] == ev), - CancellationToken.None + Arg.Is(args => + ((ScriptDeletedEvent)args[0]).ProjectId == ev.ProjectId + && ((ScriptDeletedEvent)args[0]).ScriptId == ev.ScriptId + && ((ScriptDeletedEvent)args[0]).ClientId == "test-client" + ), + TestContext.Current.CancellationToken ); } } diff --git a/ScriptBeeWebApp/test/Gateway/Application/Service.Project.Tests/ProjectStructure/CreateScriptServiceTest.cs b/ScriptBeeWebApp/test/Gateway/Application/Service.Project.Tests/ProjectStructure/CreateScriptServiceTest.cs index fb43211f..775d0816 100644 --- a/ScriptBeeWebApp/test/Gateway/Application/Service.Project.Tests/ProjectStructure/CreateScriptServiceTest.cs +++ b/ScriptBeeWebApp/test/Gateway/Application/Service.Project.Tests/ProjectStructure/CreateScriptServiceTest.cs @@ -1,4 +1,4 @@ -using DxWorks.ScriptBee.Plugin.Api.Model; +using DxWorks.ScriptBee.Plugin.Api.Model; using NSubstitute; using OneOf; using ScriptBee.Artifacts; @@ -6,6 +6,8 @@ using ScriptBee.Domain.Model.Errors; using ScriptBee.Domain.Model.Project; using ScriptBee.Domain.Model.ProjectStructure; +using ScriptBee.Ports.Notifications; +using ScriptBee.Ports.Notifications.Events; using ScriptBee.Ports.Project; using ScriptBee.Service.Project.Plugin; using ScriptBee.Service.Project.ProjectStructure; @@ -22,6 +24,9 @@ public class CreateScriptServiceTest private readonly IGuidProvider _guidProvider = Substitute.For(); private readonly ICreateScript _createScript = Substitute.For(); + private readonly IProjectNotificationsService _projectNotificationsService = + Substitute.For(); + private readonly CreateScriptService _createScriptService; public CreateScriptServiceTest() @@ -31,6 +36,7 @@ public CreateScriptServiceTest() _createFile, _guidProvider, _createScript, + _projectNotificationsService, new ScriptGeneratorStrategyFactory() ); } @@ -194,6 +200,13 @@ await _createScript Arg.Is