diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0b96905..a5f3335 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,9 +4,20 @@ stages:
- test
- release
+variables:
+ DOCKER_TLS_CERTDIR: "/certs"
+ DOCKER_CERT_PATH: "/certs/client"
+ DOCKER_TLS_VERIFY: 0
+ DOCKER_HOST: 'tcp://docker:2375'
+
test:
stage: test
+ services:
+ - docker:dind
+ variables:
+ PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/docker
script:
+ - curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.4.tgz | tar -xzv -C /usr/local/
- dotnet tool restore
- dotnet nuke Test
diff --git a/INetMock.sln b/INetMock.sln
index 2f9c80f..2d86fc6 100644
--- a/INetMock.sln
+++ b/INetMock.sln
@@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "INetMock.Client.Test", "tes
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{37738E95-E68E-4242-B7D7-9591605D8E33}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "INetMock.Client.IntegrationTest", "tests\INetMock.Client.IntegrationTest\INetMock.Client.IntegrationTest.csproj", "{D12E0C79-2E27-4FDB-94E7-402EBA267F72}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -39,9 +41,14 @@ Global
{66DAC329-57DE-4218-9880-84E65E91E623}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66DAC329-57DE-4218-9880-84E65E91E623}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66DAC329-57DE-4218-9880-84E65E91E623}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D12E0C79-2E27-4FDB-94E7-402EBA267F72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D12E0C79-2E27-4FDB-94E7-402EBA267F72}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D12E0C79-2E27-4FDB-94E7-402EBA267F72}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D12E0C79-2E27-4FDB-94E7-402EBA267F72}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{4B1CFFB0-23B5-4238-BFCC-4155F459FF8E} = {07100561-E3C0-4B95-92E1-D2D3BA12C3A6}
{66DAC329-57DE-4218-9880-84E65E91E623} = {4FF6267B-A7EC-4277-98EA-39155A82C886}
+ {D12E0C79-2E27-4FDB-94E7-402EBA267F72} = {4FF6267B-A7EC-4277-98EA-39155A82C886}
EndGlobalSection
EndGlobal
diff --git a/assets/integration-tests.dockerfile b/assets/integration-tests.dockerfile
new file mode 100644
index 0000000..552511a
--- /dev/null
+++ b/assets/integration-tests.dockerfile
@@ -0,0 +1,3 @@
+FROM registry.gitlab.com/inetmock/inetmock:latest
+
+USER root
diff --git a/build/Build.cs b/build/Build.cs
index e78fe42..e513723 100644
--- a/build/Build.cs
+++ b/build/Build.cs
@@ -1,3 +1,4 @@
+using System.IO;
using JetBrains.Annotations;
using Nuke.Common;
using Nuke.Common.CI;
@@ -7,6 +8,7 @@ using Nuke.Common.Git;
using Nuke.Common.IO;
using Nuke.Common.ProjectModel;
using Nuke.Common.Tooling;
+using Nuke.Common.Tools.Docker;
using Nuke.Common.Tools.DotNet;
using Nuke.Common.Tools.GitVersion;
using Nuke.Common.Utilities.Collections;
@@ -79,8 +81,14 @@ class Build : NukeBuild
.EnableNoRestore());
});
+ Target IntegrationTestImage => _ => _
+ .Executes(() => DockerTasks.DockerBuild(s => s
+ .SetFile(Path.Join("assets", "integration-tests.dockerfile"))
+ .SetTag("inetmock-root")
+ .SetPath(".")));
+
Target Test => _ => _
- .DependsOn(Compile)
+ .DependsOn(Compile, IntegrationTestImage)
.Executes(() => DotNetTest(s => s
.SetProjectFile(Solution)
.SetConfiguration(Configuration)
diff --git a/src/INetMock.Client/Audit/Client/AuditApiClient.cs b/src/INetMock.Client/Audit/Client/AuditApiClient.cs
index b737ebe..3559e17 100644
--- a/src/INetMock.Client/Audit/Client/AuditApiClient.cs
+++ b/src/INetMock.Client/Audit/Client/AuditApiClient.cs
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using INetMock.Client.Audit.Serialization;
+using INetMock.Client.Grpc;
using INetMock.Client.Rpc;
namespace INetMock.Client.Audit.Client
@@ -18,7 +19,7 @@ namespace INetMock.Client.Audit.Client
}
public AuditApiClient(Uri address, GrpcChannelOptions? options = null) : this(
- GrpcChannel.ForAddress(address, options ?? new GrpcChannelOptions()))
+ ChannelFactory.ForAddress(address, options ?? new GrpcChannelOptions()))
{
}
diff --git a/src/INetMock.Client/Grpc/ChannelFactory.cs b/src/INetMock.Client/Grpc/ChannelFactory.cs
new file mode 100644
index 0000000..c8cb588
--- /dev/null
+++ b/src/INetMock.Client/Grpc/ChannelFactory.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Net.Http;
+using System.Net.Sockets;
+using Grpc.Net.Client;
+
+namespace INetMock.Client.Grpc
+{
+ internal static class ChannelFactory
+ {
+ internal static GrpcChannel ForAddress(Uri uri, GrpcChannelOptions options) =>
+ uri.Scheme.ToLowerInvariant() switch
+ {
+ "unix" => ForUnixSocket(uri.AbsolutePath, options),
+ _ => GrpcChannel.ForAddress(uri, options)
+ };
+
+ private static GrpcChannel ForUnixSocket(string path, GrpcChannelOptions options)
+ {
+ var endpoint = new UnixDomainSocketEndPoint(path);
+ options.HttpHandler = new SocketsHttpHandler
+ {
+ ConnectCallback = async (_, cancellationToken) =>
+ {
+ var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
+ try
+ {
+ await socket.ConnectAsync(endpoint, cancellationToken).ConfigureAwait(false);
+ return new NetworkStream(socket, true);
+ }
+ catch
+ {
+ socket.Dispose();
+ throw;
+ }
+ }
+ };
+ return GrpcChannel.ForAddress("http://localhost", options);
+ }
+ }
+}
diff --git a/src/INetMock.Client/PCAP/Client/PcapApiClient.cs b/src/INetMock.Client/PCAP/Client/PcapApiClient.cs
index c2ce6ef..d06acdc 100644
--- a/src/INetMock.Client/PCAP/Client/PcapApiClient.cs
+++ b/src/INetMock.Client/PCAP/Client/PcapApiClient.cs
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using Grpc.Net.Client;
+using INetMock.Client.Grpc;
using INetMock.Client.Rpc;
namespace INetMock.Client.PCAP.Client
@@ -21,7 +22,7 @@ namespace INetMock.Client.PCAP.Client
}
public PcapApiClient(Uri address, GrpcChannelOptions? options = null)
- : this(GrpcChannel.ForAddress(address, options ?? new GrpcChannelOptions()))
+ : this(ChannelFactory.ForAddress(address, options ?? new GrpcChannelOptions()))
{
}
@@ -90,7 +91,7 @@ namespace INetMock.Client.PCAP.Client
throw new ArgumentOutOfRangeException();
}
- return new Subscription(key.Substring(0, splitIndex), key.Substring(splitIndex), key);
+ return new Subscription(key.Substring(splitIndex+1), key.Substring(0, splitIndex), key);
}
}
}
diff --git a/tests/INetMock.Client.IntegrationTest/INetMock.Client.IntegrationTest.csproj b/tests/INetMock.Client.IntegrationTest/INetMock.Client.IntegrationTest.csproj
new file mode 100644
index 0000000..ff06711
--- /dev/null
+++ b/tests/INetMock.Client.IntegrationTest/INetMock.Client.IntegrationTest.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net5.0
+
+ false
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/tests/INetMock.Client.IntegrationTest/INetMockServerFixture.cs b/tests/INetMock.Client.IntegrationTest/INetMockServerFixture.cs
new file mode 100644
index 0000000..c9e044c
--- /dev/null
+++ b/tests/INetMock.Client.IntegrationTest/INetMockServerFixture.cs
@@ -0,0 +1,41 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using DotNet.Testcontainers.Containers.Builders;
+using DotNet.Testcontainers.Containers.Modules;
+using DotNet.Testcontainers.Containers.WaitStrategies;
+using Xunit;
+
+namespace INetMock.Client.IntegrationTest
+{
+ public class INetMockServerFixture : IAsyncLifetime
+ {
+ private readonly TestcontainersContainer _inetmockContainer;
+
+ public INetMockServerFixture()
+ {
+ _inetmockContainer = new TestcontainersBuilder()
+ .WithImage("inetmock-root:latest")
+ .WithCommand("serve")
+ .WithPortBinding(80, true)
+ .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(80))
+ .WithDockerEndpoint(Environment.GetEnvironmentVariable("DOCKER_HOST") ?? "unix:///var/run/docker.sock")
+ .WithMount(Path.GetTempPath(), "/var/run/inetmock")
+ .WithCleanUp(true)
+ .Build();
+ }
+
+ public Uri SocketPath => new ($"unix://{Path.Join(Path.GetTempPath(), "inetmock.sock")}", UriKind.Absolute);
+
+ public async Task InitializeAsync()
+ {
+ await _inetmockContainer.StartAsync();
+ }
+
+ public async Task DisposeAsync()
+ {
+ await _inetmockContainer.StopAsync();
+ await _inetmockContainer.DisposeAsync();
+ }
+ }
+}
diff --git a/tests/INetMock.Client.IntegrationTest/PCAP/Client/PcapApiClientTests.cs b/tests/INetMock.Client.IntegrationTest/PCAP/Client/PcapApiClientTests.cs
new file mode 100644
index 0000000..0d291dc
--- /dev/null
+++ b/tests/INetMock.Client.IntegrationTest/PCAP/Client/PcapApiClientTests.cs
@@ -0,0 +1,45 @@
+using INetMock.Client.PCAP;
+using INetMock.Client.PCAP.Client;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace INetMock.Client.IntegrationTest.PCAP.Client
+{
+ public class PcapApiClientTests : IClassFixture
+ {
+ private readonly ITestOutputHelper _outputHelper;
+ private readonly IPcapApiClient _apiClient;
+
+ public PcapApiClientTests(ITestOutputHelper testOutputHelper, INetMockServerFixture inetmockFixture)
+ {
+ _outputHelper = testOutputHelper;
+ _apiClient = new PcapApiClient(inetmockFixture.SocketPath);
+ }
+
+ [Fact]
+ public async void ListAvailableDevicesAsync_RunningContainer_AtLeastLoopbackDevice()
+ {
+ var devs = await _apiClient.ListAvailableDevicesAsync();
+
+ Assert.Contains(devs, device => device.Name.Equals("lo"));
+ }
+
+ [Fact]
+ public async void ListActiveRecordingsAsync_NoRecordingsRunning_EmptyResult()
+ {
+ var recordings = await _apiClient.ListActiveRecordingsAsync();
+
+ Assert.Empty(recordings);
+ }
+
+ [Fact]
+ public async void StartPcapFileRecordingAsync_RecordLoopbackInterface_RunningRecording()
+ {
+ const string targetPath = "/tmp/lo_record.pcap";
+ await _apiClient.StartPcapFileRecordingAsync(new("lo", targetPath));
+
+ var subscriptions = await _apiClient.ListActiveRecordingsAsync();
+ Assert.Contains(subscriptions, subscription => subscription.Device.Equals("lo") && subscription.ConsumerName.Equals(targetPath));
+ }
+ }
+}