From 8a31eb8fe6f277307e12d936b8cdf6b9d9d889ff Mon Sep 17 00:00:00 2001 From: Borys Date: Sat, 14 Feb 2026 01:05:19 +0100 Subject: [PATCH 1/6] support for temporal --- Directory.Packages.props | 1 + Testcontainers.sln | 14 ++ Testcontainers.slnx | 2 + src/Testcontainers.Temporalio/.editorconfig | 1 + .../TemporalBuilder.cs | 176 ++++++++++++++++++ .../TemporalConfiguration.cs | 91 +++++++++ .../TemporalConnectionStringProvider.cs | 13 ++ .../TemporalContainer.cs | 45 +++++ .../Testcontainers.Temporalio.csproj | 12 ++ src/Testcontainers.Temporalio/Usings.cs | 9 + .../.editorconfig | 1 + .../Testcontainers.Temporalio.Tests/.runs-on | 1 + .../Dockerfile | 1 + .../TemporalContainerBuilderTest.cs | 163 ++++++++++++++++ .../TemporalContainerTest.cs | 56 ++++++ .../TemporalContainerWorkflowTest.cs | 54 ++++++ .../Testcontainers.Temporalio.Tests.csproj | 24 +++ .../Testcontainers.Temporalio.Tests/Usings.cs | 9 + 18 files changed, 673 insertions(+) create mode 100644 src/Testcontainers.Temporalio/.editorconfig create mode 100644 src/Testcontainers.Temporalio/TemporalBuilder.cs create mode 100644 src/Testcontainers.Temporalio/TemporalConfiguration.cs create mode 100644 src/Testcontainers.Temporalio/TemporalConnectionStringProvider.cs create mode 100644 src/Testcontainers.Temporalio/TemporalContainer.cs create mode 100644 src/Testcontainers.Temporalio/Testcontainers.Temporalio.csproj create mode 100644 src/Testcontainers.Temporalio/Usings.cs create mode 100644 tests/Testcontainers.Temporalio.Tests/.editorconfig create mode 100644 tests/Testcontainers.Temporalio.Tests/.runs-on create mode 100644 tests/Testcontainers.Temporalio.Tests/Dockerfile create mode 100644 tests/Testcontainers.Temporalio.Tests/TemporalContainerBuilderTest.cs create mode 100644 tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs create mode 100644 tests/Testcontainers.Temporalio.Tests/TemporalContainerWorkflowTest.cs create mode 100644 tests/Testcontainers.Temporalio.Tests/Testcontainers.Temporalio.Tests.csproj create mode 100644 tests/Testcontainers.Temporalio.Tests/Usings.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 2161e1144..5150a1671 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -90,6 +90,7 @@ + diff --git a/Testcontainers.sln b/Testcontainers.sln index 30abbbec5..1143fed3e 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -128,6 +128,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ServiceBus", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Sftp", "src\Testcontainers.Sftp\Testcontainers.Sftp.csproj", "{7D5C6816-0DD2-4E13-A585-033B5D3C80D5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Temporalio", "src\Testcontainers.Temporalio\Testcontainers.Temporalio.csproj", "{24431BF1-7BEB-4C53-BAE8-B9D9F622A240}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Toxiproxy", "src\Testcontainers.Toxiproxy\Testcontainers.Toxiproxy.csproj", "{65A47BA4-4DC8-4206-9B00-CBC87FC944FC}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Typesense", "src\Testcontainers.Typesense\Testcontainers.Typesense.csproj", "{E044A94A-3081-4EE4-8DC6-81601F96DA14}" @@ -266,6 +268,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ServiceBus.T EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Sftp.Tests", "tests\Testcontainers.Sftp.Tests\Testcontainers.Sftp.Tests.csproj", "{B73C3CC0-9F16-4B34-92BE-6EC0853912C5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Temporalio.Tests", "tests\Testcontainers.Temporalio.Tests\Testcontainers.Temporalio.Tests.csproj", "{28B5DEDF-C19B-4A7E-B276-FC4C83DBB7EF}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tests", "tests\Testcontainers.Tests\Testcontainers.Tests.csproj", "{27CDB869-A150-4593-958F-6F26E5391E7C}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Toxiproxy.Tests", "tests\Testcontainers.Toxiproxy.Tests\Testcontainers.Toxiproxy.Tests.csproj", "{10726AAA-E93F-4B40-A05E-28308423DABE}" @@ -502,6 +506,10 @@ Global {7D5C6816-0DD2-4E13-A585-033B5D3C80D5}.Debug|Any CPU.Build.0 = Debug|Any CPU {7D5C6816-0DD2-4E13-A585-033B5D3C80D5}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D5C6816-0DD2-4E13-A585-033B5D3C80D5}.Release|Any CPU.Build.0 = Release|Any CPU + {24431BF1-7BEB-4C53-BAE8-B9D9F622A240}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24431BF1-7BEB-4C53-BAE8-B9D9F622A240}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24431BF1-7BEB-4C53-BAE8-B9D9F622A240}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24431BF1-7BEB-4C53-BAE8-B9D9F622A240}.Release|Any CPU.Build.0 = Release|Any CPU {65A47BA4-4DC8-4206-9B00-CBC87FC944FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {65A47BA4-4DC8-4206-9B00-CBC87FC944FC}.Debug|Any CPU.Build.0 = Debug|Any CPU {65A47BA4-4DC8-4206-9B00-CBC87FC944FC}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -778,6 +786,10 @@ Global {B73C3CC0-9F16-4B34-92BE-6EC0853912C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {B73C3CC0-9F16-4B34-92BE-6EC0853912C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {B73C3CC0-9F16-4B34-92BE-6EC0853912C5}.Release|Any CPU.Build.0 = Release|Any CPU + {28B5DEDF-C19B-4A7E-B276-FC4C83DBB7EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28B5DEDF-C19B-4A7E-B276-FC4C83DBB7EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28B5DEDF-C19B-4A7E-B276-FC4C83DBB7EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28B5DEDF-C19B-4A7E-B276-FC4C83DBB7EF}.Release|Any CPU.Build.0 = Release|Any CPU {27CDB869-A150-4593-958F-6F26E5391E7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {27CDB869-A150-4593-958F-6F26E5391E7C}.Debug|Any CPU.Build.0 = Debug|Any CPU {27CDB869-A150-4593-958F-6F26E5391E7C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -866,6 +878,7 @@ Global {45D6F69C-4D87-4130-AA90-0DB2F7460DAE} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {2E39E532-B81E-4B48-A004-FAE18EDF9E79} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {7D5C6816-0DD2-4E13-A585-033B5D3C80D5} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} + {24431BF1-7BEB-4C53-BAE8-B9D9F622A240} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {65A47BA4-4DC8-4206-9B00-CBC87FC944FC} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {E044A94A-3081-4EE4-8DC6-81601F96DA14} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {68F8600D-24E9-4E03-9E25-5F6EB338EAC1} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} @@ -935,6 +948,7 @@ Global {9E8E6AA5-65D1-498F-BEAB-BA34723A0050} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {232DD918-46ED-4BA8-B383-1A9146D83064} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {B73C3CC0-9F16-4B34-92BE-6EC0853912C5} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {28B5DEDF-C19B-4A7E-B276-FC4C83DBB7EF} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {10726AAA-E93F-4B40-A05E-28308423DABE} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {73CC8E45-5608-1398-4029-0802428B5565} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} diff --git a/Testcontainers.slnx b/Testcontainers.slnx index f7304fdfb..e6a84283a 100644 --- a/Testcontainers.slnx +++ b/Testcontainers.slnx @@ -64,6 +64,7 @@ + @@ -135,6 +136,7 @@ + diff --git a/src/Testcontainers.Temporalio/.editorconfig b/src/Testcontainers.Temporalio/.editorconfig new file mode 100644 index 000000000..78b36ca08 --- /dev/null +++ b/src/Testcontainers.Temporalio/.editorconfig @@ -0,0 +1 @@ +root = true diff --git a/src/Testcontainers.Temporalio/TemporalBuilder.cs b/src/Testcontainers.Temporalio/TemporalBuilder.cs new file mode 100644 index 000000000..c00c6835b --- /dev/null +++ b/src/Testcontainers.Temporalio/TemporalBuilder.cs @@ -0,0 +1,176 @@ +namespace Testcontainers.Temporalio; + +/// +[PublicAPI] +public sealed class TemporalBuilder : ContainerBuilder +{ + public const int TemporalGrpcPort = 7233; + + public const int TemporalHttpPort = 8233; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The full Docker image name, including the image repository and tag + /// (e.g., temporalio/temporal:1.5.1, temporalio/temporal:latest). + /// + /// + /// Docker image tags available at . + /// + public TemporalBuilder(string image) + : this(new DockerImage(image)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// An instance that specifies the Docker image to be used + /// for the container builder configuration. + /// + /// + /// Docker image tags available at . + /// + public TemporalBuilder(IImage image) + : this(new TemporalConfiguration()) + { + DockerResourceConfiguration = Init().WithImage(image).DockerResourceConfiguration; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + private TemporalBuilder(TemporalConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + DockerResourceConfiguration = resourceConfiguration; + } + + /// + protected override TemporalConfiguration DockerResourceConfiguration { get; } + + /// + /// Adds a namespace to pre-register at startup. The "default" namespace is always + /// registered regardless. Multiple namespaces can be added by chaining calls. + /// + /// The namespace name to pre-register. + /// A configured instance of . + public TemporalBuilder WithNamespace(string @namespace) + { + return Merge(DockerResourceConfiguration, new TemporalConfiguration(namespaces: [@namespace])); + } + + /// + /// Registers a custom search attribute. Type must be one of: + /// Text, Keyword, Int, Double, Bool, Datetime, KeywordList. + /// + /// The search attribute name. + /// The search attribute type. + /// A configured instance of . + public TemporalBuilder WithSearchAttribute(string name, string type) + { + return Merge(DockerResourceConfiguration, new TemporalConfiguration(searchAttributes: [name + "=" + type])); + } + + /// + /// Sets a dynamic configuration value. Keys must be identifiers, and values must be + /// JSON values (e.g., key frontend.enableUpdateWorkflowExecution with value true). + /// + /// The configuration key. + /// The configuration value as a JSON literal. + /// A configured instance of . + public TemporalBuilder WithDynamicConfigValue(string key, string jsonValue) + { + return Merge(DockerResourceConfiguration, new TemporalConfiguration(dynamicConfigValues: [key + "=" + jsonValue])); + } + + /// + /// Sets the path to a database file inside the container for persistent Temporal state. + /// By default, Workflow Executions are lost when the container is removed. + /// + /// The path to the database file inside the container. + /// A configured instance of . + public TemporalBuilder WithDbFilename(string path) + { + return Merge(DockerResourceConfiguration, new TemporalConfiguration(dbFilename: path)); + } + + /// + public override TemporalContainer Build() + { + Validate(); + + var command = new List { "server", "start-dev", "--ip", "0.0.0.0" }; + + if (DockerResourceConfiguration.Namespaces != null && DockerResourceConfiguration.Namespaces.Any()) + { + foreach (var ns in DockerResourceConfiguration.Namespaces) + { + command.Add("--namespace"); + command.Add(ns); + } + } + + if (DockerResourceConfiguration.SearchAttributes != null && DockerResourceConfiguration.SearchAttributes.Any()) + { + foreach (var attr in DockerResourceConfiguration.SearchAttributes) + { + command.Add("--search-attribute"); + command.Add(attr); + } + } + + if (DockerResourceConfiguration.DynamicConfigValues != null && DockerResourceConfiguration.DynamicConfigValues.Any()) + { + foreach (var value in DockerResourceConfiguration.DynamicConfigValues) + { + command.Add("--dynamic-config-value"); + command.Add(value); + } + } + + if (!string.IsNullOrEmpty(DockerResourceConfiguration.DbFilename)) + { + command.Add("--db-filename"); + command.Add(DockerResourceConfiguration.DbFilename); + } + + var temporalBuilder = WithCommand(command.ToArray()); + return new TemporalContainer(temporalBuilder.DockerResourceConfiguration); + } + + /// + protected override TemporalBuilder Init() + { + return base.Init() + .WithPortBinding(TemporalGrpcPort, true) + .WithPortBinding(TemporalHttpPort, true) + .WithConnectionStringProvider(new TemporalConnectionStringProvider()) + .WithWaitStrategy(Wait.ForUnixContainer() + .UntilInternalTcpPortIsAvailable(TemporalGrpcPort) + .UntilInternalTcpPortIsAvailable(TemporalHttpPort) + .UntilHttpRequestIsSucceeded(request => + request.ForPath("/api/v1/namespaces").ForPort(TemporalHttpPort))); + } + + /// + protected override TemporalBuilder Clone(IResourceConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new TemporalConfiguration(resourceConfiguration)); + } + + /// + protected override TemporalBuilder Clone(IContainerConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new TemporalConfiguration(resourceConfiguration)); + } + + /// + protected override TemporalBuilder Merge(TemporalConfiguration oldValue, TemporalConfiguration newValue) + { + return new TemporalBuilder(new TemporalConfiguration(oldValue, newValue)); + } +} diff --git a/src/Testcontainers.Temporalio/TemporalConfiguration.cs b/src/Testcontainers.Temporalio/TemporalConfiguration.cs new file mode 100644 index 000000000..dffa0de37 --- /dev/null +++ b/src/Testcontainers.Temporalio/TemporalConfiguration.cs @@ -0,0 +1,91 @@ +namespace Testcontainers.Temporalio; + +/// +[PublicAPI] +public sealed class TemporalConfiguration : ContainerConfiguration +{ + /// + /// Initializes a new instance of the class. + /// + /// The namespaces to pre-register at startup. + /// The search attributes to register. + /// The dynamic configuration values. + /// The path to the database file for persistent state. + public TemporalConfiguration( + IEnumerable namespaces = null, + IEnumerable searchAttributes = null, + IEnumerable dynamicConfigValues = null, + string dbFilename = null) + { + Namespaces = namespaces; + SearchAttributes = searchAttributes; + DynamicConfigValues = dynamicConfigValues; + DbFilename = dbFilename; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public TemporalConfiguration(IResourceConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public TemporalConfiguration(IContainerConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public TemporalConfiguration(TemporalConfiguration resourceConfiguration) + : this(new TemporalConfiguration(), resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The old Docker resource configuration. + /// The new Docker resource configuration. + public TemporalConfiguration(TemporalConfiguration oldValue, TemporalConfiguration newValue) + : base(oldValue, newValue) + { + Namespaces = BuildConfiguration.Combine(oldValue.Namespaces, newValue.Namespaces); + SearchAttributes = BuildConfiguration.Combine(oldValue.SearchAttributes, newValue.SearchAttributes); + DynamicConfigValues = BuildConfiguration.Combine(oldValue.DynamicConfigValues, newValue.DynamicConfigValues); + DbFilename = BuildConfiguration.Combine(oldValue.DbFilename, newValue.DbFilename); + } + + /// + /// Gets the namespaces to pre-register at startup. + /// The "default" namespace is always registered regardless of this setting. + /// + public IEnumerable Namespaces { get; } + + /// + /// Gets the search attributes to register in KEY=TYPE format. + /// Type is one of: Text, Keyword, Int, Double, Bool, Datetime, KeywordList. + /// + public IEnumerable SearchAttributes { get; } + + /// + /// Gets the dynamic configuration values in KEY=JSON_VALUE format. + /// + public IEnumerable DynamicConfigValues { get; } + + /// + /// Gets the path to the database file for persistent Temporal state inside the container. + /// + public string DbFilename { get; } +} diff --git a/src/Testcontainers.Temporalio/TemporalConnectionStringProvider.cs b/src/Testcontainers.Temporalio/TemporalConnectionStringProvider.cs new file mode 100644 index 000000000..85df074fc --- /dev/null +++ b/src/Testcontainers.Temporalio/TemporalConnectionStringProvider.cs @@ -0,0 +1,13 @@ +namespace Testcontainers.Temporalio; + +/// +/// Provides the Temporal connection string. +/// +internal sealed class TemporalConnectionStringProvider : ContainerConnectionStringProvider +{ + /// + protected override string GetHostConnectionString() + { + return Container.GetGrpcAddress(); + } +} diff --git a/src/Testcontainers.Temporalio/TemporalContainer.cs b/src/Testcontainers.Temporalio/TemporalContainer.cs new file mode 100644 index 000000000..ca8406e9d --- /dev/null +++ b/src/Testcontainers.Temporalio/TemporalContainer.cs @@ -0,0 +1,45 @@ +namespace Testcontainers.Temporalio; + +/// +[PublicAPI] +public sealed class TemporalContainer : DockerContainer +{ + /// + /// Initializes a new instance of the class. + /// + /// The container configuration. + public TemporalContainer(TemporalConfiguration configuration) + : base(configuration) + { + } + + /// + /// Gets the Temporal gRPC endpoint for SDK clients and workers. + /// + /// + /// The Temporal .NET SDK expects host:port without a protocol scheme. + /// Using a URI like http://host:port will throw an . + /// + /// Usage example: + /// + /// var client = await TemporalClient.ConnectAsync( + /// new("localhost:7233") { Namespace = "default" }); + /// + /// + /// + /// + /// The Temporal gRPC endpoint in host:port format. + public string GetGrpcAddress() + { + return Hostname + ":" + GetMappedPublicPort(TemporalBuilder.TemporalGrpcPort); + } + + /// + /// Gets the Temporal Web UI address. + /// + /// The Temporal Web UI base address. + public string GetWebUiAddress() + { + return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(TemporalBuilder.TemporalHttpPort)).ToString(); + } +} diff --git a/src/Testcontainers.Temporalio/Testcontainers.Temporalio.csproj b/src/Testcontainers.Temporalio/Testcontainers.Temporalio.csproj new file mode 100644 index 000000000..16967e2c6 --- /dev/null +++ b/src/Testcontainers.Temporalio/Testcontainers.Temporalio.csproj @@ -0,0 +1,12 @@ + + + net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1 + latest + + + + + + + + diff --git a/src/Testcontainers.Temporalio/Usings.cs b/src/Testcontainers.Temporalio/Usings.cs new file mode 100644 index 000000000..e6a7c21fd --- /dev/null +++ b/src/Testcontainers.Temporalio/Usings.cs @@ -0,0 +1,9 @@ +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using Docker.DotNet.Models; +global using DotNet.Testcontainers.Builders; +global using DotNet.Testcontainers.Configurations; +global using DotNet.Testcontainers.Containers; +global using DotNet.Testcontainers.Images; +global using JetBrains.Annotations; diff --git a/tests/Testcontainers.Temporalio.Tests/.editorconfig b/tests/Testcontainers.Temporalio.Tests/.editorconfig new file mode 100644 index 000000000..78b36ca08 --- /dev/null +++ b/tests/Testcontainers.Temporalio.Tests/.editorconfig @@ -0,0 +1 @@ +root = true diff --git a/tests/Testcontainers.Temporalio.Tests/.runs-on b/tests/Testcontainers.Temporalio.Tests/.runs-on new file mode 100644 index 000000000..2c0c1ac72 --- /dev/null +++ b/tests/Testcontainers.Temporalio.Tests/.runs-on @@ -0,0 +1 @@ +ubuntu-24.04 diff --git a/tests/Testcontainers.Temporalio.Tests/Dockerfile b/tests/Testcontainers.Temporalio.Tests/Dockerfile new file mode 100644 index 000000000..cf5535adf --- /dev/null +++ b/tests/Testcontainers.Temporalio.Tests/Dockerfile @@ -0,0 +1 @@ +FROM temporalio/temporal:1.5.1 diff --git a/tests/Testcontainers.Temporalio.Tests/TemporalContainerBuilderTest.cs b/tests/Testcontainers.Temporalio.Tests/TemporalContainerBuilderTest.cs new file mode 100644 index 000000000..12a7cf985 --- /dev/null +++ b/tests/Testcontainers.Temporalio.Tests/TemporalContainerBuilderTest.cs @@ -0,0 +1,163 @@ +namespace Testcontainers.Temporalio; + +public sealed class TemporalContainerCustomNamespaceTest : IAsyncLifetime +{ + private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()) + .WithNamespace("custom-namespace") + .Build(); + + public async ValueTask InitializeAsync() + { + await _temporalContainer.StartAsync() + .ConfigureAwait(false); + } + + public ValueTask DisposeAsync() + { + return _temporalContainer.DisposeAsync(); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task CustomNamespaceIsPreRegistered() + { + // Given + var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) + { + Namespace = "custom-namespace", + }).ConfigureAwait(true); + + // When + var response = await client.Connection.WorkflowService.ListNamespacesAsync(new()).ConfigureAwait(true); + + // Then + Assert.Contains(response.Namespaces, ns => ns.NamespaceInfo.Name == "custom-namespace"); + Assert.Contains(response.Namespaces, ns => ns.NamespaceInfo.Name == "default"); + } +} + +public sealed class TemporalContainerSearchAttributeTest : IAsyncLifetime +{ + private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()) + .WithSearchAttribute("CustomerId", "Keyword") + .WithSearchAttribute("OrderDate", "Datetime") + .Build(); + + public async ValueTask InitializeAsync() + { + await _temporalContainer.StartAsync() + .ConfigureAwait(false); + } + + public ValueTask DisposeAsync() + { + return _temporalContainer.DisposeAsync(); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task CustomSearchAttributesAreRegistered() + { + // Given + var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) + { + Namespace = "default", + }).ConfigureAwait(true); + + // When + var response = await client.Connection.OperatorService.ListSearchAttributesAsync( + new() { Namespace = "default" }).ConfigureAwait(true); + + // Then + Assert.True(response.CustomAttributes.ContainsKey("CustomerId")); + Assert.True(response.CustomAttributes.ContainsKey("OrderDate")); + } +} + +public sealed class TemporalContainerDynamicConfigTest : IAsyncLifetime +{ + private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()) + .WithDynamicConfigValue("limit.maxIDLength", "10") + .Build(); + + public async ValueTask InitializeAsync() + { + await _temporalContainer.StartAsync() + .ConfigureAwait(false); + } + + public ValueTask DisposeAsync() + { + return _temporalContainer.DisposeAsync(); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task DynamicConfigValueLimitsWorkflowIdLength() + { + // Given + var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) + { + Namespace = "default", + }).ConfigureAwait(true); + + var longWorkflowId = new string('x', 50); + + // When / Then — starting a workflow with an ID exceeding maxIDLength should fail + var exception = await Assert.ThrowsAsync(() => + client.StartWorkflowAsync("TestWorkflow", Array.Empty(), new(id: longWorkflowId, taskQueue: "test-queue"))) + .ConfigureAwait(true); + + Assert.Contains("length exceeds limit", exception.Message, StringComparison.OrdinalIgnoreCase); + } +} + +public sealed class TemporalContainerDbFilenameTest : IAsyncLifetime +{ + private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()) + .WithDbFilename("/tmp/temporal.db") + .Build(); + + public async ValueTask InitializeAsync() + { + await _temporalContainer.StartAsync() + .ConfigureAwait(false); + } + + public ValueTask DisposeAsync() + { + return _temporalContainer.DisposeAsync(); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task PersistentWorkflowSurvivesContainerRestart() + { + // Given + var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) + { + Namespace = "default", + }).ConfigureAwait(true); + + var workflowId = Guid.NewGuid().ToString("D"); + + await client.StartWorkflowAsync("PersistentWorkflow", Array.Empty(), new(id: workflowId, taskQueue: "test-queue")) + .ConfigureAwait(true); + + // When + await _temporalContainer.StopAsync(CancellationToken.None).ConfigureAwait(true); + await _temporalContainer.StartAsync(CancellationToken.None).ConfigureAwait(true); + + // Then + var clientAfterRestart = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) + { + Namespace = "default", + }).ConfigureAwait(true); + + var handle = clientAfterRestart.GetWorkflowHandle(workflowId); + var description = await handle.DescribeAsync().ConfigureAwait(true); + + Assert.Equal(workflowId, description.Id); + Assert.Equal("PersistentWorkflow", description.WorkflowType); + } +} diff --git a/tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs b/tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs new file mode 100644 index 000000000..c230ea54f --- /dev/null +++ b/tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs @@ -0,0 +1,56 @@ +namespace Testcontainers.Temporalio; + +public sealed class TemporalContainerTest : IAsyncLifetime +{ + private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()).Build(); + + public async ValueTask InitializeAsync() + { + await _temporalContainer.StartAsync() + .ConfigureAwait(false); + } + + public ValueTask DisposeAsync() + { + return _temporalContainer.DisposeAsync(); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public void ConnectionStringReturnsGrpcAddress() + { + Assert.Equal(_temporalContainer.GetGrpcAddress(), _temporalContainer.GetConnectionString()); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task WebUiReturnsHttpOk() + { + // Given + using var httpClient = new HttpClient(); + httpClient.BaseAddress = new Uri(_temporalContainer.GetWebUiAddress()); + + // When + using var response = await httpClient.GetAsync("/", CancellationToken.None).ConfigureAwait(true); + + // Then + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task ListNamespacesReturnsDefaultNamespace() + { + // Given + var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) + { + Namespace = "default", + }).ConfigureAwait(true); + + // When + var response = await client.Connection.WorkflowService.ListNamespacesAsync(new()).ConfigureAwait(true); + + // Then + Assert.Contains(response.Namespaces, ns => ns.NamespaceInfo.Name == "default"); + } +} diff --git a/tests/Testcontainers.Temporalio.Tests/TemporalContainerWorkflowTest.cs b/tests/Testcontainers.Temporalio.Tests/TemporalContainerWorkflowTest.cs new file mode 100644 index 000000000..790419adf --- /dev/null +++ b/tests/Testcontainers.Temporalio.Tests/TemporalContainerWorkflowTest.cs @@ -0,0 +1,54 @@ +namespace Testcontainers.Temporalio; + +public sealed class TemporalContainerWorkflowTest : IAsyncLifetime +{ + private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()).Build(); + + public async ValueTask InitializeAsync() + { + await _temporalContainer.StartAsync() + .ConfigureAwait(false); + } + + public ValueTask DisposeAsync() + { + return _temporalContainer.DisposeAsync(); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task StartWorkflowThenListAndDescribe() + { + // Given + var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) + { + Namespace = "default", + }).ConfigureAwait(true); + + var workflowId = Guid.NewGuid().ToString("D"); + + // When — start a workflow (equivalent to: temporal workflow start) + var handle = await client.StartWorkflowAsync("MyWorkflow", Array.Empty(), new(id: workflowId, taskQueue: "my-task-queue")) + .ConfigureAwait(true); + + // Then — list workflows (equivalent to: temporal workflow list) + var workflows = new List(); + + await foreach (var execution in client.ListWorkflowsAsync($"WorkflowId = '{workflowId}'").ConfigureAwait(true)) + { + workflows.Add(execution); + } + + Assert.Single(workflows); + Assert.Equal(workflowId, workflows[0].Id); + Assert.Equal("MyWorkflow", workflows[0].WorkflowType); + + // Then — describe workflow (equivalent to: temporal workflow describe) + var description = await handle.DescribeAsync().ConfigureAwait(true); + + Assert.Equal(workflowId, description.Id); + Assert.Equal("MyWorkflow", description.WorkflowType); + Assert.Equal("my-task-queue", description.TaskQueue); + Assert.NotNull(description.RunId); + } +} diff --git a/tests/Testcontainers.Temporalio.Tests/Testcontainers.Temporalio.Tests.csproj b/tests/Testcontainers.Temporalio.Tests/Testcontainers.Temporalio.Tests.csproj new file mode 100644 index 000000000..75366c5d6 --- /dev/null +++ b/tests/Testcontainers.Temporalio.Tests/Testcontainers.Temporalio.Tests.csproj @@ -0,0 +1,24 @@ + + + net10.0 + false + false + Exe + + + + + + + + + + + + + + + PreserveNewest + + + diff --git a/tests/Testcontainers.Temporalio.Tests/Usings.cs b/tests/Testcontainers.Temporalio.Tests/Usings.cs new file mode 100644 index 000000000..c381d8df8 --- /dev/null +++ b/tests/Testcontainers.Temporalio.Tests/Usings.cs @@ -0,0 +1,9 @@ +global using System; +global using System.Collections.Generic; +global using System.Net; +global using System.Net.Http; +global using System.Threading; +global using System.Threading.Tasks; +global using DotNet.Testcontainers.Commons; +global using Temporalio.Client; +global using Xunit; From e758499127e4b9be39cd8fa218b7d7effc94e78e Mon Sep 17 00:00:00 2001 From: Borys Date: Sat, 14 Feb 2026 20:59:56 +0100 Subject: [PATCH 2/6] review comments and code cleanup --- .../TemporalBuilder.cs | 91 +--------- .../TemporalConfiguration.cs | 40 +---- src/Testcontainers.Temporalio/Usings.cs | 2 - .../TemporalContainerBuilderTest.cs | 163 ------------------ .../TemporalContainerTest.cs | 38 ++-- .../TemporalContainerWorkflowTest.cs | 54 ------ .../Testcontainers.Temporalio.Tests/Usings.cs | 3 - 7 files changed, 28 insertions(+), 363 deletions(-) delete mode 100644 tests/Testcontainers.Temporalio.Tests/TemporalContainerBuilderTest.cs delete mode 100644 tests/Testcontainers.Temporalio.Tests/TemporalContainerWorkflowTest.cs diff --git a/src/Testcontainers.Temporalio/TemporalBuilder.cs b/src/Testcontainers.Temporalio/TemporalBuilder.cs index c00c6835b..d4178c57f 100644 --- a/src/Testcontainers.Temporalio/TemporalBuilder.cs +++ b/src/Testcontainers.Temporalio/TemporalBuilder.cs @@ -52,94 +52,11 @@ private TemporalBuilder(TemporalConfiguration resourceConfiguration) /// protected override TemporalConfiguration DockerResourceConfiguration { get; } - /// - /// Adds a namespace to pre-register at startup. The "default" namespace is always - /// registered regardless. Multiple namespaces can be added by chaining calls. - /// - /// The namespace name to pre-register. - /// A configured instance of . - public TemporalBuilder WithNamespace(string @namespace) - { - return Merge(DockerResourceConfiguration, new TemporalConfiguration(namespaces: [@namespace])); - } - - /// - /// Registers a custom search attribute. Type must be one of: - /// Text, Keyword, Int, Double, Bool, Datetime, KeywordList. - /// - /// The search attribute name. - /// The search attribute type. - /// A configured instance of . - public TemporalBuilder WithSearchAttribute(string name, string type) - { - return Merge(DockerResourceConfiguration, new TemporalConfiguration(searchAttributes: [name + "=" + type])); - } - - /// - /// Sets a dynamic configuration value. Keys must be identifiers, and values must be - /// JSON values (e.g., key frontend.enableUpdateWorkflowExecution with value true). - /// - /// The configuration key. - /// The configuration value as a JSON literal. - /// A configured instance of . - public TemporalBuilder WithDynamicConfigValue(string key, string jsonValue) - { - return Merge(DockerResourceConfiguration, new TemporalConfiguration(dynamicConfigValues: [key + "=" + jsonValue])); - } - - /// - /// Sets the path to a database file inside the container for persistent Temporal state. - /// By default, Workflow Executions are lost when the container is removed. - /// - /// The path to the database file inside the container. - /// A configured instance of . - public TemporalBuilder WithDbFilename(string path) - { - return Merge(DockerResourceConfiguration, new TemporalConfiguration(dbFilename: path)); - } - /// public override TemporalContainer Build() { Validate(); - - var command = new List { "server", "start-dev", "--ip", "0.0.0.0" }; - - if (DockerResourceConfiguration.Namespaces != null && DockerResourceConfiguration.Namespaces.Any()) - { - foreach (var ns in DockerResourceConfiguration.Namespaces) - { - command.Add("--namespace"); - command.Add(ns); - } - } - - if (DockerResourceConfiguration.SearchAttributes != null && DockerResourceConfiguration.SearchAttributes.Any()) - { - foreach (var attr in DockerResourceConfiguration.SearchAttributes) - { - command.Add("--search-attribute"); - command.Add(attr); - } - } - - if (DockerResourceConfiguration.DynamicConfigValues != null && DockerResourceConfiguration.DynamicConfigValues.Any()) - { - foreach (var value in DockerResourceConfiguration.DynamicConfigValues) - { - command.Add("--dynamic-config-value"); - command.Add(value); - } - } - - if (!string.IsNullOrEmpty(DockerResourceConfiguration.DbFilename)) - { - command.Add("--db-filename"); - command.Add(DockerResourceConfiguration.DbFilename); - } - - var temporalBuilder = WithCommand(command.ToArray()); - return new TemporalContainer(temporalBuilder.DockerResourceConfiguration); + return new TemporalContainer(DockerResourceConfiguration); } /// @@ -148,12 +65,12 @@ protected override TemporalBuilder Init() return base.Init() .WithPortBinding(TemporalGrpcPort, true) .WithPortBinding(TemporalHttpPort, true) + .WithCommand("server", "start-dev", "--ip", "0.0.0.0") .WithConnectionStringProvider(new TemporalConnectionStringProvider()) .WithWaitStrategy(Wait.ForUnixContainer() - .UntilInternalTcpPortIsAvailable(TemporalGrpcPort) - .UntilInternalTcpPortIsAvailable(TemporalHttpPort) .UntilHttpRequestIsSucceeded(request => - request.ForPath("/api/v1/namespaces").ForPort(TemporalHttpPort))); + request.ForPath("/api/v1/namespaces").ForPort(TemporalHttpPort)) + .UntilInternalTcpPortIsAvailable(TemporalGrpcPort)); } /// diff --git a/src/Testcontainers.Temporalio/TemporalConfiguration.cs b/src/Testcontainers.Temporalio/TemporalConfiguration.cs index dffa0de37..616fcce63 100644 --- a/src/Testcontainers.Temporalio/TemporalConfiguration.cs +++ b/src/Testcontainers.Temporalio/TemporalConfiguration.cs @@ -7,20 +7,8 @@ public sealed class TemporalConfiguration : ContainerConfiguration /// /// Initializes a new instance of the class. /// - /// The namespaces to pre-register at startup. - /// The search attributes to register. - /// The dynamic configuration values. - /// The path to the database file for persistent state. - public TemporalConfiguration( - IEnumerable namespaces = null, - IEnumerable searchAttributes = null, - IEnumerable dynamicConfigValues = null, - string dbFilename = null) + public TemporalConfiguration() { - Namespaces = namespaces; - SearchAttributes = searchAttributes; - DynamicConfigValues = dynamicConfigValues; - DbFilename = dbFilename; } /// @@ -61,31 +49,5 @@ public TemporalConfiguration(TemporalConfiguration resourceConfiguration) public TemporalConfiguration(TemporalConfiguration oldValue, TemporalConfiguration newValue) : base(oldValue, newValue) { - Namespaces = BuildConfiguration.Combine(oldValue.Namespaces, newValue.Namespaces); - SearchAttributes = BuildConfiguration.Combine(oldValue.SearchAttributes, newValue.SearchAttributes); - DynamicConfigValues = BuildConfiguration.Combine(oldValue.DynamicConfigValues, newValue.DynamicConfigValues); - DbFilename = BuildConfiguration.Combine(oldValue.DbFilename, newValue.DbFilename); } - - /// - /// Gets the namespaces to pre-register at startup. - /// The "default" namespace is always registered regardless of this setting. - /// - public IEnumerable Namespaces { get; } - - /// - /// Gets the search attributes to register in KEY=TYPE format. - /// Type is one of: Text, Keyword, Int, Double, Bool, Datetime, KeywordList. - /// - public IEnumerable SearchAttributes { get; } - - /// - /// Gets the dynamic configuration values in KEY=JSON_VALUE format. - /// - public IEnumerable DynamicConfigValues { get; } - - /// - /// Gets the path to the database file for persistent Temporal state inside the container. - /// - public string DbFilename { get; } } diff --git a/src/Testcontainers.Temporalio/Usings.cs b/src/Testcontainers.Temporalio/Usings.cs index e6a7c21fd..ceba65588 100644 --- a/src/Testcontainers.Temporalio/Usings.cs +++ b/src/Testcontainers.Temporalio/Usings.cs @@ -1,6 +1,4 @@ global using System; -global using System.Collections.Generic; -global using System.Linq; global using Docker.DotNet.Models; global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Configurations; diff --git a/tests/Testcontainers.Temporalio.Tests/TemporalContainerBuilderTest.cs b/tests/Testcontainers.Temporalio.Tests/TemporalContainerBuilderTest.cs deleted file mode 100644 index 12a7cf985..000000000 --- a/tests/Testcontainers.Temporalio.Tests/TemporalContainerBuilderTest.cs +++ /dev/null @@ -1,163 +0,0 @@ -namespace Testcontainers.Temporalio; - -public sealed class TemporalContainerCustomNamespaceTest : IAsyncLifetime -{ - private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()) - .WithNamespace("custom-namespace") - .Build(); - - public async ValueTask InitializeAsync() - { - await _temporalContainer.StartAsync() - .ConfigureAwait(false); - } - - public ValueTask DisposeAsync() - { - return _temporalContainer.DisposeAsync(); - } - - [Fact] - [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public async Task CustomNamespaceIsPreRegistered() - { - // Given - var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) - { - Namespace = "custom-namespace", - }).ConfigureAwait(true); - - // When - var response = await client.Connection.WorkflowService.ListNamespacesAsync(new()).ConfigureAwait(true); - - // Then - Assert.Contains(response.Namespaces, ns => ns.NamespaceInfo.Name == "custom-namespace"); - Assert.Contains(response.Namespaces, ns => ns.NamespaceInfo.Name == "default"); - } -} - -public sealed class TemporalContainerSearchAttributeTest : IAsyncLifetime -{ - private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()) - .WithSearchAttribute("CustomerId", "Keyword") - .WithSearchAttribute("OrderDate", "Datetime") - .Build(); - - public async ValueTask InitializeAsync() - { - await _temporalContainer.StartAsync() - .ConfigureAwait(false); - } - - public ValueTask DisposeAsync() - { - return _temporalContainer.DisposeAsync(); - } - - [Fact] - [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public async Task CustomSearchAttributesAreRegistered() - { - // Given - var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) - { - Namespace = "default", - }).ConfigureAwait(true); - - // When - var response = await client.Connection.OperatorService.ListSearchAttributesAsync( - new() { Namespace = "default" }).ConfigureAwait(true); - - // Then - Assert.True(response.CustomAttributes.ContainsKey("CustomerId")); - Assert.True(response.CustomAttributes.ContainsKey("OrderDate")); - } -} - -public sealed class TemporalContainerDynamicConfigTest : IAsyncLifetime -{ - private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()) - .WithDynamicConfigValue("limit.maxIDLength", "10") - .Build(); - - public async ValueTask InitializeAsync() - { - await _temporalContainer.StartAsync() - .ConfigureAwait(false); - } - - public ValueTask DisposeAsync() - { - return _temporalContainer.DisposeAsync(); - } - - [Fact] - [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public async Task DynamicConfigValueLimitsWorkflowIdLength() - { - // Given - var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) - { - Namespace = "default", - }).ConfigureAwait(true); - - var longWorkflowId = new string('x', 50); - - // When / Then — starting a workflow with an ID exceeding maxIDLength should fail - var exception = await Assert.ThrowsAsync(() => - client.StartWorkflowAsync("TestWorkflow", Array.Empty(), new(id: longWorkflowId, taskQueue: "test-queue"))) - .ConfigureAwait(true); - - Assert.Contains("length exceeds limit", exception.Message, StringComparison.OrdinalIgnoreCase); - } -} - -public sealed class TemporalContainerDbFilenameTest : IAsyncLifetime -{ - private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()) - .WithDbFilename("/tmp/temporal.db") - .Build(); - - public async ValueTask InitializeAsync() - { - await _temporalContainer.StartAsync() - .ConfigureAwait(false); - } - - public ValueTask DisposeAsync() - { - return _temporalContainer.DisposeAsync(); - } - - [Fact] - [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public async Task PersistentWorkflowSurvivesContainerRestart() - { - // Given - var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) - { - Namespace = "default", - }).ConfigureAwait(true); - - var workflowId = Guid.NewGuid().ToString("D"); - - await client.StartWorkflowAsync("PersistentWorkflow", Array.Empty(), new(id: workflowId, taskQueue: "test-queue")) - .ConfigureAwait(true); - - // When - await _temporalContainer.StopAsync(CancellationToken.None).ConfigureAwait(true); - await _temporalContainer.StartAsync(CancellationToken.None).ConfigureAwait(true); - - // Then - var clientAfterRestart = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) - { - Namespace = "default", - }).ConfigureAwait(true); - - var handle = clientAfterRestart.GetWorkflowHandle(workflowId); - var description = await handle.DescribeAsync().ConfigureAwait(true); - - Assert.Equal(workflowId, description.Id); - Assert.Equal("PersistentWorkflow", description.WorkflowType); - } -} diff --git a/tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs b/tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs index c230ea54f..c7f47ccd2 100644 --- a/tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs +++ b/tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs @@ -2,6 +2,7 @@ namespace Testcontainers.Temporalio; public sealed class TemporalContainerTest : IAsyncLifetime { + // # --8<-- [start:UseTemporalContainer] private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()).Build(); public async ValueTask InitializeAsync() @@ -17,29 +18,25 @@ public ValueTask DisposeAsync() [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public void ConnectionStringReturnsGrpcAddress() - { - Assert.Equal(_temporalContainer.GetGrpcAddress(), _temporalContainer.GetConnectionString()); - } - - [Fact] - [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public async Task WebUiReturnsHttpOk() + public async Task ListNamespacesReturnsDefaultNamespace() { // Given - using var httpClient = new HttpClient(); - httpClient.BaseAddress = new Uri(_temporalContainer.GetWebUiAddress()); + var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) + { + Namespace = "default", + }).ConfigureAwait(true); // When - using var response = await httpClient.GetAsync("/", CancellationToken.None).ConfigureAwait(true); + var response = await client.Connection.WorkflowService.ListNamespacesAsync(new()).ConfigureAwait(true); // Then - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Contains(response.Namespaces, ns => ns.NamespaceInfo.Name == "default"); + Assert.Equal(_temporalContainer.GetGrpcAddress(), _temporalContainer.GetConnectionString()); } [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public async Task ListNamespacesReturnsDefaultNamespace() + public async Task DescribeWorkflowReturnsStartedWorkflow() { // Given var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) @@ -47,10 +44,21 @@ public async Task ListNamespacesReturnsDefaultNamespace() Namespace = "default", }).ConfigureAwait(true); + var workflowId = Guid.NewGuid().ToString("D"); + // When - var response = await client.Connection.WorkflowService.ListNamespacesAsync(new()).ConfigureAwait(true); + var workflowHandle = await client.StartWorkflowAsync("MyWorkflow", Array.Empty(), new(id: workflowId, taskQueue: "my-task-queue") + { + Memo = new Dictionary { ["env"] = "test" }, + }).ConfigureAwait(true); + + var workflowExecutionDescription = await workflowHandle.DescribeAsync().ConfigureAwait(true); // Then - Assert.Contains(response.Namespaces, ns => ns.NamespaceInfo.Name == "default"); + Assert.Equal(workflowId, workflowExecutionDescription.Id); + Assert.Equal("MyWorkflow", workflowExecutionDescription.WorkflowType); + Assert.Equal("my-task-queue", workflowExecutionDescription.TaskQueue); + Assert.True(workflowExecutionDescription.Memo.ContainsKey("env")); } + // # --8<-- [end:UseTemporalContainer] } diff --git a/tests/Testcontainers.Temporalio.Tests/TemporalContainerWorkflowTest.cs b/tests/Testcontainers.Temporalio.Tests/TemporalContainerWorkflowTest.cs deleted file mode 100644 index 790419adf..000000000 --- a/tests/Testcontainers.Temporalio.Tests/TemporalContainerWorkflowTest.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace Testcontainers.Temporalio; - -public sealed class TemporalContainerWorkflowTest : IAsyncLifetime -{ - private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()).Build(); - - public async ValueTask InitializeAsync() - { - await _temporalContainer.StartAsync() - .ConfigureAwait(false); - } - - public ValueTask DisposeAsync() - { - return _temporalContainer.DisposeAsync(); - } - - [Fact] - [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public async Task StartWorkflowThenListAndDescribe() - { - // Given - var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) - { - Namespace = "default", - }).ConfigureAwait(true); - - var workflowId = Guid.NewGuid().ToString("D"); - - // When — start a workflow (equivalent to: temporal workflow start) - var handle = await client.StartWorkflowAsync("MyWorkflow", Array.Empty(), new(id: workflowId, taskQueue: "my-task-queue")) - .ConfigureAwait(true); - - // Then — list workflows (equivalent to: temporal workflow list) - var workflows = new List(); - - await foreach (var execution in client.ListWorkflowsAsync($"WorkflowId = '{workflowId}'").ConfigureAwait(true)) - { - workflows.Add(execution); - } - - Assert.Single(workflows); - Assert.Equal(workflowId, workflows[0].Id); - Assert.Equal("MyWorkflow", workflows[0].WorkflowType); - - // Then — describe workflow (equivalent to: temporal workflow describe) - var description = await handle.DescribeAsync().ConfigureAwait(true); - - Assert.Equal(workflowId, description.Id); - Assert.Equal("MyWorkflow", description.WorkflowType); - Assert.Equal("my-task-queue", description.TaskQueue); - Assert.NotNull(description.RunId); - } -} diff --git a/tests/Testcontainers.Temporalio.Tests/Usings.cs b/tests/Testcontainers.Temporalio.Tests/Usings.cs index c381d8df8..531fdea22 100644 --- a/tests/Testcontainers.Temporalio.Tests/Usings.cs +++ b/tests/Testcontainers.Temporalio.Tests/Usings.cs @@ -1,8 +1,5 @@ global using System; global using System.Collections.Generic; -global using System.Net; -global using System.Net.Http; -global using System.Threading; global using System.Threading.Tasks; global using DotNet.Testcontainers.Commons; global using Temporalio.Client; From a0e4ab3b26eb5f2071f01366ef5dfc13727092c5 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Mon, 16 Feb 2026 12:21:00 +0100 Subject: [PATCH 3/6] chore: Align Temporalio --- src/Testcontainers.Temporalio/.editorconfig | 2 +- .../TemporalBuilder.cs | 13 ++--- .../TemporalConfiguration.cs | 2 +- .../TemporalConnectionStringProvider.cs | 2 +- .../TemporalContainer.cs | 26 +++++----- .../Testcontainers.Temporalio.csproj | 2 +- src/Testcontainers.Temporalio/Usings.cs | 2 +- .../.editorconfig | 2 +- .../Testcontainers.Temporalio.Tests/.runs-on | 2 +- .../Dockerfile | 2 +- .../TemporalContainerTest.cs | 51 ++++++++++--------- .../Testcontainers.Temporalio.Tests.csproj | 2 +- .../Testcontainers.Temporalio.Tests/Usings.cs | 3 +- 13 files changed, 59 insertions(+), 52 deletions(-) diff --git a/src/Testcontainers.Temporalio/.editorconfig b/src/Testcontainers.Temporalio/.editorconfig index 78b36ca08..6f066619d 100644 --- a/src/Testcontainers.Temporalio/.editorconfig +++ b/src/Testcontainers.Temporalio/.editorconfig @@ -1 +1 @@ -root = true +root = true \ No newline at end of file diff --git a/src/Testcontainers.Temporalio/TemporalBuilder.cs b/src/Testcontainers.Temporalio/TemporalBuilder.cs index d4178c57f..583b555bd 100644 --- a/src/Testcontainers.Temporalio/TemporalBuilder.cs +++ b/src/Testcontainers.Temporalio/TemporalBuilder.cs @@ -4,16 +4,16 @@ namespace Testcontainers.Temporalio; [PublicAPI] public sealed class TemporalBuilder : ContainerBuilder { - public const int TemporalGrpcPort = 7233; + public const ushort TemporalGrpcPort = 7233; - public const int TemporalHttpPort = 8233; + public const ushort TemporalHttpPort = 8233; /// /// Initializes a new instance of the class. /// /// /// The full Docker image name, including the image repository and tag - /// (e.g., temporalio/temporal:1.5.1, temporalio/temporal:latest). + /// (e.g., temporalio/temporal:1.5.1). /// /// /// Docker image tags available at . @@ -68,9 +68,10 @@ protected override TemporalBuilder Init() .WithCommand("server", "start-dev", "--ip", "0.0.0.0") .WithConnectionStringProvider(new TemporalConnectionStringProvider()) .WithWaitStrategy(Wait.ForUnixContainer() + .UntilExternalTcpPortIsAvailable(TemporalGrpcPort) + .UntilExternalTcpPortIsAvailable(TemporalHttpPort) .UntilHttpRequestIsSucceeded(request => - request.ForPath("/api/v1/namespaces").ForPort(TemporalHttpPort)) - .UntilInternalTcpPortIsAvailable(TemporalGrpcPort)); + request.ForPath("/api/v1/system-info").ForPort(TemporalHttpPort))); } /// @@ -90,4 +91,4 @@ protected override TemporalBuilder Merge(TemporalConfiguration oldValue, Tempora { return new TemporalBuilder(new TemporalConfiguration(oldValue, newValue)); } -} +} \ No newline at end of file diff --git a/src/Testcontainers.Temporalio/TemporalConfiguration.cs b/src/Testcontainers.Temporalio/TemporalConfiguration.cs index 616fcce63..db2919a78 100644 --- a/src/Testcontainers.Temporalio/TemporalConfiguration.cs +++ b/src/Testcontainers.Temporalio/TemporalConfiguration.cs @@ -50,4 +50,4 @@ public TemporalConfiguration(TemporalConfiguration oldValue, TemporalConfigurati : base(oldValue, newValue) { } -} +} \ No newline at end of file diff --git a/src/Testcontainers.Temporalio/TemporalConnectionStringProvider.cs b/src/Testcontainers.Temporalio/TemporalConnectionStringProvider.cs index 85df074fc..17d8b8ab4 100644 --- a/src/Testcontainers.Temporalio/TemporalConnectionStringProvider.cs +++ b/src/Testcontainers.Temporalio/TemporalConnectionStringProvider.cs @@ -10,4 +10,4 @@ protected override string GetHostConnectionString() { return Container.GetGrpcAddress(); } -} +} \ No newline at end of file diff --git a/src/Testcontainers.Temporalio/TemporalContainer.cs b/src/Testcontainers.Temporalio/TemporalContainer.cs index ca8406e9d..ae869d430 100644 --- a/src/Testcontainers.Temporalio/TemporalContainer.cs +++ b/src/Testcontainers.Temporalio/TemporalContainer.cs @@ -14,21 +14,21 @@ public TemporalContainer(TemporalConfiguration configuration) } /// - /// Gets the Temporal gRPC endpoint for SDK clients and workers. + /// Gets the Temporal gRPC address. /// /// - /// The Temporal .NET SDK expects host:port without a protocol scheme. - /// Using a URI like http://host:port will throw an . - /// - /// Usage example: - /// - /// var client = await TemporalClient.ConnectAsync( - /// new("localhost:7233") { Namespace = "default" }); - /// - /// - /// + /// The Temporal SDK (client library) expects host:port without a scheme. /// - /// The Temporal gRPC endpoint in host:port format. + /// + /// + /// var clientOptions = new TemporalClientConnectOptions(); + /// clientOptions.TargetHost = temporalContainer.GetGrpcAddress(); + ///
+ /// var connectedClient = await TemporalClient.ConnectAsync(clientOptions); + ///
+ ///
+ /// + /// The Temporal gRPC address in host:port format. public string GetGrpcAddress() { return Hostname + ":" + GetMappedPublicPort(TemporalBuilder.TemporalGrpcPort); @@ -42,4 +42,4 @@ public string GetWebUiAddress() { return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(TemporalBuilder.TemporalHttpPort)).ToString(); } -} +} \ No newline at end of file diff --git a/src/Testcontainers.Temporalio/Testcontainers.Temporalio.csproj b/src/Testcontainers.Temporalio/Testcontainers.Temporalio.csproj index 16967e2c6..6f204b739 100644 --- a/src/Testcontainers.Temporalio/Testcontainers.Temporalio.csproj +++ b/src/Testcontainers.Temporalio/Testcontainers.Temporalio.csproj @@ -9,4 +9,4 @@ - + \ No newline at end of file diff --git a/src/Testcontainers.Temporalio/Usings.cs b/src/Testcontainers.Temporalio/Usings.cs index ceba65588..26427f77f 100644 --- a/src/Testcontainers.Temporalio/Usings.cs +++ b/src/Testcontainers.Temporalio/Usings.cs @@ -4,4 +4,4 @@ global using DotNet.Testcontainers.Configurations; global using DotNet.Testcontainers.Containers; global using DotNet.Testcontainers.Images; -global using JetBrains.Annotations; +global using JetBrains.Annotations; \ No newline at end of file diff --git a/tests/Testcontainers.Temporalio.Tests/.editorconfig b/tests/Testcontainers.Temporalio.Tests/.editorconfig index 78b36ca08..6f066619d 100644 --- a/tests/Testcontainers.Temporalio.Tests/.editorconfig +++ b/tests/Testcontainers.Temporalio.Tests/.editorconfig @@ -1 +1 @@ -root = true +root = true \ No newline at end of file diff --git a/tests/Testcontainers.Temporalio.Tests/.runs-on b/tests/Testcontainers.Temporalio.Tests/.runs-on index 2c0c1ac72..d0395e498 100644 --- a/tests/Testcontainers.Temporalio.Tests/.runs-on +++ b/tests/Testcontainers.Temporalio.Tests/.runs-on @@ -1 +1 @@ -ubuntu-24.04 +ubuntu-24.04 \ No newline at end of file diff --git a/tests/Testcontainers.Temporalio.Tests/Dockerfile b/tests/Testcontainers.Temporalio.Tests/Dockerfile index cf5535adf..8a47f2a34 100644 --- a/tests/Testcontainers.Temporalio.Tests/Dockerfile +++ b/tests/Testcontainers.Temporalio.Tests/Dockerfile @@ -1 +1 @@ -FROM temporalio/temporal:1.5.1 +FROM temporalio/temporal:1.5.1 \ No newline at end of file diff --git a/tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs b/tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs index c7f47ccd2..2feb7056f 100644 --- a/tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs +++ b/tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs @@ -2,7 +2,6 @@ namespace Testcontainers.Temporalio; public sealed class TemporalContainerTest : IAsyncLifetime { - // # --8<-- [start:UseTemporalContainer] private readonly TemporalContainer _temporalContainer = new TemporalBuilder(TestSession.GetImageFromDockerfile()).Build(); public async ValueTask InitializeAsync() @@ -21,16 +20,18 @@ public ValueTask DisposeAsync() public async Task ListNamespacesReturnsDefaultNamespace() { // Given - var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) - { - Namespace = "default", - }).ConfigureAwait(true); + var clientOptions = new TemporalClientConnectOptions(); + clientOptions.TargetHost = _temporalContainer.GetGrpcAddress(); + + var connectedClient = await TemporalClient.ConnectAsync(clientOptions) + .ConfigureAwait(true); // When - var response = await client.Connection.WorkflowService.ListNamespacesAsync(new()).ConfigureAwait(true); + var response = await connectedClient.WorkflowService.ListNamespacesAsync(new ListNamespacesRequest()) + .ConfigureAwait(true); // Then - Assert.Contains(response.Namespaces, ns => ns.NamespaceInfo.Name == "default"); + Assert.Contains(response.Namespaces, ns => ns.NamespaceInfo.Name == clientOptions.Namespace); Assert.Equal(_temporalContainer.GetGrpcAddress(), _temporalContainer.GetConnectionString()); } @@ -39,26 +40,30 @@ public async Task ListNamespacesReturnsDefaultNamespace() public async Task DescribeWorkflowReturnsStartedWorkflow() { // Given - var client = await TemporalClient.ConnectAsync(new(_temporalContainer.GetGrpcAddress()) - { - Namespace = "default", - }).ConfigureAwait(true); + const string workflowType = "my-workflow"; + + var workflowOptions = new WorkflowOptions(); + workflowOptions.Id = Guid.NewGuid().ToString("D"); + workflowOptions.TaskQueue = Guid.NewGuid().ToString("D"); + workflowOptions.Memo = new Dictionary { { "env", "test" } }; + + var clientOptions = new TemporalClientConnectOptions(); + clientOptions.TargetHost = _temporalContainer.GetGrpcAddress(); - var workflowId = Guid.NewGuid().ToString("D"); + var connectedClient = await TemporalClient.ConnectAsync(clientOptions) + .ConfigureAwait(true); // When - var workflowHandle = await client.StartWorkflowAsync("MyWorkflow", Array.Empty(), new(id: workflowId, taskQueue: "my-task-queue") - { - Memo = new Dictionary { ["env"] = "test" }, - }).ConfigureAwait(true); + var runningWorkflow = await connectedClient.StartWorkflowAsync(workflowType, Array.Empty(), workflowOptions) + .ConfigureAwait(true); - var workflowExecutionDescription = await workflowHandle.DescribeAsync().ConfigureAwait(true); + var workflowDescription = await runningWorkflow.DescribeAsync() + .ConfigureAwait(true); // Then - Assert.Equal(workflowId, workflowExecutionDescription.Id); - Assert.Equal("MyWorkflow", workflowExecutionDescription.WorkflowType); - Assert.Equal("my-task-queue", workflowExecutionDescription.TaskQueue); - Assert.True(workflowExecutionDescription.Memo.ContainsKey("env")); + Assert.Equal(workflowType, workflowDescription.WorkflowType); + Assert.Equal(workflowOptions.Id, workflowDescription.Id); + Assert.Equal(workflowOptions.TaskQueue, workflowDescription.TaskQueue); + Assert.True(workflowDescription.Memo.ContainsKey("env")); } - // # --8<-- [end:UseTemporalContainer] -} +} \ No newline at end of file diff --git a/tests/Testcontainers.Temporalio.Tests/Testcontainers.Temporalio.Tests.csproj b/tests/Testcontainers.Temporalio.Tests/Testcontainers.Temporalio.Tests.csproj index 75366c5d6..315d81b85 100644 --- a/tests/Testcontainers.Temporalio.Tests/Testcontainers.Temporalio.Tests.csproj +++ b/tests/Testcontainers.Temporalio.Tests/Testcontainers.Temporalio.Tests.csproj @@ -21,4 +21,4 @@ PreserveNewest - + \ No newline at end of file diff --git a/tests/Testcontainers.Temporalio.Tests/Usings.cs b/tests/Testcontainers.Temporalio.Tests/Usings.cs index 531fdea22..ef18e4163 100644 --- a/tests/Testcontainers.Temporalio.Tests/Usings.cs +++ b/tests/Testcontainers.Temporalio.Tests/Usings.cs @@ -2,5 +2,6 @@ global using System.Collections.Generic; global using System.Threading.Tasks; global using DotNet.Testcontainers.Commons; +global using Temporalio.Api.WorkflowService.V1; global using Temporalio.Client; -global using Xunit; +global using Xunit; \ No newline at end of file From d93cf6822efd6086a9deebabbf519d13e8b7b656 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:37:32 +0100 Subject: [PATCH 4/6] chore: Rename Temporalio to Temporal --- Testcontainers.sln | 6 +++--- .../.editorconfig | 0 .../TemporalBuilder.cs | 0 .../TemporalConfiguration.cs | 0 .../TemporalConnectionStringProvider.cs | 0 .../TemporalContainer.cs | 0 .../Testcontainers.Temporal.csproj} | 0 .../Usings.cs | 0 .../.editorconfig | 0 .../.runs-on | 0 .../Dockerfile | 0 .../TemporalContainerTest.cs | 0 .../Testcontainers.Temporal.Tests.csproj} | 2 +- .../Usings.cs | 0 14 files changed, 4 insertions(+), 4 deletions(-) rename src/{Testcontainers.Temporalio => Testcontainers.Temporal}/.editorconfig (100%) rename src/{Testcontainers.Temporalio => Testcontainers.Temporal}/TemporalBuilder.cs (100%) rename src/{Testcontainers.Temporalio => Testcontainers.Temporal}/TemporalConfiguration.cs (100%) rename src/{Testcontainers.Temporalio => Testcontainers.Temporal}/TemporalConnectionStringProvider.cs (100%) rename src/{Testcontainers.Temporalio => Testcontainers.Temporal}/TemporalContainer.cs (100%) rename src/{Testcontainers.Temporalio/Testcontainers.Temporalio.csproj => Testcontainers.Temporal/Testcontainers.Temporal.csproj} (100%) rename src/{Testcontainers.Temporalio => Testcontainers.Temporal}/Usings.cs (100%) rename tests/{Testcontainers.Temporalio.Tests => Testcontainers.Temporal.Tests}/.editorconfig (100%) rename tests/{Testcontainers.Temporalio.Tests => Testcontainers.Temporal.Tests}/.runs-on (100%) rename tests/{Testcontainers.Temporalio.Tests => Testcontainers.Temporal.Tests}/Dockerfile (100%) rename tests/{Testcontainers.Temporalio.Tests => Testcontainers.Temporal.Tests}/TemporalContainerTest.cs (100%) rename tests/{Testcontainers.Temporalio.Tests/Testcontainers.Temporalio.Tests.csproj => Testcontainers.Temporal.Tests/Testcontainers.Temporal.Tests.csproj} (95%) rename tests/{Testcontainers.Temporalio.Tests => Testcontainers.Temporal.Tests}/Usings.cs (100%) diff --git a/Testcontainers.sln b/Testcontainers.sln index 1143fed3e..a06539918 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 @@ -128,7 +128,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ServiceBus", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Sftp", "src\Testcontainers.Sftp\Testcontainers.Sftp.csproj", "{7D5C6816-0DD2-4E13-A585-033B5D3C80D5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Temporalio", "src\Testcontainers.Temporalio\Testcontainers.Temporalio.csproj", "{24431BF1-7BEB-4C53-BAE8-B9D9F622A240}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Temporal", "src\Testcontainers.Temporal\Testcontainers.Temporal.csproj", "{24431BF1-7BEB-4C53-BAE8-B9D9F622A240}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Toxiproxy", "src\Testcontainers.Toxiproxy\Testcontainers.Toxiproxy.csproj", "{65A47BA4-4DC8-4206-9B00-CBC87FC944FC}" EndProject @@ -268,7 +268,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ServiceBus.T EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Sftp.Tests", "tests\Testcontainers.Sftp.Tests\Testcontainers.Sftp.Tests.csproj", "{B73C3CC0-9F16-4B34-92BE-6EC0853912C5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Temporalio.Tests", "tests\Testcontainers.Temporalio.Tests\Testcontainers.Temporalio.Tests.csproj", "{28B5DEDF-C19B-4A7E-B276-FC4C83DBB7EF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Temporal.Tests", "tests\Testcontainers.Temporal.Tests\Testcontainers.Temporal.Tests.csproj", "{28B5DEDF-C19B-4A7E-B276-FC4C83DBB7EF}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tests", "tests\Testcontainers.Tests\Testcontainers.Tests.csproj", "{27CDB869-A150-4593-958F-6F26E5391E7C}" EndProject diff --git a/src/Testcontainers.Temporalio/.editorconfig b/src/Testcontainers.Temporal/.editorconfig similarity index 100% rename from src/Testcontainers.Temporalio/.editorconfig rename to src/Testcontainers.Temporal/.editorconfig diff --git a/src/Testcontainers.Temporalio/TemporalBuilder.cs b/src/Testcontainers.Temporal/TemporalBuilder.cs similarity index 100% rename from src/Testcontainers.Temporalio/TemporalBuilder.cs rename to src/Testcontainers.Temporal/TemporalBuilder.cs diff --git a/src/Testcontainers.Temporalio/TemporalConfiguration.cs b/src/Testcontainers.Temporal/TemporalConfiguration.cs similarity index 100% rename from src/Testcontainers.Temporalio/TemporalConfiguration.cs rename to src/Testcontainers.Temporal/TemporalConfiguration.cs diff --git a/src/Testcontainers.Temporalio/TemporalConnectionStringProvider.cs b/src/Testcontainers.Temporal/TemporalConnectionStringProvider.cs similarity index 100% rename from src/Testcontainers.Temporalio/TemporalConnectionStringProvider.cs rename to src/Testcontainers.Temporal/TemporalConnectionStringProvider.cs diff --git a/src/Testcontainers.Temporalio/TemporalContainer.cs b/src/Testcontainers.Temporal/TemporalContainer.cs similarity index 100% rename from src/Testcontainers.Temporalio/TemporalContainer.cs rename to src/Testcontainers.Temporal/TemporalContainer.cs diff --git a/src/Testcontainers.Temporalio/Testcontainers.Temporalio.csproj b/src/Testcontainers.Temporal/Testcontainers.Temporal.csproj similarity index 100% rename from src/Testcontainers.Temporalio/Testcontainers.Temporalio.csproj rename to src/Testcontainers.Temporal/Testcontainers.Temporal.csproj diff --git a/src/Testcontainers.Temporalio/Usings.cs b/src/Testcontainers.Temporal/Usings.cs similarity index 100% rename from src/Testcontainers.Temporalio/Usings.cs rename to src/Testcontainers.Temporal/Usings.cs diff --git a/tests/Testcontainers.Temporalio.Tests/.editorconfig b/tests/Testcontainers.Temporal.Tests/.editorconfig similarity index 100% rename from tests/Testcontainers.Temporalio.Tests/.editorconfig rename to tests/Testcontainers.Temporal.Tests/.editorconfig diff --git a/tests/Testcontainers.Temporalio.Tests/.runs-on b/tests/Testcontainers.Temporal.Tests/.runs-on similarity index 100% rename from tests/Testcontainers.Temporalio.Tests/.runs-on rename to tests/Testcontainers.Temporal.Tests/.runs-on diff --git a/tests/Testcontainers.Temporalio.Tests/Dockerfile b/tests/Testcontainers.Temporal.Tests/Dockerfile similarity index 100% rename from tests/Testcontainers.Temporalio.Tests/Dockerfile rename to tests/Testcontainers.Temporal.Tests/Dockerfile diff --git a/tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs b/tests/Testcontainers.Temporal.Tests/TemporalContainerTest.cs similarity index 100% rename from tests/Testcontainers.Temporalio.Tests/TemporalContainerTest.cs rename to tests/Testcontainers.Temporal.Tests/TemporalContainerTest.cs diff --git a/tests/Testcontainers.Temporalio.Tests/Testcontainers.Temporalio.Tests.csproj b/tests/Testcontainers.Temporal.Tests/Testcontainers.Temporal.Tests.csproj similarity index 95% rename from tests/Testcontainers.Temporalio.Tests/Testcontainers.Temporalio.Tests.csproj rename to tests/Testcontainers.Temporal.Tests/Testcontainers.Temporal.Tests.csproj index 315d81b85..9322f65c0 100644 --- a/tests/Testcontainers.Temporalio.Tests/Testcontainers.Temporalio.Tests.csproj +++ b/tests/Testcontainers.Temporal.Tests/Testcontainers.Temporal.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/tests/Testcontainers.Temporalio.Tests/Usings.cs b/tests/Testcontainers.Temporal.Tests/Usings.cs similarity index 100% rename from tests/Testcontainers.Temporalio.Tests/Usings.cs rename to tests/Testcontainers.Temporal.Tests/Usings.cs From 9a806ca12a86ccf33540a3429f8f103f370c55fe Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:40:36 +0100 Subject: [PATCH 5/6] chore: Rename ns to Testcontainers.Temporal --- src/Testcontainers.Temporal/TemporalBuilder.cs | 2 +- src/Testcontainers.Temporal/TemporalConfiguration.cs | 2 +- src/Testcontainers.Temporal/TemporalConnectionStringProvider.cs | 2 +- src/Testcontainers.Temporal/TemporalContainer.cs | 2 +- tests/Testcontainers.Temporal.Tests/TemporalContainerTest.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Testcontainers.Temporal/TemporalBuilder.cs b/src/Testcontainers.Temporal/TemporalBuilder.cs index 583b555bd..fd8fc41ae 100644 --- a/src/Testcontainers.Temporal/TemporalBuilder.cs +++ b/src/Testcontainers.Temporal/TemporalBuilder.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.Temporalio; +namespace Testcontainers.Temporal; /// [PublicAPI] diff --git a/src/Testcontainers.Temporal/TemporalConfiguration.cs b/src/Testcontainers.Temporal/TemporalConfiguration.cs index db2919a78..b9b9051c0 100644 --- a/src/Testcontainers.Temporal/TemporalConfiguration.cs +++ b/src/Testcontainers.Temporal/TemporalConfiguration.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.Temporalio; +namespace Testcontainers.Temporal; /// [PublicAPI] diff --git a/src/Testcontainers.Temporal/TemporalConnectionStringProvider.cs b/src/Testcontainers.Temporal/TemporalConnectionStringProvider.cs index 17d8b8ab4..c48bd57e9 100644 --- a/src/Testcontainers.Temporal/TemporalConnectionStringProvider.cs +++ b/src/Testcontainers.Temporal/TemporalConnectionStringProvider.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.Temporalio; +namespace Testcontainers.Temporal; /// /// Provides the Temporal connection string. diff --git a/src/Testcontainers.Temporal/TemporalContainer.cs b/src/Testcontainers.Temporal/TemporalContainer.cs index ae869d430..64336dc44 100644 --- a/src/Testcontainers.Temporal/TemporalContainer.cs +++ b/src/Testcontainers.Temporal/TemporalContainer.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.Temporalio; +namespace Testcontainers.Temporal; /// [PublicAPI] diff --git a/tests/Testcontainers.Temporal.Tests/TemporalContainerTest.cs b/tests/Testcontainers.Temporal.Tests/TemporalContainerTest.cs index 2feb7056f..c705bf1c3 100644 --- a/tests/Testcontainers.Temporal.Tests/TemporalContainerTest.cs +++ b/tests/Testcontainers.Temporal.Tests/TemporalContainerTest.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.Temporalio; +namespace Testcontainers.Temporal; public sealed class TemporalContainerTest : IAsyncLifetime { From 1c272aa7394ba21b7238dc4e8b71cfeba5fb14df Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:46:29 +0100 Subject: [PATCH 6/6] chore: Remove BOM --- Testcontainers.sln | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Testcontainers.sln b/Testcontainers.sln index a06539918..9a148f2ad 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1