using EdiApiGateway.Models; using EdiApiGateway.Services; using FluentAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Moq; using System.Text; using Xunit; namespace EdiApiGateway.Tests.Services; /// /// EdiCommunicationService のユニットテスト /// public class EdiCommunicationServiceTests { private readonly Mock _mockConfig; private readonly Mock> _mockLogger; private readonly EdiCommunicationService _service; public EdiCommunicationServiceTests() { _mockConfig = new Mock(); _mockLogger = new Mock>(); // テスト用のパートナー設定をモック SetupMockConfiguration("trusco", new EdiPartnerConfig { Uid = "TRUSCO001", Fid = "110000000000", Name = "トラスコ中山株式会社", ReceiveBasePath = "C:\\ACMS\\receive\\trusco", SendBasePath = "C:\\ACMS\\send\\trusco" }); SetupMockConfiguration("yodobashi", new EdiPartnerConfig { Uid = "YODOBASHI001", Fid = "YODORCVB0001", Name = "株式会社ヨドバシカメラ", ReceiveBasePath = "C:\\ACMS\\receive\\yodobashi", SendBasePath = "C:\\ACMS\\send\\yodobashi" }); _service = new EdiCommunicationService(_mockConfig.Object, _mockLogger.Object); } private void SetupMockConfiguration(string partner, EdiPartnerConfig config) { // IConfigurationSection のモックを作成 var section = new Mock(); // 各プロパティのセクションをモック var uidSection = new Mock(); uidSection.Setup(s => s.Value).Returns(config.Uid); uidSection.Setup(s => s.Key).Returns("Uid"); uidSection.Setup(s => s.Path).Returns($"EdiPartners:{partner}:Uid"); section.Setup(s => s.GetSection("Uid")).Returns(uidSection.Object); var fidSection = new Mock(); fidSection.Setup(s => s.Value).Returns(config.Fid); fidSection.Setup(s => s.Key).Returns("Fid"); fidSection.Setup(s => s.Path).Returns($"EdiPartners:{partner}:Fid"); section.Setup(s => s.GetSection("Fid")).Returns(fidSection.Object); var nameSection = new Mock(); nameSection.Setup(s => s.Value).Returns(config.Name); nameSection.Setup(s => s.Key).Returns("Name"); nameSection.Setup(s => s.Path).Returns($"EdiPartners:{partner}:Name"); section.Setup(s => s.GetSection("Name")).Returns(nameSection.Object); var receiveSection = new Mock(); receiveSection.Setup(s => s.Value).Returns(config.ReceiveBasePath); receiveSection.Setup(s => s.Key).Returns("ReceiveBasePath"); receiveSection.Setup(s => s.Path).Returns($"EdiPartners:{partner}:ReceiveBasePath"); section.Setup(s => s.GetSection("ReceiveBasePath")).Returns(receiveSection.Object); var sendSection = new Mock(); sendSection.Setup(s => s.Value).Returns(config.SendBasePath); sendSection.Setup(s => s.Key).Returns("SendBasePath"); sendSection.Setup(s => s.Path).Returns($"EdiPartners:{partner}:SendBasePath"); section.Setup(s => s.GetSection("SendBasePath")).Returns(sendSection.Object); // 子セクションのリストも設定(ConfigurationBinder が使用) var children = new List { uidSection.Object, fidSection.Object, nameSection.Object, receiveSection.Object, sendSection.Object }; section.Setup(s => s.GetChildren()).Returns(children); section.Setup(s => s.Value).Returns((string?)null); section.Setup(s => s["Uid"]).Returns(config.Uid); section.Setup(s => s["Fid"]).Returns(config.Fid); section.Setup(s => s["Name"]).Returns(config.Name); section.Setup(s => s["ReceiveBasePath"]).Returns(config.ReceiveBasePath); section.Setup(s => s["SendBasePath"]).Returns(config.SendBasePath); _mockConfig.Setup(c => c.GetSection($"EdiPartners:{partner}")).Returns(section.Object); } #region ReceiveFile Tests [Fact] public void ReceiveFile_未設定パートナー_エラーレスポンス() { // Arrange var request = new ReceiveRequest { Partner = "unknown" }; // Act var response = _service.ReceiveFile(request); // Assert response.Success.Should().BeFalse(); response.Error.Should().NotBeNullOrEmpty(); } [Fact] public void ReceiveFile_正常系_ファイル名生成確認() { // Arrange var request = new ReceiveRequest { Partner = "trusco", SaveFileName = null // 自動生成 }; // Act var response = _service.ReceiveFile(request); // Assert // ACMS DLLがない環境ではエラーになるが、ファイル名生成ロジックは検証可能 if (!response.Success && response.Error != null && response.Error.Contains("DllNotFoundException")) { // DLL未配置環境では期待通りの挙動 response.Error.Should().Contain("ACMSCTL1.DLL"); } } [Theory] [InlineData("")] [InlineData(null)] public void ReceiveFile_空パートナー名_エラーレスポンス(string? partner) { // Arrange var request = new ReceiveRequest { Partner = partner! }; // Act var response = _service.ReceiveFile(request); // Assert response.Success.Should().BeFalse(); } [Fact] public void ReceiveFile_カスタムファイル名_指定ファイル名使用() { // Arrange var customFileName = "custom_receive.dat"; var request = new ReceiveRequest { Partner = "trusco", SaveFileName = customFileName }; // Act var response = _service.ReceiveFile(request); // Assert if (!response.Success && response.Error != null && response.Error.Contains("DllNotFoundException")) { // DLL未配置環境でも、カスタムファイル名が設定に使われたことを間接的に確認 response.Error.Should().Contain("ACMSCTL1.DLL"); } } [Fact] public void ReceiveFile_ヨドバシパートナー_正しい設定取得() { // Arrange var request = new ReceiveRequest { Partner = "yodobashi" }; // Act var response = _service.ReceiveFile(request); // Assert // DLLがない環境では失敗するが、設定取得は成功 if (!response.Success && response.Error != null) { response.Error.Should().NotContain("設定が見つかりません"); } } #endregion #region SendFile Tests [Fact] public void SendFile_未設定パートナー_エラーレスポンス() { // Arrange var request = new SendRequest { Partner = "unknown", FilePath = "test.dat" }; // Act var response = _service.SendFile(request); // Assert response.Success.Should().BeFalse(); response.Error.Should().NotBeNullOrEmpty(); } [Theory] [InlineData("")] [InlineData(null)] public void SendFile_空ファイルパス_エラーレスポンス(string? filePath) { // Arrange var request = new SendRequest { Partner = "trusco", FilePath = filePath! }; // Act var response = _service.SendFile(request); // Assert response.Success.Should().BeFalse(); response.Error.Should().Contain("FilePath"); } [Fact] public void SendFile_パストラバーサル攻撃_エラーレスポンス() { // Arrange var request = new SendRequest { Partner = "trusco", FilePath = "..\\..\\..\\sensitive.dat" // パストラバーサル試行 }; // Act var response = _service.SendFile(request); // Assert response.Success.Should().BeFalse(); response.Error.Should().Contain("Invalid file path"); } [Fact] public void SendFile_絶対パス試行_エラーレスポンス() { // Arrange var request = new SendRequest { Partner = "trusco", FilePath = "C:\\Windows\\System32\\config\\SAM" // 絶対パス試行 }; // Act var response = _service.SendFile(request); // Assert response.Success.Should().BeFalse(); response.Error.Should().Contain("Invalid file path"); } [Fact] public void SendFile_正常系パス_検証通過() { // Arrange // ベースパス (C:\ACMS\send\trusco) の中にあるファイルを想定 var request = new SendRequest { Partner = "trusco", FilePath = "C:\\ACMS\\send\\trusco\\test_send.dat" // ベースパス内の絶対パス }; // Act var response = _service.SendFile(request); // Assert // パストラバーサルエラーではないことを確認 if (!response.Success && response.Error != null) { response.Error.Should().NotContain("Invalid file path", "ベースパス内のファイルはパストラバーサルエラーにならない"); } } #endregion #region Error Message Tests // Note: GetErrorMessage は private メソッドのため、 // 間接的に ReceiveFile/SendFile のエラーレスポンスを通じてテストする #endregion #region Configuration Tests [Fact] public void Configuration_トラスコ設定_正しい値取得() { // Arrange & Act var section = _mockConfig.Object.GetSection("EdiPartners:trusco"); // Assert section["Uid"].Should().Be("TRUSCO001"); section["Fid"].Should().Be("110000000000"); section["ReceiveBasePath"].Should().Be("C:\\ACMS\\receive\\trusco"); } [Fact] public void Configuration_ヨドバシ設定_正しい値取得() { // Arrange & Act var section = _mockConfig.Object.GetSection("EdiPartners:yodobashi"); // Assert section["Uid"].Should().Be("YODOBASHI001"); section["Fid"].Should().Be("YODORCVB0001"); section["ReceiveBasePath"].Should().Be("C:\\ACMS\\receive\\yodobashi"); } #endregion }