From fcc5f02c48c150d26534875b977a5758bd23697a Mon Sep 17 00:00:00 2001 From: shchoholiev Date: Sat, 21 Oct 2023 13:40:10 +0000 Subject: [PATCH] Added models for product search. --- .../IServices/IProductService.cs | 5 +- .../Models/ProductSearch/MessagePart.cs | 6 ++ .../Models/ProductSearch/ServerSentEvent.cs | 10 ++++ .../Models/ProductSearch/Suggestion.cs | 6 ++ .../Enums/SearchEventType.cs | 24 ++++++++ .../Services/ProductService.cs | 7 +++ .../ProductTests.cs | 58 +++++++++---------- 7 files changed, 84 insertions(+), 32 deletions(-) create mode 100644 ShoppingAssistantApi.Application/Models/ProductSearch/MessagePart.cs create mode 100644 ShoppingAssistantApi.Application/Models/ProductSearch/ServerSentEvent.cs create mode 100644 ShoppingAssistantApi.Application/Models/ProductSearch/Suggestion.cs create mode 100644 ShoppingAssistantApi.Domain/Enums/SearchEventType.cs diff --git a/ShoppingAssistantApi.Application/IServices/IProductService.cs b/ShoppingAssistantApi.Application/IServices/IProductService.cs index 8f929f4..8bd24b6 100644 --- a/ShoppingAssistantApi.Application/IServices/IProductService.cs +++ b/ShoppingAssistantApi.Application/IServices/IProductService.cs @@ -1,6 +1,5 @@ -using System.Collections.ObjectModel; +using ShoppingAssistantApi.Application.Models.CreateDtos; using ShoppingAssistantApi.Application.Models.Dtos; -using ShoppingAssistantApi.Application.Models.OpenAi; using ShoppingAssistantApi.Application.Models.ProductSearch; using ShoppingAssistantApi.Domain.Entities; @@ -10,6 +9,8 @@ public interface IProductService { IAsyncEnumerable<(List ProductNames, WishlistDto Wishlist)> StartNewSearchAndReturnWishlist(Message message, CancellationToken cancellationToken); + IAsyncEnumerable SearchProductAsync(string wishlistId, MessageCreateDto message, CancellationToken cancellationToken); + IAsyncEnumerable GetProductFromSearch(Message message, CancellationToken cancellationToken); IAsyncEnumerable GetRecommendationsForProductFromSearchStream(Message message, diff --git a/ShoppingAssistantApi.Application/Models/ProductSearch/MessagePart.cs b/ShoppingAssistantApi.Application/Models/ProductSearch/MessagePart.cs new file mode 100644 index 0000000..feacc20 --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/ProductSearch/MessagePart.cs @@ -0,0 +1,6 @@ +namespace ShoppingAssistantApi.Application.Models.ProductSearch; + +public class MessagePart +{ + public string Text { get; set; } +} diff --git a/ShoppingAssistantApi.Application/Models/ProductSearch/ServerSentEvent.cs b/ShoppingAssistantApi.Application/Models/ProductSearch/ServerSentEvent.cs new file mode 100644 index 0000000..e7be1cb --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/ProductSearch/ServerSentEvent.cs @@ -0,0 +1,10 @@ +using ShoppingAssistantApi.Domain.Enums; + +namespace ShoppingAssistantApi.Application.Models.ProductSearch; + +public class ServerSentEvent +{ + public SearchEventType Event { get; set; } + + public string Data { get; set; } +} diff --git a/ShoppingAssistantApi.Application/Models/ProductSearch/Suggestion.cs b/ShoppingAssistantApi.Application/Models/ProductSearch/Suggestion.cs new file mode 100644 index 0000000..ccefea2 --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/ProductSearch/Suggestion.cs @@ -0,0 +1,6 @@ +namespace ShoppingAssistantApi.Application.Models.ProductSearch; + +public class Suggestion +{ + public string Text { get; set; } +} diff --git a/ShoppingAssistantApi.Domain/Enums/SearchEventType.cs b/ShoppingAssistantApi.Domain/Enums/SearchEventType.cs new file mode 100644 index 0000000..3388692 --- /dev/null +++ b/ShoppingAssistantApi.Domain/Enums/SearchEventType.cs @@ -0,0 +1,24 @@ +namespace ShoppingAssistantApi.Domain.Enums; + +public enum SearchEventType +{ + Wishlist = 0, + Message = 1, + Suggestion = 2, + Product = 3 +} + +public static class SearchEventTypeExtensions +{ + public static string ToSseEventString(this SearchEventType eventType) + { + return eventType switch + { + SearchEventType.Wishlist => "wishlist", + SearchEventType.Message => "message", + SearchEventType.Suggestion => "suggestion", + SearchEventType.Product => "product", + _ => throw new ArgumentOutOfRangeException(nameof(eventType), eventType, null), + }; + } +} diff --git a/ShoppingAssistantApi.Infrastructure/Services/ProductService.cs b/ShoppingAssistantApi.Infrastructure/Services/ProductService.cs index ecaf0fb..c108f78 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/ProductService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/ProductService.cs @@ -25,6 +25,13 @@ public class ProductService : IProductService _wishlistsService = wishlistsService; } + public IAsyncEnumerable SearchProductAsync(string wishlistId, MessageCreateDto message, CancellationToken cancellationToken) + { + // get all messages from wishlist + + throw new NotImplementedException(); + } + public async IAsyncEnumerable<(List ProductNames, WishlistDto Wishlist)> StartNewSearchAndReturnWishlist(Message message, CancellationToken cancellationToken) { List messages = new List() diff --git a/ShoppingAssistantApi.UnitTests/ProductTests.cs b/ShoppingAssistantApi.UnitTests/ProductTests.cs index 1ddee69..78b1376 100644 --- a/ShoppingAssistantApi.UnitTests/ProductTests.cs +++ b/ShoppingAssistantApi.UnitTests/ProductTests.cs @@ -17,20 +17,43 @@ public class ProductTests { private Mock _openAiServiceMock; - private Mock _productServiceMock; + private IProductService _productService; public Mock _wishListServiceMock; public ProductTests() { _openAiServiceMock = new Mock(); - _productServiceMock = new Mock(); _wishListServiceMock = new Mock(); + _productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object); } [Fact] public async Task StartNewSearchAndReturnWishlist_CreatesWishlistObject() { + // Arrange + var expectedOpenAiMessage = new OpenAiMessage + { + Role = OpenAiRole.User, + Content = "{ \"Name\": [{ \"Name\": \"NVIDIA GeForce RTX 3080\" }, { \"Name\": \"AMD Radeon RX 6900 XT\" }] }" + }; + + _openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny(), CancellationToken.None)) + .Returns((ChatCompletionRequest request, CancellationToken token) => + { + var asyncEnumerable = new List { expectedOpenAiMessage.Content }.ToAsyncEnumerable(); + return asyncEnumerable; + }); + + _wishListServiceMock.Setup(x => x.StartPersonalWishlistAsync(It.IsAny(), CancellationToken.None)) + .ReturnsAsync(new WishlistDto + { + Id = "someID", + Name = "MacBook", + Type = "Product", // Use enum + CreatedById = "someId" + }); + var message = new Message { Id = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), @@ -38,45 +61,20 @@ public class ProductTests CreatedById = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), Role = "user" }; - var cancellationToken = CancellationToken.None; - - var expectedOpenAiMessage = new OpenAiMessage - { - Role = OpenAiRole.User, - Content = "{ \"Name\": [{ \"Name\": \"NVIDIA GeForce RTX 3080\" }, { \"Name\": \"AMD Radeon RX 6900 XT\" }] }" - }; - - _openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny(), cancellationToken)) - .Returns((ChatCompletionRequest request, CancellationToken token) => - { - var asyncEnumerable = new List { expectedOpenAiMessage.Content }.ToAsyncEnumerable(); - return asyncEnumerable; - }); - - _wishListServiceMock.Setup(x => x.StartPersonalWishlistAsync(It.IsAny(), cancellationToken)) - .ReturnsAsync(new WishlistDto - { - Id = "someID", - Name = "MacBook", - Type = "Product", - CreatedById = "someId" - }); - - var productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object); List productNames = null; WishlistDto createdWishList = null; - var result = productService.StartNewSearchAndReturnWishlist(message, cancellationToken); + // Act + var result = _productService.StartNewSearchAndReturnWishlist(message, CancellationToken.None); await foreach (var (productList, wishlist) in result) { productNames = productList; createdWishList = wishlist; } - - var openAiContent = JObject.Parse(expectedOpenAiMessage.Content); + // Assert Assert.NotNull(createdWishList); Assert.NotNull(productNames); }