Skip to content

Commit 109d930

Browse files
feat: Add KAFKA mocking (#13)
* ci: disable sonar for fork Signed-off-by: SebastienDegodez <sebastien.degodez@gmail.com> * chore: ignore .idea Signed-off-by: SebastienDegodez <sebastien.degodez@gmail.com> * chore(deps): bump FluentAssertions from 6.12.0 to 7.0.0 Bumps [FluentAssertions](https://github.com/fluentassertions/fluentassertions) from 6.12.0 to 7.0.0. - [Release notes](https://github.com/fluentassertions/fluentassertions/releases) - [Changelog](https://github.com/fluentassertions/fluentassertions/blob/develop/AcceptApiChanges.ps1) - [Commits](fluentassertions/fluentassertions@6.12.0...7.0.0) --- updated-dependencies: - dependency-name: FluentAssertions dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): Bump xunit from 2.9.0 to 2.9.2 Bumps [xunit](https://github.com/xunit/xunit) from 2.9.0 to 2.9.2. - [Commits](xunit/xunit@v2-2.9.0...v2-2.9.2) --- updated-dependencies: - dependency-name: xunit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> * chore(deps): bump RestAssured.Net from 4.4.0 to 4.5.1 Bumps [RestAssured.Net](https://github.com/basdijkstra/rest-assured-net) from 4.4.0 to 4.5.1. - [Changelog](https://github.com/basdijkstra/rest-assured-net/blob/main/CHANGELOG.md) - [Commits](basdijkstra/rest-assured-net@rest-assured-net-4.4.0...rest-assured-net-4.5.1) --- updated-dependencies: - dependency-name: RestAssured.Net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> * chore: update RestAssured logging configuration in tests (obsolete) Signed-off-by: SebastienDegodez <sebastien.degodez@gmail.com> * feat: add Kafka functionality test with TestContainers Signed-off-by: SebastienDegodez <sebastien.degodez@gmail.com> * docs: Advanced Features with MicrocksContainerEnsemble Signed-off-by: SebastienDegodez <sebastien.degodez@gmail.com> * refactor: adjust cancellation token handling in WaitForConditionAsync Signed-off-by: SebastienDegodez <sebastien.degodez@gmail.com> * ci: add conditional check for pull request in CI workflow Signed-off-by: SebastienDegodez <sebastien.degodez@gmail.com> --------- Signed-off-by: SebastienDegodez <sebastien.degodez@gmail.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent 04f2fb6 commit 109d930

25 files changed

+1224
-22
lines changed

.github/workflows/cicd.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ jobs:
6060
uses: ./.github/workflows/steps.publish-test-reporter.yml
6161
with:
6262
runs-on: ubuntu-latest
63+
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event_name == 'pull_request' }}
6364
secrets: inherit
6465

6566
nuget_publish:

.github/workflows/steps.dotnet-build-test.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,16 @@ jobs:
5555
run: dotnet tool install --global dotnet-sonarscanner
5656

5757
- name: 🔧 Restore .NET Tools
58-
run: dotnet tool restore
59-
58+
run: dotnet tool restore
59+
6060
- name: 🔧 Restore dependencies
6161
run: dotnet restore
6262

6363
- name: 🔍 Start SonarQube Analysis
64-
if: ${{ inputs.use-sonarcloud == true }}
64+
# Only run SonarCloud analysis only for the original repository and not for forks (either on push or PR from the same repo)
65+
if: ${{ inputs.use-sonarcloud == true &&
66+
( github.repository == 'microcks/microcks-testcontainers-dotnet') &&
67+
( github.event_name != 'pull_request' || ( github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') ) }}
6568
env:
6669
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
6770
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
@@ -99,7 +102,11 @@ jobs:
99102
shell: pwsh
100103

101104
- name: Stop SonarQube Analysis
102-
if: ${{ inputs.use-sonarcloud == true && (success() || steps.test-with-coverage.conclusion == 'failure') }}
105+
# Only run SonarCloud analysis only for the original repository and not for forks (either on push or PR from the same repo)
106+
if: ${{ inputs.use-sonarcloud == true &&
107+
( success() || steps.test-with-coverage.conclusion == 'failure' ) &&
108+
( github.repository == 'microcks/microcks-testcontainers-dotnet' ) &&
109+
( github.event_name != 'pull_request' || ( github.event.pull_request.head.repo.full_name == github.repository ) ) }}
103110
id: sonar
104111
env:
105112
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ FodyWeavers.xsd
395395
*.msix
396396
*.msm
397397
*.msp
398+
*.sln.iml
398399

399400
# JetBrains Rider
400-
*.sln.iml
401+
.idea

README.md

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ await container.StartAsync();
6666

6767
### Import content in Microcks
6868

69-
To use Microcks mocks or contract-testing features, you first need to import OpenAPI, Postman Collection, GraphQL or gRPC artifacts.
69+
To use Microcks mocks or contract-testing features, you first need to import OpenAPI, Postman Collection, GraphQL or gRPC artifacts.
7070
Artifacts can be imported as main/Primary ones or as secondary ones. See [Multi-artifacts support](https://microcks.io/documentation/using/importers/#multi-artifacts-support) for details.
7171

7272
You can do it before starting the container using simple paths:
@@ -97,7 +97,7 @@ await container.StartAsync();
9797

9898
### Using mock endpoints for your dependencies
9999

100-
During your test setup, you'd probably need to retrieve mock endpoints provided by Microcks containers to
100+
During your test setup, you'd probably need to retrieve mock endpoints provided by Microcks containers to
101101
setup your base API url calls. You can do it like this:
102102

103103
```csharp
@@ -141,3 +141,69 @@ public async Task testOpenAPIContract()
141141
```
142142

143143
The `TestResult` gives you access to all details regarding success of failure on different test cases.
144+
145+
### Advanced features with MicrocksContainersEnsemble
146+
147+
The `MicrocksContainer` referenced above supports essential features of Microcks provided by the main Microcks container.
148+
The list of supported features is the following:
149+
150+
* Mocking of REST APIs using different kinds of artifacts,
151+
* Contract-testing of REST APIs using `OPEN_API_SCHEMA` runner/strategy,
152+
* Mocking and contract-testing of SOAP WebServices,
153+
* Mocking and contract-testing of GraphQL APIs,
154+
* Mocking and contract-testing of gRPC APIs.
155+
156+
To support features like Asynchronous contract-testing, we introduced `MicrocksContainersEnsemble` that allows managing
157+
additional Microcks services. `MicrocksContainersEnsemble` allow you to implement
158+
[Different levels of API contract testing](https://medium.com/@lbroudoux/different-levels-of-api-contract-testing-with-microcks-ccc0847f8c97)
159+
in the Inner Loop with Testcontainers!
160+
161+
A `MicrocksContainersEnsemble` presents the same methods as a `MicrocksContainer`.
162+
You can create and build an ensemble that way:
163+
164+
```csharp
165+
MicrocksContainersEnsemble ensemble = new MicrocksContainerEnsemble(network, MicrocksImage)
166+
.WithMainArtifacts("pastry-orders-asyncapi.yml")
167+
.WithKafkaConnection(new KafkaConnection($"kafka:19092"));
168+
169+
ensemble.StartAsync();
170+
```
171+
172+
A `MicrocksContainer` is wrapped by an ensemble and is still available to import artifacts and execute test methods.
173+
You have to access it using:
174+
175+
```csharp
176+
MicrocksContainer microcks = ensemble.MicrocksContainer;
177+
microcks.ImportAsMainArtifact(...);
178+
```
179+
##### Using mock endpoints for your dependencies
180+
181+
Once started, the `ensemble.AsyncMinionContainer` provides methods for retrieving mock endpoint names for the different
182+
supported protocols (Kafka only at the moment).
183+
184+
```csharp
185+
string kafkaTopic = ensemble.AsyncMinionContainer
186+
.GetKafkaMockTopic("Pastry orders API", "0.1.0", "SUBSCRIBE pastry/orders");
187+
```
188+
##### Launching new contract-tests
189+
190+
Using contract-testing techniques on Asynchronous endpoints may require a different style of interacting with the Microcks
191+
container. For example, you may need to:
192+
1. Start the test making Microcks listen to the target async endpoint,
193+
2. Activate your System Under Tests so that it produces an event,
194+
3. Finalize the Microcks tests and actually ensure you received one or many well-formed events.
195+
196+
For that the `MicrocksContainer` now provides a `TestEndpointAsync(TestRequest request)` method that actually returns a `Task<TestResult>`.
197+
Once invoked, you may trigger your application events and then `await` the future result to assert like this:
198+
199+
```csharp
200+
// Start the test, making Microcks listen the endpoint provided in testRequest
201+
Task<TestResult> testResultFuture = ensemble.MicrocksContainer.TestEndpointAsync(testRequest);
202+
203+
// Here below: activate your app to make it produce events on this endpoint.
204+
// myapp.InvokeBusinessMethodThatTriggerEvents();
205+
206+
// Now retrieve the final test result and assert.
207+
TestResult testResult = await testResultFuture;
208+
testResult.IsSuccess.Should().BeTrue();
209+
```

microcks-testcontainers-dotnet.sln

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
77
ProjectSection(SolutionItems) = preProject
88
.editorconfig = .editorconfig
99
global.json = global.json
10+
README.md = README.md
1011
EndProjectSection
1112
EndProject
1213
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6B414F17-C857-4B37-B48F-4913F7E083CD}"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// Copyright The Microcks Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License")
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
//
17+
18+
namespace Microcks.Testcontainers.Connection;
19+
20+
/// <summary>
21+
/// Represents a connection to a Kafka broker.
22+
/// </summary>
23+
public class KafkaConnection
24+
{
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="KafkaConnection"/> class with the specified bootstrap servers.
27+
/// </summary>
28+
/// <param name="bootstrapServers">The Kafka bootstrap servers.</param>
29+
public KafkaConnection(string bootstrapServers)
30+
{
31+
this.BootstrapServers = bootstrapServers;
32+
}
33+
34+
/// <summary>
35+
/// Gets the Kafka bootstrap servers.
36+
/// </summary>
37+
public string BootstrapServers { get; }
38+
}

src/Microcks.Testcontainers/Microcks.Testcontainers.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
</PropertyGroup>
66
<ItemGroup>
77
<PackageReference Include="Testcontainers" Version="4.0.0" />
8+
<PackageReference Include="System.Net.Http.Json" Version="8.0.1" />
89
</ItemGroup>
9-
</Project>
10+
</Project>
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//
2+
// Copyright The Microcks Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License")
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
//
17+
18+
19+
using DotNet.Testcontainers.Networks;
20+
using Microcks.Testcontainers.Connection;
21+
22+
namespace Microcks.Testcontainers;
23+
24+
/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
25+
public sealed class MicrocksAsyncMinionBuilder
26+
: ContainerBuilder<MicrocksAsyncMinionBuilder, MicrocksAsyncMinionContainer, MicrocksAsyncMinionConfiguration>
27+
{
28+
/// <summary>
29+
/// The default HTTP port for the Microcks async minion.
30+
/// </summary>
31+
public const ushort MicrocksAsyncMinionHttpPort = 8081;
32+
33+
private const string MicrocksAsyncMinionFullImageName = "quay.io/microcks/microcks-uber-async-minion";
34+
35+
private readonly HashSet<string> _extraProtocols = [];
36+
private readonly INetwork _network;
37+
38+
/// <inheritdoc />
39+
protected override MicrocksAsyncMinionConfiguration DockerResourceConfiguration { get; }
40+
41+
/// <summary>
42+
/// Initializes a new instance of the <see cref="MicrocksAsyncMinionBuilder"/> class with the specified network.
43+
/// </summary>
44+
/// <param name="network">The network to be used by the Microcks async minion container.</param>
45+
public MicrocksAsyncMinionBuilder(INetwork network)
46+
: this(new MicrocksAsyncMinionConfiguration())
47+
{
48+
this._network = network;
49+
DockerResourceConfiguration = Init().DockerResourceConfiguration;
50+
}
51+
52+
/// <summary>
53+
/// Initializes a new instance of the <see cref="MicrocksAsyncMinionBuilder"/> class with the specified resource configuration.
54+
/// </summary>
55+
/// <param name="resourceConfiguration">The resource configuration for the Microcks async minion container.</param>
56+
private MicrocksAsyncMinionBuilder(MicrocksAsyncMinionConfiguration resourceConfiguration)
57+
: base(resourceConfiguration)
58+
{
59+
DockerResourceConfiguration = resourceConfiguration;
60+
}
61+
62+
/// <inheritdoc />
63+
public override MicrocksAsyncMinionContainer Build()
64+
{
65+
Validate();
66+
67+
return new MicrocksAsyncMinionContainer(DockerResourceConfiguration);
68+
}
69+
70+
/// <inheritdoc />
71+
protected override MicrocksAsyncMinionBuilder Init()
72+
{
73+
return base.Init()
74+
.WithImage(MicrocksAsyncMinionFullImageName)
75+
.WithNetwork(this._network)
76+
.WithNetworkAliases("microcks-async-minion")
77+
.WithEnvironment("MICROCKS_HOST_PORT", "microcks:" + MicrocksBuilder.MicrocksHttpPort)
78+
.WithExposedPort(MicrocksAsyncMinionHttpPort)
79+
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged(".*Profile prod activated\\..*"));
80+
}
81+
82+
/// <inheritdoc />
83+
protected override MicrocksAsyncMinionBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
84+
{
85+
return Merge(DockerResourceConfiguration, new MicrocksAsyncMinionConfiguration(resourceConfiguration));
86+
}
87+
88+
/// <inheritdoc />
89+
protected override MicrocksAsyncMinionBuilder Clone(IContainerConfiguration resourceConfiguration)
90+
{
91+
return Merge(DockerResourceConfiguration, new MicrocksAsyncMinionConfiguration(resourceConfiguration));
92+
}
93+
94+
/// <inheritdoc />
95+
protected override MicrocksAsyncMinionBuilder Merge(MicrocksAsyncMinionConfiguration oldValue, MicrocksAsyncMinionConfiguration newValue)
96+
{
97+
return new MicrocksAsyncMinionBuilder(new MicrocksAsyncMinionConfiguration(oldValue, newValue));
98+
}
99+
100+
101+
/// <summary>
102+
/// Configures the MicrocksAsyncMinionBuilder to use a Kafka connection.
103+
/// </summary>
104+
/// <param name="kafkaConnection">The Kafka connection details.</param>
105+
/// <returns>The updated MicrocksAsyncMinionBuilder instance.</returns>
106+
public MicrocksAsyncMinionBuilder WithKafkaConnection(KafkaConnection kafkaConnection)
107+
{
108+
_extraProtocols.Add("KAFKA");
109+
var environments = new Dictionary<string, string>
110+
{
111+
{ "ASYNC_PROTOCOLS", $",{string.Join(",", _extraProtocols)}" },
112+
{ "KAFKA_BOOTSTRAP_SERVER", kafkaConnection.BootstrapServers },
113+
};
114+
115+
return Merge(DockerResourceConfiguration, new MicrocksAsyncMinionConfiguration(new ContainerConfiguration(environments: environments)));
116+
}
117+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// Copyright The Microcks Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License")
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
//
17+
18+
namespace Microcks.Testcontainers;
19+
20+
/// <inheritdoc cref="ContainerConfiguration" />
21+
public sealed class MicrocksAsyncMinionConfiguration : ContainerConfiguration
22+
{
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="MicrocksAsyncMinionConfiguration" /> class.
25+
/// </summary>
26+
/// <param name="config">The Microcks config.</param>
27+
public MicrocksAsyncMinionConfiguration(object config = null)
28+
{
29+
}
30+
31+
/// <summary>
32+
/// Initializes a new instance of the <see cref="MicrocksAsyncMinionConfiguration" /> class.
33+
/// </summary>
34+
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
35+
public MicrocksAsyncMinionConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
36+
: base(resourceConfiguration)
37+
{
38+
}
39+
40+
/// <summary>
41+
/// Initializes a new instance of the <see cref="MicrocksAsyncMinionConfiguration" /> class.
42+
/// </summary>
43+
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
44+
public MicrocksAsyncMinionConfiguration(IContainerConfiguration resourceConfiguration)
45+
: base(resourceConfiguration)
46+
{
47+
}
48+
49+
/// <summary>
50+
/// Initializes a new instance of the <see cref="MicrocksAsyncMinionConfiguration" /> class.
51+
/// </summary>
52+
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
53+
public MicrocksAsyncMinionConfiguration(MicrocksAsyncMinionConfiguration resourceConfiguration)
54+
: this(new MicrocksAsyncMinionConfiguration(), resourceConfiguration)
55+
{
56+
}
57+
58+
/// <summary>
59+
/// Initializes a new instance of the <see cref="MicrocksAsyncMinionConfiguration" /> class.
60+
/// </summary>
61+
/// <param name="oldValue">The old Docker resource configuration.</param>
62+
/// <param name="newValue">The new Docker resource configuration.</param>
63+
public MicrocksAsyncMinionConfiguration(MicrocksAsyncMinionConfiguration oldValue, MicrocksAsyncMinionConfiguration newValue)
64+
: base(oldValue, newValue)
65+
{
66+
}
67+
}

0 commit comments

Comments
 (0)