Skip to content

Commit db16f85

Browse files
feat: Add PubSub module (#1005)
Co-authored-by: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
1 parent 44a1d3a commit db16f85

11 files changed

+277
-0
lines changed

Testcontainers.sln

+14
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle", "sr
6161
EndProject
6262
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PostgreSql", "src\Testcontainers.PostgreSql\Testcontainers.PostgreSql.csproj", "{8AB91636-9055-4900-A72A-7CFFACDFDBF0}"
6363
EndProject
64+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PubSub", "src\Testcontainers.PubSub\Testcontainers.PubSub.csproj", "{E6642255-667D-476B-B584-089AA5E6C0B1}"
65+
EndProject
6466
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.RabbitMq", "src\Testcontainers.RabbitMq\Testcontainers.RabbitMq.csproj", "{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE}"
6567
EndProject
6668
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.RavenDb", "src\Testcontainers.RavenDb\Testcontainers.RavenDb.csproj", "{F6394475-D6F1-46E2-81BF-4BA78A40B878}"
@@ -131,6 +133,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Platform.Win
131133
EndProject
132134
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PostgreSql.Tests", "tests\Testcontainers.PostgreSql.Tests\Testcontainers.PostgreSql.Tests.csproj", "{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6}"
133135
EndProject
136+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PubSub.Tests", "tests\Testcontainers.PubSub.Tests\Testcontainers.PubSub.Tests.csproj", "{0F86BCE8-62E1-4BFC-AA84-63C7514C90AC}"
137+
EndProject
134138
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.RabbitMq.Tests", "tests\Testcontainers.RabbitMq.Tests\Testcontainers.RabbitMq.Tests.csproj", "{19564567-1736-4626-B406-17E4E02F18B2}"
135139
EndProject
136140
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.RavenDb.Tests", "tests\Testcontainers.RavenDb.Tests\Testcontainers.RavenDb.Tests.csproj", "{D53726B6-5447-47E6-B881-A44EFF6E5534}"
@@ -252,6 +256,10 @@ Global
252256
{8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
253257
{8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
254258
{8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Release|Any CPU.Build.0 = Release|Any CPU
259+
{E6642255-667D-476B-B584-089AA5E6C0B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
260+
{E6642255-667D-476B-B584-089AA5E6C0B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
261+
{E6642255-667D-476B-B584-089AA5E6C0B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
262+
{E6642255-667D-476B-B584-089AA5E6C0B1}.Release|Any CPU.Build.0 = Release|Any CPU
255263
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
256264
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
257265
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -392,6 +400,10 @@ Global
392400
{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
393401
{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
394402
{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6}.Release|Any CPU.Build.0 = Release|Any CPU
403+
{0F86BCE8-62E1-4BFC-AA84-63C7514C90AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
404+
{0F86BCE8-62E1-4BFC-AA84-63C7514C90AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
405+
{0F86BCE8-62E1-4BFC-AA84-63C7514C90AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
406+
{0F86BCE8-62E1-4BFC-AA84-63C7514C90AC}.Release|Any CPU.Build.0 = Release|Any CPU
395407
{19564567-1736-4626-B406-17E4E02F18B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
396408
{19564567-1736-4626-B406-17E4E02F18B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
397409
{19564567-1736-4626-B406-17E4E02F18B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -450,6 +462,7 @@ Global
450462
{ADC2372B-6FE0-421D-8277-BB628E8EFC22} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
451463
{596EAFC1-0496-495C-B382-D57415FA456A} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
452464
{8AB91636-9055-4900-A72A-7CFFACDFDBF0} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
465+
{E6642255-667D-476B-B584-089AA5E6C0B1} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
453466
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
454467
{F6394475-D6F1-46E2-81BF-4BA78A40B878} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
455468
{BFDA179A-40EB-4CEB-B8E9-0DF32C65E2C5} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
@@ -485,6 +498,7 @@ Global
485498
{DA1D7ADE-452C-4369-83CC-56289176EACD} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
486499
{3E55CBE8-AFE8-426D-9470-49D63CD1051C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
487500
{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
501+
{0F86BCE8-62E1-4BFC-AA84-63C7514C90AC} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
488502
{19564567-1736-4626-B406-17E4E02F18B2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
489503
{D53726B6-5447-47E6-B881-A44EFF6E5534} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
490504
{31EE94A0-E721-4073-B6F1-DD912D004DEF} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
root = true
+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
namespace Testcontainers.PubSub;
2+
3+
/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
4+
[PublicAPI]
5+
public sealed class PubSubBuilder : ContainerBuilder<PubSubBuilder, PubSubContainer, PubSubConfiguration>
6+
{
7+
public const string GoogleCloudCliImage = "gcr.io/google.com/cloudsdktool/google-cloud-cli:446.0.1-emulators";
8+
9+
public const ushort PubSubPort = 8085;
10+
11+
/// <summary>
12+
/// Initializes a new instance of the <see cref="PubSubBuilder" /> class.
13+
/// </summary>
14+
public PubSubBuilder()
15+
: this(new PubSubConfiguration())
16+
{
17+
DockerResourceConfiguration = Init().DockerResourceConfiguration;
18+
}
19+
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="PubSubBuilder" /> class.
22+
/// </summary>
23+
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
24+
private PubSubBuilder(PubSubConfiguration resourceConfiguration)
25+
: base(resourceConfiguration)
26+
{
27+
DockerResourceConfiguration = resourceConfiguration;
28+
}
29+
30+
/// <inheritdoc />
31+
protected override PubSubConfiguration DockerResourceConfiguration { get; }
32+
33+
/// <inheritdoc />
34+
public override PubSubContainer Build()
35+
{
36+
Validate();
37+
return new PubSubContainer(DockerResourceConfiguration, TestcontainersSettings.Logger);
38+
}
39+
40+
/// <inheritdoc />
41+
protected override PubSubBuilder Init()
42+
{
43+
return base.Init()
44+
.WithImage(GoogleCloudCliImage)
45+
.WithPortBinding(PubSubPort, true)
46+
.WithEntrypoint("gcloud")
47+
.WithCommand("beta", "emulators", "pubsub", "start", "--host-port", "0.0.0.0:" + PubSubPort)
48+
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("(?s).*started.*$"));
49+
}
50+
51+
/// <inheritdoc />
52+
protected override PubSubBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
53+
{
54+
return Merge(DockerResourceConfiguration, new PubSubConfiguration(resourceConfiguration));
55+
}
56+
57+
/// <inheritdoc />
58+
protected override PubSubBuilder Clone(IContainerConfiguration resourceConfiguration)
59+
{
60+
return Merge(DockerResourceConfiguration, new PubSubConfiguration(resourceConfiguration));
61+
}
62+
63+
/// <inheritdoc />
64+
protected override PubSubBuilder Merge(PubSubConfiguration oldValue, PubSubConfiguration newValue)
65+
{
66+
return new PubSubBuilder(new PubSubConfiguration(oldValue, newValue));
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
namespace Testcontainers.PubSub;
2+
3+
/// <inheritdoc cref="ContainerConfiguration" />
4+
[PublicAPI]
5+
public sealed class PubSubConfiguration : ContainerConfiguration
6+
{
7+
/// <summary>
8+
/// Initializes a new instance of the <see cref="PubSubConfiguration" /> class.
9+
/// </summary>
10+
public PubSubConfiguration()
11+
{
12+
}
13+
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="PubSubConfiguration" /> class.
16+
/// </summary>
17+
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
18+
public PubSubConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
19+
: base(resourceConfiguration)
20+
{
21+
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
22+
}
23+
24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="PubSubConfiguration" /> class.
26+
/// </summary>
27+
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
28+
public PubSubConfiguration(IContainerConfiguration resourceConfiguration)
29+
: base(resourceConfiguration)
30+
{
31+
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
32+
}
33+
34+
/// <summary>
35+
/// Initializes a new instance of the <see cref="PubSubConfiguration" /> class.
36+
/// </summary>
37+
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
38+
public PubSubConfiguration(PubSubConfiguration resourceConfiguration)
39+
: this(new PubSubConfiguration(), resourceConfiguration)
40+
{
41+
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
42+
}
43+
44+
/// <summary>
45+
/// Initializes a new instance of the <see cref="PubSubConfiguration" /> class.
46+
/// </summary>
47+
/// <param name="oldValue">The old Docker resource configuration.</param>
48+
/// <param name="newValue">The new Docker resource configuration.</param>
49+
public PubSubConfiguration(PubSubConfiguration oldValue, PubSubConfiguration newValue)
50+
: base(oldValue, newValue)
51+
{
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace Testcontainers.PubSub;
2+
3+
/// <inheritdoc cref="DockerContainer" />
4+
[PublicAPI]
5+
public sealed class PubSubContainer : DockerContainer
6+
{
7+
/// <summary>
8+
/// Initializes a new instance of the <see cref="PubSubContainer" /> class.
9+
/// </summary>
10+
/// <param name="configuration">The container configuration.</param>
11+
/// <param name="logger">The logger.</param>
12+
public PubSubContainer(PubSubConfiguration configuration, ILogger logger)
13+
: base(configuration, logger)
14+
{
15+
}
16+
17+
/// <summary>
18+
/// Gets the PubSub emulator endpoint.
19+
/// </summary>
20+
/// <returns>The PubSub emulator endpoint.</returns>
21+
public string GetEmulatorEndpoint()
22+
{
23+
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(PubSubBuilder.PubSubPort)).ToString();
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
4+
<LangVersion>latest</LangVersion>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
8+
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" PrivateAssets="All"/>
9+
</ItemGroup>
10+
<ItemGroup>
11+
<ProjectReference Include="$(SolutionDir)src/Testcontainers/Testcontainers.csproj"/>
12+
</ItemGroup>
13+
</Project>

src/Testcontainers.PubSub/Usings.cs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
global using System;
2+
global using Docker.DotNet.Models;
3+
global using DotNet.Testcontainers.Builders;
4+
global using DotNet.Testcontainers.Configurations;
5+
global using DotNet.Testcontainers.Containers;
6+
global using JetBrains.Annotations;
7+
global using Microsoft.Extensions.Logging;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
root = true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
namespace Testcontainers.PubSub;
2+
3+
public sealed class PubSubContainerTests : IAsyncLifetime
4+
{
5+
private readonly PubSubContainer _pubSubContainer = new PubSubBuilder().Build();
6+
7+
public Task InitializeAsync()
8+
{
9+
return _pubSubContainer.StartAsync();
10+
}
11+
12+
public Task DisposeAsync()
13+
{
14+
return _pubSubContainer.DisposeAsync().AsTask();
15+
}
16+
17+
[Fact]
18+
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
19+
public async Task SubTopicReturnsPubMessage()
20+
{
21+
// Given
22+
const string helloPubSub = "Hello, PubSub!";
23+
24+
const string projectId = "hello-pub-sub";
25+
26+
const string topicId = "hello-topic";
27+
28+
const string subscriptionId = "hello-subscription";
29+
30+
var topicName = new TopicName(projectId, topicId);
31+
32+
var subscriptionName = new SubscriptionName(projectId, subscriptionId);
33+
34+
var message = new PubsubMessage();
35+
message.Data = ByteString.CopyFromUtf8(helloPubSub);
36+
37+
var publisherClientBuilder = new PublisherServiceApiClientBuilder();
38+
publisherClientBuilder.Endpoint = _pubSubContainer.GetEmulatorEndpoint();
39+
publisherClientBuilder.ChannelCredentials = ChannelCredentials.Insecure;
40+
41+
var subscriberClientBuilder = new SubscriberServiceApiClientBuilder();
42+
subscriberClientBuilder.Endpoint = _pubSubContainer.GetEmulatorEndpoint();
43+
subscriberClientBuilder.ChannelCredentials = ChannelCredentials.Insecure;
44+
45+
// When
46+
var publisher = await publisherClientBuilder.BuildAsync()
47+
.ConfigureAwait(false);
48+
49+
_ = await publisher.CreateTopicAsync(topicName)
50+
.ConfigureAwait(false);
51+
52+
var subscriber = await subscriberClientBuilder.BuildAsync()
53+
.ConfigureAwait(false);
54+
55+
_ = await subscriber.CreateSubscriptionAsync(subscriptionName, topicName, null, 60)
56+
.ConfigureAwait(false);
57+
58+
_ = await publisher.PublishAsync(topicName, new[] { message })
59+
.ConfigureAwait(false);
60+
61+
var response = await subscriber.PullAsync(subscriptionName, 1)
62+
.ConfigureAwait(false);
63+
64+
await subscriber.AcknowledgeAsync(subscriptionName, response.ReceivedMessages.Select(receivedMessage => receivedMessage.AckId))
65+
.ConfigureAwait(false);
66+
67+
// Then
68+
Assert.Equal(helloPubSub, response.ReceivedMessages.Single().Message.Data.ToStringUtf8());
69+
}
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFrameworks>net6.0</TargetFrameworks>
4+
<IsPackable>false</IsPackable>
5+
<IsPublishable>false</IsPublishable>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2"/>
9+
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
10+
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0"/>
11+
<PackageReference Include="xunit" Version="2.5.0"/>
12+
<PackageReference Include="Google.Cloud.PubSub.V1" Version="3.5.0"/>
13+
</ItemGroup>
14+
<ItemGroup>
15+
<ProjectReference Include="$(SolutionDir)src/Testcontainers.PubSub/Testcontainers.PubSub.csproj"/>
16+
<ProjectReference Include="$(SolutionDir)tests/Testcontainers.Commons/Testcontainers.Commons.csproj"/>
17+
</ItemGroup>
18+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
global using System.Linq;
2+
global using System.Threading.Tasks;
3+
global using DotNet.Testcontainers.Commons;
4+
global using Google.Cloud.PubSub.V1;
5+
global using Google.Protobuf;
6+
global using Grpc.Core;
7+
global using Xunit;

0 commit comments

Comments
 (0)