using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using AMWD.Common.Packing.Ar; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace UnitTests.Common.Packing.Ar { [TestClass] [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class ArWriterTests { private readonly DateTime fixedDateTime = new(2023, 03, 01, 10, 20, 30, 0, DateTimeKind.Utc); private readonly Dictionary files = new(); private Stream outStream; [TestInitialize] public void Initialize() { files.Clear(); for (int i = 0; i < 3; i++) { var (filePath, content) = GenerateTestFile(); files.Add(filePath, content); } outStream = new MemoryStream(); } [TestCleanup] public void Cleanup() { foreach (var kvp in files) File.Delete(kvp.Key); files.Clear(); outStream.Dispose(); outStream = null; } [TestMethod] public void ShouldInitializeArchive() { // Arrange byte[] initBytes = new byte[8]; // Act _ = new ArWriter(outStream); outStream.Seek(0, SeekOrigin.Begin); // Assert Assert.AreEqual(initBytes.Length, outStream.Length); outStream.Read(initBytes, 0, initBytes.Length); CollectionAssert.AreEqual(Encoding.ASCII.GetBytes("!\n"), initBytes); } [TestMethod] public void ShouldWriteOneFile() { // Arrange var firstFileKvp = files.First(); byte[] headerBytes = new byte[60]; byte[] contentBytes = new byte[14]; var writer = new ArWriter(outStream); // Act writer.WriteFile(firstFileKvp.Key, 123, 456); outStream.Seek(8, SeekOrigin.Begin); // set behind init bytes // Assert Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, outStream.Length); outStream.Read(headerBytes, 0, headerBytes.Length); outStream.Read(contentBytes, 0, contentBytes.Length); string header = Encoding.ASCII.GetString(headerBytes); string content = Encoding.UTF8.GetString(contentBytes); Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 123 456 100644 14 `\n", header); Assert.AreEqual(firstFileKvp.Value, content); } [TestMethod] public void ShouldWriteMultipleFiles() { // Arrange var writer = new ArWriter(outStream); // Act foreach (var kvp in files) writer.WriteFile(kvp.Key, 123, 456); outStream.Seek(8, SeekOrigin.Begin); // Assert Assert.AreEqual((8 + 3 * 60 + 3 * 14), outStream.Length); foreach (var kvp in files) { byte[] headerBytes = new byte[60]; byte[] contentBytes = new byte[14]; outStream.Read(headerBytes, 0, headerBytes.Length); outStream.Read(contentBytes, 0, contentBytes.Length); string header = Encoding.ASCII.GetString(headerBytes); string content = Encoding.UTF8.GetString(contentBytes); Assert.AreEqual($"{Path.GetFileName(kvp.Key),-16}1677666030 123 456 100644 14 `\n", header); Assert.AreEqual(kvp.Value, content); } } [TestMethod] public void ShouldPadToEven() { // Arrange var (filePath, fileContent) = GenerateTestFile(13); try { byte[] headerBytes = new byte[60]; byte[] contentBytes = new byte[14]; var writer = new ArWriter(outStream); // Act writer.WriteFile(filePath, 123, 456); outStream.Seek(8, SeekOrigin.Begin); // Assert Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, outStream.Length); outStream.Read(headerBytes, 0, headerBytes.Length); outStream.Read(contentBytes, 0, contentBytes.Length); string header = Encoding.ASCII.GetString(headerBytes); string content = Encoding.UTF8.GetString(contentBytes); Assert.AreEqual($"{Path.GetFileName(filePath),-16}1677666030 123 456 100644 13 `\n", header); Assert.AreEqual(fileContent + "\n", content); } finally { File.Delete(filePath); } } [TestMethod] [ExpectedException(typeof(ArgumentException))] public void ShouldFailOnFileNameTooLong() { // Arrange var (filePath, _) = GenerateTestFile(); try { string path = Path.GetDirectoryName(filePath); string fileName = Path.GetFileName(filePath); fileName = fileName.PadLeft(20, 'a'); File.Move(filePath, Path.Combine(path, fileName)); filePath = Path.Combine(path, fileName); var writer = new ArWriter(outStream); // Act writer.WriteFile(filePath, 123, 456); // Assert - Exception Assert.Fail(); } finally { File.Delete(filePath); } } [TestMethod] public void ShouldWriteEmptyOnNegativeUserId() { // Arrange var firstFileKvp = files.First(); byte[] headerBytes = new byte[60]; byte[] contentBytes = new byte[14]; var writer = new ArWriter(outStream); // Act writer.WriteFile(firstFileKvp.Key, -123, 456); outStream.Seek(8, SeekOrigin.Begin); // Assert Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, outStream.Length); outStream.Read(headerBytes, 0, headerBytes.Length); outStream.Read(contentBytes, 0, contentBytes.Length); string header = Encoding.ASCII.GetString(headerBytes); string content = Encoding.UTF8.GetString(contentBytes); Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 456 100644 14 `\n", header); Assert.AreEqual(firstFileKvp.Value, content); } [TestMethod] public void ShouldWriteEmptyOnNegativeGroupId() { // Arrange var firstFileKvp = files.First(); byte[] headerBytes = new byte[60]; byte[] contentBytes = new byte[14]; var writer = new ArWriter(outStream); // Act writer.WriteFile(firstFileKvp.Key, 123, -456); outStream.Seek(8, SeekOrigin.Begin); // Assert Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, outStream.Length); outStream.Read(headerBytes, 0, headerBytes.Length); outStream.Read(contentBytes, 0, contentBytes.Length); string header = Encoding.ASCII.GetString(headerBytes); string content = Encoding.UTF8.GetString(contentBytes); Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 123 100644 14 `\n", header); Assert.AreEqual(firstFileKvp.Value, content); } [TestMethod] public void ShouldWriteEmptyOnNegativeMode() { // Arrange var firstFileKvp = files.First(); byte[] headerBytes = new byte[60]; byte[] contentBytes = new byte[14]; var writer = new ArWriter(outStream); // Act writer.WriteFile(firstFileKvp.Key, 123, 456, -1); outStream.Seek(8, SeekOrigin.Begin); // Assert Assert.AreEqual(8 + headerBytes.Length + contentBytes.Length, outStream.Length); outStream.Read(headerBytes, 0, headerBytes.Length); outStream.Read(contentBytes, 0, contentBytes.Length); string header = Encoding.ASCII.GetString(headerBytes); string content = Encoding.UTF8.GetString(contentBytes); Assert.AreEqual($"{Path.GetFileName(firstFileKvp.Key),-16}1677666030 123 456 14 `\n", header); Assert.AreEqual(firstFileKvp.Value, content); } [TestMethod] [ExpectedException(typeof(ArgumentException))] public void ShouldFailOnNonWritableStream() { // Arrange using var testStream = new NonWriteStream(); // Act _ = new ArWriter(testStream); // Assert - ArgumentException Assert.Fail(); } private (string filePath, string content) GenerateTestFile(int length = 14) { string filePath = Path.GetTempFileName(); string text = CryptographyHelper.GetRandomString(length); File.WriteAllText(filePath, text); File.SetLastWriteTimeUtc(filePath, fixedDateTime); return (filePath, text); } private class NonWriteStream : MemoryStream { public NonWriteStream() : base() { } public override bool CanWrite => false; } } }