diff --git a/ShoppingAssistantApi.Api/Controllers/WeatherForecastController.cs b/ShoppingAssistantApi.Api/Controllers/WeatherForecastController.cs deleted file mode 100644 index 86d61b0..0000000 --- a/ShoppingAssistantApi.Api/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace ShoppingAssistantApi.Api.Controllers; -[ApiController] -[Route("[controller]")] -public class WeatherForecastController : ControllerBase -{ - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } -} diff --git a/ShoppingAssistantApi.Api/Program.cs b/ShoppingAssistantApi.Api/Program.cs index affb266..e7f574b 100644 --- a/ShoppingAssistantApi.Api/Program.cs +++ b/ShoppingAssistantApi.Api/Program.cs @@ -12,6 +12,7 @@ builder.Services.AddJWTTokenAuthentication(builder.Configuration); builder.Services.AddMapper(); builder.Services.AddInfrastructure(); builder.Services.AddServices(); +builder.Services.AddHttpClient(builder.Configuration); builder.Services.AddGraphQl(); builder.Services.AddControllers(); @@ -36,14 +37,12 @@ app.AddGlobalUserMiddleware(); app.MapGraphQL(); app.MapControllers(); -/* -using var scope = app.Services.CreateScope(); -var serviceProvider = scope.ServiceProvider; -using var cancellationTokenSource = new CancellationTokenSource(); -var cancellationToken = cancellationTokenSource.Token; -var initializer = new DbInitialaizer(serviceProvider); -initializer.InitialaizeDb(cancellationToken); -*/ + +// using var scope = app.Services.CreateScope(); +// var serviceProvider = scope.ServiceProvider; +// var initializer = new DbInitialaizer(serviceProvider); +// await initializer.InitialaizeDb(CancellationToken.None); + app.Run(); public partial class Program { } \ No newline at end of file diff --git a/ShoppingAssistantApi.Api/ShoppingAssistantApi.Api.csproj b/ShoppingAssistantApi.Api/ShoppingAssistantApi.Api.csproj index 33cec29..50dd57a 100644 --- a/ShoppingAssistantApi.Api/ShoppingAssistantApi.Api.csproj +++ b/ShoppingAssistantApi.Api/ShoppingAssistantApi.Api.csproj @@ -21,4 +21,8 @@ + + + + diff --git a/ShoppingAssistantApi.Api/WeatherForecast.cs b/ShoppingAssistantApi.Api/WeatherForecast.cs deleted file mode 100644 index 360f533..0000000 --- a/ShoppingAssistantApi.Api/WeatherForecast.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace ShoppingAssistantApi.Api; - -public class WeatherForecast -{ - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } -} diff --git a/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiChoice.cs b/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiChoice.cs new file mode 100644 index 0000000..3ce06dc --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiChoice.cs @@ -0,0 +1,12 @@ +namespace ShoppingAssistantApi.Application.Models.OpenAi; + +public class OpenAiChoice +{ + public OpenAiMessage Message { get; set; } + + public OpenAiDelta Delta { get; set; } + + public string FinishReason { get; set; } + + public int Index { get; set; } +} \ No newline at end of file diff --git a/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiDelta.cs b/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiDelta.cs new file mode 100644 index 0000000..c9b7dbc --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiDelta.cs @@ -0,0 +1,8 @@ +namespace ShoppingAssistantApi.Application.Models.OpenAi; + +public class OpenAiDelta +{ + public string Role { get; set; } + + public string Content { get; set; } +} \ No newline at end of file diff --git a/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiMessage.cs b/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiMessage.cs index 91bd757..edb4cba 100644 --- a/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiMessage.cs +++ b/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiMessage.cs @@ -4,7 +4,7 @@ namespace ShoppingAssistantApi.Application.Models.OpenAi; public class OpenAiMessage { - public OpenAiRole Role { get; set; } + public string Role { get; set; } public string Content { get; set; } } diff --git a/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiResponse.cs b/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiResponse.cs new file mode 100644 index 0000000..991b854 --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiResponse.cs @@ -0,0 +1,16 @@ +namespace ShoppingAssistantApi.Application.Models.OpenAi; + +public class OpenAiResponse +{ + public string Id { get; set; } + + public string Object { get; set; } + + public int Created { get; set; } + + public string Model { get; set; } + + public OpenAiUsage Usage { get; set; } + + public List Choices { get; set; } +} \ No newline at end of file diff --git a/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiUsage.cs b/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiUsage.cs new file mode 100644 index 0000000..6789eac --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiUsage.cs @@ -0,0 +1,10 @@ +namespace ShoppingAssistantApi.Application.Models.OpenAi; + +public class OpenAiUsage +{ + public int PromptTokens { get; set; } + + public int CompletionTokens { get; set; } + + public int TotalTokens { get; set; } +} \ No newline at end of file diff --git a/ShoppingAssistantApi.Domain/Enums/OpenAiRole.cs b/ShoppingAssistantApi.Domain/Enums/OpenAiRole.cs index 54d2c0a..a01e6a5 100644 --- a/ShoppingAssistantApi.Domain/Enums/OpenAiRole.cs +++ b/ShoppingAssistantApi.Domain/Enums/OpenAiRole.cs @@ -6,3 +6,21 @@ public enum OpenAiRole User, Assistant } + +public static class OpenAiRoleExtensions +{ + public static string RequestConvert(this OpenAiRole role) + { + switch (role) + { + case OpenAiRole.System: + return "system"; + case OpenAiRole.Assistant: + return "assistant"; + case OpenAiRole.User: + return "user"; + default: + return ""; + } + } +} \ No newline at end of file diff --git a/ShoppingAssistantApi.Infrastructure/InfrastructureExtentions/ServicesExtention.cs b/ShoppingAssistantApi.Infrastructure/InfrastructureExtentions/ServicesExtention.cs index 0db9d03..cbc7b65 100644 --- a/ShoppingAssistantApi.Infrastructure/InfrastructureExtentions/ServicesExtention.cs +++ b/ShoppingAssistantApi.Infrastructure/InfrastructureExtentions/ServicesExtention.cs @@ -1,8 +1,10 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using ShoppingAssistantApi.Application.IServices; using ShoppingAssistantApi.Application.IServices.Identity; using ShoppingAssistantApi.Infrastructure.Services; using ShoppingAssistantApi.Infrastructure.Services.Identity; +using System.Net.Http.Headers; namespace ShoppingAssistantApi.Infrastructure.InfrastructureExtentions; public static class ServicesExtention @@ -15,6 +17,21 @@ public static class ServicesExtention services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); + + return services; + } + + public static IServiceCollection AddHttpClient(this IServiceCollection services, IConfiguration configuration) + { + services.AddHttpClient( + "OpenAiHttpClient", + client => + { + client.BaseAddress = new Uri(configuration.GetValue("ApiUri")); + client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", configuration.GetValue("ApiKey")); + }); return services; } diff --git a/ShoppingAssistantApi.Infrastructure/Services/OpenAiService.cs b/ShoppingAssistantApi.Infrastructure/Services/OpenAiService.cs index 9ed750a..233388c 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/OpenAiService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/OpenAiService.cs @@ -1,3 +1,9 @@ +using System.IO; +using System.Net.Http.Headers; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; using ShoppingAssistantApi.Application.IServices; using ShoppingAssistantApi.Application.Models.OpenAi; @@ -5,20 +11,61 @@ namespace ShoppingAssistantApi.Infrastructure.Services; public class OpenAiService : IOpenAiService { + + private readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings + { + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new SnakeCaseNamingStrategy() + }, + NullValueHandling = NullValueHandling.Ignore, + }; + private readonly HttpClient _httpClient; - public OpenAiService(HttpClient client) + public OpenAiService(IHttpClientFactory httpClientFactory) { - _httpClient = client; + _httpClient = httpClientFactory.CreateClient("OpenAiHttpClient"); } - public Task GetChatCompletion(ChatCompletionRequest chat, CancellationToken cancellationToken) + public async Task GetChatCompletion(ChatCompletionRequest chat, CancellationToken cancellationToken) { - throw new NotImplementedException(); + chat.Stream = false; + var jsonBody = JsonConvert.SerializeObject(chat, _jsonSettings); + var body = new StringContent(jsonBody, Encoding.UTF8, "application/json"); + + using var httpResponse = await _httpClient.PostAsync("", body, cancellationToken); + + var responseBody = await httpResponse.Content.ReadAsStringAsync(cancellationToken); + + var data = JsonConvert.DeserializeObject(responseBody); + + return data.Choices[0].Message; } - public IAsyncEnumerable GetChatCompletionStream(ChatCompletionRequest chat, CancellationToken cancellationToken) + public async IAsyncEnumerable GetChatCompletionStream(ChatCompletionRequest chat, CancellationToken cancellationToken) { - throw new NotImplementedException(); + chat.Stream = true; + var jsonBody = JsonConvert.SerializeObject(chat, _jsonSettings); + + var body = new StringContent(jsonBody, Encoding.UTF8, "application/json"); + + using var httpResponse = await _httpClient.PostAsync("", body, cancellationToken); + + using var responseStream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken); + using var reader = new StreamReader(responseStream, Encoding.UTF8); + + while (!cancellationToken.IsCancellationRequested) + { + var jsonChunk = await reader.ReadLineAsync(); + if (jsonChunk.StartsWith("data: ")) + { + jsonChunk = jsonChunk.Substring("data: ".Length); + if (jsonChunk == "[DONE]") break; + var data = JsonConvert.DeserializeObject(jsonChunk); + if (data.Choices[0].Delta.Content == "" || data.Choices[0].Delta.Content == null) continue; + yield return data.Choices[0].Delta.Content; + } + } } -} +} \ No newline at end of file diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index 4d40e28..98425a6 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -1,47 +1,33 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using MongoDB.Bson; using MongoDB.Driver; -using ShoppingAssistantApi.Application.GlobalInstances; -using ShoppingAssistantApi.Application.IServices; using ShoppingAssistantApi.Application.IServices.Identity; -using ShoppingAssistantApi.Application.Models.CreateDtos; -using ShoppingAssistantApi.Application.Models.Dtos; -using ShoppingAssistantApi.Application.Models.Identity; using ShoppingAssistantApi.Domain.Entities; using ShoppingAssistantApi.Domain.Enums; +using ShoppingAssistantApi.Infrastructure.Services.Identity; using ShoppingAssistantApi.Persistance.Database; namespace ShoppingAssistantApi.Persistance.PersistanceExtentions; public class DbInitialaizer { - private readonly IUsersService _usersService; - - private readonly IUserManager _userManager; - - private readonly IRolesService _rolesService; - - private readonly ITokensService _tokensService; - - private readonly IWishlistsService _wishlistsService; - private readonly IMongoCollection _userCollection; + private readonly IMongoCollection _roleCollection; + private readonly IMongoCollection _wishlistCollection; - + private readonly IMongoCollection _productCollection; - public IEnumerable Roles { get; set; } + private readonly PasswordHasher passwordHasher; public DbInitialaizer(IServiceProvider serviceProvider) { - _usersService = serviceProvider.GetService(); - _rolesService = serviceProvider.GetService(); - _userManager = serviceProvider.GetService(); - _tokensService = serviceProvider.GetService(); - _wishlistsService = serviceProvider.GetService(); + passwordHasher = new PasswordHasher(new Logger(new LoggerFactory())); _wishlistCollection = serviceProvider.GetService().Db.GetCollection("Wishlists"); _userCollection = serviceProvider.GetService().Db.GetCollection("Users"); + _roleCollection = serviceProvider.GetService().Db.GetCollection("Roles"); _productCollection = serviceProvider.GetService().Db.GetCollection("Product"); } @@ -55,123 +41,140 @@ public class DbInitialaizer public async Task AddUsers(CancellationToken cancellationToken) { - var guestModel1 = new AccessGuestModel + var userRole = await (await _roleCollection.FindAsync(x => x.Name.Equals("User"))).FirstAsync(); + var guestRole = await (await _roleCollection.FindAsync(x => x.Name.Equals("Guest"))).FirstAsync(); + var adminRole = await (await _roleCollection.FindAsync(x => x.Name.Equals("Admin"))).FirstAsync(); + + var users = new User[] { - GuestId = Guid.NewGuid(), + new User + { + Id = ObjectId.Parse("6533bb29c8c22b038c71cf46"), + GuestId = Guid.NewGuid(), + Roles = new List {guestRole}, + CreatedById = ObjectId.Parse("6533bb29c8c22b038c71cf46"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bb29c8c22b038c71cf46"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false + }, + + new User + { + Id = ObjectId.Parse("6533bde5755745116be42ce7"), + GuestId = Guid.NewGuid(), + Roles = new List + { + guestRole, + userRole + }, + Phone = "+380953326869", + Email = "mykhailo.bilodid@nure.ua", + PasswordHash = this.passwordHasher.Hash("Yuiop12345"), + CreatedById = ObjectId.Parse("6533bde5755745116be42ce7"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bde5755745116be42ce7"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false + }, + + new User + { + Id = ObjectId.Parse("6533bded80fbc6e96250575b"), + GuestId = Guid.NewGuid(), + Roles = new List + { + guestRole, + userRole, + adminRole + }, + Phone = "+380953826869", + Email = "shopping.assistant.team@gmail.com", + PasswordHash = this.passwordHasher.Hash("Yuiop12345"), + CreatedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false }, + + new User + { + Id = ObjectId.Parse("6533bdf9efaca5bb0894f992"), + GuestId = Guid.NewGuid(), + Roles = new List + { + guestRole, + userRole + }, + Phone = "+380983326869", + Email = "vitalii.krasnorutski@nure.ua", + PasswordHash = this.passwordHasher.Hash("Yuiop12345"), + CreatedById = ObjectId.Parse("6533bdf9efaca5bb0894f992"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bdf9efaca5bb0894f992"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false }, + + new User + { + Id = ObjectId.Parse("6533be06d1b78a76c664ddae"), + GuestId = Guid.NewGuid(), + Roles = new List + { + guestRole, + userRole + }, + Phone = "+380953326888", + Email = "serhii.shchoholiev@nure.ua", + PasswordHash = this.passwordHasher.Hash("Yuiop12345"), + CreatedById = ObjectId.Parse("6533be06d1b78a76c664ddae"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533be06d1b78a76c664ddae"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false } }; - var guestModel2 = new AccessGuestModel - { - GuestId = Guid.NewGuid(), - }; - - var guestModel3 = new AccessGuestModel - { - GuestId = Guid.NewGuid(), - }; - - var guestModel4 = new AccessGuestModel - { - GuestId = Guid.NewGuid(), - }; - - var guestModel5 = new AccessGuestModel - { - GuestId = Guid.NewGuid(), - }; - - Task.WaitAll( - _userManager.AccessGuestAsync(guestModel1, cancellationToken), - _userManager.AccessGuestAsync(guestModel2, cancellationToken), - _userManager.AccessGuestAsync(guestModel3, cancellationToken), - _userManager.AccessGuestAsync(guestModel4, cancellationToken), - _userManager.AccessGuestAsync(guestModel5, cancellationToken) - ); - - var guests = await _usersService.GetUsersPageAsync(1, 4, cancellationToken); - var guestsResult = guests.Items.ToList(); - - var user1 = new UserDto - { - Id = guestsResult[0].Id, - GuestId = guestsResult[0].GuestId, - Roles = guestsResult[0].Roles, - Phone = "+380953326869", - Email = "mykhailo.bilodid@nure.ua", - Password = "Yuiop12345", - RefreshToken = _tokensService.GenerateRefreshToken(), - RefreshTokenExpiryDate = DateTime.Now.AddDays(7), - }; - - var user2 = new UserDto - { - Id = guestsResult[1].Id, - GuestId = guestsResult[1].GuestId, - Roles = guestsResult[1].Roles, - Phone = "+380953326888", - Email = "serhii.shchoholiev@nure.ua", - Password = "Yuiop12345", - RefreshToken = _tokensService.GenerateRefreshToken(), - RefreshTokenExpiryDate = DateTime.Now.AddDays(7), - }; - - var user3 = new UserDto - { - Id = guestsResult[2].Id, - GuestId = guestsResult[2].GuestId, - Roles = guestsResult[2].Roles, - Phone = "+380983326869", - Email = "vitalii.krasnorutski@nure.ua", - Password = "Yuiop12345", - RefreshToken = _tokensService.GenerateRefreshToken(), - RefreshTokenExpiryDate = DateTime.Now.AddDays(7), - }; - - var user4 = new UserDto - { - Id = guestsResult[3].Id, - GuestId = guestsResult[3].GuestId, - Roles = guestsResult[3].Roles, - Phone = "+380953826869", - Email = "shopping.assistant.team@gmail.com", - Password = "Yuiop12345", - RefreshToken = _tokensService.GenerateRefreshToken(), - RefreshTokenExpiryDate = DateTime.Now.AddDays(7), - }; - - GlobalUser.Id = ObjectId.Parse(user1.Id); - await _userManager.UpdateAsync(user1, cancellationToken); - - GlobalUser.Id = ObjectId.Parse(user2.Id); - await _userManager.UpdateAsync(user2, cancellationToken); - - GlobalUser.Id = ObjectId.Parse(user3.Id); - await _userManager.UpdateAsync(user3, cancellationToken); - - GlobalUser.Id = ObjectId.Parse(user4.Id); - await _userManager.UpdateAsync(user4, cancellationToken); + await _userCollection.InsertManyAsync(users); } public async Task AddRoles(CancellationToken cancellationToken) { - var role1 = new RoleCreateDto + var roles = new Role[] { - Name = "User" - }; + new Role + { + Id = ObjectId.Parse("6533b5882e7867b8b21e7b27"), + Name = "User", + CreatedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false + }, - var role2 = new RoleCreateDto - { - Name = "Admin" - }; + new Role + { + Id = ObjectId.Parse("6533b591a7f31776cd2d50fc"), + Name = "Guest", + CreatedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false + }, - var role3 = new RoleCreateDto - { - Name = "Guest" + new Role + { + Id = ObjectId.Parse("6533b59d1b09ab2618af5ff3"), + Name = "Admin", + CreatedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false + }, }; - - var dto1 = await _rolesService.AddRoleAsync(role1, cancellationToken); - var dto2 = await _rolesService.AddRoleAsync(role2, cancellationToken); - var dto3 = await _rolesService.AddRoleAsync(role3, cancellationToken); + await _roleCollection.InsertManyAsync(roles); } public async Task AddWishlistsWithMessages(CancellationToken cancellationToken) @@ -219,8 +222,8 @@ public class DbInitialaizer CreatedDateUtc = DateTime.UtcNow, WasOpened = false, WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab65cd") - }, - + }, + new Product() { Name = "Apple MagSafe Battery Pack", @@ -238,7 +241,7 @@ public class DbInitialaizer WasOpened = false, WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab65cd") }, - + new Product() { Name = "Logitech K400 Plus Wireless Touch With Easy Media Control and Built-in Touchpad", @@ -256,7 +259,7 @@ public class DbInitialaizer WasOpened = false, WishlistId = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab") }, - + new Product() { Name = "Logitech MX Anywhere 2S Wireless Mouse Use On Any Surface", diff --git a/ShoppingAssistantApi.Persistance/ShoppingAssistantApi.Persistance.csproj b/ShoppingAssistantApi.Persistance/ShoppingAssistantApi.Persistance.csproj index 951f76f..743a04f 100644 --- a/ShoppingAssistantApi.Persistance/ShoppingAssistantApi.Persistance.csproj +++ b/ShoppingAssistantApi.Persistance/ShoppingAssistantApi.Persistance.csproj @@ -14,6 +14,7 @@ + diff --git a/ShoppingAssistantApi.UnitTests/OpenAiServiceTests.cs b/ShoppingAssistantApi.UnitTests/OpenAiServiceTests.cs new file mode 100644 index 0000000..d9dffd9 --- /dev/null +++ b/ShoppingAssistantApi.UnitTests/OpenAiServiceTests.cs @@ -0,0 +1,155 @@ +using System.Net; +using Moq; +using Moq.Protected; +using ShoppingAssistantApi.Application.IServices; +using ShoppingAssistantApi.Application.Models.OpenAi; +using ShoppingAssistantApi.Domain.Enums; +using ShoppingAssistantApi.Infrastructure.Services; + +namespace ShoppingAssistantApi.UnitTests; + +public class OpenAiServiceTests +{ + private readonly IOpenAiService _openAiService; + + private readonly Mock _mockHttpMessageHandler; + + private readonly Mock _mockHttpClientFactory; + + public OpenAiServiceTests() + { + _mockHttpClientFactory = new Mock(); + _mockHttpMessageHandler = new Mock(); + + var client = new HttpClient(_mockHttpMessageHandler.Object); + client.BaseAddress = new Uri("https://www.google.com.ua/"); + + _mockHttpClientFactory + .Setup(factory => factory.CreateClient(It.IsAny())) + .Returns(() => + { + return client; + }); + + _openAiService = new OpenAiService(_mockHttpClientFactory.Object); + } + + [Fact] + public async Task GetChatCompletion_ValidChat_ReturnsNewMessage() + { + // Arrange + _mockHttpMessageHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(@" + { + ""id"": ""chatcmpl-89OMdgTZXOLAXv7bPUJ4SwrPpS5Md"", + ""object"": ""chat.completion"", + ""created"": 1697249299, + ""model"": ""gpt-3.5-turbo-0613"", + ""choices"": [ + { + ""index"": 0, + ""message"": { + ""role"": ""assistant"", + ""content"": ""Hello, World!"" + }, + ""finish_reason"": ""stop"" + } + ], + ""usage"": { + ""prompt_tokens"": 10, + ""completion_tokens"": 3, + ""total_tokens"": 13 + } + }"), + }); + + var chat = new ChatCompletionRequest + { + Messages = new List + { + new OpenAiMessage + { + Role = OpenAiRole.User.RequestConvert(), + Content = "Return Hello World!" + } + } + }; + + // Act + var newMessage = await _openAiService.GetChatCompletion(chat, CancellationToken.None); + + // Assert + Assert.NotNull(newMessage); + Assert.Equal("Hello, World!", newMessage.Content); + } + + // TODO: Add more tests +/* + [Fact] + public async Task GetChatCompletionStream_ValidChat_ReturnsNewMessage() + { + // Arrange + _mockHttpMessageHandler + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + .ReturnsAsync(new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(@" + { + ""id"": ""chatcmpl-89OMdgTZXOLAXv7bPUJ4SwrPpS5Md"", + ""object"": ""chat.completion"", + ""created"": 1697249299, + ""model"": ""gpt-3.5-turbo-0613"", + ""choices"": [ + { + ""index"": 0, + ""message"": { + ""role"": ""assistant"", + ""content"": ""Hello World!"" + }, + ""finish_reason"": ""stop"" + } + ], + ""usage"": { + ""prompt_tokens"": 10, + ""completion_tokens"": 3, + ""total_tokens"": 13 + } + }"), + }); + + var chat = new ChatCompletionRequest + { + Messages = new List + { + new OpenAiMessage + { + Role = OpenAiRole.User.RequestConvert(), + Content = "Return Hello World!" + } + } + }; + + // Act + var newMessage = _openAiService.GetChatCompletionStream(chat, CancellationToken.None); + + // Assert + Assert.NotNull(newMessage); + Assert.Equal("Hello World!", newMessage.ToString()); + } +*/ +} \ No newline at end of file