using System.IO.Abstractions.TestingHelpers; using System.Text.Json; using Quadarax.Foundation.Core.Logging; using qdr.app.studiou.orders2printpack.EmailStorage; using Moq; namespace qdr.app.studiou.orders2printpack.Test { [TestFixture] public class EmailStorageProcessorTests { private Mock _mockLogger; private Mock _mockLog; private MockFileSystem _mockFileSystem; private EmailTemplate _emailTemplate; private EmailProcessorConfiguration _configuration; private string _testBasePath; private EmailStorageProcessor _processor; [SetUp] public void SetUp() { // Setup mocks _mockLogger = new Mock(); _mockLog = new Mock(); _mockLogger.Setup(x => x.GetLogger(It.IsAny())).Returns(_mockLog.Object); // Setup test paths _testBasePath = @"C:\TestInput"; // Setup mock file system with initial directory structure _mockFileSystem = new MockFileSystem(new Dictionary { // Create base directory { _testBasePath, new MockDirectoryData() }, // Create a test recipient directory with JPG files { Path.Combine(_testBasePath, "test@example.com"), new MockDirectoryData() }, { Path.Combine(_testBasePath, "test@example.com", "1001_photo1.jpg"), new MockFileData("fake jpg content") }, { Path.Combine(_testBasePath, "test@example.com", "1001_photo2.jpg"), new MockFileData("fake jpg content") }, { Path.Combine(_testBasePath, "test@example.com", "1002_photo1.jpg"), new MockFileData("fake jpg content") }, }); // Setup email template _emailTemplate = new EmailTemplate { Code = "TEST_TEMPLATE", Subject = "Your photos are ready - Orders {{ORDERS}}", Body = "Hello {{RECIPIENT}},\n\nYour photos are ready for download:\n{{DOWNLOAD_LINK}}\nPassword: {{PASSWORD}}\n\nOrders: {{ORDERS}}" }; // Setup configuration _configuration = new EmailProcessorConfiguration { TssAPIKey = "test-api-key", TssUrl = "https://test-tss-url.com", SmtpServer = "smtp.test.com", SmtpServerPort = 587, SmtpServerUser = "test@smtp.com", SmtpServerPassword = "test-password", SmtpEnableSsl = true }; } [TearDown] public void TearDown() { _processor?.Dispose(); } private EmailStorageProcessor CreateProcessor() { return new EmailStorageProcessor(_mockFileSystem, _testBasePath, _emailTemplate, _configuration, _mockLogger.Object); } #region Constructor Tests [Test] public void Constructor_WithValidParameters_InitializesSuccessfully() { // Act _processor = CreateProcessor(); // Assert Assert.That(_processor, Is.Not.Null); Assert.That(_processor.Metadata, Is.Not.Null); Assert.That(_processor.Metadata.Items, Is.Not.Null); } [Test] public void Constructor_WithNullFileSystem_ThrowsArgumentNullException() { // Act & Assert Assert.Throws(() => new EmailStorageProcessor(null, _testBasePath, _emailTemplate, _configuration, _mockLogger.Object)); } [Test] public void Constructor_WithNullPath_ThrowsArgumentNullException() { // Act & Assert Assert.Throws(() => new EmailStorageProcessor(_mockFileSystem, null, _emailTemplate, _configuration, _mockLogger.Object)); } [Test] public void Constructor_WithNullEmailTemplate_ThrowsArgumentNullException() { // Act & Assert Assert.Throws(() => new EmailStorageProcessor(_mockFileSystem, _testBasePath, null, _configuration, _mockLogger.Object)); } [Test] public void Constructor_WithNullConfiguration_ThrowsArgumentNullException() { // Act & Assert Assert.Throws(() => new EmailStorageProcessor(_mockFileSystem, _testBasePath, _emailTemplate, null, _mockLogger.Object)); } [Test] public void Constructor_WithNullLogger_ThrowsArgumentNullException() { // Act & Assert Assert.Throws(() => new EmailStorageProcessor(_mockFileSystem, _testBasePath, _emailTemplate, _configuration, null)); } [Test] public void Constructor_CreatesRequiredDirectories() { // Act _processor = CreateProcessor(); // Assert Assert.That(_mockFileSystem.Directory.Exists(Path.Combine(_testBasePath, ".processing")), Is.True); Assert.That(_mockFileSystem.Directory.Exists(Path.Combine(_testBasePath, ".sending")), Is.True); Assert.That(_mockFileSystem.Directory.Exists(Path.Combine(_testBasePath, ".sent")), Is.True); } [Test] public void Constructor_ScansExistingDirectories_CreatesMetadataItems() { // Act _processor = CreateProcessor(); // Assert Assert.That(_processor.Metadata.Items.Count, Is.EqualTo(1)); var item = _processor.Metadata.Items.First(); Assert.That(item.DirectoryName, Is.EqualTo("test@example.com")); Assert.That(item.EmailAddress, Is.EqualTo("test@example.com")); Assert.That(item.State, Is.EqualTo(EmailStateEnum.Ready)); Assert.That(item.InternalFilesCount, Is.EqualTo(3)); Assert.That(item.Orders, Is.EqualTo("1001,1002")); } #endregion #region Property Tests [Test] public void CountTotal_ReturnsCorrectCount() { // Arrange _processor = CreateProcessor(); // Act var count = _processor.CountTotal; // Assert Assert.That(count, Is.EqualTo(1)); } [Test] public void CountProcessing_WithProcessingItems_ReturnsCorrectCount() { // Arrange _processor = CreateProcessor(); _processor.Metadata.Items.First().State = EmailStateEnum.Processing; // Act var count = _processor.CountProcessing; // Assert Assert.That(count, Is.EqualTo(1)); } [Test] public void CountSending_WithSendingItems_ReturnsCorrectCount() { // Arrange _processor = CreateProcessor(); _processor.Metadata.Items.First().State = EmailStateEnum.Sending; // Act var count = _processor.CountSending; // Assert Assert.That(count, Is.EqualTo(1)); } [Test] public void CountSent_WithSentItems_ReturnsCorrectCount() { // Arrange _processor = CreateProcessor(); _processor.Metadata.Items.First().State = EmailStateEnum.Sent; // Act var count = _processor.CountSent; // Assert Assert.That(count, Is.EqualTo(1)); } #endregion #region ResetItem Tests [Test] public void ResetItem_WithValidRecipient_ResetsItemToReadyState() { // Arrange _processor = CreateProcessor(); var item = _processor.Metadata.Items.First(); item.State = EmailStateEnum.Error; item.FileNameAttachment = "test.zip"; item.SizeAttachment = 1000; item.UploadPermalink = "http://test.com"; item.DownloadPassword = "password"; item.ProcessedDate = DateTime.Now; item.SentDate = DateTime.Now; item.ErrorMessage = "Test error"; // Act _processor.ResetItem("test@example.com"); // Assert Assert.That(item.State, Is.EqualTo(EmailStateEnum.Ready)); Assert.That(item.FileNameAttachment, Is.EqualTo(string.Empty)); Assert.That(item.SizeAttachment, Is.EqualTo(0)); Assert.That(item.UploadPermalink, Is.EqualTo(string.Empty)); Assert.That(item.DownloadPassword, Is.EqualTo(string.Empty)); Assert.That(item.ProcessedDate, Is.Null); Assert.That(item.SentDate, Is.Null); Assert.That(item.ErrorMessage, Is.Null); } [Test] public void ResetItem_WithNonExistentRecipient_LogsWarning() { // Arrange _processor = CreateProcessor(); // Act _processor.ResetItem("nonexistent@example.com"); // Assert _mockLog.Verify(x => x.Log(LogSeverityEnum.Warn, It.IsAny()), Times.Once); } [Test] public void ResetItem_CleansUpAssociatedFiles() { // Arrange _processor = CreateProcessor(); var item = _processor.Metadata.Items.First(); item.FileNameAttachment = "test.zip"; // Create test files in processing, sending, and sent directories var processingFile = Path.Combine(_testBasePath, ".processing", "test.zip"); var sendingFile = Path.Combine(_testBasePath, ".sending", "test.zip"); var sentFile = Path.Combine(_testBasePath, ".sent", "test.zip"); _mockFileSystem.AddFile(processingFile, new MockFileData("test")); _mockFileSystem.AddFile(sendingFile, new MockFileData("test")); _mockFileSystem.AddFile(sentFile, new MockFileData("test")); // Act _processor.ResetItem("test@example.com"); // Assert Assert.That(_mockFileSystem.File.Exists(processingFile), Is.False); Assert.That(_mockFileSystem.File.Exists(sendingFile), Is.False); Assert.That(_mockFileSystem.File.Exists(sentFile), Is.False); } #endregion #region ProcessItemAsync Tests [Test] public async Task ProcessItemAsync_WithNonExistentRecipient_LogsErrorAndReturns() { // Arrange _processor = CreateProcessor(); // Act await _processor.ProcessItemAsync("nonexistent@example.com"); // Assert _mockLog.Verify(x => x.Log(LogSeverityEnum.Error, It.IsAny()), Times.Once); } [Test] public async Task ProcessItemAsync_WithValidRecipient_UpdatesStateToProcessing() { // Arrange _processor = CreateProcessor(); bool stateChangeEventFired = false; EmailStateEnum capturedState = EmailStateEnum.Ready; _processor.EmailItemStateChanged += (sender, recipient, state, progress) => { if (state == EmailStateEnum.Processing) { stateChangeEventFired = true; capturedState = state; } }; // Act try { await _processor.ProcessItemAsync("test@example.com"); } catch { // Expected to fail due to missing external dependencies, but we want to check state change } // Assert Assert.That(stateChangeEventFired, Is.True); Assert.That(capturedState, Is.EqualTo(EmailStateEnum.Processing)); } #endregion #region ProcessAllAsync Tests [Test] public async Task ProcessAllAsync_ProcessesAllReadyItems() { // Arrange _processor = CreateProcessor(); // Add another test item _mockFileSystem.AddDirectory(Path.Combine(_testBasePath, "test2@example.com")); _mockFileSystem.AddFile(Path.Combine(_testBasePath, "test2@example.com", "2001_photo1.jpg"), new MockFileData("content")); // Recreate processor to pick up new directory _processor.Dispose(); _processor = CreateProcessor(); // Act try { await _processor.ProcessAllAsync(); } catch { // Expected to fail due to missing external dependencies } // Assert // Verify that processing was attempted for all items _mockLog.Verify(x => x.Log(LogSeverityEnum.Info, It.Is(s => s.Contains("Starting processing for recipient:"))), Times.AtLeast(1)); } #endregion #region Stop Method Tests [Test] public void Stop_SetsCancellationToken() { // Arrange _processor = CreateProcessor(); // Act _processor.Stop(); // Assert _mockLog.Verify(x => x.Log(LogSeverityEnum.Info, "Processing stop requested."), Times.Once); } #endregion #region Event Tests [Test] public void EmailItemStateChanged_WhenStateUpdated_FiresEvent() { // Arrange _processor = CreateProcessor(); bool eventFired = false; string capturedRecipient = ""; EmailStateEnum capturedState = EmailStateEnum.Ready; int capturedProgress = 0; _processor.EmailItemStateChanged += (sender, recipient, state, progress) => { eventFired = true; capturedRecipient = recipient; capturedState = state; capturedProgress = progress; }; var item = _processor.Metadata.Items.First(); // Act _processor.ResetItem("test@example.com"); // This should trigger state change // Assert Assert.That(eventFired, Is.True); Assert.That(capturedRecipient, Is.EqualTo("test@example.com")); Assert.That(capturedState, Is.EqualTo(EmailStateEnum.Ready)); } #endregion #region Metadata Management Tests [Test] public void Constructor_WithExistingMetadataFile_LoadsMetadata() { // Arrange var existingMetadata = new Metadata { Created = DateTime.Now.AddDays(-1), Modified = DateTime.Now.AddHours(-1), Items = new List { new MetadataItem { DirectoryName = "existing@example.com", EmailAddress = "existing@example.com", State = EmailStateEnum.Sent, Subject = "Test Subject", Body = "Test Body", InternalFilesCount = 5, Orders = "3001,3002" } } }; var metadataJson = JsonSerializer.Serialize(existingMetadata, new JsonSerializerOptions { WriteIndented = true }); var metadataPath = Path.Combine(_testBasePath, "metadata.json"); _mockFileSystem.AddFile(metadataPath, new MockFileData(metadataJson)); // Act _processor = CreateProcessor(); // Assert Assert.That(_processor.Metadata.Items.Count, Is.GreaterThan(0)); var existingItem = _processor.Metadata.Items.FirstOrDefault(x => x.EmailAddress == "existing@example.com"); Assert.That(existingItem, Is.Null); existingItem = _processor.Metadata.Items.FirstOrDefault(x => x.EmailAddress == "test@example.com"); Assert.That(existingItem.State, Is.EqualTo(EmailStateEnum.Ready)); } [Test] public void Constructor_ScansDirectoriesAndUpdatesMetadata() { // Arrange - metadata file exists but doesn't contain current directory var existingMetadata = new Metadata { Created = DateTime.Now.AddDays(-1), Items = new List() }; var metadataJson = JsonSerializer.Serialize(existingMetadata); var metadataPath = Path.Combine(_testBasePath, "metadata.json"); _mockFileSystem.AddFile(metadataPath, new MockFileData(metadataJson)); // Act _processor = CreateProcessor(); // Assert Assert.That(_processor.Metadata.Items.Count, Is.EqualTo(1)); var newItem = _processor.Metadata.Items.First(); Assert.That(newItem.DirectoryName, Is.EqualTo("test@example.com")); Assert.That(newItem.EmailAddress, Is.EqualTo("test@example.com")); } #endregion #region File Operations Tests [Test] public void Constructor_WithOrderNumbersInFilenames_ExtractsOrdersCorrectly() { // Arrange - setup files with different order patterns _mockFileSystem.RemoveFile(Path.Combine(_testBasePath, "test@example.com", "1001_photo1.jpg")); _mockFileSystem.RemoveFile(Path.Combine(_testBasePath, "test@example.com", "1001_photo2.jpg")); _mockFileSystem.RemoveFile(Path.Combine(_testBasePath, "test@example.com", "1002_photo1.jpg")); _mockFileSystem.AddFile(Path.Combine(_testBasePath, "test@example.com", "5001_portrait.jpg"), new MockFileData("content")); _mockFileSystem.AddFile(Path.Combine(_testBasePath, "test@example.com", "5002_landscape.jpg"), new MockFileData("content")); _mockFileSystem.AddFile(Path.Combine(_testBasePath, "test@example.com", "5001_closeup.jpg"), new MockFileData("content")); _mockFileSystem.AddFile(Path.Combine(_testBasePath, "test@example.com", "invalid_name.jpg"), new MockFileData("content")); // Act _processor = CreateProcessor(); // Assert var item = _processor.Metadata.Items.First(); Assert.That(item.Orders, Is.EqualTo("5001,5002")); // Should extract unique order numbers Assert.That(item.InternalFilesCount, Is.EqualTo(4)); // Should count all JPG files } #endregion #region Integration Tests [Test] public void Constructor_WithMultipleRecipientDirectories_CreatesMultipleItems() { // Arrange _mockFileSystem.AddDirectory(Path.Combine(_testBasePath, "user1@test.com")); _mockFileSystem.AddFile(Path.Combine(_testBasePath, "user1@test.com", "1001_photo.jpg"), new MockFileData("content")); _mockFileSystem.AddDirectory(Path.Combine(_testBasePath, "user2@test.com")); _mockFileSystem.AddFile(Path.Combine(_testBasePath, "user2@test.com", "2001_photo.jpg"), new MockFileData("content")); // Act _processor = CreateProcessor(); // Assert Assert.That(_processor.Metadata.Items.Count, Is.EqualTo(3)); // Original + 2 new var emails = _processor.Metadata.Items.Select(x => x.EmailAddress).ToList(); Assert.That(emails, Contains.Item("test@example.com")); Assert.That(emails, Contains.Item("user1@test.com")); Assert.That(emails, Contains.Item("user2@test.com")); } [Test] public void Constructor_IgnoresHiddenDirectories() { // Arrange _mockFileSystem.AddDirectory(Path.Combine(_testBasePath, ".hidden")); _mockFileSystem.AddFile(Path.Combine(_testBasePath, ".hidden", "1001_photo.jpg"), new MockFileData("content")); _mockFileSystem.AddDirectory(Path.Combine(_testBasePath, ".processing")); _mockFileSystem.AddFile(Path.Combine(_testBasePath, ".processing", "test.zip"), new MockFileData("content")); // Act _processor = CreateProcessor(); // Assert // Should only have the original test@example.com directory, not the hidden ones Assert.That(_processor.Metadata.Items.Count, Is.EqualTo(1)); Assert.That(_processor.Metadata.Items.First().DirectoryName, Is.EqualTo("test@example.com")); } #endregion #region Dispose Tests [Test] public void Dispose_DisposesResourcesProperly() { // Arrange _processor = CreateProcessor(); // Act _processor.Dispose(); // Assert - Should not throw Assert.Pass("Dispose completed without exceptions"); } [Test] public void Dispose_CalledMultipleTimes_DoesNotThrow() { // Arrange _processor = CreateProcessor(); // Act & Assert Assert.DoesNotThrow(() => { _processor.Dispose(); _processor.Dispose(); _processor.Dispose(); }); } #endregion } }