using Xunit; using meilisearch.NET; using Microsoft.Extensions.Logging; using meilisearch.NET.Configurations; using System.Net.Http; using Moq; using Meilisearch; using Microsoft.Extensions.Configuration; using System.Threading.Tasks; using System.Collections.Generic; using meilisearch.NET.Interfaces; using System.Diagnostics; using System.Runtime.InteropServices; using System.IO; using System.Threading; using System.Collections.ObjectModel; namespace meilisearch.NET.Tests; public class MeiliSearchServiceTests { private readonly Mock _httpClientMock; private readonly Mock> _loggerMock; private readonly Mock _meiliConfigurationMock; private readonly MeiliSearchService _meiliSearchService; public MeiliSearchServiceTests() { _httpClientMock = new Mock(); _loggerMock = new Mock>(); _meiliConfigurationMock = new Mock(); _meiliConfigurationMock.Setup(x => x.MeiliPort).Returns(7700); _meiliSearchService = new MeiliSearchService(_httpClientMock.Object, _loggerMock.Object, _meiliConfigurationMock.Object); } [Fact] public void Test_Service_Initialized() { Assert.NotNull(_meiliSearchService); } [Fact] public async Task Test_StartMeilisearch_Success() { // Mock the process start var processMock = new Mock(); processMock.Setup(p => p.Start()).Returns(true); // Mock the Process.Start method Mock processWrapperMock = new Mock(); processWrapperMock.Setup(p => p.Start(It.IsAny())).Returns(processMock.Object); // Override the GetMeilisearchBinaryName method var meiliSearchService = new TestMeiliSearchService(_httpClientMock.Object, _loggerMock.Object, _meiliConfigurationMock.Object, processWrapperMock.Object); // Call the StartMeilisearch method await meiliSearchService.StartMeilisearch(); // Assert that the process was started processWrapperMock.Verify(p => p.Start(It.IsAny()), Times.Once); } [Fact] public void Test_Stop_Success() { // Mock the process var processMock = new Mock(); processMock.Setup(p => p.Kill()); // Set the process to the MeiliSearchService _meiliSearchService.process = processMock.Object; // Call the Stop method _meiliSearchService.Stop(); // Assert that the process was killed processMock.Verify(p => p.Kill(), Times.Once); } [Fact] public void Test_IsMeilisearchRunning_Success() { // Mock the Process.GetProcessesByName method var processMock = new Mock(); var processes = new Process[] { processMock.Object }; Mock processStaticWrapperMock = new Mock(); processStaticWrapperMock.Setup(p => p.GetProcessesByName(It.IsAny())).Returns(processes); // Override the GetMeilisearchBinaryName method var meiliSearchService = new TestMeiliSearchService(_httpClientMock.Object, _loggerMock.Object, _meiliConfigurationMock.Object, null, processStaticWrapperMock.Object); // Call the IsMeilisearchRunning method var isRunning = meiliSearchService.IsMeilisearchRunning(); // Assert that the method returns true Assert.True(isRunning); } [Fact] public async Task Test_CreateIndex_Success() { // Arrange var indexName = "test_index"; var meilisearchClientMock = new Mock("http://localhost:7700", "masterKey"); var indexMock = new Mock(meilisearchClientMock.Object, indexName); var indexes = new List { new IndexResponse { Uid = indexName } }; meilisearchClientMock.Setup(c => c.GetAllIndexesAsync(null, null, It.IsAny())).ReturnsAsync(new IndexesResults { Results = new List() }); meilisearchClientMock.Setup(c => c.CreateIndexAsync(indexName, null, It.IsAny())).Returns(Task.CompletedTask); meilisearchClientMock.Setup(c => c.GetIndexAsync(indexName, It.IsAny())).ReturnsAsync(indexMock.Object); indexMock.Setup(i => i.UpdateFilterableAttributesAsync(It.IsAny(), null, It.IsAny())).Returns(Task.CompletedTask); // Override the MeilisearchClient var meiliSearchService = new TestMeiliSearchService(_httpClientMock.Object, _loggerMock.Object, _meiliConfigurationMock.Object, null, null, meilisearchClientMock.Object); // Act meiliSearchService.CreateIndex(indexName); // Assert meilisearchClientMock.Verify(c => c.CreateIndexAsync(indexName, null, It.IsAny()), Times.Once); } [Fact] public async Task Test_DeleteIndex_Success() { // Arrange var indexName = "test_index"; var meilisearchClientMock = new Mock("http://localhost:7700", "masterKey"); var indexMock = new Mock(meilisearchClientMock.Object, indexName); var indexes = new List { new IndexResponse { Uid = indexName } }; meilisearchClientMock.Setup(c => c.GetAllIndexesAsync(null, null, It.IsAny())).ReturnsAsync(new IndexesResults { Results = indexes }); meilisearchClientMock.Setup(c => c.DeleteIndexAsync(indexName, null, It.IsAny())).Returns(Task.CompletedTask); // Override the MeilisearchClient var meiliSearchService = new TestMeiliSearchService(_httpClientMock.Object, _loggerMock.Object, _meiliConfigurationMock.Object, null, null, meilisearchClientMock.Object); // Act meiliSearchService.DeleteIndex(indexName); // Assert meilisearchClientMock.Verify(c => c.DeleteIndexAsync(indexName, null, It.IsAny()), Times.Once); } [Fact] public async Task Test_GetAllIndexes_Success() { // Arrange var indexName = "test_index"; var meilisearchClientMock = new Mock("http://localhost:7700", "masterKey"); var indexMock = new Mock(meilisearchClientMock.Object, indexName); var indexes = new List { new IndexResponse { Uid = indexName } }; meilisearchClientMock.Setup(c => c.GetAllIndexesAsync(null, null, It.IsAny())).ReturnsAsync(new IndexesResults { Results = indexes }); // Override the MeilisearchClient var meiliSearchService = new TestMeiliSearchService(_httpClientMock.Object, _loggerMock.Object, _meiliConfigurationMock.Object, null, null, meilisearchClientMock.Object); // Act var result = meiliSearchService.GetAllIndexes(); // Assert Assert.Contains(indexName, result); } [Fact] public void Test_AddDocument_Success() { // Arrange var indexName = "test_index"; var document = new TestDocument { Id = "1" }; var documentCollectionMock = new Mock>>(); // Override the DocumentCollection var meiliSearchService = new TestMeiliSearchService(_httpClientMock.Object, _loggerMock.Object, _meiliConfigurationMock.Object, null, null, null, documentCollectionMock.Object); // Act meiliSearchService.AddDocument(indexName, document); // Assert documentCollectionMock.Verify(c => c.Add(It.IsAny>()), Times.Once); } [Fact] public void Test_Configuration_Dependencies_Success() { // Arrange var httpClient = _httpClientMock.Object; var logger = _loggerMock.Object; var meiliConfiguration = _meiliConfigurationMock.Object; // Act var meiliSearchService = new MeiliSearchService(httpClient, logger, meiliConfiguration); // Assert Assert.NotNull(meiliSearchService); } [Fact] public void Test_GenerateApiKey_Success() { // Arrange var length = 64; // Act var apiKey = MeiliSearchService.GenerateApiKey(length); // Assert Assert.NotNull(apiKey); Assert.Equal(length, apiKey.Length); } [Fact] public void Test_Dispose_Success() { // Arrange var httpClientMock = new Mock(); var processMock = new Mock(); // Set the process to the MeiliSearchService _meiliSearchService.process = processMock.Object; // Act _meiliSearchService.Dispose(); // Assert httpClientMock.Verify(c => c.Dispose(), Times.Never); // HttpClient is disposed in base class processMock.Verify(p => p.Kill(), Times.Once); } } // Create a wrapper interface for Process.Start public interface IProcessWrapper { Process Start(ProcessStartInfo startInfo); } // Create a wrapper class for Process.Start public class ProcessWrapper : IProcessWrapper { public Process Start(ProcessStartInfo startInfo) { return Process.Start(startInfo); } } // Create a wrapper interface for Process.GetProcessesByName public interface IProcessStaticWrapper { Process[] GetProcessesByName(string processName); } // Create a wrapper class for Process.GetProcessesByName public class ProcessStaticWrapper : IProcessStaticWrapper { public Process[] GetProcessesByName(string processName) { return Process.GetProcessesByName(processName); } } // Create a test class to override the GetMeilisearchBinaryName method public class TestMeiliSearchService : MeiliSearchService { private readonly IProcessWrapper _processWrapper; private readonly IProcessStaticWrapper _processStaticWrapper; private readonly MeilisearchClient _meilisearchClient; private readonly ICollection> _documentCollection; public TestMeiliSearchService(HttpClient httpClient, ILogger logger, MeiliSearchConfiguration meiliConfiguration, IProcessWrapper processWrapper = null, IProcessStaticWrapper processStaticWrapper = null, MeilisearchClient meilisearchClient = null, ICollection> documentCollection = null) : base(httpClient, logger, meiliConfiguration) { _processWrapper = processWrapper; _processStaticWrapper = processStaticWrapper; _meilisearchClient = meilisearchClient; _documentCollection = documentCollection; } public override async Task StartMeilisearch() { var binaryName = GetMeilisearchBinaryName(); var binaryPath = Path.Combine(AppContext.BaseDirectory, binaryName); if (!File.Exists(binaryPath)) { Logger.LogError($"Meilisearch binary not found at: {binaryPath}"); throw new FileNotFoundException($"Could not find Meilisearch binary: {binaryName}"); } // Set execute permissions on Unix-like systems if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { try { // Mock the chmod process //var chmod = Process.Start("chmod", $"+x {binaryPath}"); //chmod?.WaitForExit(); } catch (Exception ex) { Logger.LogWarning($"Failed to set execute permissions on binary: {ex.Message}"); } } var host = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "localhost" : "127.0.0.1"; var args = "--http-addr " + host + ":" + MeiliConfiguration.MeiliPort + " --env development --db-path " + Path.Combine(AppContext.BaseDirectory, binaryName); var processStartInfo = new ProcessStartInfo { FileName = binaryPath, Arguments = args, UseShellExecute = false, RedirectStandardOutput = false, RedirectStandardError = false, CreateNoWindow = false, }; // Use the mocked Process.Start method process = _processWrapper.Start(processStartInfo); await Task.Delay(5000); // Wait for the process to start Logger.LogInformation($"Started Meilisearch process using binary: {binaryName}"); } public override string GetMeilisearchBinaryName() { return "meilisearch-mock"; } public override bool IsMeilisearchRunning() { // Mock the Process.GetProcessesByName method var processName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "meilisearch-windows" : "meilisearch"; var processes = _processStaticWrapper.GetProcessesByName(processName); return processes.Any(); } public override async Task GetAllIndexesAsync(CancellationToken cancellationToken = default) { return _meilisearchClient.GetAllIndexesAsync(null, null, cancellationToken); } public override async Task CreateIndexAsync(string indexName, string primaryKey = null, CancellationToken cancellationToken = default) { return _meilisearchClient.CreateIndexAsync(indexName, primaryKey, cancellationToken); } public override async Task DeleteIndexAsync(string indexName, CancellationToken cancellationToken = default) { return _meilisearchClient.DeleteIndexAsync(indexName, cancellationToken); } public override async Task GetIndexAsync(string indexName, CancellationToken cancellationToken = default) { return _meilisearchClient.GetIndexAsync(indexName, cancellationToken); } private ILogger Logger { get { return _logger; } } private MeiliSearchConfiguration MeiliConfiguration { get { return _meiliConfiguration; } } private string ApiKey { get { return _apiKey; } } protected override ObservableCollection> DocumentCollection { get { return _documentCollection as ObservableCollection>; } } protected virtual void Dispose(bool disposing) { //base.Dispose(disposing); } } public class TestDocument : IDocument { public new string Id { get; set; } }