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
}