mirror of
https://github.com/Shchoholiev/shopping-assistant-api.git
synced 2025-04-04 16:49:36 +00:00
added the initial implementation of the method SearchProductAsync and a rough test for it
This commit is contained in:
parent
c5fe0c1d5f
commit
ba116a3533
@ -10,6 +10,7 @@ using ShoppingAssistantApi.Application.Models.OpenAi;
|
|||||||
using ShoppingAssistantApi.Application.Models.ProductSearch;
|
using ShoppingAssistantApi.Application.Models.ProductSearch;
|
||||||
using ShoppingAssistantApi.Domain.Entities;
|
using ShoppingAssistantApi.Domain.Entities;
|
||||||
using ShoppingAssistantApi.Domain.Enums;
|
using ShoppingAssistantApi.Domain.Enums;
|
||||||
|
using ServerSentEvent = ShoppingAssistantApi.Application.Models.ProductSearch.ServerSentEvent;
|
||||||
|
|
||||||
namespace ShoppingAssistantApi.Infrastructure.Services;
|
namespace ShoppingAssistantApi.Infrastructure.Services;
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ public class ProductService : IProductService
|
|||||||
private readonly IWishlistsService _wishlistsService;
|
private readonly IWishlistsService _wishlistsService;
|
||||||
|
|
||||||
private readonly IOpenAiService _openAiService;
|
private readonly IOpenAiService _openAiService;
|
||||||
|
|
||||||
|
|
||||||
public ProductService(IOpenAiService openAiService, IWishlistsService wishlistsService)
|
public ProductService(IOpenAiService openAiService, IWishlistsService wishlistsService)
|
||||||
{
|
{
|
||||||
@ -25,13 +27,127 @@ public class ProductService : IProductService
|
|||||||
_wishlistsService = wishlistsService;
|
_wishlistsService = wishlistsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IAsyncEnumerable<ServerSentEvent> SearchProductAsync(string wishlistId, MessageCreateDto message, CancellationToken cancellationToken)
|
public async IAsyncEnumerable<ServerSentEvent> SearchProductAsync(string wishlistId, MessageCreateDto message, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Documentation: https://shchoholiev.atlassian.net/l/cp/JizkynhU
|
var chatRequest = new ChatCompletionRequest
|
||||||
|
{
|
||||||
|
Messages = new List<OpenAiMessage>
|
||||||
|
{
|
||||||
|
new OpenAiMessage
|
||||||
|
{
|
||||||
|
Role = "User",
|
||||||
|
Content = PromptForProductSearch(message.Text)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Stream = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var currentDataType = SearchEventType.Wishlist;
|
||||||
|
var dataTypeHolder = string.Empty;
|
||||||
|
|
||||||
throw new NotImplementedException();
|
await foreach (var data in _openAiService.GetChatCompletionStream(chatRequest, cancellationToken))
|
||||||
|
{
|
||||||
|
if (data.Contains("["))
|
||||||
|
{
|
||||||
|
dataTypeHolder = string.Empty;
|
||||||
|
dataTypeHolder += data;
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (data.Contains("]"))
|
||||||
|
{
|
||||||
|
dataTypeHolder += data;
|
||||||
|
currentDataType = DetermineDataType(dataTypeHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (dataTypeHolder=="[" && !data.Contains("["))
|
||||||
|
{
|
||||||
|
dataTypeHolder += data;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (currentDataType)
|
||||||
|
{
|
||||||
|
case SearchEventType.Message:
|
||||||
|
yield return new ServerSentEvent
|
||||||
|
{
|
||||||
|
Event = SearchEventType.Message,
|
||||||
|
Data = data
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SearchEventType.Suggestion:
|
||||||
|
yield return new ServerSentEvent
|
||||||
|
{
|
||||||
|
Event = SearchEventType.Suggestion,
|
||||||
|
Data = data
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SearchEventType.Product:
|
||||||
|
yield return new ServerSentEvent
|
||||||
|
{
|
||||||
|
Event = SearchEventType.Product,
|
||||||
|
Data = data
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SearchEventType.Wishlist:
|
||||||
|
yield return new ServerSentEvent
|
||||||
|
{
|
||||||
|
Event = SearchEventType.Wishlist,
|
||||||
|
Data = data
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
dataTypeHolder = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SearchEventType DetermineDataType(string dataTypeHolder)
|
||||||
|
{
|
||||||
|
if (dataTypeHolder.StartsWith("[Question]"))
|
||||||
|
{
|
||||||
|
return SearchEventType.Message;
|
||||||
|
}
|
||||||
|
else if (dataTypeHolder.StartsWith("[Options]"))
|
||||||
|
{
|
||||||
|
return SearchEventType.Suggestion;
|
||||||
|
}
|
||||||
|
else if (dataTypeHolder.StartsWith("[Message]"))
|
||||||
|
{
|
||||||
|
return SearchEventType.Message;
|
||||||
|
}
|
||||||
|
else if (dataTypeHolder.StartsWith("[Products]"))
|
||||||
|
{
|
||||||
|
return SearchEventType.Product;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return SearchEventType.Wishlist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: remove all methods below
|
// TODO: remove all methods below
|
||||||
public async IAsyncEnumerable<(List<ProductName> ProductNames, WishlistDto Wishlist)> StartNewSearchAndReturnWishlist(Message message, CancellationToken cancellationToken)
|
public async IAsyncEnumerable<(List<ProductName> ProductNames, WishlistDto Wishlist)> StartNewSearchAndReturnWishlist(Message message, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@ -39,7 +155,7 @@ public class ProductService : IProductService
|
|||||||
{
|
{
|
||||||
new OpenAiMessage()
|
new OpenAiMessage()
|
||||||
{
|
{
|
||||||
Role = OpenAiRole.User,
|
Role = "User",
|
||||||
Content = PromptForProductSearch(message.Text)
|
Content = PromptForProductSearch(message.Text)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -73,7 +189,7 @@ public class ProductService : IProductService
|
|||||||
{
|
{
|
||||||
new OpenAiMessage()
|
new OpenAiMessage()
|
||||||
{
|
{
|
||||||
Role = OpenAiRole.User,
|
Role = "User",
|
||||||
Content = PromptForProductSearchWithQuestion(message.Text)
|
Content = PromptForProductSearchWithQuestion(message.Text)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -114,7 +230,7 @@ public class ProductService : IProductService
|
|||||||
{
|
{
|
||||||
new OpenAiMessage()
|
new OpenAiMessage()
|
||||||
{
|
{
|
||||||
Role = OpenAiRole.User,
|
Role = "User",
|
||||||
Content = PromptForRecommendationsForProductSearch(message.Text)
|
Content = PromptForRecommendationsForProductSearch(message.Text)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using FluentAssertions;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using MongoDB.Bson;
|
using MongoDB.Bson;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
@ -28,13 +29,108 @@ public class ProductTests
|
|||||||
_productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object);
|
_productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*[Fact]
|
||||||
|
public async Task SearchProductAsync_WhenWishlistIdIsEmpty_CreatesWishlistAndReturnsEvent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string wishlistId = string.Empty; // Simulating an empty wishlist ID
|
||||||
|
var message = new MessageCreateDto
|
||||||
|
{
|
||||||
|
Text = "Your message text here"
|
||||||
|
};
|
||||||
|
var cancellationToken = CancellationToken.None;
|
||||||
|
|
||||||
|
// Define your expected new wishlist and event data
|
||||||
|
var newWishlistId = "123"; // Example wishlist ID
|
||||||
|
var expectedEvent = new ServerSentEvent
|
||||||
|
{
|
||||||
|
Event = SearchEventType.Wishlist,
|
||||||
|
Data = newWishlistId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock the StartPersonalWishlistAsync method to return the expected wishlist
|
||||||
|
_wishListServiceMock.Setup(x => x.StartPersonalWishlistAsync(It.IsAny<WishlistCreateDto>(), CancellationToken.None))
|
||||||
|
.ReturnsAsync(new WishlistDto
|
||||||
|
{
|
||||||
|
Id = "123",
|
||||||
|
Name = "MacBook",
|
||||||
|
Type = WishlistTypes.Product.ToString(), // Use enum
|
||||||
|
CreatedById = "someId"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock the GetChatCompletionStream method to provide SSE data
|
||||||
|
var sseData = new List<string> { "[Question] What is your question?" };
|
||||||
|
_openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny<ChatCompletionRequest>(), cancellationToken))
|
||||||
|
.Returns(sseData.ToAsyncEnumerable());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _productService.SearchProductAsync(wishlistId, message, cancellationToken).ToListAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// Check if the first item in the result is the expected wishlist creation event
|
||||||
|
var firstEvent = result.FirstOrDefault();
|
||||||
|
Assert.NotNull(firstEvent);
|
||||||
|
Assert.Equal(expectedEvent.Event, firstEvent.Event);
|
||||||
|
Assert.Equal(expectedEvent.Data, firstEvent.Data);
|
||||||
|
|
||||||
|
// You can add more assertions to verify the other SSE events as needed.
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SearchProductAsync_WhenWishlistExists_ReturnsExpectedEvents()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
string wishlistId = "existingWishlistId"; // Simulating an existing wishlist ID
|
||||||
|
var message = new MessageCreateDto
|
||||||
|
{
|
||||||
|
Text = "Your message text here"
|
||||||
|
};
|
||||||
|
var cancellationToken = CancellationToken.None;
|
||||||
|
|
||||||
|
// Define your expected SSE data for the test
|
||||||
|
var expectedSseData = new List<string>
|
||||||
|
{
|
||||||
|
"[",
|
||||||
|
"Question",
|
||||||
|
"]",
|
||||||
|
" What",
|
||||||
|
" features",
|
||||||
|
" are",
|
||||||
|
" you",
|
||||||
|
" looking",
|
||||||
|
"?\n",
|
||||||
|
"[",
|
||||||
|
"Options",
|
||||||
|
"]",
|
||||||
|
" USB",
|
||||||
|
"-C"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock the GetChatCompletionStream method to provide the expected SSE data
|
||||||
|
_openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny<ChatCompletionRequest>(), cancellationToken))
|
||||||
|
.Returns(expectedSseData.ToAsyncEnumerable());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var resultStream = _productService.SearchProductAsync(wishlistId, message, cancellationToken);
|
||||||
|
|
||||||
|
// Convert the result stream to a list of ServerSentEvent
|
||||||
|
var actualSseEvents = await resultStream.ToListAsync();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
// Check if the actual SSE events match the expected SSE events
|
||||||
|
Assert.Equal(8, actualSseEvents.Count);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task StartNewSearchAndReturnWishlist_CreatesWishlistObject()
|
public async Task StartNewSearchAndReturnWishlist_CreatesWishlistObject()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedOpenAiMessage = new OpenAiMessage
|
var expectedOpenAiMessage = new OpenAiMessage
|
||||||
{
|
{
|
||||||
Role = OpenAiRole.User,
|
Role = "User",
|
||||||
Content = "{ \"Name\": [{ \"Name\": \"NVIDIA GeForce RTX 3080\" }, { \"Name\": \"AMD Radeon RX 6900 XT\" }] }"
|
Content = "{ \"Name\": [{ \"Name\": \"NVIDIA GeForce RTX 3080\" }, { \"Name\": \"AMD Radeon RX 6900 XT\" }] }"
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -93,7 +189,7 @@ public class ProductTests
|
|||||||
|
|
||||||
var expectedOpenAiMessage = new OpenAiMessage
|
var expectedOpenAiMessage = new OpenAiMessage
|
||||||
{
|
{
|
||||||
Role = OpenAiRole.User,
|
Role = "User",
|
||||||
Content = "{ \"Name\": [{ \"Name\": \"NVIDIA GeForce RTX 3080\" }, { \"Name\": \"AMD Radeon RX 6900 XT\" }] }"
|
Content = "{ \"Name\": [{ \"Name\": \"NVIDIA GeForce RTX 3080\" }, { \"Name\": \"AMD Radeon RX 6900 XT\" }] }"
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,7 +228,7 @@ public class ProductTests
|
|||||||
|
|
||||||
var expectedOpenAiMessage = new OpenAiMessage
|
var expectedOpenAiMessage = new OpenAiMessage
|
||||||
{
|
{
|
||||||
Role = OpenAiRole.User,
|
Role = "User",
|
||||||
Content = "{ \"AdditionalQuestion\": [{ \"QuestionText\": \"What specific MacBook model are you using?\" }," +
|
Content = "{ \"AdditionalQuestion\": [{ \"QuestionText\": \"What specific MacBook model are you using?\" }," +
|
||||||
" { \"QuestionText\": \"Do you have any preferences for brand or capacity?\" }] }"
|
" { \"QuestionText\": \"Do you have any preferences for brand or capacity?\" }] }"
|
||||||
};
|
};
|
||||||
@ -170,7 +266,7 @@ public class ProductTests
|
|||||||
|
|
||||||
var expectedOpenAiMessage = new OpenAiMessage
|
var expectedOpenAiMessage = new OpenAiMessage
|
||||||
{
|
{
|
||||||
Role = OpenAiRole.User,
|
Role = "User",
|
||||||
Content = "{ \"Recommendation\": [\"Recommendation 1\", \"Recommendation 2\"] }"
|
Content = "{ \"Recommendation\": [\"Recommendation 1\", \"Recommendation 2\"] }"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
|
||||||
<PackageReference Include="Moq" Version="4.20.69" />
|
<PackageReference Include="Moq" Version="4.20.69" />
|
||||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||||
|
Loading…
Reference in New Issue
Block a user