diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 0c20e61..e90bc6e 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -32,6 +32,7 @@ - **External Integrations:** Use `src/Core.Application/Common/` and `src/Infrastructure.AgentFramework/` for connectors. - **RBAC & Security:** Enforced in API layer, see `ConfigureServicesAuth.cs`. - **Auth-Triggered User Provisioning:** Keep the flow UI-tied (not middleware/pipeline) because OBO token acquisition requires user context on the main UI thread/circuit. +- **Not-Found Integration Scenarios:** When adding not-found integration scenarios for commands, use non-empty IDs for missing entities so tests validate not-found behavior instead of bad-request validation for empty IDs. ## UI Design Guidelines - **ChatPage Layout:** Implement distinct desktop and mobile layouts with a strict row/column structure on mobile. diff --git a/src/Core.Application/Abstractions/IActorsTool.cs b/src/Core.Application/Abstractions/IActorsTool.cs index e042c28..69066a7 100644 --- a/src/Core.Application/Abstractions/IActorsTool.cs +++ b/src/Core.Application/Abstractions/IActorsTool.cs @@ -2,6 +2,6 @@ public interface IActorsTool { - Task GetActorByIdAsync(Guid actorId, CancellationToken cancellationToken); + Task GetActorByIdAsync(Guid actorId, CancellationToken cancellationToken); Task> GetActorsByNameAsync(string name, CancellationToken cancellationToken); -} \ No newline at end of file +} diff --git a/src/Core.Application/Abstractions/IChatSessionsTool.cs b/src/Core.Application/Abstractions/IChatSessionsTool.cs index 8a5c185..12b3952 100644 --- a/src/Core.Application/Abstractions/IChatSessionsTool.cs +++ b/src/Core.Application/Abstractions/IChatSessionsTool.cs @@ -3,5 +3,5 @@ public interface IChatSessionsTool { Task> ListRecentSessionsAsync(DateTime? startDate, DateTime? endDate, CancellationToken cancellationToken); - Task UpdateChatSessionTitleAsync(Guid sessionId, string newTitle, CancellationToken cancellationToken); -} \ No newline at end of file + Task UpdateChatSessionTitleAsync(Guid sessionId, string newTitle, CancellationToken cancellationToken); +} diff --git a/src/Core.Application/Actor/ActorGuard.cs b/src/Core.Application/Actor/ActorGuard.cs index ec36d3e..da71625 100644 --- a/src/Core.Application/Actor/ActorGuard.cs +++ b/src/Core.Application/Actor/ActorGuard.cs @@ -1,6 +1,3 @@ -using Goodtocode.AgentFramework.Core.Domain.Actor; -using Goodtocode.AgentFramework.Core.Domain.Chat; - namespace Goodtocode.AgentFramework.Core.Application.Actor; public static class ActorGuard @@ -13,22 +10,6 @@ public static void GuardAgainstEmptyUserContext(IUserContext? userContext) ]); } - public static void GuardAgainstNotFound(ActorEntity? actor) - { - if (actor == null) - { - throw new CustomNotFoundException("Actor Not Found"); - } - } - - public static void GuardAgainstNotFound(ChatSessionEntity? entity) - { - if (entity is null) - { - throw new CustomNotFoundException("Chat Session Not Found"); - } - } - public static void GuardAgainstInvalidUserContext(IUserContext? userContext) { if (userContext is null) diff --git a/src/Core.Application/Actor/DeleteOurActorByOwnerIdCommand.cs b/src/Core.Application/Actor/DeleteOurActorByOwnerIdCommand.cs index 4dc78c0..08e50de 100644 --- a/src/Core.Application/Actor/DeleteOurActorByOwnerIdCommand.cs +++ b/src/Core.Application/Actor/DeleteOurActorByOwnerIdCommand.cs @@ -1,21 +1,26 @@ namespace Goodtocode.AgentFramework.Core.Application.Actor; -public class DeleteActorByOwnerIdCommand : UserScopedRequest, IRequest +public class DeleteActorByOwnerIdCommand : UserScopedRequest, IRequest { public Guid OwnerId { get; set; } } -public class DeleteActorByOwnerIdCommandHandler(IAgentFrameworkContext context) : IRequestHandler +public class DeleteActorByOwnerIdCommandHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; - public async Task Handle(DeleteActorByOwnerIdCommand request, CancellationToken cancellationToken) + public async Task Handle(DeleteActorByOwnerIdCommand request, CancellationToken cancellationToken) { var actor = await _context.Actors.Where(x => x.OwnerId == request.OwnerId).FirstOrDefaultAsync(cancellationToken); - ActorGuard.GuardAgainstNotFound(actor); + if (actor is null) + { + return CommandResult.NotFound(); + } - _context.Actors.Remove(actor!); + _context.Actors.Remove(actor); await _context.SaveChangesAsync(cancellationToken); + + return CommandResult.Success(); } -} \ No newline at end of file +} diff --git a/src/Core.Application/Actor/DeleteOurActorCommand.cs b/src/Core.Application/Actor/DeleteOurActorCommand.cs index c007dcb..c1a8252 100644 --- a/src/Core.Application/Actor/DeleteOurActorCommand.cs +++ b/src/Core.Application/Actor/DeleteOurActorCommand.cs @@ -1,21 +1,26 @@ namespace Goodtocode.AgentFramework.Core.Application.Actor; -public class DeleteOurActorCommand : UserScopedRequest, IRequest +public class DeleteOurActorCommand : UserScopedRequest, IRequest { public Guid Id { get; set; } } -public class DeleteActorCommandHandler(IAgentFrameworkContext context) : IRequestHandler +public class DeleteActorCommandHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; - public async Task Handle(DeleteOurActorCommand request, CancellationToken cancellationToken) + public async Task Handle(DeleteOurActorCommand request, CancellationToken cancellationToken) { - var Actor = _context.Actors.Find(request.Id); - ActorGuard.GuardAgainstNotFound(Actor); + var actor = await _context.Actors.FindAsync([request.Id, cancellationToken], cancellationToken: cancellationToken); + if (actor is null) + { + return CommandResult.NotFound(); + } - _context.Actors.Remove(Actor!); + _context.Actors.Remove(actor); await _context.SaveChangesAsync(cancellationToken); + + return CommandResult.Success(); } -} \ No newline at end of file +} diff --git a/src/Core.Application/Actor/GetMyActorQuery.cs b/src/Core.Application/Actor/GetMyActorQuery.cs index fb7036e..1390d81 100644 --- a/src/Core.Application/Actor/GetMyActorQuery.cs +++ b/src/Core.Application/Actor/GetMyActorQuery.cs @@ -1,21 +1,20 @@ namespace Goodtocode.AgentFramework.Core.Application.Actor; -public class GetMyActorQuery : UserScopedRequest, IRequest +public class GetMyActorQuery : UserScopedRequest, IRequest { public Guid OwnerId { get; set; } } -public class GetActorByOwnerIdQueryHandler(IAgentFrameworkContext context) : IRequestHandler +public class GetActorByOwnerIdQueryHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; - public async Task Handle(GetMyActorQuery request, CancellationToken cancellationToken) + public async Task Handle(GetMyActorQuery request, CancellationToken cancellationToken) { var actor = await _context.Actors .FirstOrDefaultAsync(x => x.OwnerId == request.UserContext.OwnerId && x.TenantId == request.UserContext.TenantId, cancellationToken: cancellationToken); - ActorGuard.GuardAgainstNotFound(actor); - return ActorDto.CreateFrom(actor); + return actor is null ? null : ActorDto.CreateFrom(actor); } -} \ No newline at end of file +} diff --git a/src/Core.Application/Actor/GetOurActorChatSessionQuery.cs b/src/Core.Application/Actor/GetOurActorChatSessionQuery.cs index dce441d..286e71e 100644 --- a/src/Core.Application/Actor/GetOurActorChatSessionQuery.cs +++ b/src/Core.Application/Actor/GetOurActorChatSessionQuery.cs @@ -2,23 +2,22 @@ namespace Goodtocode.AgentFramework.Core.Application.Actor; -public class GetOurActorChatSessionQuery : UserScopedRequest, IRequest +public class GetOurActorChatSessionQuery : UserScopedRequest, IRequest { public Guid ActorId { get; set; } public Guid ChatSessionId { get; set; } } -public class GetOurActorChatSessionQueryHandler(IAgentFrameworkContext context) : IRequestHandler +public class GetOurActorChatSessionQueryHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; - public async Task Handle(GetOurActorChatSessionQuery request, CancellationToken cancellationToken) + public async Task Handle(GetOurActorChatSessionQuery request, CancellationToken cancellationToken) { var returnData = await _context.ChatSessions .FirstOrDefaultAsync(x => x.Id == request.ChatSessionId && x.ActorId == request.ActorId && x.TenantId == request.UserContext.TenantId, cancellationToken: cancellationToken); - ActorGuard.GuardAgainstNotFound(returnData); - return ChatSessionDto.CreateFrom(returnData); + return returnData is null ? null : ChatSessionDto.CreateFrom(returnData); } -} \ No newline at end of file +} diff --git a/src/Core.Application/Actor/GetOurActorQuery.cs b/src/Core.Application/Actor/GetOurActorQuery.cs index af03658..241a139 100644 --- a/src/Core.Application/Actor/GetOurActorQuery.cs +++ b/src/Core.Application/Actor/GetOurActorQuery.cs @@ -1,20 +1,19 @@ namespace Goodtocode.AgentFramework.Core.Application.Actor; -public class GetOurActorQuery : UserScopedRequest, IRequest +public class GetOurActorQuery : UserScopedRequest, IRequest { public Guid ActorId { get; set; } } -public class GetActorQueryHandler(IAgentFrameworkContext context) : IRequestHandler +public class GetActorQueryHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; - public async Task Handle(GetOurActorQuery request, CancellationToken cancellationToken) + public async Task Handle(GetOurActorQuery request, CancellationToken cancellationToken) { var actor = await _context.Actors .FirstOrDefaultAsync(x => x.Id == request.ActorId && x.TenantId == request.UserContext.TenantId, cancellationToken: cancellationToken); - ActorGuard.GuardAgainstNotFound(actor); - return ActorDto.CreateFrom(actor); + return actor is null ? null : ActorDto.CreateFrom(actor); } -} \ No newline at end of file +} diff --git a/src/Core.Application/Chat/ChatGuard.cs b/src/Core.Application/Chat/ChatGuard.cs index df49797..61da490 100644 --- a/src/Core.Application/Chat/ChatGuard.cs +++ b/src/Core.Application/Chat/ChatGuard.cs @@ -49,18 +49,6 @@ public static void GuardAgainstNullAgentResponse(object? response) ]); } - public static void GuardAgainstNotFound(ChatSessionEntity? chatSession) - { - if (chatSession == null) - throw new CustomNotFoundException("Chat Session not found."); - } - - public static void GuardAgainstNotFound(ChatMessageEntity? chatMessage) - { - if (chatMessage == null) - throw new CustomNotFoundException("Chat Message Not Found"); - } - public static void GuardAgainstUnauthorized(ChatMessageEntity chatMessage, IUserContext userInfo) { if (chatMessage.ChatSession?.OwnerId != userInfo.OwnerId) diff --git a/src/Core.Application/Chat/CreateMyChatMessageCommand.cs b/src/Core.Application/Chat/CreateMyChatMessageCommand.cs index 6c8f6a9..03a6795 100644 --- a/src/Core.Application/Chat/CreateMyChatMessageCommand.cs +++ b/src/Core.Application/Chat/CreateMyChatMessageCommand.cs @@ -4,30 +4,34 @@ namespace Goodtocode.AgentFramework.Core.Application.Chat; -public class CreateMyChatMessageCommand : UserScopedRequest, IRequest +public class CreateMyChatMessageCommand : UserScopedRequest, IRequest> { public Guid ChatSessionId { get; set; } public string? Message { get; set; } } -public class CreateChatMessageCommandHandler(AIAgent agent, IAgentFrameworkContext context) : IRequestHandler +public class CreateChatMessageCommandHandler(AIAgent agent, IAgentFrameworkContext context) : IRequestHandler> { private readonly AIAgent _agent = agent; private readonly IAgentFrameworkContext _context = context; - public async Task Handle(CreateMyChatMessageCommand request, CancellationToken cancellationToken) + public async Task> Handle(CreateMyChatMessageCommand request, CancellationToken cancellationToken) { ChatGuard.GuardAgainstEmptyMessage(request?.Message); ChatGuard.GuardAgainstEmptyUser(request?.UserContext); var chatSession = await _context.ChatSessions .FirstOrDefaultAsync(x => x.Id == request!.ChatSessionId && x.OwnerId == request.UserContext.OwnerId && x.TenantId == request.UserContext.TenantId, cancellationToken); - ChatGuard.GuardAgainstNotFound(chatSession); - ChatGuard.GuardAgainstUnauthorized(chatSession!, request!.UserContext!); + if (chatSession is null) + { + return CommandResult.NotFound(); + } + + ChatGuard.GuardAgainstUnauthorized(chatSession, request!.UserContext!); var chatHistory = new List(); - foreach (ChatMessageEntity message in chatSession!.Messages) + foreach (ChatMessageEntity message in chatSession.Messages) { chatHistory.Add(new ChatMessage( role: message.Role == ChatMessageRole.user ? ChatRole.User : ChatRole.Assistant, @@ -64,6 +68,6 @@ public async Task Handle(CreateMyChatMessageCommand request, Can await _context.SaveChangesAsync(cancellationToken); - return ChatMessageDto.CreateFrom(chatMessage); + return CommandResult.Success(ChatMessageDto.CreateFrom(chatMessage)); } } diff --git a/src/Core.Application/Chat/DeleteMyChatSessionCommand.cs b/src/Core.Application/Chat/DeleteMyChatSessionCommand.cs index 022fbb9..61f9ca9 100644 --- a/src/Core.Application/Chat/DeleteMyChatSessionCommand.cs +++ b/src/Core.Application/Chat/DeleteMyChatSessionCommand.cs @@ -1,24 +1,30 @@ namespace Goodtocode.AgentFramework.Core.Application.Chat; -public class DeleteMyChatSessionCommand : UserScopedRequest, IRequest +public class DeleteMyChatSessionCommand : UserScopedRequest, IRequest { public Guid Id { get; set; } } -public class DeleteMyChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler +public class DeleteMyChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; - public async Task Handle(DeleteMyChatSessionCommand request, CancellationToken cancellationToken) + public async Task Handle(DeleteMyChatSessionCommand request, CancellationToken cancellationToken) { ChatGuard.GuardAgainstEmptyUser(request?.UserContext); - var chatSession = _context.ChatSessions.Find(request!.Id); - ChatGuard.GuardAgainstNotFound(chatSession); - ChatGuard.GuardAgainstUnauthorized(chatSession!, request.UserContext!); + var chatSession = await _context.ChatSessions.FindAsync([request!.Id, cancellationToken], cancellationToken: cancellationToken); + if (chatSession is null) + { + return CommandResult.NotFound(); + } - _context.ChatSessions.Remove(chatSession!); + ChatGuard.GuardAgainstUnauthorized(chatSession, request.UserContext!); + + _context.ChatSessions.Remove(chatSession); await _context.SaveChangesAsync(cancellationToken); + + return CommandResult.Success(); } -} \ No newline at end of file +} diff --git a/src/Core.Application/Chat/GetMyChatMessageQuery.cs b/src/Core.Application/Chat/GetMyChatMessageQuery.cs index bea8bc7..1099452 100644 --- a/src/Core.Application/Chat/GetMyChatMessageQuery.cs +++ b/src/Core.Application/Chat/GetMyChatMessageQuery.cs @@ -1,24 +1,28 @@ namespace Goodtocode.AgentFramework.Core.Application.Chat; -public class GetMyChatMessageQuery : UserScopedRequest, IRequest +public class GetMyChatMessageQuery : UserScopedRequest, IRequest { public Guid Id { get; set; } } -public class GetMyChatMessageQueryHandler(IAgentFrameworkContext context) : IRequestHandler +public class GetMyChatMessageQueryHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; - public async Task Handle(GetMyChatMessageQuery request, + public async Task Handle(GetMyChatMessageQuery request, CancellationToken cancellationToken) { ChatGuard.GuardAgainstEmptyUser(request?.UserContext); var chatMessage = await _context.ChatMessages.FindAsync([request!.Id, cancellationToken], cancellationToken: cancellationToken); - ChatGuard.GuardAgainstNotFound(chatMessage); - ChatGuard.GuardAgainstUnauthorized(chatMessage!, request.UserContext!); + if (chatMessage is null) + { + return null; + } + + ChatGuard.GuardAgainstUnauthorized(chatMessage, request.UserContext!); return ChatMessageDto.CreateFrom(chatMessage); } -} \ No newline at end of file +} diff --git a/src/Core.Application/Chat/GetMyChatSessionQuery.cs b/src/Core.Application/Chat/GetMyChatSessionQuery.cs index c177f23..0fc29b4 100644 --- a/src/Core.Application/Chat/GetMyChatSessionQuery.cs +++ b/src/Core.Application/Chat/GetMyChatSessionQuery.cs @@ -1,25 +1,30 @@ namespace Goodtocode.AgentFramework.Core.Application.Chat; -public class GetMyChatSessionQuery : UserScopedRequest, IRequest +public class GetMyChatSessionQuery : UserScopedRequest, IRequest { public Guid Id { get; set; } } -public class GetMyChatSessionQueryHandler(IAgentFrameworkContext context) : IRequestHandler +public class GetMyChatSessionQueryHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; - public async Task Handle(GetMyChatSessionQuery request, CancellationToken cancellationToken) + public async Task Handle(GetMyChatSessionQuery request, CancellationToken cancellationToken) { ChatGuard.GuardAgainstEmptyUserForQuery(request?.UserContext); ChatGuard.GuardAgainstEmptyId(request?.Id); var chatSession = await _context.ChatSessions .FirstOrDefaultAsync(x => x.Id == request!.Id && x.OwnerId == request.UserContext.OwnerId && x.TenantId == request.UserContext.TenantId, cancellationToken: cancellationToken); - ChatGuard.GuardAgainstNotFound(chatSession); - ChatGuard.GuardAgainstUnauthorized(chatSession!, request!.UserContext!); + + if (chatSession is null) + { + return null; + } + + ChatGuard.GuardAgainstUnauthorized(chatSession, request!.UserContext!); return ChatSessionDto.CreateFrom(chatSession); } -} \ No newline at end of file +} diff --git a/src/Core.Application/Chat/PatchMyChatSessionCommand.cs b/src/Core.Application/Chat/PatchMyChatSessionCommand.cs index e3d1fc6..8d08b0f 100644 --- a/src/Core.Application/Chat/PatchMyChatSessionCommand.cs +++ b/src/Core.Application/Chat/PatchMyChatSessionCommand.cs @@ -1,28 +1,34 @@ namespace Goodtocode.AgentFramework.Core.Application.Chat; -public class PatchMyChatSessionCommand : UserScopedRequest, IRequest +public class PatchMyChatSessionCommand : UserScopedRequest, IRequest { public Guid Id { get; set; } public string Title { get; set; } = string.Empty; } -public class PatchChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler +public class PatchChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler { private readonly IAgentFrameworkContext _context = context; - public async Task Handle(PatchMyChatSessionCommand request, CancellationToken cancellationToken) + public async Task Handle(PatchMyChatSessionCommand request, CancellationToken cancellationToken) { ChatGuard.GuardAgainstEmptyTitle(request.Title); ChatGuard.GuardAgainstEmptyUserForPatch(request?.UserContext); - var chatSession = _context.ChatSessions.Find(request!.Id); - ChatGuard.GuardAgainstNotFound(chatSession); - ChatGuard.GuardAgainstUnauthorized(chatSession!, request.UserContext!); + var chatSession = await _context.ChatSessions.FindAsync([request!.Id, cancellationToken], cancellationToken: cancellationToken); + if (chatSession is null) + { + return CommandResult.NotFound(); + } - chatSession!.Update(request.Title); + ChatGuard.GuardAgainstUnauthorized(chatSession, request.UserContext!); + + chatSession.Update(request.Title); _context.ChatSessions.Update(chatSession); await _context.SaveChangesAsync(cancellationToken); + + return CommandResult.Success(); } -} \ No newline at end of file +} diff --git a/src/Core.Application/Common/Models/CommandResult.cs b/src/Core.Application/Common/Models/CommandResult.cs new file mode 100644 index 0000000..390402f --- /dev/null +++ b/src/Core.Application/Common/Models/CommandResult.cs @@ -0,0 +1,19 @@ +namespace Goodtocode.AgentFramework.Core.Application.Common.Models; + +public class CommandResult +{ + public bool IsSuccess { get; init; } + public bool IsNotFound { get; init; } + + public static CommandResult Success() => new() { IsSuccess = true }; + public static CommandResult NotFound() => new() { IsNotFound = true }; +} + +public class CommandResult +{ + public T? Value { get; init; } + public bool IsNotFound { get; init; } + + public static CommandResult Success(T value) => new() { Value = value }; + public static CommandResult NotFound() => new() { IsNotFound = true }; +} \ No newline at end of file diff --git a/src/Core.Application/GlobalUsings.cs b/src/Core.Application/GlobalUsings.cs index 713aa45..968e5b2 100644 --- a/src/Core.Application/GlobalUsings.cs +++ b/src/Core.Application/GlobalUsings.cs @@ -2,6 +2,7 @@ global using Goodtocode.AgentFramework.Core.Application.Common.Auth; global using Goodtocode.AgentFramework.Core.Application.Common.Exceptions; global using Goodtocode.AgentFramework.Core.Application.Common.Mappings; +global using Goodtocode.AgentFramework.Core.Application.Common.Models; global using Goodtocode.AgentFramework.Core.Application.Common.Pagination; global using Goodtocode.AgentFramework.Core.Application.Common.Validators; global using Goodtocode.Mediator; diff --git a/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs b/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs index 8f569e1..452f268 100644 --- a/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs +++ b/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs @@ -26,7 +26,7 @@ public sealed class ActorsTool(IServiceProvider serviceProvider) : AITool, IActo private Dictionary _currentParameters = []; [Description("Returns structured actor info by ID including name, status, and explanation.")] - public async Task GetActorByIdAsync(Guid actorId, CancellationToken cancellationToken) + public async Task GetActorByIdAsync(Guid actorId, CancellationToken cancellationToken) { _currentFunctionName = "get_actor_by_id"; _currentParameters = new() @@ -40,13 +40,7 @@ public async Task GetActorByIdAsync(Guid actorId, CancellationTo if (actor == null) { - return new ActorResponse - { - ActorId = actorId, - Name = null, - Status = "NotFound", - Message = "No actor found with the specified ID." - }; + return null; } return new ActorResponse @@ -95,27 +89,14 @@ public async Task> GetActorsByNameAsync(string name, ) .ToListAsync(cancellationToken); - if (actors.Count == 0) + return [.. actors.Select(a => new ActorResponse { - return [ new ActorResponse - { - ActorId = Guid.Empty, - Name = name, - Status = "NotFound", - Message = "No actor found with the specified name." - } ]; - } - else - { - return [.. actors.Select(a => new ActorResponse - { - ActorId = a.Id, - Name = $"{a.FirstName} {a.LastName}", - Status = string.IsNullOrWhiteSpace($"{a.FirstName} {a.LastName}") ? "Partial" : "Found", - Message = string.IsNullOrWhiteSpace($"{a.FirstName} {a.LastName}") - ? "Actor exists but name is not yet linked to Entra External ID." - : "Actor found." - })]; - } + ActorId = a.Id, + Name = $"{a.FirstName} {a.LastName}", + Status = string.IsNullOrWhiteSpace($"{a.FirstName} {a.LastName}") ? "Partial" : "Found", + Message = string.IsNullOrWhiteSpace($"{a.FirstName} {a.LastName}") + ? "Actor exists but name is not yet linked to Entra External ID." + : "Actor found." + })]; } } diff --git a/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs b/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs index 736afa1..124a065 100644 --- a/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs +++ b/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs @@ -44,7 +44,7 @@ public async Task> ListRecentSessionsAsync(DateTime? startDa } [Description("Changes the title on this chat session.")] - public async Task UpdateChatSessionTitleAsync(Guid sessionId, string newTitle, CancellationToken cancellationToken = default) + public async Task UpdateChatSessionTitleAsync(Guid sessionId, string newTitle, CancellationToken cancellationToken = default) { _currentFunctionName = "change_title"; _currentParameters = new() @@ -61,7 +61,7 @@ public async Task UpdateChatSessionTitleAsync(Guid sessionId, string new if (chatSession == null) { - return $"Session {sessionId} not found."; + return null; } chatSession.Update(newTitle); diff --git a/src/Infrastructure.SqlServer/Migrations/20260729054123_InitialCreate-AgentFrameworkContext.Designer.cs b/src/Infrastructure.SqlServer/Migrations/20260809193939_InitialCreate-AgentFrameworkContext.Designer.cs similarity index 99% rename from src/Infrastructure.SqlServer/Migrations/20260729054123_InitialCreate-AgentFrameworkContext.Designer.cs rename to src/Infrastructure.SqlServer/Migrations/20260809193939_InitialCreate-AgentFrameworkContext.Designer.cs index b80e9f2..8af13c8 100644 --- a/src/Infrastructure.SqlServer/Migrations/20260729054123_InitialCreate-AgentFrameworkContext.Designer.cs +++ b/src/Infrastructure.SqlServer/Migrations/20260809193939_InitialCreate-AgentFrameworkContext.Designer.cs @@ -12,7 +12,7 @@ namespace Goodtocode.AgentFramework.Infrastructure.SqlServer.Migrations { [DbContext(typeof(AgentFrameworkContext))] - [Migration("20260729054123_InitialCreate-AgentFrameworkContext")] + [Migration("20260809193939_InitialCreate-AgentFrameworkContext")] partial class InitialCreateAgentFrameworkContext { /// diff --git a/src/Infrastructure.SqlServer/Migrations/20260729054123_InitialCreate-AgentFrameworkContext.cs b/src/Infrastructure.SqlServer/Migrations/20260809193939_InitialCreate-AgentFrameworkContext.cs similarity index 100% rename from src/Infrastructure.SqlServer/Migrations/20260729054123_InitialCreate-AgentFrameworkContext.cs rename to src/Infrastructure.SqlServer/Migrations/20260809193939_InitialCreate-AgentFrameworkContext.cs diff --git a/src/Presentation.Api/Common/ApiResponseMapper.cs b/src/Presentation.Api/Common/ApiResponseMapper.cs new file mode 100644 index 0000000..ea48796 --- /dev/null +++ b/src/Presentation.Api/Common/ApiResponseMapper.cs @@ -0,0 +1,26 @@ +using Goodtocode.AgentFramework.Core.Application.Common.Models; + +namespace Goodtocode.AgentFramework.Presentation.Api.Common; + +public static class ApiResponseMapper +{ + public static IResult SingleOrNotFound(T? value) + { + return value is null ? TypedResults.NotFound() : TypedResults.Ok(value); + } + + public static IResult ListOrOk(IEnumerable? values) + { + return TypedResults.Ok(values ?? Enumerable.Empty()); + } + + public static IResult FromCommand(CommandResult result) + { + return result.IsNotFound ? TypedResults.NotFound() : TypedResults.NoContent(); + } + + public static IResult FromCommand(CommandResult result) + { + return result.IsNotFound || result.Value is null ? TypedResults.NotFound() : TypedResults.Ok(result.Value); + } +} diff --git a/src/Presentation.Api/Endpoints/Actor/MyActorEndpoints.cs b/src/Presentation.Api/Endpoints/Actor/MyActorEndpoints.cs index 834f3d5..65e23dd 100644 --- a/src/Presentation.Api/Endpoints/Actor/MyActorEndpoints.cs +++ b/src/Presentation.Api/Endpoints/Actor/MyActorEndpoints.cs @@ -38,9 +38,10 @@ public static IEndpointRouteBuilder MapMyActorEndpoints(this IEndpointRouteBuild return endpoints; } - private static async Task GetMyActorProfile(ISender sender, Guid ownerId) + private static async Task GetMyActorProfile(ISender sender, Guid ownerId) { - return await sender.Send(new GetMyActorQuery { OwnerId = ownerId }); + var actor = await sender.Send(new GetMyActorQuery { OwnerId = ownerId }); + return ApiResponseMapper.SingleOrNotFound(actor); } private static async Task SaveMyActor(HttpContext httpContext, ISender sender, SaveMyActorCommand command) diff --git a/src/Presentation.Api/Endpoints/Chat/MyChatMessageEndpoints.cs b/src/Presentation.Api/Endpoints/Chat/MyChatMessageEndpoints.cs index 7444d32..0ad98c9 100644 --- a/src/Presentation.Api/Endpoints/Chat/MyChatMessageEndpoints.cs +++ b/src/Presentation.Api/Endpoints/Chat/MyChatMessageEndpoints.cs @@ -38,6 +38,7 @@ public static IEndpointRouteBuilder MapMyChatMessageEndpoints(this IEndpointRout .Produces(StatusCodes.Status201Created) .Produces(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status500InternalServerError); group.MapPatch("{id:guid}", Patch) @@ -50,9 +51,10 @@ public static IEndpointRouteBuilder MapMyChatMessageEndpoints(this IEndpointRout return endpoints; } - private static async Task Get(ISender sender, Guid id) + private static async Task Get(ISender sender, Guid id) { - return await sender.Send(new GetMyChatMessageQuery { Id = id }); + var message = await sender.Send(new GetMyChatMessageQuery { Id = id }); + return ApiResponseMapper.SingleOrNotFound(message); } private static async Task GetPaginated(ISender sender, [AsParameters] GetMyChatMessagesPaginatedQuery query) @@ -64,18 +66,23 @@ private static async Task GetPaginated(ISender sender, [AsParameters] G private static async Task Post(HttpContext httpContext, ISender sender, CreateMyChatMessageCommand command) { var response = await sender.Send(command); + if (response.IsNotFound || response.Value is null) + { + return TypedResults.NotFound(); + } + var version = httpContext.Request.RouteValues["version"]?.ToString() ?? "1.0"; return TypedResults.CreatedAtRoute( - response, + response.Value, "GetMyChatMessage", - new { version, id = response.Id }); + new { version, id = response.Value.Id }); } private static async Task Patch(ISender sender, Guid id, PatchMyChatSessionCommand command) { command.Id = id; - await sender.Send(command); - return TypedResults.NoContent(); + var result = await sender.Send(command); + return ApiResponseMapper.FromCommand(result); } } diff --git a/src/Presentation.Api/Endpoints/Chat/MyChatSessionEndpoints.cs b/src/Presentation.Api/Endpoints/Chat/MyChatSessionEndpoints.cs index c894a76..4663c1a 100644 --- a/src/Presentation.Api/Endpoints/Chat/MyChatSessionEndpoints.cs +++ b/src/Presentation.Api/Endpoints/Chat/MyChatSessionEndpoints.cs @@ -24,7 +24,6 @@ public static IEndpointRouteBuilder MapMyChatSessionEndpoints(this IEndpointRout .WithName("GetMyChatSessions") .Produces>(StatusCodes.Status200OK) .Produces(StatusCodes.Status401Unauthorized) - .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status500InternalServerError); group.MapGet("Paginated", GetPaginated) @@ -50,9 +49,10 @@ public static IEndpointRouteBuilder MapMyChatSessionEndpoints(this IEndpointRout return endpoints; } - private static async Task> GetAll(ISender sender) + private static async Task GetAll(ISender sender) { - return await sender.Send(new GetMyChatSessionsQuery()); + var sessions = await sender.Send(new GetMyChatSessionsQuery()); + return ApiResponseMapper.ListOrOk(sessions); } private static async Task GetPaginated( @@ -74,9 +74,10 @@ private static async Task GetPaginated( return TypedResults.Ok(result); } - private static async Task Get(ISender sender, Guid id) + private static async Task Get(ISender sender, Guid id) { - return await sender.Send(new GetMyChatSessionQuery { Id = id }); + var session = await sender.Send(new GetMyChatSessionQuery { Id = id }); + return ApiResponseMapper.SingleOrNotFound(session); } private static async Task Post(HttpContext httpContext, ISender sender, CreateMyChatSessionCommand command) diff --git a/src/Presentation.Web/Features/Chat/ChatPage.razor b/src/Presentation.Web/Features/Chat/ChatPage.razor index b807174..e2aeb60 100644 --- a/src/Presentation.Web/Features/Chat/ChatPage.razor +++ b/src/Presentation.Web/Features/Chat/ChatPage.razor @@ -138,7 +138,12 @@ private async Task HandleMessageSubmitted() { - chatSessions.RefreshItem(await chatService.GetChatSessionAsync(chatSessions?.ActiveSession?.Id ?? Guid.Empty)); + var session = await chatService.GetChatSessionAsync(chatSessions?.ActiveSession?.Id ?? Guid.Empty); + if (session is not null) + { + chatSessions.RefreshItem(session); + } + shouldScrollToBottom = true; StateHasChanged(); } diff --git a/src/Presentation.Web/Features/Chat/Services/ChatService.cs b/src/Presentation.Web/Features/Chat/Services/ChatService.cs index ab41087..15bb8a4 100644 --- a/src/Presentation.Web/Features/Chat/Services/ChatService.cs +++ b/src/Presentation.Web/Features/Chat/Services/ChatService.cs @@ -7,7 +7,7 @@ namespace Goodtocode.AgentFramework.Presentation.Web.Features.Chat.Services; public interface IChatService { Task> GetChatSessionsAsync(); - Task GetChatSessionAsync(Guid chatSessionId); + Task GetChatSessionAsync(Guid chatSessionId); Task CreateSessionAsync(string firstMessage); Task RenameSessionAsync(Guid chatSessionId, string newTitle); Task SendMessageAsync(Guid chatSessionId, string newMessage); @@ -30,12 +30,12 @@ public async Task> GetChatSessionsAsync() return ChatSessionModel.Create(response.Items); } - public async Task GetChatSessionAsync(Guid chatSessionId) + public async Task GetChatSessionAsync(Guid chatSessionId) { - var response = await HandleApiException(() => _apiClient.GetMyChatSessionAsync( + var response = await HandleApiExceptionOrDefault(() => _apiClient.GetMyChatSessionAsync( chatSessionId)); - return ChatSessionModel.Create(response); + return response is null ? null : ChatSessionModel.Create(response); } public async Task CreateSessionAsync(string firstMessage) @@ -51,7 +51,7 @@ public async Task CreateSessionAsync(string firstMessage) public async Task RenameSessionAsync(Guid chatSessionId, string newTitle) { - await HandleApiException(() => _apiClient.PatchMyChatSessionAsync(chatSessionId, new PatchMyChatSessionCommand { Id = chatSessionId, Title = newTitle })); + await HandleApiExceptionIgnoreNotFound(() => _apiClient.PatchMyChatSessionAsync(chatSessionId, new PatchMyChatSessionCommand { Id = chatSessionId, Title = newTitle })); } public async Task SendMessageAsync(Guid chatSessionId, string newMessage) diff --git a/src/Presentation.Web/Infrastructure/Clients/BackendApiClient.g.cs b/src/Presentation.Web/Infrastructure/Clients/BackendApiClient.g.cs index 71a3c47..09db02c 100644 --- a/src/Presentation.Web/Infrastructure/Clients/BackendApiClient.g.cs +++ b/src/Presentation.Web/Infrastructure/Clients/BackendApiClient.g.cs @@ -672,6 +672,12 @@ public virtual async System.Threading.Tasks.Task CreateMyChatMes throw new ApiException("Unauthorized", status_, responseText_, headers_, null); } else + if (status_ == 404) + { + string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); + throw new ApiException("Not Found", status_, responseText_, headers_, null); + } + else if (status_ == 500) { string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); @@ -762,12 +768,6 @@ public virtual async System.Threading.Tasks.Task CreateMyChatMes throw new ApiException("Unauthorized", status_, responseText_, headers_, null); } else - if (status_ == 404) - { - string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); - throw new ApiException("Not Found", status_, responseText_, headers_, null); - } - else if (status_ == 500) { string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false); diff --git a/src/Presentation.Web/Infrastructure/Http/ApiService.cs b/src/Presentation.Web/Infrastructure/Http/ApiService.cs index c657f62..7a85b76 100644 --- a/src/Presentation.Web/Infrastructure/Http/ApiService.cs +++ b/src/Presentation.Web/Infrastructure/Http/ApiService.cs @@ -18,6 +18,23 @@ protected static async Task HandleApiException(Func apiCall) } } + protected static async Task HandleApiExceptionIgnoreNotFound(Func apiCall) + { + try + { + await apiCall().ConfigureAwait(false); + } + catch (ApiException ex) when (ex.StatusCode == 404) + { + return; + } + catch (ApiException ex) when (ex.StatusCode == 400) + { + var errors = ParseValidationErrors(ex.Response); + throw new ValidationException("Validation failed", null, errors); + } + } + protected static async Task HandleApiException(Func> apiCall) { try @@ -31,6 +48,23 @@ protected static async Task HandleApiException(Func> apiCall) } } + protected static async Task HandleApiExceptionOrDefault(Func> apiCall) where T : class + { + try + { + return await apiCall().ConfigureAwait(false); + } + catch (ApiException ex) when (ex.StatusCode == 404) + { + return null; + } + catch (ApiException ex) when (ex.StatusCode == 400) + { + var errors = ParseValidationErrors(ex.Response); + throw new ValidationException("Validation failed", null, errors); + } + } + protected static Dictionary> ParseValidationErrors(string content) { var result = new Dictionary>(); diff --git a/src/Tests.Integration/Actor/DeleteActorCommand.feature b/src/Tests.Integration/Actor/DeleteActorCommand.feature index 8a79b9b..a678b5a 100644 --- a/src/Tests.Integration/Actor/DeleteActorCommand.feature +++ b/src/Tests.Integration/Actor/DeleteActorCommand.feature @@ -15,4 +15,4 @@ Scenario: Delete Actor Examples: | def | response | responseErrors | id | exists | | success | Success | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | true | - | not found | Conflict | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | false | \ No newline at end of file + | not found | NotFound | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | false | diff --git a/src/Tests.Integration/Actor/DeleteActorCommand.feature.cs b/src/Tests.Integration/Actor/DeleteActorCommand.feature.cs index c4f4245..d9151ae 100644 --- a/src/Tests.Integration/Actor/DeleteActorCommand.feature.cs +++ b/src/Tests.Integration/Actor/DeleteActorCommand.feature.cs @@ -126,7 +126,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "Delete Actor Command")] [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestCategoryAttribute("deleteActorCommand")] [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success", "Success", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "true", "0", null, DisplayName="Delete Actor(success,Success,,038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,true,0)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("not found", "Conflict", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "false", "1", null, DisplayName="Delete Actor(not found,Conflict,,038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,false,1)")] + [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("not found", "NotFound", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "false", "1", null, DisplayName="Delete Actor(not found,NotFound,,038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,false,1)")] public async global::System.Threading.Tasks.Task DeleteActor(string def, string response, string responseErrors, string id, string exists, string @__pickleIndex, string[] exampleTags) { string[] tagsOfScenario = exampleTags; diff --git a/src/Tests.Integration/Actor/DeleteActorCommandStepDefinitions.cs b/src/Tests.Integration/Actor/DeleteActorCommandStepDefinitions.cs index b32f39a..7f13b7a 100644 --- a/src/Tests.Integration/Actor/DeleteActorCommandStepDefinitions.cs +++ b/src/Tests.Integration/Actor/DeleteActorCommandStepDefinitions.cs @@ -52,8 +52,10 @@ public async Task WhenIDeleteTheAuthor() try { - await Sender.Send(request, CancellationToken.None); - responseType = CommandResponseType.Successful; + var result = await Sender.Send(request, CancellationToken.None); + responseType = result.IsNotFound + ? CommandResponseType.NotFound + : CommandResponseType.Successful; } catch (Exception e) { diff --git a/src/Tests.Integration/Actor/GetActorByOwnerIdQuery.feature b/src/Tests.Integration/Actor/GetActorByOwnerIdQuery.feature index 95f9ad7..c0ca2c5 100644 --- a/src/Tests.Integration/Actor/GetActorByOwnerIdQuery.feature +++ b/src/Tests.Integration/Actor/GetActorByOwnerIdQuery.feature @@ -16,4 +16,4 @@ Scenario: Get Actor By OwnerId Examples: | def | response | responseErrors | exists | | success | Success | | true | - | not found | NotFound | | false | \ No newline at end of file + | not found | NotFound | | false | diff --git a/src/Tests.Integration/Actor/GetActorByOwnerIdQueryStepDefinitions.cs b/src/Tests.Integration/Actor/GetActorByOwnerIdQueryStepDefinitions.cs index 6b028b2..51f5572 100644 --- a/src/Tests.Integration/Actor/GetActorByOwnerIdQueryStepDefinitions.cs +++ b/src/Tests.Integration/Actor/GetActorByOwnerIdQueryStepDefinitions.cs @@ -51,7 +51,9 @@ public async Task WhenIGetAAuthor() try { _response = await Sender.Send(request, CancellationToken.None); - responseType = CommandResponseType.Successful; + responseType = _response is null + ? CommandResponseType.NotFound + : CommandResponseType.Successful; } catch (Exception e) { diff --git a/src/Tests.Integration/Actor/GetActorQueryStepDefinitions.cs b/src/Tests.Integration/Actor/GetActorQueryStepDefinitions.cs index 6199886..32cb946 100644 --- a/src/Tests.Integration/Actor/GetActorQueryStepDefinitions.cs +++ b/src/Tests.Integration/Actor/GetActorQueryStepDefinitions.cs @@ -59,7 +59,9 @@ public async Task WhenIGetAAuthor() try { _response = await Sender.Send(request, CancellationToken.None); - responseType = CommandResponseType.Successful; + responseType = _response is null + ? CommandResponseType.NotFound + : CommandResponseType.Successful; } catch (Exception e) { diff --git a/src/Tests.Integration/AgentFramework/McpNotFoundSemanticsTests.cs b/src/Tests.Integration/AgentFramework/McpNotFoundSemanticsTests.cs new file mode 100644 index 0000000..ed20530 --- /dev/null +++ b/src/Tests.Integration/AgentFramework/McpNotFoundSemanticsTests.cs @@ -0,0 +1,27 @@ +using Goodtocode.AgentFramework.Infrastructure.AgentFramework.Tools; + +namespace Goodtocode.AgentFramework.Tests.Integration.AgentFramework; + +[TestClass] +public class McpNotFoundSemanticsTests : TestBase +{ + [TestMethod] + public async Task ActorsToolGetActorByIdReturnsNullWhenMissing() + { + var sut = new ActorsTool(ServiceProvider); + + var result = await sut.GetActorByIdAsync(Guid.NewGuid(), CancellationToken.None); + + result.ShouldBeNull(); + } + + [TestMethod] + public async Task ChatSessionsToolUpdateTitleReturnsNullWhenSessionMissing() + { + var sut = new ChatSessionsTool(ServiceProvider); + + var result = await sut.UpdateChatSessionTitleAsync(Guid.NewGuid(), "Updated Title", CancellationToken.None); + + result.ShouldBeNull(); + } +} diff --git a/src/Tests.Integration/Application/NotFoundSemanticsTests.cs b/src/Tests.Integration/Application/NotFoundSemanticsTests.cs new file mode 100644 index 0000000..7ad99f6 --- /dev/null +++ b/src/Tests.Integration/Application/NotFoundSemanticsTests.cs @@ -0,0 +1,59 @@ +using Goodtocode.AgentFramework.Core.Application.Actor; +using Goodtocode.AgentFramework.Core.Application.Chat; + +namespace Goodtocode.AgentFramework.Tests.Integration.Application; + +[TestClass] +public class NotFoundSemanticsTests : TestBase +{ + [TestMethod] + public async Task GetOurActorQueryReturnsNullWhenActorMissing() + { + var result = await Sender.Send(new GetOurActorQuery { ActorId = Guid.NewGuid() }, CancellationToken.None); + + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetMyChatSessionQueryReturnsNullWhenSessionMissing() + { + var result = await Sender.Send(new GetMyChatSessionQuery { Id = Guid.NewGuid() }, CancellationToken.None); + + result.ShouldBeNull(); + } + + [TestMethod] + public async Task DeleteMyChatSessionCommandReturnsNotFoundWhenSessionMissing() + { + var result = await Sender.Send(new DeleteMyChatSessionCommand { Id = Guid.NewGuid() }, CancellationToken.None); + + result.IsNotFound.ShouldBeTrue(); + result.IsSuccess.ShouldBeFalse(); + } + + [TestMethod] + public async Task PatchMyChatSessionCommandReturnsNotFoundWhenSessionMissing() + { + var result = await Sender.Send(new PatchMyChatSessionCommand + { + Id = Guid.NewGuid(), + Title = "Updated" + }, CancellationToken.None); + + result.IsNotFound.ShouldBeTrue(); + result.IsSuccess.ShouldBeFalse(); + } + + [TestMethod] + public async Task CreateMyChatMessageCommandReturnsNotFoundWhenSessionMissing() + { + var result = await Sender.Send(new CreateMyChatMessageCommand + { + ChatSessionId = Guid.NewGuid(), + Message = "Hello" + }, CancellationToken.None); + + result.IsNotFound.ShouldBeTrue(); + result.Value.ShouldBeNull(); + } +} diff --git a/src/Tests.Integration/Chat/CreateChatMessageCommand.feature b/src/Tests.Integration/Chat/CreateChatMessageCommand.feature index 2b95f76..95b84ea 100644 --- a/src/Tests.Integration/Chat/CreateChatMessageCommand.feature +++ b/src/Tests.Integration/Chat/CreateChatMessageCommand.feature @@ -19,4 +19,5 @@ Examples: | success actor tool | Success | | 00000000-0000-0000-0000-000000000000 | true | Please call get_author that Returns the actor's name for the specified actor ID, or 'Actor not found' if no match exists | | success session tool | Success | | 00000000-0000-0000-0000-000000000000 | true | Please call list_sessions that Lists all sessions, optionally by date | | success messages tool | Success | | 00000000-0000-0000-0000-000000000000 | true | Please call list_messages that Lists all sessions, optionally by date | - | bad request: empty message | BadRequest | Message | 00000000-0000-0000-0000-000000000000 | false | | \ No newline at end of file + | bad request: empty message | BadRequest | Message | 00000000-0000-0000-0000-000000000000 | false | | + | not found session | NotFound | | 00000000-0000-0000-0000-000000000000 | false | Hello there | diff --git a/src/Tests.Integration/Chat/CreateChatMessageCommand.feature.cs b/src/Tests.Integration/Chat/CreateChatMessageCommand.feature.cs index a9c5e43..f7b1ec2 100644 --- a/src/Tests.Integration/Chat/CreateChatMessageCommand.feature.cs +++ b/src/Tests.Integration/Chat/CreateChatMessageCommand.feature.cs @@ -119,7 +119,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa private static global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages InitializeCucumberMessages() { - return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("Chat/CreateChatMessageCommand.feature.ndjson", 7); + return new global::Reqnroll.Formatters.RuntimeSupport.FeatureLevelCucumberMessages("Chat/CreateChatMessageCommand.feature.ndjson", 8); } [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute(callerLineNumber: 7, DisplayName="Create Chat Message")] @@ -140,6 +140,8 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "e,3)")] [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("bad request: empty message", "BadRequest", "Message", "00000000-0000-0000-0000-000000000000", "false", "", "4", null, DisplayName="Create Chat Message(bad request: empty message,BadRequest,Message,00000000-0000-0" + "000-0000-000000000000,false,,4)")] + [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("not found session", "NotFound", "", "00000000-0000-0000-0000-000000000000", "false", "Hello there", "5", null, DisplayName="Create Chat Message(not found session,NotFound,,00000000-0000-0000-0000-000000000" + + "000,false,Hello there,5)")] public async global::System.Threading.Tasks.Task CreateChatMessage(string def, string response, string responseErrors, string id, string chatMessageExists, string message, string @__pickleIndex, string[] exampleTags) { string[] tagsOfScenario = exampleTags; diff --git a/src/Tests.Integration/Chat/CreateChatMessageCommandStepDefinitions.cs b/src/Tests.Integration/Chat/CreateChatMessageCommandStepDefinitions.cs index 9b78799..63d4d96 100644 --- a/src/Tests.Integration/Chat/CreateChatMessageCommandStepDefinitions.cs +++ b/src/Tests.Integration/Chat/CreateChatMessageCommandStepDefinitions.cs @@ -61,6 +61,10 @@ public async Task WhenICreateAChatMessageWithTheMessage() await context.SaveChangesAsync(CancellationToken.None); _chatSessionId = chatSession.Id; } + else + { + _chatSessionId = Guid.NewGuid(); + } var request = new CreateMyChatMessageCommand() { @@ -71,7 +75,13 @@ public async Task WhenICreateAChatMessageWithTheMessage() try { var created = await Sender.Send(request, CancellationToken.None); - _id = created.Id; + if (created.IsNotFound || created.Value is null) + { + responseType = CommandResponseType.NotFound; + return; + } + + _id = created.Value.Id; responseType = CommandResponseType.Successful; } catch (Exception e) diff --git a/src/Tests.Integration/Chat/DeleteChatSessionCommand.feature b/src/Tests.Integration/Chat/DeleteChatSessionCommand.feature index ec3a54b..191bb49 100644 --- a/src/Tests.Integration/Chat/DeleteChatSessionCommand.feature +++ b/src/Tests.Integration/Chat/DeleteChatSessionCommand.feature @@ -15,5 +15,5 @@ Scenario: Delete Chat Session Examples: | def | response | responseErrors | id | exists | | success | Success | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | true | - | not found | Conflict | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | false | + | not found | NotFound | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | false | | bad request: empty id | BadRequest | Id | 00000000-0000-0000-0000-000000000000 | false | \ No newline at end of file diff --git a/src/Tests.Integration/Chat/DeleteChatSessionCommand.feature.cs b/src/Tests.Integration/Chat/DeleteChatSessionCommand.feature.cs index 42f2e38..b725ca0 100644 --- a/src/Tests.Integration/Chat/DeleteChatSessionCommand.feature.cs +++ b/src/Tests.Integration/Chat/DeleteChatSessionCommand.feature.cs @@ -128,7 +128,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa [global::Microsoft.VisualStudio.TestTools.UnitTesting.TestCategoryAttribute("deleteChatSessionCommand")] [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("success", "Success", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "true", "0", null, DisplayName="Delete Chat Session(success,Success,,038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,true,0)" + "")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("not found", "Conflict", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "false", "1", null, DisplayName="Delete Chat Session(not found,Conflict,,038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,fals" + + [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("not found", "NotFound", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "false", "1", null, DisplayName="Delete Chat Session(not found,NotFound,,038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9,fals" + "e,1)")] [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("bad request: empty id", "BadRequest", "Id", "00000000-0000-0000-0000-000000000000", "false", "2", null, DisplayName="Delete Chat Session(bad request: empty id,BadRequest,Id,00000000-0000-0000-0000-0" + "00000000000,false,2)")] diff --git a/src/Tests.Integration/Chat/DeleteChatSessionCommandStepDefinitions.cs b/src/Tests.Integration/Chat/DeleteChatSessionCommandStepDefinitions.cs index c072b6c..274b4ba 100644 --- a/src/Tests.Integration/Chat/DeleteChatSessionCommandStepDefinitions.cs +++ b/src/Tests.Integration/Chat/DeleteChatSessionCommandStepDefinitions.cs @@ -51,8 +51,10 @@ public async Task WhenIDeleteTheChatSession() try { - await Sender.Send(request, CancellationToken.None); - responseType = CommandResponseType.Successful; + var result = await Sender.Send(request, CancellationToken.None); + responseType = result.IsNotFound + ? CommandResponseType.NotFound + : CommandResponseType.Successful; } catch (Exception e) { diff --git a/src/Tests.Integration/Chat/GetChatMessageQueryStepDefinitions.cs b/src/Tests.Integration/Chat/GetChatMessageQueryStepDefinitions.cs index ff0b9b4..e1428c4 100644 --- a/src/Tests.Integration/Chat/GetChatMessageQueryStepDefinitions.cs +++ b/src/Tests.Integration/Chat/GetChatMessageQueryStepDefinitions.cs @@ -67,7 +67,9 @@ public async Task WhenIGetAChatMessage() try { _response = await Sender.Send(request, CancellationToken.None); - responseType = CommandResponseType.Successful; + responseType = _response is null + ? CommandResponseType.NotFound + : CommandResponseType.Successful; } catch (Exception e) { diff --git a/src/Tests.Integration/Chat/GetMyChatSessionQueryStepDefinitions.cs b/src/Tests.Integration/Chat/GetMyChatSessionQueryStepDefinitions.cs index 02cb26b..60f1921 100644 --- a/src/Tests.Integration/Chat/GetMyChatSessionQueryStepDefinitions.cs +++ b/src/Tests.Integration/Chat/GetMyChatSessionQueryStepDefinitions.cs @@ -58,7 +58,9 @@ public async Task WhenIGetAChatSession() try { _response = await Sender.Send(request, CancellationToken.None); - responseType = CommandResponseType.Successful; + responseType = _response is null + ? CommandResponseType.NotFound + : CommandResponseType.Successful; } catch (Exception e) { diff --git a/src/Tests.Integration/Chat/PatchChatSessionCommand.feature b/src/Tests.Integration/Chat/PatchChatSessionCommand.feature index cbc0179..bdf8080 100644 --- a/src/Tests.Integration/Chat/PatchChatSessionCommand.feature +++ b/src/Tests.Integration/Chat/PatchChatSessionCommand.feature @@ -16,4 +16,4 @@ Examples: | def | response | responseErrors | id | chatSessionExists | title | | success : patch title | Success | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | true | New Title | | bad request: empty id | BadRequest | Id | 00000000-0000-0000-0000-000000000000 | false | Changed Title | - | not found : patch title | Conflict | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | false | Title | \ No newline at end of file + | not found : patch title | NotFound | | 038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9 | false | Title | diff --git a/src/Tests.Integration/Chat/PatchChatSessionCommand.feature.cs b/src/Tests.Integration/Chat/PatchChatSessionCommand.feature.cs index d2aad76..76bbd0b 100644 --- a/src/Tests.Integration/Chat/PatchChatSessionCommand.feature.cs +++ b/src/Tests.Integration/Chat/PatchChatSessionCommand.feature.cs @@ -129,7 +129,7 @@ public void ScenarioInitialize(global::Reqnroll.ScenarioInfo scenarioInfo, globa "9fed9,true,New Title,0)")] [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("bad request: empty id", "BadRequest", "Id", "00000000-0000-0000-0000-000000000000", "false", "Changed Title", "1", null, DisplayName="Patch Chat Session(bad request: empty id,BadRequest,Id,00000000-0000-0000-0000-00" + "0000000000,false,Changed Title,1)")] - [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("not found : patch title", "Conflict", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "false", "Title", "2", null, DisplayName="Patch Chat Session(not found : patch title,Conflict,,038d8e7f-f18f-4a8e-8b3c-3b6a" + + [global::Microsoft.VisualStudio.TestTools.UnitTesting.DataRowAttribute("not found : patch title", "NotFound", "", "038d8e7f-f18f-4a8e-8b3c-3b6a6889fed9", "false", "Title", "2", null, DisplayName="Patch Chat Session(not found : patch title,NotFound,,038d8e7f-f18f-4a8e-8b3c-3b6a" + "6889fed9,false,Title,2)")] public async global::System.Threading.Tasks.Task PatchChatSession(string def, string response, string responseErrors, string id, string chatSessionExists, string title, string @__pickleIndex, string[] exampleTags) { diff --git a/src/Tests.Integration/Chat/PatchChatSessionCommandStepDefinitions.cs b/src/Tests.Integration/Chat/PatchChatSessionCommandStepDefinitions.cs index b5fb71b..d70821e 100644 --- a/src/Tests.Integration/Chat/PatchChatSessionCommandStepDefinitions.cs +++ b/src/Tests.Integration/Chat/PatchChatSessionCommandStepDefinitions.cs @@ -59,8 +59,10 @@ public async Task WhenIPatchTheChatSession() try { - await Sender.Send(request, CancellationToken.None); - responseType = CommandResponseType.Successful; + var result = await Sender.Send(request, CancellationToken.None); + responseType = result.IsNotFound + ? CommandResponseType.NotFound + : CommandResponseType.Successful; } catch (Exception e) { diff --git a/src/Tests.Integration/Tests.Integration.csproj b/src/Tests.Integration/Tests.Integration.csproj index 8955015..a554097 100644 --- a/src/Tests.Integration/Tests.Integration.csproj +++ b/src/Tests.Integration/Tests.Integration.csproj @@ -34,4 +34,8 @@ + + + + diff --git a/src/dotnet-tools.json b/src/dotnet-tools.json index 9071619..0bf0f40 100644 --- a/src/dotnet-tools.json +++ b/src/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-ef": { - "version": "10.0.7", + "version": "10.0.10", "commands": [ "dotnet-ef" ],