diff --git a/src/Testcontainers/Builders/ContainerBuilder`3.cs b/src/Testcontainers/Builders/ContainerBuilder`3.cs index 468c894c9..ccdaf2d7d 100644 --- a/src/Testcontainers/Builders/ContainerBuilder`3.cs +++ b/src/Testcontainers/Builders/ContainerBuilder`3.cs @@ -206,6 +206,12 @@ public TBuilderEntity WithResourceMapping(byte[] resourceContent, string filePat return WithResourceMapping(new BinaryResourceMapping(resourceContent, filePath, fileMode)); } + /// + public TBuilderEntity WithResourceMapping(byte[] resourceContent, FileInfo filePath, UnixFileModes fileMode = Unix.FileMode644) + { + return WithResourceMapping(new BinaryResourceMapping(resourceContent, filePath.FullName, fileMode)); + } + /// public TBuilderEntity WithResourceMapping(string source, string target, UnixFileModes fileMode = Unix.FileMode644) { @@ -232,12 +238,24 @@ public TBuilderEntity WithResourceMapping(DirectoryInfo source, string target, U return WithResourceMapping(new FileResourceMapping(source.FullName, target, fileMode)); } + /// + public TBuilderEntity WithResourceMapping(DirectoryInfo source, DirectoryInfo target, UnixFileModes fileMode = Unix.FileMode644) + { + return WithResourceMapping(new FileResourceMapping(source.FullName, target.FullName, fileMode)); + } + /// public TBuilderEntity WithResourceMapping(FileInfo source, string target, UnixFileModes fileMode = Unix.FileMode644) { return WithResourceMapping(new FileResourceMapping(source.FullName, target, fileMode)); } + /// + public TBuilderEntity WithResourceMapping(FileInfo source, DirectoryInfo target, UnixFileModes fileMode = Unix.FileMode644) + { + return WithResourceMapping(new FileResourceMapping(source.FullName, target.FullName, fileMode)); + } + /// public TBuilderEntity WithResourceMapping(FileInfo source, FileInfo target, UnixFileModes fileMode = Unix.FileMode644) { @@ -253,15 +271,31 @@ public TBuilderEntity WithResourceMapping(FileInfo source, FileInfo target, Unix /// public TBuilderEntity WithResourceMapping(Uri source, string target, UnixFileModes fileMode = Unix.FileMode644) + { + return WithResourceMapping(source, new DirectoryInfo(target), fileMode); + } + + /// + public TBuilderEntity WithResourceMapping(Uri source, DirectoryInfo target, + UnixFileModes fileMode = Unix.FileMode644) { if (source.IsFile) { - return WithResourceMapping(new FileResourceMapping(source.AbsolutePath, target, fileMode)); + return WithResourceMapping(new FileResourceMapping(source.AbsolutePath, target.FullName, fileMode)); } - else + + return WithResourceMapping(new UriResourceMapping(source, target.FullName, fileMode)); + } + + /// + public TBuilderEntity WithResourceMapping(Uri source, FileInfo target, UnixFileModes fileMode = Unix.FileMode644) + { + if (source.IsFile) { - return WithResourceMapping(new UriResourceMapping(source, target, fileMode)); + return WithResourceMapping(new FileInfo(source.AbsolutePath), target, fileMode); } + + return WithResourceMapping(new UriResourceMapping(source, target.FullName, fileMode)); } /// diff --git a/src/Testcontainers/Builders/IContainerBuilder`2.cs b/src/Testcontainers/Builders/IContainerBuilder`2.cs index 0493fcbe5..5e05dcb99 100644 --- a/src/Testcontainers/Builders/IContainerBuilder`2.cs +++ b/src/Testcontainers/Builders/IContainerBuilder`2.cs @@ -228,8 +228,19 @@ public interface IContainerBuilder : I /// The POSIX file mode permission. /// A configured instance of . [PublicAPI] + [Obsolete("Use WithResourceMapping(byte[], FileInfo, UnixFileModes) instead.")] TBuilderEntity WithResourceMapping(byte[] resourceContent, string filePath, UnixFileModes fileMode = Unix.FileMode644); + /// + /// Copies the byte array content to the created container before it starts. + /// + /// The byte array content of the resource mapping. + /// The target file path to copy the file to. + /// The POSIX file mode permission. + /// A configured instance of . + [PublicAPI] + TBuilderEntity WithResourceMapping(byte[] resourceContent, FileInfo filePath, UnixFileModes fileMode = Unix.FileMode644); + /// /// Copies the contents of a URL, a test host directory or file to the container before it starts. /// @@ -246,6 +257,7 @@ public interface IContainerBuilder : I /// The POSIX file mode permission. /// A configured instance of . [PublicAPI] + [Obsolete("Use one of the more specific WithResourceMapping(…) methods instead.")] TBuilderEntity WithResourceMapping(string source, string target, UnixFileModes fileMode = Unix.FileMode644); /// @@ -256,18 +268,40 @@ public interface IContainerBuilder : I /// The POSIX file mode permission. /// A configured instance of . [PublicAPI] + [Obsolete("Use WithResourceMapping(DirectoryInfo, DirectoryInfo, UnixFileModes) instead.")] TBuilderEntity WithResourceMapping(DirectoryInfo source, string target, UnixFileModes fileMode = Unix.FileMode644); /// /// Copies a test host directory or file to the container before it starts. /// + /// The source directory to be copied. + /// The target directory path to copy the files to. + /// The POSIX file mode permission. + /// A configured instance of . + [PublicAPI] + TBuilderEntity WithResourceMapping(DirectoryInfo source, DirectoryInfo target, UnixFileModes fileMode = Unix.FileMode644); + + /// + /// Copies a test host directory or file to the given directory in the container before it starts. + /// /// The source file to be copied. /// The target directory path to copy the file to. /// The POSIX file mode permission. /// A configured instance of . [PublicAPI] + [Obsolete("Use WithResourceMapping(FileInfo, DirectoryInfo, UnixFileModes) instead.")] TBuilderEntity WithResourceMapping(FileInfo source, string target, UnixFileModes fileMode = Unix.FileMode644); + /// + /// Copies a test host directory or file to the given directory in the container before it starts. + /// + /// The source file to be copied. + /// The target directory path to copy the file to. + /// The POSIX file mode permission. + /// A configured instance of . + [PublicAPI] + TBuilderEntity WithResourceMapping(FileInfo source, DirectoryInfo target, UnixFileModes fileMode = Unix.FileMode644); + /// /// Copies a test host file to the container before it starts. /// @@ -295,8 +329,47 @@ public interface IContainerBuilder : I /// The target directory or file path to copy the file to. /// The POSIX file mode permission. /// A configured instance of . + [PublicAPI] + [Obsolete("Use WithResourceMapping(Uri, FileInfo, UnixFileModes) or WithResourceMapping(Uri, DirectoryInfo, UnixFileModes) instead.")] TBuilderEntity WithResourceMapping(Uri source, string target, UnixFileModes fileMode = Unix.FileMode644); + /// + /// Copies a file from a URL to the container before it starts. + /// + /// + /// If the Uri scheme corresponds to a file, the content is copied to the target + /// directory path. If the Uri scheme corresponds to HTTP or HTTPS, the content is + /// copied to the target file path. + /// + /// The Uri scheme must be either http, https or file. + /// + /// If you prefer to copy a file to a specific target file path instead of a + /// directory, use: . + /// + /// The source URL of the file to be copied. + /// The target directory path to copy the file to. + /// The POSIX file mode permission. + /// A configured instance of . + [PublicAPI] + TBuilderEntity WithResourceMapping(Uri source, DirectoryInfo target, UnixFileModes fileMode = Unix.FileMode644); + + /// + /// Copies a file from a URL to the container before it starts. + /// + /// + /// If the Uri scheme corresponds to a file, the content is copied to the target + /// directory path. If the Uri scheme corresponds to HTTP or HTTPS, the content is + /// copied to the target file path. + /// + /// The Uri scheme must be either http, https or file. + /// + /// The source URL of the file to be copied. + /// The target file path to copy the file to. + /// The POSIX file mode permission. + /// A configured instance of . + [PublicAPI] + TBuilderEntity WithResourceMapping(Uri source, FileInfo target, UnixFileModes fileMode = Unix.FileMode644); + /// /// Assigns the mount configuration to manage data in the container. /// diff --git a/tests/Testcontainers.Tests/Fixtures/Configurations/AlpineBuilderFixture.cs b/tests/Testcontainers.Tests/Fixtures/Configurations/AlpineBuilderFixture.cs new file mode 100644 index 000000000..a1e5f7125 --- /dev/null +++ b/tests/Testcontainers.Tests/Fixtures/Configurations/AlpineBuilderFixture.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; + +namespace DotNet.Testcontainers.Tests.Fixtures +{ + using System; + using System.Threading.Tasks; + using DotNet.Testcontainers.Builders; + using DotNet.Testcontainers.Commons; + using DotNet.Testcontainers.Containers; + using JetBrains.Annotations; + using Xunit; + + [UsedImplicitly] + public sealed class AlpineBuilderFixture : IAsyncLifetime + { + private readonly List _containers = []; + + public IContainer Container(Func builder) + { + var containerBuilder = builder(new ContainerBuilder()); + + var container = containerBuilder + .WithImage(CommonImages.Alpine) + .WithCommand(CommonCommands.SleepInfinity) + .Build(); + + _containers.Add(container); + + return container; + } + + public ValueTask InitializeAsync() + { + return ValueTask.CompletedTask; + } + + public async ValueTask DisposeAsync() + { + foreach (var container in _containers) + { + await container.DisposeAsync(); + } + } + } +} diff --git a/tests/Testcontainers.Tests/Unit/Configurations/WithResourceMappingTest.cs b/tests/Testcontainers.Tests/Unit/Configurations/WithResourceMappingTest.cs new file mode 100644 index 000000000..0ad557dac --- /dev/null +++ b/tests/Testcontainers.Tests/Unit/Configurations/WithResourceMappingTest.cs @@ -0,0 +1,278 @@ +using System; +using System.IO; + +namespace DotNet.Testcontainers.Tests.Unit +{ + using System.Threading.Tasks; + using DotNet.Testcontainers.Tests.Fixtures; + using Xunit; + + public sealed class WithResourceMappingTest(AlpineBuilderFixture image) : IClassFixture + { + [Fact] + public async Task WithResourceMappingUriDirectoryInfo() + { + // Given + var hostDir = new DirectoryInfo(Path.GetTempPath()).CreateSubdirectory("test"); + var hostFile = new FileInfo(Path.Combine(hostDir.FullName, "test.txt")); + + var expectedContent = new byte[20]; + Random.Shared.NextBytes(expectedContent); + using (var fileStream = hostFile.OpenWrite()) + { + fileStream.Write(expectedContent, 0, expectedContent.Length); + } + + // When + var container = image.Container(b => + b.WithResourceMapping(new Uri($"file://{hostFile.FullName}"), new DirectoryInfo("/tmp/"))); + await container.StartAsync(TestContext.Current.CancellationToken) + .ConfigureAwait(true); + + // Then + var actualContent = await container.ReadFileAsync($"/tmp/{hostFile.Name}", TestContext.Current.CancellationToken) + .ConfigureAwait(true); + Assert.Equal(expectedContent, actualContent); + } + + [Fact] + public async Task WithResourceMappingUriFileInfo() + { + // Given + var hostDir = new DirectoryInfo(Path.GetTempPath()).CreateSubdirectory("test"); + var hostFile = new FileInfo(Path.Combine(hostDir.FullName, "test.txt")); + + var expectedContent = new byte[20]; + Random.Shared.NextBytes(expectedContent); + using (var fileStream = hostFile.OpenWrite()) + { + fileStream.Write(expectedContent, 0, expectedContent.Length); + } + + // When + var container = image.Container(b => + b.WithResourceMapping(new Uri($"file://{hostFile.FullName}"), new FileInfo("/tmp/test.txt"))); + await container.StartAsync(TestContext.Current.CancellationToken) + .ConfigureAwait(true); + + // Then + var actualContent = await container.ReadFileAsync($"/tmp/test.txt", TestContext.Current.CancellationToken) + .ConfigureAwait(true); + Assert.Equal(expectedContent, actualContent); + } + + [Fact] + public async Task WithResourceMappingUriString() + { + // Given + var hostDir = new DirectoryInfo(Path.GetTempPath()).CreateSubdirectory("test"); + var hostFile = new FileInfo(Path.Combine(hostDir.FullName, "test.txt")); + + var expectedContent = new byte[20]; + Random.Shared.NextBytes(expectedContent); + using (var fileStream = hostFile.OpenWrite()) + { + fileStream.Write(expectedContent, 0, expectedContent.Length); + } + + // When + var container = image.Container(b => + b.WithResourceMapping(new Uri($"file://{hostFile.FullName}"), "/tmp/")); + await container.StartAsync(TestContext.Current.CancellationToken) + .ConfigureAwait(true); + + // Then + var actualContent = await container.ReadFileAsync($"/tmp/{hostFile.Name}", TestContext.Current.CancellationToken) + .ConfigureAwait(true); + Assert.Equal(expectedContent, actualContent); + } + + [Fact] + public async Task WithResourceMappingStringString() + { + // Given + var hostDir = new DirectoryInfo(Path.GetTempPath()).CreateSubdirectory("test"); + var hostFile = new FileInfo(Path.Combine(hostDir.FullName, "test.txt")); + + var expectedContent = new byte[20]; + Random.Shared.NextBytes(expectedContent); + using (var fileStream = hostFile.OpenWrite()) + { + fileStream.Write(expectedContent, 0, expectedContent.Length); + } + + // When + var container = image.Container(b => + b.WithResourceMapping(hostDir.FullName, "/tmp")); + await container.StartAsync(TestContext.Current.CancellationToken) + .ConfigureAwait(true); + + // Then + var actualContent = await container.ReadFileAsync($"/tmp/{hostFile.Name}", TestContext.Current.CancellationToken) + .ConfigureAwait(true); + Assert.Equal(expectedContent, actualContent); + } + + [Fact] + public async Task WithResourceMappingDirectoryInfoDirectoryInfo() + { + // Given + var hostDir = new DirectoryInfo(Path.GetTempPath()).CreateSubdirectory("test"); + var hostFile = new FileInfo(Path.Combine(hostDir.FullName, "test.txt")); + + var expectedContent = new byte[20]; + Random.Shared.NextBytes(expectedContent); + using (var fileStream = hostFile.OpenWrite()) + { + fileStream.Write(expectedContent, 0, expectedContent.Length); + } + + // When + var container = image.Container(b => + b.WithResourceMapping(hostDir, new DirectoryInfo("/tmp/"))); + await container.StartAsync(TestContext.Current.CancellationToken) + .ConfigureAwait(true); + + // Then + var actualContent = await container.ReadFileAsync($"/tmp/{hostFile.Name}", TestContext.Current.CancellationToken) + .ConfigureAwait(true); + Assert.Equal(expectedContent, actualContent); + } + + [Fact] + public async Task WithResourceMappingDirectoryInfoString() + { + // Given + var hostDir = new DirectoryInfo(Path.GetTempPath()).CreateSubdirectory("test"); + var hostFile = new FileInfo(Path.Combine(hostDir.FullName, "test.txt")); + + var expectedContent = new byte[20]; + Random.Shared.NextBytes(expectedContent); + using (var fileStream = hostFile.OpenWrite()) + { + fileStream.Write(expectedContent, 0, expectedContent.Length); + } + + // When + var container = image.Container(b => + b.WithResourceMapping(hostDir, "/tmp/")); + await container.StartAsync(TestContext.Current.CancellationToken) + .ConfigureAwait(true); + + // Then + var actualContent = await container.ReadFileAsync($"/tmp/{hostFile.Name}", TestContext.Current.CancellationToken) + .ConfigureAwait(true); + Assert.Equal(expectedContent, actualContent); + } + + [Fact] + public async Task WithResourceMappingFileInfoFileInfo() + { + // Given + var hostFile = new FileInfo(Path.GetTempFileName()); + var expectedContent = new byte[20]; + Random.Shared.NextBytes(expectedContent); + using (var fileStream = hostFile.OpenWrite()) + { + fileStream.Write(expectedContent, 0, expectedContent.Length); + } + + // When + var container = image.Container(b => + b.WithResourceMapping(hostFile, new FileInfo("/tmp/test.txt"))); + await container.StartAsync(TestContext.Current.CancellationToken) + .ConfigureAwait(true); + + // Then + var actualContent = await container.ReadFileAsync("/tmp/test.txt", TestContext.Current.CancellationToken) + .ConfigureAwait(true); + Assert.Equal(expectedContent, actualContent); + } + + [Fact] + public async Task WithResourceMappingFileInfoDirectoryInfo() + { + // Given + var hostFile = new FileInfo(Path.GetTempFileName()); + var expectedContent = new byte[20]; + Random.Shared.NextBytes(expectedContent); + using (var fileStream = hostFile.OpenWrite()) + { + fileStream.Write(expectedContent, 0, expectedContent.Length); + } + + // When + var container = image.Container(b => + b.WithResourceMapping(hostFile, new DirectoryInfo("/tmp"))); + await container.StartAsync(TestContext.Current.CancellationToken) + .ConfigureAwait(true); + + // Then + var actualContent = await container.ReadFileAsync($"/tmp/{hostFile.Name}", TestContext.Current.CancellationToken) + .ConfigureAwait(true); + Assert.Equal(expectedContent, actualContent); + } + + [Fact] + public async Task WithResourceMappingFileInfoString() + { + // Given + var hostFile = new FileInfo(Path.GetTempFileName()); + var expectedContent = new byte[20]; + Random.Shared.NextBytes(expectedContent); + using (var fileStream = hostFile.OpenWrite()) + { + fileStream.Write(expectedContent, 0, expectedContent.Length); + } + + // When + var container = image.Container(b => + b.WithResourceMapping(hostFile, "/tmp/")); + await container.StartAsync(TestContext.Current.CancellationToken) + .ConfigureAwait(true); + + // Then + var actualContent = await container.ReadFileAsync($"/tmp/{hostFile.Name}", TestContext.Current.CancellationToken) + .ConfigureAwait(true); + Assert.Equal(expectedContent, actualContent); + } + + [Fact] + public async Task WithResourceMappingBytesString() + { + // Given + var expectedContent = new byte[100]; + Random.Shared.NextBytes(expectedContent); + + // When + var container = image.Container(b => + b.WithResourceMapping(expectedContent, "/tmp/test.txt")); + await container.StartAsync(TestContext.Current.CancellationToken) + .ConfigureAwait(true); + + // Then + var actualContent = await container.ReadFileAsync("/tmp/test.txt", TestContext.Current.CancellationToken) + .ConfigureAwait(true); + Assert.Equal(expectedContent, actualContent); + } + + [Fact] + public async Task WithResourceMappingBytesFileInfo() + { + // Given + var expectedContent = new byte[100]; + Random.Shared.NextBytes(expectedContent); + + // When + var container = image.Container(b => + b.WithResourceMapping(expectedContent, new FileInfo("/test.txt"))); + await container.StartAsync(TestContext.Current.CancellationToken) + .ConfigureAwait(true); + + // Then + var actualContent = await container.ReadFileAsync("/test.txt", TestContext.Current.CancellationToken) + .ConfigureAwait(true); + Assert.Equal(expectedContent, actualContent); + } + } +}