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)); + } + } +}