| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 |
- 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<ILogger> _mockLogger;
- private Mock<ILog> _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<ILogger>();
- _mockLog = new Mock<ILog>();
- _mockLogger.Setup(x => x.GetLogger(It.IsAny<Type>())).Returns(_mockLog.Object);
- // Setup test paths
- _testBasePath = @"C:\TestInput";
- // Setup mock file system with initial directory structure
- _mockFileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
- {
- // 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<ArgumentNullException>(() =>
- new EmailStorageProcessor(null, _testBasePath, _emailTemplate, _configuration, _mockLogger.Object));
- }
- [Test]
- public void Constructor_WithNullPath_ThrowsArgumentNullException()
- {
- // Act & Assert
- Assert.Throws<ArgumentNullException>(() =>
- new EmailStorageProcessor(_mockFileSystem, null, _emailTemplate, _configuration, _mockLogger.Object));
- }
- [Test]
- public void Constructor_WithNullEmailTemplate_ThrowsArgumentNullException()
- {
- // Act & Assert
- Assert.Throws<ArgumentNullException>(() =>
- new EmailStorageProcessor(_mockFileSystem, _testBasePath, null, _configuration, _mockLogger.Object));
- }
- [Test]
- public void Constructor_WithNullConfiguration_ThrowsArgumentNullException()
- {
- // Act & Assert
- Assert.Throws<ArgumentNullException>(() =>
- new EmailStorageProcessor(_mockFileSystem, _testBasePath, _emailTemplate, null, _mockLogger.Object));
- }
- [Test]
- public void Constructor_WithNullLogger_ThrowsArgumentNullException()
- {
- // Act & Assert
- Assert.Throws<ArgumentNullException>(() =>
- 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<string>()), 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<string>()), 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<string>(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<MetadataItem>
- {
- 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<MetadataItem>()
- };
- 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
- }
- }
|