fix: enforce RAG conversation ownership
This commit is contained in:
parent
6980b2ebba
commit
0d1fd55ef0
@ -1,5 +1,6 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using BuildFlow.Shared.Domain.Exceptions;
|
using BuildFlow.Shared.Domain.Exceptions;
|
||||||
|
using BuildFlow.Shared.Domain.Common;
|
||||||
using BuildFlow.Domain.Interfaces;
|
using BuildFlow.Domain.Interfaces;
|
||||||
using BuildFlow.Infrastructure.Persistence;
|
using BuildFlow.Infrastructure.Persistence;
|
||||||
using BuildFlow.Application.Chat.Commands;
|
using BuildFlow.Application.Chat.Commands;
|
||||||
@ -9,12 +10,18 @@ namespace BuildFlow.Application.Chat.Handlers;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete会话Command处理器。执行业务校验后持久化。
|
/// Delete会话Command处理器。执行业务校验后持久化。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DeleteConversationCommandHandler(RagDbContext db, IChatMessageCache cache)
|
public class DeleteConversationCommandHandler(
|
||||||
|
RagDbContext db,
|
||||||
|
IChatMessageCache cache,
|
||||||
|
ICurrentUserContext userContext)
|
||||||
: IRequestHandler<DeleteConversationCommand, Unit>
|
: IRequestHandler<DeleteConversationCommand, Unit>
|
||||||
{
|
{
|
||||||
public async Task<Unit> Handle(DeleteConversationCommand request, CancellationToken ct)
|
public async Task<Unit> Handle(DeleteConversationCommand request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var conversation = await db.Conversations.FindAsync([request.Id], ct)
|
var userId = Guid.Parse(userContext.GetRequiredUserId());
|
||||||
|
var conversation = await db.Conversations.FirstOrDefaultAsync(
|
||||||
|
x => x.Id == request.Id && x.UserId == userId,
|
||||||
|
ct)
|
||||||
?? throw new NotFoundException(ErrorCode.NotFound, "会话不存在");
|
?? throw new NotFoundException(ErrorCode.NotFound, "会话不存在");
|
||||||
|
|
||||||
db.Conversations.Remove(conversation);
|
db.Conversations.Remove(conversation);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using BuildFlow.Application.Chat.DTOs;
|
using BuildFlow.Application.Chat.DTOs;
|
||||||
using BuildFlow.Shared.Domain.Exceptions;
|
using BuildFlow.Shared.Domain.Exceptions;
|
||||||
|
using BuildFlow.Shared.Domain.Common;
|
||||||
using BuildFlow.Domain.Interfaces;
|
using BuildFlow.Domain.Interfaces;
|
||||||
using BuildFlow.Infrastructure.Persistence;
|
using BuildFlow.Infrastructure.Persistence;
|
||||||
using BuildFlow.Application.Chat.Queries;
|
using BuildFlow.Application.Chat.Queries;
|
||||||
@ -10,12 +11,18 @@ namespace BuildFlow.Application.Chat.Handlers;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get会话Detail查询处理器。执行业务校验后持久化。
|
/// Get会话Detail查询处理器。执行业务校验后持久化。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GetConversationDetailQueryHandler(RagDbContext db, IChatMessageCache cache)
|
public class GetConversationDetailQueryHandler(
|
||||||
|
RagDbContext db,
|
||||||
|
IChatMessageCache cache,
|
||||||
|
ICurrentUserContext userContext)
|
||||||
: IRequestHandler<GetConversationDetailQuery, ConversationDetailDto>
|
: IRequestHandler<GetConversationDetailQuery, ConversationDetailDto>
|
||||||
{
|
{
|
||||||
public async Task<ConversationDetailDto> Handle(GetConversationDetailQuery request, CancellationToken ct)
|
public async Task<ConversationDetailDto> Handle(GetConversationDetailQuery request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var conversation = await db.Conversations.FindAsync([request.Id], ct)
|
var userId = Guid.Parse(userContext.GetRequiredUserId());
|
||||||
|
var conversation = await db.Conversations.FirstOrDefaultAsync(
|
||||||
|
x => x.Id == request.Id && x.UserId == userId,
|
||||||
|
ct)
|
||||||
?? throw new NotFoundException(ErrorCode.NotFound, "会话不存在");
|
?? throw new NotFoundException(ErrorCode.NotFound, "会话不存在");
|
||||||
|
|
||||||
// 先查 Redis 缓存
|
// 先查 Redis 缓存
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using BuildFlow.Application.Chat.DTOs;
|
|||||||
using BuildFlow.Domain.Entities.ChatMessage;
|
using BuildFlow.Domain.Entities.ChatMessage;
|
||||||
using BuildFlow.Domain.Enums;
|
using BuildFlow.Domain.Enums;
|
||||||
using BuildFlow.Shared.Domain.Exceptions;
|
using BuildFlow.Shared.Domain.Exceptions;
|
||||||
|
using BuildFlow.Shared.Domain.Common;
|
||||||
using BuildFlow.Domain.Interfaces;
|
using BuildFlow.Domain.Interfaces;
|
||||||
using BuildFlow.Infrastructure.Persistence;
|
using BuildFlow.Infrastructure.Persistence;
|
||||||
using BuildFlow.Application.Chat.Commands;
|
using BuildFlow.Application.Chat.Commands;
|
||||||
@ -12,28 +13,38 @@ using AiChatRole = Microsoft.Extensions.AI.ChatRole;
|
|||||||
|
|
||||||
namespace BuildFlow.Application.Chat.Handlers;
|
namespace BuildFlow.Application.Chat.Handlers;
|
||||||
|
|
||||||
public class SendMessageCommandHandler(RagDbContext db, IAIChatAgent chatAgent, IChatMessageCache cache)
|
/// <summary>
|
||||||
|
/// 向当前用户拥有的会话发送消息,并持久化用户消息和 AI 回复。
|
||||||
|
/// </summary>
|
||||||
|
public class SendMessageCommandHandler(
|
||||||
|
RagDbContext db,
|
||||||
|
IAIChatAgent chatAgent,
|
||||||
|
IChatMessageCache cache,
|
||||||
|
ICurrentUserContext userContext)
|
||||||
: IRequestHandler<SendMessageCommand, SendMessageResponse>
|
: IRequestHandler<SendMessageCommand, SendMessageResponse>
|
||||||
{
|
{
|
||||||
public async Task<SendMessageResponse> Handle(SendMessageCommand request, CancellationToken ct)
|
public async Task<SendMessageResponse> Handle(SendMessageCommand request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var conversation = await db.Conversations.FindAsync([request.ConversationId], ct)
|
var userId = Guid.Parse(userContext.GetRequiredUserId());
|
||||||
|
var conversation = await db.Conversations.FirstOrDefaultAsync(
|
||||||
|
x => x.Id == request.ConversationId && x.UserId == userId,
|
||||||
|
ct)
|
||||||
?? throw new NotFoundException(ErrorCode.NotFound, "会话不存在");
|
?? throw new NotFoundException(ErrorCode.NotFound, "会话不存在");
|
||||||
|
|
||||||
var userMessage = new ChatMessageEntity
|
var userMessage = new ChatMessageEntity
|
||||||
{
|
{
|
||||||
ConversationId = request.ConversationId,
|
ConversationId = conversation.Id,
|
||||||
Role = ChatRoleEnum.User,
|
Role = ChatRoleEnum.User,
|
||||||
Content = request.Content
|
Content = request.Content
|
||||||
};
|
};
|
||||||
await db.ChatMessages.AddAsync(userMessage, ct);
|
await db.ChatMessages.AddAsync(userMessage, ct);
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
|
|
||||||
await cache.AppendMessageAsync(request.ConversationId,
|
await cache.AppendMessageAsync(conversation.Id,
|
||||||
new CachedChatMessage(userMessage.Id, ChatRoleEnum.User.ToString(), userMessage.Content, null, userMessage.CreatedAt), ct);
|
new CachedChatMessage(userMessage.Id, ChatRoleEnum.User.ToString(), userMessage.Content, null, userMessage.CreatedAt), ct);
|
||||||
|
|
||||||
var history = await db.ChatMessages
|
var history = await db.ChatMessages
|
||||||
.Where(m => m.ConversationId == request.ConversationId)
|
.Where(m => m.ConversationId == conversation.Id)
|
||||||
.OrderBy(m => m.CreatedAt)
|
.OrderBy(m => m.CreatedAt)
|
||||||
.ToListAsync(ct);
|
.ToListAsync(ct);
|
||||||
|
|
||||||
@ -45,14 +56,14 @@ public class SendMessageCommandHandler(RagDbContext db, IAIChatAgent chatAgent,
|
|||||||
|
|
||||||
var assistantMessage = new ChatMessageEntity
|
var assistantMessage = new ChatMessageEntity
|
||||||
{
|
{
|
||||||
ConversationId = request.ConversationId,
|
ConversationId = conversation.Id,
|
||||||
Role = ChatRoleEnum.Assistant,
|
Role = ChatRoleEnum.Assistant,
|
||||||
Content = reply
|
Content = reply
|
||||||
};
|
};
|
||||||
await db.ChatMessages.AddAsync(assistantMessage, ct);
|
await db.ChatMessages.AddAsync(assistantMessage, ct);
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
|
|
||||||
await cache.AppendMessageAsync(request.ConversationId,
|
await cache.AppendMessageAsync(conversation.Id,
|
||||||
new CachedChatMessage(assistantMessage.Id, ChatRoleEnum.Assistant.ToString(), assistantMessage.Content, null, assistantMessage.CreatedAt), ct);
|
new CachedChatMessage(assistantMessage.Id, ChatRoleEnum.Assistant.ToString(), assistantMessage.Content, null, assistantMessage.CreatedAt), ct);
|
||||||
|
|
||||||
return new SendMessageResponse(assistantMessage.Id, reply);
|
return new SendMessageResponse(assistantMessage.Id, reply);
|
||||||
|
|||||||
@ -1,19 +1,28 @@
|
|||||||
using BuildFlow.Application.Chat.Commands;
|
using BuildFlow.Application.Chat.Commands;
|
||||||
using BuildFlow.Application.Chat.DTOs;
|
using BuildFlow.Application.Chat.DTOs;
|
||||||
using BuildFlow.Domain.Entities.Conversation;
|
|
||||||
using BuildFlow.Infrastructure.Persistence;
|
using BuildFlow.Infrastructure.Persistence;
|
||||||
|
using BuildFlow.Shared.Domain.Common;
|
||||||
using BuildFlow.Shared.Domain.Errors;
|
using BuildFlow.Shared.Domain.Errors;
|
||||||
using BuildFlow.Shared.Domain.Exceptions;
|
using BuildFlow.Shared.Domain.Exceptions;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
|
||||||
namespace BuildFlow.Application.Chat.Handlers;
|
namespace BuildFlow.Application.Chat.Handlers;
|
||||||
|
|
||||||
public class UpdateConversationCommandHandler(RagDbContext db) : IRequestHandler<UpdateConversationCommand, ConversationDto>
|
/// <summary>
|
||||||
|
/// 更新当前用户拥有的 RAG 会话基本信息。
|
||||||
|
/// </summary>
|
||||||
|
public class UpdateConversationCommandHandler(
|
||||||
|
RagDbContext db,
|
||||||
|
ICurrentUserContext userContext)
|
||||||
|
: IRequestHandler<UpdateConversationCommand, ConversationDto>
|
||||||
{
|
{
|
||||||
public async Task<ConversationDto> Handle(UpdateConversationCommand request, CancellationToken ct)
|
public async Task<ConversationDto> Handle(UpdateConversationCommand request, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var conversation = await db.Conversations.FindAsync([request.Id], ct)
|
var userId = Guid.Parse(userContext.GetRequiredUserId());
|
||||||
?? throw new BusinessException(ErrorCode.NotFound, "会话不存在");
|
var conversation = await db.Conversations.FirstOrDefaultAsync(
|
||||||
|
x => x.Id == request.Id && x.UserId == userId,
|
||||||
|
ct)
|
||||||
|
?? throw new NotFoundException(ErrorCode.NotFound, "会话不存在");
|
||||||
if (!string.IsNullOrWhiteSpace(request.Title))
|
if (!string.IsNullOrWhiteSpace(request.Title))
|
||||||
conversation.Title = request.Title;
|
conversation.Title = request.Title;
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using BuildFlow.Domain.Entities.Conversation;
|
|||||||
using BuildFlow.Domain.Enums;
|
using BuildFlow.Domain.Enums;
|
||||||
using BuildFlow.Infrastructure.Persistence;
|
using BuildFlow.Infrastructure.Persistence;
|
||||||
using BuildFlow.Rag.Tests.Stub;
|
using BuildFlow.Rag.Tests.Stub;
|
||||||
|
using BuildFlow.Shared.Domain.Errors;
|
||||||
using BuildFlow.Shared.Domain.Exceptions;
|
using BuildFlow.Shared.Domain.Exceptions;
|
||||||
using BuildFlow.Shared.Infrastructure;
|
using BuildFlow.Shared.Infrastructure;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
@ -67,13 +68,14 @@ public class ChatEndToEndTests
|
|||||||
{
|
{
|
||||||
// Given: 已创建会话 + 两轮对话
|
// Given: 已创建会话 + 两轮对话
|
||||||
await using var db = CreateDb("multi-turn");
|
await using var db = CreateDb("multi-turn");
|
||||||
var userId = Guid.Parse(new TestCurrentUserContext().UserId);
|
var userContext = new TestCurrentUserContext();
|
||||||
|
var userId = Guid.Parse(userContext.UserId);
|
||||||
var conversationId = Guid.NewGuid();
|
var conversationId = Guid.NewGuid();
|
||||||
SeedConversation(db, conversationId, userId);
|
SeedConversation(db, conversationId, userId);
|
||||||
|
|
||||||
var cache = new StubChatMessageCache();
|
var cache = new StubChatMessageCache();
|
||||||
var chatAgent = new StubAIChatAgent { ReplyText = "你好!我是 AI 助手。" };
|
var chatAgent = new StubAIChatAgent { ReplyText = "你好!我是 AI 助手。" };
|
||||||
var handler = new SendMessageCommandHandler(db, chatAgent, cache);
|
var handler = new SendMessageCommandHandler(db, chatAgent, cache, userContext);
|
||||||
|
|
||||||
// When: 第一轮
|
// When: 第一轮
|
||||||
var round1 = await handler.Handle(
|
var round1 = await handler.Handle(
|
||||||
@ -113,7 +115,11 @@ public class ChatEndToEndTests
|
|||||||
await using var db = CreateDb("not-found");
|
await using var db = CreateDb("not-found");
|
||||||
var cache = new StubChatMessageCache();
|
var cache = new StubChatMessageCache();
|
||||||
var chatAgent = new StubAIChatAgent();
|
var chatAgent = new StubAIChatAgent();
|
||||||
var handler = new SendMessageCommandHandler(db, chatAgent, cache);
|
var handler = new SendMessageCommandHandler(
|
||||||
|
db,
|
||||||
|
chatAgent,
|
||||||
|
cache,
|
||||||
|
new TestCurrentUserContext());
|
||||||
|
|
||||||
// When/Then: 发消息到不存在的会话
|
// When/Then: 发消息到不存在的会话
|
||||||
var act = async () => await handler.Handle(
|
var act = async () => await handler.Handle(
|
||||||
@ -123,6 +129,29 @@ public class ChatEndToEndTests
|
|||||||
await act.Should().ThrowAsync<NotFoundException>();
|
await act.Should().ThrowAsync<NotFoundException>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendMessage_OtherUsersConversation_ThrowsNotFoundWithoutWriting()
|
||||||
|
{
|
||||||
|
await using var db = CreateDb("send-other-user");
|
||||||
|
var userContext = new TestCurrentUserContext();
|
||||||
|
var conversationId = Guid.NewGuid();
|
||||||
|
SeedConversation(db, conversationId, Guid.NewGuid());
|
||||||
|
var cache = new StubChatMessageCache();
|
||||||
|
var handler = new SendMessageCommandHandler(
|
||||||
|
db,
|
||||||
|
new StubAIChatAgent(),
|
||||||
|
cache,
|
||||||
|
userContext);
|
||||||
|
var request = new SendMessageCommand(conversationId, "越权消息");
|
||||||
|
|
||||||
|
var act = () => handler.Handle(request, CancellationToken.None);
|
||||||
|
|
||||||
|
await act.Should().ThrowAsync<NotFoundException>()
|
||||||
|
.Where(x => x.Code == ErrorCode.NotFound && x.Message == "会话不存在");
|
||||||
|
db.ChatMessages.Should().BeEmpty();
|
||||||
|
cache.Count(conversationId).Should().Be(0);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CreateConversation_UnauthorizedUser_Throws()
|
public async Task CreateConversation_UnauthorizedUser_Throws()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -12,6 +12,8 @@ using BuildFlow.Domain.Enums;
|
|||||||
using BuildFlow.Domain.Interfaces;
|
using BuildFlow.Domain.Interfaces;
|
||||||
using BuildFlow.Infrastructure.Persistence;
|
using BuildFlow.Infrastructure.Persistence;
|
||||||
using BuildFlow.Rag.Tests.Stub;
|
using BuildFlow.Rag.Tests.Stub;
|
||||||
|
using BuildFlow.Shared.Domain.Errors;
|
||||||
|
using BuildFlow.Shared.Domain.Exceptions;
|
||||||
using BuildFlow.Shared.Infrastructure;
|
using BuildFlow.Shared.Infrastructure;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -107,14 +109,15 @@ public class EmbeddingAndQueryTests
|
|||||||
{
|
{
|
||||||
var db = CreateDb();
|
var db = CreateDb();
|
||||||
var convId = Guid.NewGuid();
|
var convId = Guid.NewGuid();
|
||||||
var userId = Guid.Parse(new TestCurrentUserContext().UserId);
|
var userContext = new TestCurrentUserContext();
|
||||||
|
var userId = Guid.Parse(userContext.UserId);
|
||||||
db.Conversations.Add(new ConversationEntity { Id = convId, Title = "详情", UserId = userId });
|
db.Conversations.Add(new ConversationEntity { Id = convId, Title = "详情", UserId = userId });
|
||||||
db.ChatMessages.Add(new ChatMessageEntity { ConversationId = convId, Role = ChatRoleEnum.User, Content = "你好" });
|
db.ChatMessages.Add(new ChatMessageEntity { ConversationId = convId, Role = ChatRoleEnum.User, Content = "你好" });
|
||||||
db.ChatMessages.Add(new ChatMessageEntity { ConversationId = convId, Role = ChatRoleEnum.Assistant, Content = "回复" });
|
db.ChatMessages.Add(new ChatMessageEntity { ConversationId = convId, Role = ChatRoleEnum.Assistant, Content = "回复" });
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
var cache = new StubChatMessageCache();
|
var cache = new StubChatMessageCache();
|
||||||
var handler = new GetConversationDetailQueryHandler(db, cache);
|
var handler = new GetConversationDetailQueryHandler(db, cache, userContext);
|
||||||
var result = await handler.Handle(new GetConversationDetailQuery(convId), CancellationToken.None);
|
var result = await handler.Handle(new GetConversationDetailQuery(convId), CancellationToken.None);
|
||||||
|
|
||||||
result.Title.Should().Be("详情");
|
result.Title.Should().Be("详情");
|
||||||
@ -126,17 +129,93 @@ public class EmbeddingAndQueryTests
|
|||||||
{
|
{
|
||||||
var db = CreateDb();
|
var db = CreateDb();
|
||||||
var convId = Guid.NewGuid();
|
var convId = Guid.NewGuid();
|
||||||
var userId = Guid.Parse(new TestCurrentUserContext().UserId);
|
var userContext = new TestCurrentUserContext();
|
||||||
|
var userId = Guid.Parse(userContext.UserId);
|
||||||
db.Conversations.Add(new ConversationEntity { Id = convId, Title = "待删", UserId = userId });
|
db.Conversations.Add(new ConversationEntity { Id = convId, Title = "待删", UserId = userId });
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
var cache = new StubChatMessageCache();
|
var cache = new StubChatMessageCache();
|
||||||
await cache.SetMessagesAsync(convId, [new CachedChatMessage(Guid.NewGuid(), "user", "hi", null, DateTime.UtcNow)]);
|
await cache.SetMessagesAsync(convId, [new CachedChatMessage(Guid.NewGuid(), "user", "hi", null, DateTime.UtcNow)]);
|
||||||
|
|
||||||
var handler = new DeleteConversationCommandHandler(db, cache);
|
var handler = new DeleteConversationCommandHandler(db, cache, userContext);
|
||||||
await handler.Handle(new DeleteConversationCommand(convId), CancellationToken.None);
|
await handler.Handle(new DeleteConversationCommand(convId), CancellationToken.None);
|
||||||
|
|
||||||
(await db.Conversations.AnyAsync(c => c.Id == convId)).Should().BeFalse();
|
(await db.Conversations.AnyAsync(c => c.Id == convId)).Should().BeFalse();
|
||||||
cache.Count(convId).Should().Be(0, "cache 也应清除");
|
cache.Count(convId).Should().Be(0, "cache 也应清除");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetConversationDetail_OtherUsersConversation_ThrowsNotFound()
|
||||||
|
{
|
||||||
|
await using var db = CreateDb();
|
||||||
|
var userContext = new TestCurrentUserContext();
|
||||||
|
var conversationId = Guid.NewGuid();
|
||||||
|
db.Conversations.Add(new ConversationEntity
|
||||||
|
{
|
||||||
|
Id = conversationId,
|
||||||
|
Title = "他人的详情",
|
||||||
|
UserId = Guid.NewGuid(),
|
||||||
|
});
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
var handler = new GetConversationDetailQueryHandler(
|
||||||
|
db,
|
||||||
|
new StubChatMessageCache(),
|
||||||
|
userContext);
|
||||||
|
var request = new GetConversationDetailQuery(conversationId);
|
||||||
|
|
||||||
|
var act = () => handler.Handle(request, CancellationToken.None);
|
||||||
|
|
||||||
|
await act.Should().ThrowAsync<NotFoundException>()
|
||||||
|
.Where(x => x.Code == ErrorCode.NotFound && x.Message == "会话不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateConversation_OtherUsersConversation_DoesNotChangeTitle()
|
||||||
|
{
|
||||||
|
await using var db = CreateDb();
|
||||||
|
var userContext = new TestCurrentUserContext();
|
||||||
|
var conversation = new ConversationEntity
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
Title = "原始标题",
|
||||||
|
UserId = Guid.NewGuid(),
|
||||||
|
};
|
||||||
|
db.Conversations.Add(conversation);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
var handler = new UpdateConversationCommandHandler(db, userContext);
|
||||||
|
var request = new UpdateConversationCommand(conversation.Id, "越权更新", null);
|
||||||
|
|
||||||
|
var act = () => handler.Handle(request, CancellationToken.None);
|
||||||
|
|
||||||
|
await act.Should().ThrowAsync<NotFoundException>()
|
||||||
|
.Where(x => x.Code == ErrorCode.NotFound && x.Message == "会话不存在");
|
||||||
|
conversation.Title.Should().Be("原始标题");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteConversation_OtherUsersConversation_LeavesDbAndCacheUntouched()
|
||||||
|
{
|
||||||
|
await using var db = CreateDb();
|
||||||
|
var userContext = new TestCurrentUserContext();
|
||||||
|
var conversationId = Guid.NewGuid();
|
||||||
|
db.Conversations.Add(new ConversationEntity
|
||||||
|
{
|
||||||
|
Id = conversationId,
|
||||||
|
Title = "不可删除",
|
||||||
|
UserId = Guid.NewGuid(),
|
||||||
|
});
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
var cache = new StubChatMessageCache();
|
||||||
|
await cache.SetMessagesAsync(conversationId,
|
||||||
|
[new CachedChatMessage(Guid.NewGuid(), "user", "保留", null, DateTime.UtcNow)]);
|
||||||
|
var handler = new DeleteConversationCommandHandler(db, cache, userContext);
|
||||||
|
var request = new DeleteConversationCommand(conversationId);
|
||||||
|
|
||||||
|
var act = () => handler.Handle(request, CancellationToken.None);
|
||||||
|
|
||||||
|
await act.Should().ThrowAsync<NotFoundException>()
|
||||||
|
.Where(x => x.Code == ErrorCode.NotFound && x.Message == "会话不存在");
|
||||||
|
(await db.Conversations.AnyAsync(x => x.Id == conversationId)).Should().BeTrue();
|
||||||
|
cache.Count(conversationId).Should().Be(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user