395 lines
14 KiB
C#
395 lines
14 KiB
C#
|
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<HttpClient> _httpClientMock;
|
||
|
private readonly Mock<ILogger<MeiliSearchService>> _loggerMock;
|
||
|
private readonly Mock<MeiliSearchConfiguration> _meiliConfigurationMock;
|
||
|
private readonly MeiliSearchService _meiliSearchService;
|
||
|
|
||
|
public MeiliSearchServiceTests()
|
||
|
{
|
||
|
_httpClientMock = new Mock<HttpClient>();
|
||
|
_loggerMock = new Mock<ILogger<MeiliSearchService>>();
|
||
|
_meiliConfigurationMock = new Mock<MeiliSearchConfiguration>();
|
||
|
_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<Process>();
|
||
|
processMock.Setup(p => p.Start()).Returns(true);
|
||
|
|
||
|
// Mock the Process.Start method
|
||
|
Mock<IProcessWrapper> processWrapperMock = new Mock<IProcessWrapper>();
|
||
|
processWrapperMock.Setup(p => p.Start(It.IsAny<ProcessStartInfo>())).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<ProcessStartInfo>()), Times.Once);
|
||
|
}
|
||
|
|
||
|
[Fact]
|
||
|
public void Test_Stop_Success()
|
||
|
{
|
||
|
// Mock the process
|
||
|
var processMock = new Mock<Process>();
|
||
|
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<Process>();
|
||
|
var processes = new Process[] { processMock.Object };
|
||
|
Mock<IProcessStaticWrapper> processStaticWrapperMock = new Mock<IProcessStaticWrapper>();
|
||
|
processStaticWrapperMock.Setup(p => p.GetProcessesByName(It.IsAny<string>())).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<MeilisearchClient>("http://localhost:7700", "masterKey");
|
||
|
var indexMock = new Mock<Meilisearch.Index>(meilisearchClientMock.Object, indexName);
|
||
|
var indexes = new List<IndexResponse> { new IndexResponse { Uid = indexName } };
|
||
|
|
||
|
meilisearchClientMock.Setup(c => c.GetAllIndexesAsync(null, null, It.IsAny<CancellationToken>())).ReturnsAsync(new IndexesResults { Results = new List<IndexResponse>() });
|
||
|
meilisearchClientMock.Setup(c => c.CreateIndexAsync(indexName, null, It.IsAny<CancellationToken>())).Returns(Task.CompletedTask);
|
||
|
meilisearchClientMock.Setup(c => c.GetIndexAsync(indexName, It.IsAny<CancellationToken>())).ReturnsAsync(indexMock.Object);
|
||
|
indexMock.Setup(i => i.UpdateFilterableAttributesAsync(It.IsAny<string[]>(), null, It.IsAny<CancellationToken>())).Returns(Task.CompletedTask);
|
||
|
|
||
|
// Override the MeilisearchClient
|
||
|
var meiliSearchService = new TestMeiliSearchService(_httpClientMock.Object, _loggerMock.Object, _meiliConfigurationMock.Object, null, null, meilisearchClientMock.Object);
|
||
|
|
||
|
// Act
|
||
|
meiliSearchService.CreateIndex<TestDocument>(indexName);
|
||
|
|
||
|
// Assert
|
||
|
meilisearchClientMock.Verify(c => c.CreateIndexAsync(indexName, null, It.IsAny<CancellationToken>()), Times.Once);
|
||
|
}
|
||
|
|
||
|
[Fact]
|
||
|
public async Task Test_DeleteIndex_Success()
|
||
|
{
|
||
|
// Arrange
|
||
|
var indexName = "test_index";
|
||
|
var meilisearchClientMock = new Mock<MeilisearchClient>("http://localhost:7700", "masterKey");
|
||
|
var indexMock = new Mock<Meilisearch.Index>(meilisearchClientMock.Object, indexName);
|
||
|
var indexes = new List<IndexResponse> { new IndexResponse { Uid = indexName } };
|
||
|
|
||
|
meilisearchClientMock.Setup(c => c.GetAllIndexesAsync(null, null, It.IsAny<CancellationToken>())).ReturnsAsync(new IndexesResults { Results = indexes });
|
||
|
meilisearchClientMock.Setup(c => c.DeleteIndexAsync(indexName, null, It.IsAny<CancellationToken>())).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<CancellationToken>()), Times.Once);
|
||
|
}
|
||
|
|
||
|
[Fact]
|
||
|
public async Task Test_GetAllIndexes_Success()
|
||
|
{
|
||
|
// Arrange
|
||
|
var indexName = "test_index";
|
||
|
var meilisearchClientMock = new Mock<MeilisearchClient>("http://localhost:7700", "masterKey");
|
||
|
var indexMock = new Mock<Meilisearch.Index>(meilisearchClientMock.Object, indexName);
|
||
|
var indexes = new List<IndexResponse> { new IndexResponse { Uid = indexName } };
|
||
|
|
||
|
meilisearchClientMock.Setup(c => c.GetAllIndexesAsync(null, null, It.IsAny<CancellationToken>())).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<ICollection<KeyValuePair<string, IDocument>>>();
|
||
|
|
||
|
// 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<KeyValuePair<string, IDocument>>()), 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<HttpClient>();
|
||
|
var processMock = new Mock<Process>();
|
||
|
|
||
|
// 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<KeyValuePair<string, IDocument>> _documentCollection;
|
||
|
|
||
|
public TestMeiliSearchService(HttpClient httpClient, ILogger<MeiliSearchService> logger, MeiliSearchConfiguration meiliConfiguration, IProcessWrapper processWrapper = null, IProcessStaticWrapper processStaticWrapper = null, MeilisearchClient meilisearchClient = null, ICollection<KeyValuePair<string, IDocument>> 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<IndexesResults> 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<Index> GetIndexAsync(string indexName, CancellationToken cancellationToken = default)
|
||
|
{
|
||
|
return _meilisearchClient.GetIndexAsync(indexName, cancellationToken);
|
||
|
}
|
||
|
|
||
|
private ILogger<MeiliSearchService> Logger
|
||
|
{
|
||
|
get { return _logger; }
|
||
|
}
|
||
|
|
||
|
private MeiliSearchConfiguration MeiliConfiguration
|
||
|
{
|
||
|
get { return _meiliConfiguration; }
|
||
|
}
|
||
|
|
||
|
private string ApiKey
|
||
|
{
|
||
|
get { return _apiKey; }
|
||
|
}
|
||
|
|
||
|
protected override ObservableCollection<KeyValuePair<string, IDocument>> DocumentCollection
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return _documentCollection as ObservableCollection<KeyValuePair<string, IDocument>>;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual void Dispose(bool disposing)
|
||
|
{
|
||
|
//base.Dispose(disposing);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public class TestDocument : IDocument
|
||
|
{
|
||
|
public new string Id { get; set; }
|
||
|
}
|