added new tests

This commit is contained in:
stasex 2023-10-25 17:28:55 +03:00
parent 73b9b5213f
commit 40f294f61b
4 changed files with 213 additions and 133 deletions

View File

@ -2,7 +2,7 @@ namespace ShoppingAssistantApi.Application.Models.OpenAi;
public class ChatCompletionRequest
{
public string Model { get; set; } = "gpt-3.5-turbo";
public string Model { get; set; } = "gpt-4";
public List<OpenAiMessage> Messages { get; set; }

View File

@ -29,6 +29,14 @@ public class ProductService : IProductService
public async IAsyncEnumerable<ServerSentEvent> SearchProductAsync(string wishlistId, MessageCreateDto message, CancellationToken cancellationToken)
{
string promptForGpt =
"You are a Shopping Assistant that helps people find product recommendations. Ask user additional questions if more context needed." +
"\nYou must return data with one of the prefixes:" +
"\n[Question] - return question" +
"\n[Suggestions] - return semicolon separated suggestion how to answer to a question" +
"\n[Message] - return text" +
"\n[Products] - return semicolon separated product names";
var isFirstMessage = await _messagesRepository.GetCountAsync(message=>message.WishlistId==ObjectId.Parse((wishlistId)), cancellationToken);
var chatRequest = new ChatCompletionRequest();
@ -42,12 +50,7 @@ public class ProductService : IProductService
new OpenAiMessage
{
Role = OpenAiRole.System.ToString().ToLower(),
Content = "You are a Shopping Assistant that helps people find product recommendations. Ask user additional questions if more context needed." +
"\nYou must return data with one of the prefixes:" +
"\n[Question] - return question" +
"\n[Suggestions] - return semicolon separated suggestion how to answer to a question" +
"\n[Message] - return text" +
"\n[Products] - return semicolon separated product names"
Content = promptForGpt
},
new OpenAiMessage()
@ -70,17 +73,6 @@ public class ProductService : IProductService
Data = "What are you looking for?"
};
yield return new ServerSentEvent
{
Event = SearchEventType.Suggestion,
Data = "Bicycle"
};
yield return new ServerSentEvent
{
Event = SearchEventType.Suggestion,
Data = "Laptop"
};
}
if(isFirstMessage!=0)
@ -89,6 +81,11 @@ public class ProductService : IProductService
.GetMessagesPageFromPersonalWishlistAsync(wishlistId, 1, 50, cancellationToken).Result.Items.ToList();
var messagesForOpenAI = new List<OpenAiMessage>();
messagesForOpenAI.Add(new OpenAiMessage()
{
Role = OpenAiRole.System.ToString().ToLower(),
Content = promptForGpt
});
foreach (var item in previousMessages )
{
messagesForOpenAI.Add(
@ -128,18 +125,6 @@ public class ProductService : IProductService
Text = messageBuffer.Text,
}, cancellationToken);
}
if (dataTypeHolder=="[Products]" && productBuffer.Name!=null)
{
_wishlistsService.AddProductToPersonalWishlistAsync(wishlistId, new ProductCreateDto()
{
Url = "",
Name = productBuffer.Name,
Rating = 0,
Description = "",
ImagesUrls = new []{"", ""},
WasOpened = false
}, cancellationToken);
}
dataTypeHolder = string.Empty;
dataTypeHolder += data;
}
@ -169,7 +154,6 @@ public class ProductService : IProductService
break;
case SearchEventType.Suggestion:
suggestionBuffer.Text += data;
if (data.Contains(";"))
{
yield return new ServerSentEvent
@ -178,11 +162,12 @@ public class ProductService : IProductService
Data = suggestionBuffer.Text
};
suggestionBuffer.Text = string.Empty;
break;
}
suggestionBuffer.Text += data;
break;
case SearchEventType.Product:
productBuffer.Name += data;
if (data.Contains(";"))
{
yield return new ServerSentEvent
@ -191,7 +176,20 @@ public class ProductService : IProductService
Data = productBuffer.Name
};
productBuffer.Name = string.Empty;
await _wishlistsService.AddProductToPersonalWishlistAsync(wishlistId, new ProductCreateDto()
{
Url = "",
Name = productBuffer.Name,
Rating = 0,
Description = "",
ImagesUrls = new []{"", ""},
WasOpened = false
}, cancellationToken);
break;
}
productBuffer.Name += data;
break;
}
}

View File

@ -25,8 +25,10 @@ public class ProductsTests : TestsBase
// Act
var response = await _httpClient.PostAsJsonAsync($"http://127.0.0.1:5183/api/ProductsSearch/search/{wishlistId}", message);
var responseContent = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(responseContent);
}
}

View File

@ -1,18 +1,14 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Bson;
using Moq;
using Newtonsoft.Json.Linq;
using Moq;
using ShoppingAssistantApi.Application.IRepositories;
using ShoppingAssistantApi.Application.IServices;
using ShoppingAssistantApi.Application.Models.CreateDtos;
using ShoppingAssistantApi.Application.Models.Dtos;
using ShoppingAssistantApi.Application.Models.OpenAi;
using ShoppingAssistantApi.Application.Models.ProductSearch;
using ShoppingAssistantApi.Application.Paging;
using ShoppingAssistantApi.Domain.Entities;
using ShoppingAssistantApi.Domain.Enums;
using ShoppingAssistantApi.Infrastructure.Services;
using System.Linq.Expressions;
namespace ShoppingAssistantApi.Tests.Tests;
@ -24,21 +20,21 @@ public class ProductTests
private Mock<IWishlistsService> _wishListServiceMock;
private IMessagesRepository _messagesRepository;
private Mock<IMessagesRepository> _messagesRepositoryMock;
public ProductTests(IMessagesRepository messagesRepository)
public ProductTests()
{
_messagesRepository = messagesRepository;
_messagesRepositoryMock = new Mock<IMessagesRepository>();
_openAiServiceMock = new Mock<IOpenAiService>();
_wishListServiceMock = new Mock<IWishlistsService>();
_productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object, _messagesRepository);
_productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object, _messagesRepositoryMock.Object);
}
[Fact]
public async Task SearchProductAsync_WhenWishlistExists_ReturnsExpectedEvents()
public async Task SearchProductAsync_WhenWishlistsWithoutMessages_ReturnsExpectedEvents()
{
// Arrange
string wishlistId = "existingWishlistId"; // Simulating an existing wishlist ID
string wishlistId = "existingWishlistId";
var message = new MessageCreateDto
{
Text = "Your message text here"
@ -48,67 +44,44 @@ public class ProductTests
// Define your expected SSE data for the test
var expectedSseData = new List<string>
{
"[",
"Message",
"]",
" What",
" u",
" want",
" ?",
"[",
"Options",
"]",
" USB-C",
" ;",
" Keyboard",
" ultra",
" ;",
"?\n",
"[",
"Options",
"]",
" USB",
"-C",
" ;",
"[",
"Products",
"]",
" GTX",
" 3090",
" ;",
" GTX",
" 3070TI",
" ;",
" GTX",
" 4070TI",
" ;",
" ?"
"[", "Message", "]", " What", " u", " want", " ?", "[", "Options", "]", " USB-C", " ;", " Keyboard", " ultra",
" ;", "[", "Options", "]", " USB", "-C", " ;", "[", "Products", "]", " GTX", " 3090", " ;", " GTX",
" 3070TI", " ;", " GTX", " 4070TI", " ;", " ?"
};
var expectedMessages = new List<string> { "What are you looking for?" };
// Mock the GetChatCompletionStream method to provide the expected SSE data
_openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny<ChatCompletionRequest>(), cancellationToken))
.Returns(expectedSseData.ToAsyncEnumerable());
_wishListServiceMock.Setup(w => w.GetMessagesPageFromPersonalWishlistAsync(wishlistId, 1, 1, cancellationToken))
.ReturnsAsync(new PagedList<MessageDto>(new List<MessageDto>
{
new MessageDto
{
Text = "Some existing message",
Id = "",
CreatedById = "",
Role = ""
}
}, 1, 1, 1));
_messagesRepositoryMock.Setup(m => m.GetCountAsync(It.IsAny<Expression<Func<Message, bool>>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(0);
_wishListServiceMock.Setup(w => w.AddMessageToPersonalWishlistAsync(wishlistId, It.IsAny<MessageCreateDto>(), cancellationToken))
.Verifiable();
// Act
var resultStream = _productService.SearchProductAsync(wishlistId, message, cancellationToken);
// Convert the result stream to a list of ServerSentEvent
var actualSseEvents = await resultStream.ToListAsync();
var receivedMessages = actualSseEvents
.Where(e => e.Event == SearchEventType.Message)
.Select(e => e.Data)
.ToList();
var receivedSuggestions = actualSseEvents
.Where(e => e.Event == SearchEventType.Suggestion)
.Select(e => e.Data)
.ToList();
// Assert
// Check if the actual SSE events match the expected SSE events
Assert.NotNull(actualSseEvents);
Assert.Equal(expectedMessages, receivedMessages);
_wishListServiceMock.Verify(w => w.AddMessageToPersonalWishlistAsync(wishlistId, It.IsAny<MessageCreateDto>(), cancellationToken), Times.Once);
}
@ -120,58 +93,165 @@ public class ProductTests
var message = new MessageCreateDto { Text = "Your message text" };
var cancellationToken = new CancellationToken();
var productService = new ProductService(_openAiServiceMock.Object, _wishListServiceMock.Object, _messagesRepository);
var productService = _productService;
var expectedSseData = new List<string>
{
"[",
"Message",
"]",
" What",
" u",
" want",
" ?",
"[",
"Options",
"]",
" USB-C",
" ;",
" Keyboard",
" ultra",
" ;",
"?\n",
"[",
"Options",
"]",
" USB",
"-C",
" ;",
"[",
"Products",
"]",
" GTX",
" 3090",
" ;",
" GTX",
" 3070TI",
" ;",
" GTX",
" 4070TI",
" ;",
" ?"
"[", "Message", "]", " What", " u", " want", " ?", "[", "Options", "]", "USB-C", " ;", "Keyboard", " ultra",
" ;", "[", "Options", "]", "USB", "-C", " ;"
};
var expectedMessages = new List<string> { " What", " u", " want", " ?" };
var expectedSuggestions = new List<string> { "USB-C", "Keyboard ultra", "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);
_messagesRepositoryMock.Setup(m => m.GetCountAsync(It.IsAny<Expression<Func<Message, bool>>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(3);
_wishListServiceMock.Setup(w => w.AddMessageToPersonalWishlistAsync(wishlistId, It.IsAny<MessageCreateDto>(), cancellationToken))
.Verifiable();
_wishListServiceMock
.Setup(w => w.GetMessagesPageFromPersonalWishlistAsync(
It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new PagedList<MessageDto>(new List<MessageDto>
{
new MessageDto
{
Text = "Message 1",
Id = "1",
CreatedById = "User2",
Role = "User"
},
new MessageDto
{
Text = "Message 2",
Id = "2",
CreatedById = "User2",
Role = "User"
},
new MessageDto
{
Text = "Message 3",
Id = "3",
CreatedById = "User2",
Role = "User"
},
}, 1, 3, 3));
// Act
var resultStream = _productService.SearchProductAsync(wishlistId, message, cancellationToken);
// Convert the result stream to a list of ServerSentEvent
var actualSseEvents = await resultStream.ToListAsync();
var receivedMessages = actualSseEvents
.Where(e => e.Event == SearchEventType.Message)
.Select(e => e.Data)
.ToList();
var receivedSuggestions = actualSseEvents
.Where(e => e.Event == SearchEventType.Suggestion)
.Select(e => e.Data)
.ToList();
// Assert
Assert.NotNull(actualSseEvents);
Assert.Equal(3, actualSseEvents.Count);
Assert.Equal(expectedMessages, receivedMessages);
Assert.Equal(expectedSuggestions, receivedSuggestions);
_wishListServiceMock.Verify(w => w.AddMessageToPersonalWishlistAsync(wishlistId, It.IsAny<MessageCreateDto>(), cancellationToken), Times.Once);
}
[Fact]
public async void SearchProductAsync_WithExistingMessageInWishlistAndAddProduct_ReturnsExpectedEvents()
{
// Arrange
var wishlistId = "your_wishlist_id";
var message = new MessageCreateDto { Text = "Your message text" };
var cancellationToken = new CancellationToken();
var productService = _productService;
var expectedSseData = new List<string>
{
"[", "Message", "]", " What", " u", " want", " ?", "[", "Options", "]", "USB-C", " ;", "Keyboard", " ultra",
" ;", "[", "Options", "]", "USB", "-C", " ;", "[", "Products", "]", " GTX", " 3090", " ;", " GTX",
" 3070TI", " ;", " GTX", " 4070TI", " ;", " ?"
};
var expectedMessages = new List<string> { " What", " u", " want", " ?" };
var expectedSuggestions = new List<string> { "USB-C", "Keyboard ultra", "USB-C" };
// Mock the GetChatCompletionStream method to provide the expected SSE data
_openAiServiceMock.Setup(x => x.GetChatCompletionStream(It.IsAny<ChatCompletionRequest>(), cancellationToken))
.Returns(expectedSseData.ToAsyncEnumerable());
_messagesRepositoryMock.Setup(m => m.GetCountAsync(It.IsAny<Expression<Func<Message, bool>>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(3);
_wishListServiceMock
.Setup(w => w.AddProductToPersonalWishlistAsync(
It.IsAny<string>(), It.IsAny<ProductCreateDto>(), It.IsAny<CancellationToken>()))
.Verifiable();
_wishListServiceMock.Setup(w => w.AddProductToPersonalWishlistAsync(wishlistId, It.IsAny<ProductCreateDto>(), cancellationToken))
.Verifiable();
_wishListServiceMock
.Setup(w => w.GetMessagesPageFromPersonalWishlistAsync(
It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new PagedList<MessageDto>(new List<MessageDto>
{
new MessageDto
{
Text = "Message 1",
Id = "1",
CreatedById = "User2",
Role = "User"
},
new MessageDto
{
Text = "Message 2",
Id = "2",
CreatedById = "User2",
Role = "User"
},
new MessageDto
{
Text = "Message 3",
Id = "3",
CreatedById = "User2",
Role = "User"
},
}, 1, 3, 3));
// Act
var resultStream = _productService.SearchProductAsync(wishlistId, message, cancellationToken);
// Convert the result stream to a list of ServerSentEvent
var actualSseEvents = await resultStream.ToListAsync();
var receivedMessages = actualSseEvents
.Where(e => e.Event == SearchEventType.Message)
.Select(e => e.Data)
.ToList();
var receivedSuggestions = actualSseEvents
.Where(e => e.Event == SearchEventType.Suggestion)
.Select(e => e.Data)
.ToList();
// Assert
Assert.NotNull(actualSseEvents);
Assert.Equal(expectedMessages, receivedMessages);
Assert.Equal(expectedSuggestions, receivedSuggestions);
_wishListServiceMock.Verify(w => w.AddProductToPersonalWishlistAsync(
It.IsAny<string>(), It.IsAny<ProductCreateDto>(), It.IsAny<CancellationToken>()), Times.Exactly(3));
_wishListServiceMock.Verify(w => w.AddMessageToPersonalWishlistAsync(
wishlistId, It.IsAny<MessageCreateDto>(), cancellationToken), Times.Once);
}
}