Update to .NET 6 and latest InetMock API
This commit is contained in:
parent
794159e137
commit
56b77353f2
45 changed files with 1000 additions and 835 deletions
|
@ -3,13 +3,13 @@
|
||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"dotnet-grpc": {
|
"dotnet-grpc": {
|
||||||
"version": "2.40.0",
|
"version": "2.42.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-grpc"
|
"dotnet-grpc"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nuke.globaltool": {
|
"nuke.globaltool": {
|
||||||
"version": "5.3.0",
|
"version": "6.0.1",
|
||||||
"commands": [
|
"commands": [
|
||||||
"nuke"
|
"nuke"
|
||||||
]
|
]
|
||||||
|
|
|
@ -12,6 +12,8 @@ indent_size = 4
|
||||||
|
|
||||||
[*.cs]
|
[*.cs]
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
|
csharp_style_namespace_declarations = file_scoped:warning
|
||||||
|
csharp_prefer_braces = true:warning
|
||||||
|
|
||||||
[*.json]
|
[*.json]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
|
@ -14,13 +14,30 @@ test:
|
||||||
stage: test
|
stage: test
|
||||||
services:
|
services:
|
||||||
- docker:dind
|
- docker:dind
|
||||||
|
before_script:
|
||||||
|
- |
|
||||||
|
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.9.tgz | tar -xzv -C /usr/local/
|
||||||
|
|
||||||
|
docker run --rm -d \
|
||||||
|
--cap-add CAP_NET_RAW \
|
||||||
|
--cap-add CAP_NET_ADMIN \
|
||||||
|
--cap-add CAP_NET_BIND_SERVICE \
|
||||||
|
-u root \
|
||||||
|
-p 6767:6767 \
|
||||||
|
-e INETMOCK_API_LISTEN=tcp://0.0.0.0:6767 \
|
||||||
|
--name inetmock \
|
||||||
|
registry.gitlab.com/inetmock/inetmock:latest
|
||||||
|
|
||||||
|
for i in `seq 1 10`
|
||||||
|
do
|
||||||
|
docker exec -i inetmock /usr/lib/inetmock/bin/imctl health container 2>&1 > /dev/null || sleep 1;
|
||||||
|
done;
|
||||||
|
after_script:
|
||||||
|
- docker stop inetmock
|
||||||
variables:
|
variables:
|
||||||
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/docker
|
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/docker
|
||||||
|
INETMOCK_SOCKET: http://docker:6767
|
||||||
script:
|
script:
|
||||||
- curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.4.tgz | tar -xzv -C /usr/local/
|
|
||||||
- mkdir /usr/local/share/ca-certificates/docker-ca
|
|
||||||
- cp "${DOCKER_CERT_PATH}/ca.pem" /usr/local/share/ca-certificates/docker-ca/
|
|
||||||
- update-ca-certificates --fresh
|
|
||||||
- dotnet tool restore
|
- dotnet tool restore
|
||||||
- dotnet nuke Test
|
- dotnet nuke Test
|
||||||
|
|
||||||
|
@ -30,7 +47,7 @@ protobuf-lint:
|
||||||
name: docker.io/bufbuild/buf:latest
|
name: docker.io/bufbuild/buf:latest
|
||||||
entrypoint: [""]
|
entrypoint: [""]
|
||||||
script:
|
script:
|
||||||
- cd api/
|
- cd api/proto/
|
||||||
- buf ls-files
|
- buf ls-files
|
||||||
- buf lint
|
- buf lint
|
||||||
|
|
||||||
|
@ -39,6 +56,7 @@ nuget-publish:
|
||||||
only:
|
only:
|
||||||
refs:
|
refs:
|
||||||
- tags
|
- tags
|
||||||
|
- main
|
||||||
script:
|
script:
|
||||||
- dotnet tool restore
|
- dotnet tool restore
|
||||||
- dotnet nuke NuGetPush
|
- dotnet nuke NuGetPush
|
||||||
|
|
1
.nuke
1
.nuke
|
@ -1 +0,0 @@
|
||||||
INetMock.sln
|
|
127
.nuke/build.schema.json
Normal file
127
.nuke/build.schema.json
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"title": "Build Schema",
|
||||||
|
"$ref": "#/definitions/build",
|
||||||
|
"definitions": {
|
||||||
|
"build": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"Configuration": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)",
|
||||||
|
"enum": [
|
||||||
|
"Debug",
|
||||||
|
"Release"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Continue": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Indicates to continue a previously failed build attempt"
|
||||||
|
},
|
||||||
|
"Help": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Shows the help text for this build assembly"
|
||||||
|
},
|
||||||
|
"Host": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Host for execution. Default is 'automatic'",
|
||||||
|
"enum": [
|
||||||
|
"AppVeyor",
|
||||||
|
"AzurePipelines",
|
||||||
|
"Bamboo",
|
||||||
|
"Bitrise",
|
||||||
|
"GitHubActions",
|
||||||
|
"GitLab",
|
||||||
|
"Jenkins",
|
||||||
|
"Rider",
|
||||||
|
"SpaceAutomation",
|
||||||
|
"TeamCity",
|
||||||
|
"Terminal",
|
||||||
|
"TravisCI",
|
||||||
|
"VisualStudio",
|
||||||
|
"VSCode"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"NoLogo": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Disables displaying the NUKE logo"
|
||||||
|
},
|
||||||
|
"nuget-password": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Password to use for publishing NuGet packages"
|
||||||
|
},
|
||||||
|
"nuget-username": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username to use for publishing NuGet packages"
|
||||||
|
},
|
||||||
|
"Partition": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Partition to use on CI"
|
||||||
|
},
|
||||||
|
"Plan": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Shows the execution plan (HTML)"
|
||||||
|
},
|
||||||
|
"Profile": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "Defines the profiles to load",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Root": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Root directory during build execution"
|
||||||
|
},
|
||||||
|
"Skip": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List of targets to be skipped. Empty list skips all dependencies",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"AddNugetSource",
|
||||||
|
"Clean",
|
||||||
|
"Compile",
|
||||||
|
"Format",
|
||||||
|
"NuGetPush",
|
||||||
|
"Pack",
|
||||||
|
"Restore",
|
||||||
|
"Test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Solution": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to a solution file that is automatically loaded"
|
||||||
|
},
|
||||||
|
"Target": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List of targets to be invoked. Default is '{default_target}'",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"AddNugetSource",
|
||||||
|
"Clean",
|
||||||
|
"Compile",
|
||||||
|
"Format",
|
||||||
|
"NuGetPush",
|
||||||
|
"Pack",
|
||||||
|
"Restore",
|
||||||
|
"Test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Verbosity": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Logging verbosity during build execution. Default is 'Normal'",
|
||||||
|
"enum": [
|
||||||
|
"Minimal",
|
||||||
|
"Normal",
|
||||||
|
"Quiet",
|
||||||
|
"Verbose"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
.nuke/parameters.json
Normal file
4
.nuke/parameters.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "./build.schema.json",
|
||||||
|
"Solution": "INetMock.sln"
|
||||||
|
}
|
13
.pre-commit-config.yaml
Normal file
13
.pre-commit-config.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/dotnet/format
|
||||||
|
rev: "v5.1.225507" # Specify a tag or sha here, or run "pre-commit autoupdate"
|
||||||
|
hooks:
|
||||||
|
- id: dotnet-format
|
||||||
|
args:
|
||||||
|
- ""
|
||||||
|
- --folder
|
||||||
|
- --check
|
||||||
|
- --verbosity=detailed
|
||||||
|
- --include
|
|
@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
.gitignore = .gitignore
|
.gitignore = .gitignore
|
||||||
.gitlab-ci.yml = .gitlab-ci.yml
|
.gitlab-ci.yml = .gitlab-ci.yml
|
||||||
|
.pre-commit-config.yaml = .pre-commit-config.yaml
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{07100561-E3C0-4B95-92E1-D2D3BA12C3A6}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{07100561-E3C0-4B95-92E1-D2D3BA12C3A6}"
|
||||||
|
|
|
@ -4,4 +4,4 @@
|
||||||
:; exit $?
|
:; exit $?
|
||||||
|
|
||||||
@ECHO OFF
|
@ECHO OFF
|
||||||
powershell -ExecutionPolicy ByPass -NoProfile "%~dp0build.ps1" %*
|
powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*
|
||||||
|
|
|
@ -14,7 +14,7 @@ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
|
||||||
###########################################################################
|
###########################################################################
|
||||||
|
|
||||||
$BuildProjectFile = "$PSScriptRoot\build\_build.csproj"
|
$BuildProjectFile = "$PSScriptRoot\build\_build.csproj"
|
||||||
$TempDirectory = "$PSScriptRoot\\.tmp"
|
$TempDirectory = "$PSScriptRoot\\.nuke\temp"
|
||||||
|
|
||||||
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
|
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
|
||||||
$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
|
$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
|
||||||
|
@ -56,9 +56,9 @@ else {
|
||||||
# Install by channel or version
|
# Install by channel or version
|
||||||
$DotNetDirectory = "$TempDirectory\dotnet-win"
|
$DotNetDirectory = "$TempDirectory\dotnet-win"
|
||||||
if (!(Test-Path variable:DotNetVersion)) {
|
if (!(Test-Path variable:DotNetVersion)) {
|
||||||
ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
|
ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
|
||||||
} else {
|
} else {
|
||||||
ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
|
ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
|
||||||
}
|
}
|
||||||
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
|
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
|
||||||
}
|
}
|
||||||
|
|
2
build.sh
2
build.sh
|
@ -10,7 +10,7 @@ SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||||
###########################################################################
|
###########################################################################
|
||||||
|
|
||||||
BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj"
|
BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj"
|
||||||
TEMP_DIRECTORY="$SCRIPT_DIR//.tmp"
|
TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
|
||||||
|
|
||||||
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
|
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
|
||||||
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
|
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System.IO;
|
using System;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Nuke.Common;
|
using Nuke.Common;
|
||||||
using Nuke.Common.CI;
|
using Nuke.Common.CI;
|
||||||
|
@ -8,7 +8,6 @@ using Nuke.Common.Git;
|
||||||
using Nuke.Common.IO;
|
using Nuke.Common.IO;
|
||||||
using Nuke.Common.ProjectModel;
|
using Nuke.Common.ProjectModel;
|
||||||
using Nuke.Common.Tooling;
|
using Nuke.Common.Tooling;
|
||||||
using Nuke.Common.Tools.Docker;
|
|
||||||
using Nuke.Common.Tools.DotNet;
|
using Nuke.Common.Tools.DotNet;
|
||||||
using Nuke.Common.Tools.GitVersion;
|
using Nuke.Common.Tools.GitVersion;
|
||||||
using Nuke.Common.Utilities.Collections;
|
using Nuke.Common.Utilities.Collections;
|
||||||
|
@ -20,38 +19,29 @@ using static Nuke.Common.Tools.DotNet.DotNetTasks;
|
||||||
class Build : NukeBuild
|
class Build : NukeBuild
|
||||||
{
|
{
|
||||||
private const string NuGetSourceName = "GitLab";
|
private const string NuGetSourceName = "GitLab";
|
||||||
public static int Main() => Execute<Build>(x => x.Compile);
|
public static int Main() => Execute<Build>(x => x.Test);
|
||||||
|
|
||||||
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
|
[Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")]
|
||||||
readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
|
readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release;
|
||||||
|
|
||||||
|
[Parameter("Username to use for publishing NuGet packages", Name = "nuget-username")]
|
||||||
|
string NuGetUsername { get; } = Environment.GetEnvironmentVariable("NUGET_USERNAME") ?? string.Empty;
|
||||||
|
|
||||||
|
[Parameter("Password to use for publishing NuGet packages", Name = "nuget-password")]
|
||||||
|
string NuGetPassword { get; } = Environment.GetEnvironmentVariable("NUGET_PASSWORD") ?? string.Empty;
|
||||||
|
|
||||||
[Solution] readonly Solution Solution;
|
[Solution] readonly Solution Solution;
|
||||||
[GitRepository] readonly GitRepository GitRepository;
|
[GitRepository] readonly GitRepository GitRepository;
|
||||||
[GitVersion(NoFetch = true, Framework = "net5.0")] readonly GitVersion GitVersion;
|
[GitVersion(NoFetch = true)] readonly GitVersion GitVersion;
|
||||||
|
|
||||||
|
|
||||||
[CanBeNull] GitLab CI => GitLab.Instance;
|
[CanBeNull]
|
||||||
|
GitLab CI => GitLab.Instance;
|
||||||
|
|
||||||
AbsolutePath SourceDirectory => RootDirectory / "src";
|
AbsolutePath SourceDirectory => RootDirectory / "src";
|
||||||
AbsolutePath TestsDirectory => RootDirectory / "tests";
|
AbsolutePath TestsDirectory => RootDirectory / "tests";
|
||||||
AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts";
|
AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts";
|
||||||
|
|
||||||
Target PrintEnv => _ => _
|
|
||||||
.Executes(() =>
|
|
||||||
{
|
|
||||||
if (CI == null)
|
|
||||||
{
|
|
||||||
Logger.Info("Running in local environment");
|
|
||||||
Logger.Info($"Git commit: {GitRepository.Commit}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Info("Running in GitLab CI");
|
|
||||||
Logger.Info($"Git commit: {CI.CommitSha}");
|
|
||||||
Logger.Info($"Pipeline ID: {CI.PipelineId}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Target Clean => _ => _
|
Target Clean => _ => _
|
||||||
.Before(Restore)
|
.Before(Restore)
|
||||||
.Executes(() =>
|
.Executes(() =>
|
||||||
|
@ -69,7 +59,7 @@ class Build : NukeBuild
|
||||||
});
|
});
|
||||||
|
|
||||||
Target Compile => _ => _
|
Target Compile => _ => _
|
||||||
.DependsOn(Restore, PrintEnv)
|
.DependsOn(Restore)
|
||||||
.Executes(() =>
|
.Executes(() =>
|
||||||
{
|
{
|
||||||
DotNetBuild(s => s
|
DotNetBuild(s => s
|
||||||
|
@ -81,14 +71,12 @@ class Build : NukeBuild
|
||||||
.EnableNoRestore());
|
.EnableNoRestore());
|
||||||
});
|
});
|
||||||
|
|
||||||
Target IntegrationTestImage => _ => _
|
Target Format => _ => _
|
||||||
.Executes(() => DockerTasks.DockerBuild(s => s
|
.DependsOn(Restore)
|
||||||
.SetFile(Path.Join("assets", "integration-tests.dockerfile"))
|
.Executes(() => DotNet(CI == null ? "format --no-restore" : "format --no-restore --verify-no-changes"));
|
||||||
.SetTag("inetmock-root")
|
|
||||||
.SetPath(".")));
|
|
||||||
|
|
||||||
Target Test => _ => _
|
Target Test => _ => _
|
||||||
.DependsOn(Compile, IntegrationTestImage)
|
.DependsOn(Compile, Format)
|
||||||
.Executes(() => DotNetTest(s => s
|
.Executes(() => DotNetTest(s => s
|
||||||
.SetProjectFile(Solution)
|
.SetProjectFile(Solution)
|
||||||
.SetConfiguration(Configuration)
|
.SetConfiguration(Configuration)
|
||||||
|
@ -97,20 +85,20 @@ class Build : NukeBuild
|
||||||
.EnableProcessLogOutput()));
|
.EnableProcessLogOutput()));
|
||||||
|
|
||||||
Target AddNugetSource => _ => _
|
Target AddNugetSource => _ => _
|
||||||
.OnlyWhenStatic(() => GitRepository.IsOnMasterBranch())
|
.OnlyWhenStatic(() => GitRepository.IsOnMainBranch())
|
||||||
.OnlyWhenStatic(() => CI != null)
|
.OnlyWhenStatic(() => CI != null)
|
||||||
|
.OnlyWhenStatic(() => !string.IsNullOrEmpty(NuGetUsername) && !string.IsNullOrEmpty(NuGetPassword))
|
||||||
.ProceedAfterFailure()
|
.ProceedAfterFailure()
|
||||||
.Executes(() => DotNetNuGetAddSource(s => s
|
.Executes(() => DotNetNuGetAddSource(s => s
|
||||||
.SetName(NuGetSourceName)
|
.SetName(NuGetSourceName)
|
||||||
//.SetSource($"https://gitlab.com/api/v4/projects/{CI.ProjectId}/packages/nuget/index.json")
|
.SetSource($"https://gitlab.com/api/v4/projects/{CI.ProjectId}/packages/nuget/index.json")
|
||||||
.SetSource($"https://gitlab.com/api/v4/projects/24385200/packages/nuget/index.json")
|
.SetUsername(NuGetUsername)
|
||||||
.SetUsername("baez90")
|
.SetPassword(NuGetPassword)
|
||||||
.SetPassword("RcMwfaXgvBxSWt4ZMB6z")
|
|
||||||
.EnableStorePasswordInClearText()));
|
.EnableStorePasswordInClearText()));
|
||||||
|
|
||||||
Target Pack => _ => _
|
Target Pack => _ => _
|
||||||
.DependsOn(Test)
|
.DependsOn(Compile)
|
||||||
.OnlyWhenStatic(() => GitRepository.IsOnMasterBranch())
|
.OnlyWhenStatic(() => GitRepository.IsOnMainBranch())
|
||||||
.Executes(() => SourceDirectory
|
.Executes(() => SourceDirectory
|
||||||
.GlobFiles("**/*.csproj")
|
.GlobFiles("**/*.csproj")
|
||||||
.ForEach(csproj => DotNetPack(s => s
|
.ForEach(csproj => DotNetPack(s => s
|
||||||
|
@ -127,7 +115,7 @@ class Build : NukeBuild
|
||||||
|
|
||||||
Target NuGetPush => _ => _
|
Target NuGetPush => _ => _
|
||||||
.DependsOn(Pack, AddNugetSource)
|
.DependsOn(Pack, AddNugetSource)
|
||||||
.OnlyWhenStatic(() => GitRepository.IsOnMasterBranch())
|
.OnlyWhenStatic(() => GitRepository.IsOnMainBranch())
|
||||||
.Executes(() => ArtifactsDirectory
|
.Executes(() => ArtifactsDirectory
|
||||||
.GlobFiles("**/*.nupkg")
|
.GlobFiles("**/*.nupkg")
|
||||||
.ForEach(nupkg => DotNetNuGetPush(s => s
|
.ForEach(nupkg => DotNetNuGetPush(s => s
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
using System;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
|
||||||
using Nuke.Common.Tooling;
|
using Nuke.Common.Tooling;
|
||||||
|
|
||||||
[TypeConverter(typeof(TypeConverter<Configuration>))]
|
[TypeConverter(typeof(TypeConverter<Configuration>))]
|
||||||
public class Configuration : Enumeration
|
public class Configuration : Enumeration
|
||||||
{
|
{
|
||||||
public static Configuration Debug = new Configuration { Value = nameof(Debug) };
|
public static Configuration Debug = new() { Value = nameof(Debug) };
|
||||||
public static Configuration Release = new Configuration { Value = nameof(Release) };
|
public static Configuration Release = new() { Value = nameof(Release) };
|
||||||
|
|
||||||
public static implicit operator string(Configuration configuration)
|
public static implicit operator string(Configuration configuration)
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,12 +6,14 @@
|
||||||
<NoWarn>CS0649;CS0169</NoWarn>
|
<NoWarn>CS0649;CS0169</NoWarn>
|
||||||
<NukeRootDirectory>..</NukeRootDirectory>
|
<NukeRootDirectory>..</NukeRootDirectory>
|
||||||
<NukeScriptDirectory>..</NukeScriptDirectory>
|
<NukeScriptDirectory>..</NukeScriptDirectory>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<NukeTelemetryVersion>1</NukeTelemetryVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Nuke.Common" Version="5.3.0" />
|
<PackageReference Include="Nuke.Common" Version="6.0.1" />
|
||||||
<PackageDownload Include="GitVersion.Tool" Version="[5.6.6]" />
|
<PackageDownload Include="GitVersion.Tool" Version="[5.8.0]" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
6
global.json
Normal file
6
global.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "6.0.100",
|
||||||
|
"rollForward": "latestMinor"
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,69 +6,67 @@ using Grpc.Core;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using INetMock.Client.Audit.Serialization;
|
using INetMock.Client.Audit.Serialization;
|
||||||
using INetMock.Client.Grpc;
|
using INetMock.Client.Grpc;
|
||||||
using INetMock.Client.Rpc;
|
|
||||||
|
|
||||||
namespace INetMock.Client.Audit.Client
|
namespace INetMock.Client.Audit.Client;
|
||||||
|
|
||||||
|
public class AuditApiClient : IAuditApiClient
|
||||||
{
|
{
|
||||||
public class AuditApiClient : IAuditApiClient
|
private readonly AuditService.AuditServiceClient _auditClient;
|
||||||
|
|
||||||
|
public AuditApiClient(string address, GrpcChannelOptions? options = null) : this(new Uri(address), options)
|
||||||
{
|
{
|
||||||
private readonly AuditService.AuditServiceClient _auditClient;
|
}
|
||||||
|
|
||||||
public AuditApiClient(string address, GrpcChannelOptions? options = null) : this(new Uri(address), options)
|
public AuditApiClient(Uri address, GrpcChannelOptions? options = null) : this(
|
||||||
|
ChannelFactory.ForAddress(address, options ?? new GrpcChannelOptions()))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuditApiClient(ChannelBase channel)
|
||||||
|
{
|
||||||
|
_auditClient = new AuditService.AuditServiceClient(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<string>> ListSinksAsync(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var sinks = await _auditClient.ListSinksAsync(new ListSinksRequest(), Metadata.Empty, null, token);
|
||||||
|
return sinks.Sinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> RegisterFileSinkAsync(string targetPath, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var resp = await _auditClient.RegisterFileSinkAsync(
|
||||||
|
new RegisterFileSinkRequest { TargetPath = targetPath },
|
||||||
|
Metadata.Empty,
|
||||||
|
null,
|
||||||
|
token
|
||||||
|
);
|
||||||
|
|
||||||
|
return resp.ResolvedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> RemoveFileSinkAsync(string targetPath, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var resp = await _auditClient.RemoveFileSinkAsync(new RemoveFileSinkRequest
|
||||||
{
|
{
|
||||||
}
|
TargetPath = targetPath
|
||||||
|
},
|
||||||
|
Metadata.Empty,
|
||||||
|
null,
|
||||||
|
token
|
||||||
|
);
|
||||||
|
|
||||||
public AuditApiClient(Uri address, GrpcChannelOptions? options = null) : this(
|
return resp.SinkGotRemoved;
|
||||||
ChannelFactory.ForAddress(address, options ?? new GrpcChannelOptions()))
|
}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuditApiClient(ChannelBase channel)
|
public IProtoEventReader EventStreamAsync(string watcherName, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
_auditClient = new AuditService.AuditServiceClient(channel);
|
var stream = _auditClient.WatchEvents(
|
||||||
}
|
new WatchEventsRequest { WatcherName = watcherName },
|
||||||
|
Metadata.Empty,
|
||||||
public async Task<IReadOnlyList<string>> ListSinksAsync(CancellationToken token = default)
|
null,
|
||||||
{
|
token
|
||||||
var sinks = await _auditClient.ListSinksAsync(new ListSinksRequest(), Metadata.Empty, null, token);
|
);
|
||||||
return sinks.Sinks;
|
return new EventServerStreamReader(stream);
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> RegisterFileSinkAsync(string targetPath, CancellationToken token = default)
|
|
||||||
{
|
|
||||||
var resp = await _auditClient.RegisterFileSinkAsync(
|
|
||||||
new RegisterFileSinkRequest {TargetPath = targetPath},
|
|
||||||
Metadata.Empty,
|
|
||||||
null,
|
|
||||||
token
|
|
||||||
);
|
|
||||||
|
|
||||||
return resp.ResolvedPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> RemoveFileSinkAsync(string targetPath, CancellationToken token = default)
|
|
||||||
{
|
|
||||||
var resp = await _auditClient.RemoveFileSinkAsync(new RemoveFileSinkRequest
|
|
||||||
{
|
|
||||||
TargetPath = targetPath
|
|
||||||
},
|
|
||||||
Metadata.Empty,
|
|
||||||
null,
|
|
||||||
token
|
|
||||||
);
|
|
||||||
|
|
||||||
return resp.SinkGotRemoved;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IProtoEventReader EventStreamAsync(string watcherName, CancellationToken token = default)
|
|
||||||
{
|
|
||||||
var stream = _auditClient.WatchEvents(
|
|
||||||
new WatchEventsRequest {WatcherName = watcherName},
|
|
||||||
Metadata.Empty,
|
|
||||||
null,
|
|
||||||
token
|
|
||||||
);
|
|
||||||
return new EventServerStreamReader(stream);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,88 +3,51 @@ using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using Google.Protobuf.Collections;
|
using Google.Protobuf.Collections;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
|
||||||
|
|
||||||
namespace INetMock.Client.Audit
|
namespace INetMock.Client.Audit;
|
||||||
|
|
||||||
|
public abstract record EventDetails;
|
||||||
|
|
||||||
|
public record HttpDetails() : EventDetails
|
||||||
{
|
{
|
||||||
public abstract record EventDetails;
|
public HttpDetails(HTTPDetailsEntity detailsEntity) : this()
|
||||||
|
|
||||||
public record EmptyDetails : EventDetails;
|
|
||||||
|
|
||||||
public record GenericDetails : EventDetails
|
|
||||||
{
|
{
|
||||||
private readonly Any? _detailsAny;
|
Method = new HttpMethod(detailsEntity.Method.ToString());
|
||||||
|
Host = detailsEntity.Host;
|
||||||
public GenericDetails()
|
Uri = detailsEntity.Uri;
|
||||||
{
|
Proto = detailsEntity.Proto;
|
||||||
_detailsAny = null;
|
Headers = new INetMockHttpHeaders(detailsEntity.Headers);
|
||||||
}
|
|
||||||
|
|
||||||
public GenericDetails(Any? any)
|
|
||||||
{
|
|
||||||
_detailsAny = any;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator HttpDetails(GenericDetails gd)
|
|
||||||
{
|
|
||||||
if (gd._detailsAny == null || gd._detailsAny.Value == null) return new();
|
|
||||||
if (!gd._detailsAny.TypeUrl.EndsWith(HTTPDetailsEntity.Descriptor.FullName))
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
return new HttpDetails(gd._detailsAny);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static implicit operator DnsDetails(GenericDetails gd)
|
|
||||||
{
|
|
||||||
if (gd._detailsAny == null || gd._detailsAny.Value == null) return new();
|
|
||||||
if (!gd._detailsAny.TypeUrl.EndsWith(HTTPDetailsEntity.Descriptor.FullName))
|
|
||||||
throw new InvalidOperationException();
|
|
||||||
return new DnsDetails(gd._detailsAny);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public record HttpDetails : EventDetails
|
public HttpMethod Method { get; init; } = HttpMethod.Get;
|
||||||
{
|
public string Host { get; init; } = string.Empty;
|
||||||
public HttpDetails()
|
public string Uri { get; init; } = string.Empty;
|
||||||
{
|
public string Proto { get; init; } = string.Empty;
|
||||||
}
|
public HttpHeaders Headers { get; init; } = new INetMockHttpHeaders(new MapField<string, HTTPHeaderValue>());
|
||||||
|
}
|
||||||
public HttpDetails(Any? any)
|
|
||||||
{
|
public record DnsDetails() : EventDetails
|
||||||
if (any == null || any.Value == null) return;
|
{
|
||||||
|
public DnsDetails(DNSDetailsEntity entity) : this()
|
||||||
var detailsEntity = HTTPDetailsEntity.Parser.ParseFrom(any.Value);
|
{
|
||||||
|
OpCode = entity.Opcode;
|
||||||
Method = new HttpMethod(detailsEntity.Method.ToString());
|
Questions = entity.Questions;
|
||||||
Host = detailsEntity.Host;
|
}
|
||||||
Uri = detailsEntity.Uri;
|
|
||||||
Proto = detailsEntity.Proto;
|
public DNSOpCode OpCode { get; init; } = DNSOpCode.Query;
|
||||||
Headers = new INetMockHttpHeaders(detailsEntity.Headers);
|
public IReadOnlyList<DNSQuestionEntity> Questions { get; init; } = Array.Empty<DNSQuestionEntity>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpMethod Method { get; init; } = HttpMethod.Get;
|
public record DhcpDetails() : EventDetails
|
||||||
public string Host { get; init; } = string.Empty;
|
{
|
||||||
public string Uri { get; init; } = string.Empty;
|
public DhcpDetails(DHCPDetailsEntity entity) : this()
|
||||||
public string Proto { get; init; } = string.Empty;
|
{
|
||||||
public HttpHeaders Headers { get; init; } = new INetMockHttpHeaders(new MapField<string, HTTPHeaderValue>());
|
HopCount = entity.HopCount;
|
||||||
}
|
OpCode = entity.Opcode;
|
||||||
|
HardwareType = entity.HwType;
|
||||||
public record DnsDetails : EventDetails
|
}
|
||||||
{
|
|
||||||
public DnsDetails()
|
public int HopCount { get; init; }
|
||||||
{
|
public DHCPOpCode OpCode { get; init; } = DHCPOpCode.Unspecified;
|
||||||
}
|
public DHCPHwType HardwareType { get; init; } = DHCPHwType.Unspecified;
|
||||||
|
|
||||||
public DNSOpCode OpCode { get; init; } = DNSOpCode.Query;
|
|
||||||
|
|
||||||
public IReadOnlyList<DNSQuestionEntity> Questions { get; init; } = Array.Empty<DNSQuestionEntity>();
|
|
||||||
|
|
||||||
public DnsDetails(Any? any)
|
|
||||||
{
|
|
||||||
if (any == null || any.Value == null) return;
|
|
||||||
|
|
||||||
var entity = DNSDetailsEntity.Parser.ParseFrom(any.Value);
|
|
||||||
OpCode = entity.Opcode;
|
|
||||||
Questions = entity.Questions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,90 +1,76 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
namespace INetMock.Client.Audit
|
namespace INetMock.Client.Audit;
|
||||||
|
|
||||||
|
public abstract record EventBase
|
||||||
{
|
{
|
||||||
public record Event<T> where T : EventDetails, new()
|
protected EventBase(EventEntity entity)
|
||||||
{
|
{
|
||||||
public Event()
|
if (entity == null)
|
||||||
{
|
{
|
||||||
|
throw new ArgumentNullException(nameof(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Event(EventEntity entity)
|
Id = entity.Id;
|
||||||
{
|
Timestamp = entity.Timestamp.ToDateTimeOffset();
|
||||||
if (entity == null) throw new ArgumentNullException(nameof(entity));
|
Transport = entity.Transport;
|
||||||
|
SourceIp = new IPAddress(entity.SourceIp.Span);
|
||||||
var details = (new T(), entity.Application) switch
|
SourcePort = Convert.ToUInt16(entity.SourcePort);
|
||||||
{
|
DestinationIp = new IPAddress(entity.DestinationIp.Span);
|
||||||
(GenericDetails, _) => new GenericDetails(entity.ProtocolDetails) as T,
|
DestinationPort = Convert.ToUInt16(entity.DestinationPort);
|
||||||
(_, AppProtocol.Dns) => new DnsDetails(entity.ProtocolDetails) as T,
|
TlsDetails = entity.Tls;
|
||||||
(_, AppProtocol.Http or AppProtocol.HttpProxy) => new HttpDetails(entity.ProtocolDetails) as T,
|
Application = entity.Application;
|
||||||
(_, _) => new EmptyDetails() as T
|
|
||||||
};
|
|
||||||
|
|
||||||
Id = entity.Id;
|
|
||||||
Timestamp = entity.Timestamp.ToDateTimeOffset();
|
|
||||||
Transport = entity.Transport;
|
|
||||||
SourceIp = new IPAddress(entity.SourceIp.Span);
|
|
||||||
SourcePort = Convert.ToUInt16(entity.SourcePort);
|
|
||||||
DestinationIp = new IPAddress(entity.DestinationIp.Span);
|
|
||||||
DestinationPort = Convert.ToUInt16(entity.DestinationPort);
|
|
||||||
TlsDetails = entity.Tls;
|
|
||||||
Application = entity.Application;
|
|
||||||
Details = details;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long Id { get; init; }
|
|
||||||
public DateTimeOffset Timestamp { get; init; }
|
|
||||||
public TransportProtocol Transport { get; init; }
|
|
||||||
public AppProtocol Application { get; init; }
|
|
||||||
public IPAddress SourceIp { get; init; } = IPAddress.Any;
|
|
||||||
public IPAddress DestinationIp { get; init; } = IPAddress.Any;
|
|
||||||
public ushort SourcePort { get; init; }
|
|
||||||
public ushort DestinationPort { get; init; }
|
|
||||||
public TLSDetailsEntity TlsDetails { get; init; } = new();
|
|
||||||
public T? Details { get; init; }
|
|
||||||
|
|
||||||
public bool CanConvert<TTarget>() where TTarget : EventDetails, new()
|
|
||||||
{
|
|
||||||
if (Details == null) return false;
|
|
||||||
|
|
||||||
return (new TTarget(), Application) switch
|
|
||||||
{
|
|
||||||
(DnsDetails, AppProtocol.Dns) => true,
|
|
||||||
(HttpDetails, AppProtocol.Http or AppProtocol.HttpProxy) => true,
|
|
||||||
(_, _) => false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static explicit operator Event<T>?(Event<GenericDetails> ge)
|
|
||||||
{
|
|
||||||
T? details = null;
|
|
||||||
if (ge.Details != null)
|
|
||||||
{
|
|
||||||
details = ge.Application switch
|
|
||||||
{
|
|
||||||
AppProtocol.Dns => (DnsDetails) ge.Details as T,
|
|
||||||
AppProtocol.Http or AppProtocol.HttpProxy => (HttpDetails) ge.Details as T,
|
|
||||||
_ => new EmptyDetails() as T
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (details == null) return null;
|
|
||||||
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
Id = ge.Id,
|
|
||||||
Timestamp = ge.Timestamp,
|
|
||||||
Transport = ge.Transport,
|
|
||||||
SourceIp = ge.SourceIp,
|
|
||||||
SourcePort = ge.SourcePort,
|
|
||||||
DestinationIp = ge.DestinationIp,
|
|
||||||
DestinationPort = ge.DestinationPort,
|
|
||||||
TlsDetails = ge.TlsDetails,
|
|
||||||
Application = ge.Application,
|
|
||||||
Details = details
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long Id { get; init; }
|
||||||
|
public DateTimeOffset Timestamp { get; init; }
|
||||||
|
public TransportProtocol Transport { get; init; }
|
||||||
|
public AppProtocol Application { get; init; }
|
||||||
|
public IPAddress SourceIp { get; init; } = IPAddress.Any;
|
||||||
|
public IPAddress DestinationIp { get; init; } = IPAddress.Any;
|
||||||
|
public ushort SourcePort { get; init; }
|
||||||
|
public ushort DestinationPort { get; init; }
|
||||||
|
public TLSDetailsEntity TlsDetails { get; init; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Event : EventBase
|
||||||
|
{
|
||||||
|
public Event(EventEntity entity) : base(entity)
|
||||||
|
{
|
||||||
|
Details = entity.ProtocolDetailsCase switch
|
||||||
|
{
|
||||||
|
EventEntity.ProtocolDetailsOneofCase.Http => new HttpDetails(entity.Http),
|
||||||
|
EventEntity.ProtocolDetailsOneofCase.Dns => new DnsDetails(entity.Dns),
|
||||||
|
EventEntity.ProtocolDetailsOneofCase.Dhcp => new DhcpDetails(entity.Dhcp),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? Details { get; init; }
|
||||||
|
|
||||||
|
public T? DetailsAs<T>() where T : EventDetails => Details as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Event<T> : EventBase where T : EventDetails, new()
|
||||||
|
{
|
||||||
|
public Event(EventEntity entity) : base(entity)
|
||||||
|
{
|
||||||
|
Details = entity.ProtocolDetailsCase switch
|
||||||
|
{
|
||||||
|
EventEntity.ProtocolDetailsOneofCase.Http => new HttpDetails(entity.Http) as T,
|
||||||
|
EventEntity.ProtocolDetailsOneofCase.Dns => new DnsDetails(entity.Dns) as T,
|
||||||
|
EventEntity.ProtocolDetailsOneofCase.Dhcp => new DhcpDetails(entity.Dhcp) as T,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Event(Event raw) : base(raw)
|
||||||
|
{
|
||||||
|
Details = raw.Details as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T? Details { get; init; }
|
||||||
|
|
||||||
|
public static explicit operator Event<T>(Event raw) => new(raw);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,12 @@ using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace INetMock.Client.Audit
|
namespace INetMock.Client.Audit;
|
||||||
|
|
||||||
|
public interface IAuditApiClient
|
||||||
{
|
{
|
||||||
public interface IAuditApiClient
|
IProtoEventReader EventStreamAsync(string watcherName, CancellationToken token = default);
|
||||||
{
|
Task<IReadOnlyList<string>> ListSinksAsync(CancellationToken token = default);
|
||||||
IProtoEventReader EventStreamAsync(string watcherName, CancellationToken token = default);
|
Task<string> RegisterFileSinkAsync(string targetPath, CancellationToken token = default);
|
||||||
Task<IReadOnlyList<string>> ListSinksAsync(CancellationToken token = default);
|
Task<bool> RemoveFileSinkAsync(string targetPath, CancellationToken token = default);
|
||||||
Task<string> RegisterFileSinkAsync(string targetPath, CancellationToken token = default);
|
|
||||||
Task<bool> RemoveFileSinkAsync(string targetPath, CancellationToken token = default);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,20 +3,34 @@ using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace INetMock.Client.Audit
|
namespace INetMock.Client.Audit;
|
||||||
{
|
|
||||||
public interface IEventReader<T> : IDisposable, IAsyncDisposable where T : EventDetails, new()
|
|
||||||
{
|
|
||||||
IAsyncEnumerable<Event<T>> ReadAllAsync(CancellationToken token = default);
|
|
||||||
|
|
||||||
/// <summary>
|
public interface IEventReader : IDisposable, IAsyncDisposable
|
||||||
///
|
{
|
||||||
/// </summary>
|
IAsyncEnumerable<Event> ReadAllAsync(CancellationToken token = default);
|
||||||
/// <param name="token"></param>
|
|
||||||
/// <returns>
|
/// <summary>
|
||||||
/// An event as long as underlying stream has data.
|
///
|
||||||
/// When the end of the stream has been reached it will return null.
|
/// </summary>
|
||||||
/// </returns>
|
/// <param name="token"></param>
|
||||||
Task<Event<T>?> ReadAsync(CancellationToken token = default);
|
/// <returns>
|
||||||
}
|
/// An event as long as underlying stream has data.
|
||||||
|
/// When the end of the stream has been reached it will return null.
|
||||||
|
/// </returns>
|
||||||
|
Task<Event?> ReadAsync(CancellationToken token = default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IEventReader<T> : IDisposable, IAsyncDisposable where T : EventDetails, new()
|
||||||
|
{
|
||||||
|
IAsyncEnumerable<Event<T>> ReadAllAsync(CancellationToken token = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token"></param>
|
||||||
|
/// <returns>
|
||||||
|
/// An event as long as underlying stream has data.
|
||||||
|
/// When the end of the stream has been reached it will return null.
|
||||||
|
/// </returns>
|
||||||
|
Task<Event<T>?> ReadAsync(CancellationToken token = default);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using Google.Protobuf.Collections;
|
using Google.Protobuf.Collections;
|
||||||
|
|
||||||
namespace INetMock.Client.Audit
|
namespace INetMock.Client.Audit;
|
||||||
|
|
||||||
|
internal class INetMockHttpHeaders : HttpHeaders
|
||||||
{
|
{
|
||||||
internal class INetMockHttpHeaders : HttpHeaders
|
internal INetMockHttpHeaders(MapField<string, HTTPHeaderValue> headers)
|
||||||
{
|
{
|
||||||
internal INetMockHttpHeaders(MapField<string, HTTPHeaderValue> headers)
|
foreach (var (key, values) in headers)
|
||||||
{
|
{
|
||||||
foreach (var (key, values) in headers)
|
if (string.IsNullOrEmpty(key) || values == null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(key) || values == null) continue;
|
continue;
|
||||||
Add(key, values.Values);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Add(key, values.Values);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Inetmock.Audit.V1;
|
||||||
|
|
||||||
namespace INetMock.Client.Audit
|
namespace INetMock.Client.Audit;
|
||||||
|
|
||||||
|
public interface IProtoEventReader : IDisposable, IAsyncDisposable
|
||||||
{
|
{
|
||||||
public interface IProtoEventReader : IDisposable, IAsyncDisposable
|
Task<EventEntity?> ReadAsync(CancellationToken token = default);
|
||||||
{
|
|
||||||
Task<EventEntity?> ReadAsync(CancellationToken token = default);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,33 @@
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
using INetMock.Client.Rpc;
|
|
||||||
|
|
||||||
namespace INetMock.Client.Audit.Serialization
|
namespace INetMock.Client.Audit.Serialization;
|
||||||
|
|
||||||
|
public sealed class EventServerStreamReader : IProtoEventReader
|
||||||
{
|
{
|
||||||
public sealed class EventServerStreamReader : IProtoEventReader
|
private readonly AsyncServerStreamingCall<WatchEventsResponse> _asyncEventStream;
|
||||||
|
|
||||||
|
public EventServerStreamReader(AsyncServerStreamingCall<WatchEventsResponse> asyncEventStream)
|
||||||
{
|
{
|
||||||
private readonly AsyncServerStreamingCall<WatchEventsResponse> _asyncEventStream;
|
_asyncEventStream = asyncEventStream;
|
||||||
|
}
|
||||||
|
|
||||||
public EventServerStreamReader(AsyncServerStreamingCall<WatchEventsResponse> asyncEventStream)
|
public async Task<EventEntity?> ReadAsync(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (!await _asyncEventStream.ResponseStream.MoveNext(token))
|
||||||
{
|
{
|
||||||
_asyncEventStream = asyncEventStream;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EventEntity?> ReadAsync(CancellationToken token = default)
|
return _asyncEventStream.ResponseStream.Current.Entity;
|
||||||
{
|
}
|
||||||
if (!await _asyncEventStream.ResponseStream.MoveNext(token)) return null;
|
|
||||||
return _asyncEventStream.ResponseStream.Current.Entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() => _asyncEventStream.Dispose();
|
public void Dispose() => _asyncEventStream.Dispose();
|
||||||
|
|
||||||
public ValueTask DisposeAsync()
|
public ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
_asyncEventStream.Dispose();
|
_asyncEventStream.Dispose();
|
||||||
return ValueTask.CompletedTask;
|
return ValueTask.CompletedTask;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,44 +3,42 @@ using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace INetMock.Client.Audit.Serialization
|
namespace INetMock.Client.Audit.Serialization;
|
||||||
|
|
||||||
|
public sealed class GenericReader : IEventReader
|
||||||
{
|
{
|
||||||
public sealed class GenericReader : IEventReader<GenericDetails>
|
private readonly IProtoEventReader _reader;
|
||||||
|
|
||||||
|
public GenericReader(IProtoEventReader reader)
|
||||||
{
|
{
|
||||||
private readonly IProtoEventReader _reader;
|
_reader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
public GenericReader(IProtoEventReader reader)
|
public async IAsyncEnumerable<Event> ReadAllAsync(
|
||||||
|
[EnumeratorCancellation] CancellationToken token = default)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
_reader = reader;
|
var ev = await ReadAsync(token);
|
||||||
}
|
if (ev == null)
|
||||||
|
|
||||||
public async IAsyncEnumerable<Event<GenericDetails>> ReadAllAsync(
|
|
||||||
[EnumeratorCancellation] CancellationToken token = default)
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
{
|
||||||
var ev = await ReadAsync(token);
|
yield break;
|
||||||
if (ev == null)
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return ev;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Event<GenericDetails>?> ReadAsync(CancellationToken token = default)
|
yield return ev;
|
||||||
{
|
|
||||||
var entity = await _reader.ReadAsync(token);
|
|
||||||
if (entity == null) return null;
|
|
||||||
return new Event<GenericDetails>(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValueTask DisposeAsync() => _reader.DisposeAsync();
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_reader.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Event?> ReadAsync(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var entity = await _reader.ReadAsync(token);
|
||||||
|
return entity == null ? null : new Event(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync() => _reader.DisposeAsync();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_reader.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,64 +5,63 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Google.Protobuf;
|
using Google.Protobuf;
|
||||||
|
|
||||||
namespace INetMock.Client.Audit.Serialization
|
namespace INetMock.Client.Audit.Serialization;
|
||||||
|
|
||||||
|
public sealed class ProtoReader : IProtoEventReader
|
||||||
{
|
{
|
||||||
public sealed class ProtoReader : IProtoEventReader
|
private readonly MemoryPool<byte> _memoryPool;
|
||||||
|
private readonly Stream _sourceStream;
|
||||||
|
private readonly bool _keepStreamOpen;
|
||||||
|
|
||||||
|
public ProtoReader(Stream sourceStream, bool keepStreamOpen = false)
|
||||||
{
|
{
|
||||||
private readonly MemoryPool<byte> _memoryPool;
|
_memoryPool = MemoryPool<byte>.Shared;
|
||||||
private readonly Stream _sourceStream;
|
_sourceStream = sourceStream;
|
||||||
private readonly bool _keepStreamOpen;
|
_keepStreamOpen = keepStreamOpen;
|
||||||
|
}
|
||||||
|
|
||||||
public ProtoReader(Stream sourceStream, bool keepStreamOpen = false)
|
public async Task<EventEntity?> ReadAsync(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
using var rentedLengthMem = _memoryPool.Rent(4);
|
||||||
|
var lengthMem = rentedLengthMem.Memory.Slice(0, 4);
|
||||||
|
var read = await _sourceStream.ReadAsync(lengthMem, token);
|
||||||
|
if (read != 4)
|
||||||
{
|
{
|
||||||
_memoryPool = MemoryPool<byte>.Shared;
|
return null;
|
||||||
_sourceStream = sourceStream;
|
|
||||||
_keepStreamOpen = keepStreamOpen;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EventEntity?> ReadAsync(CancellationToken token = default)
|
var messageLength = BinaryPrimitives.ReadInt32BigEndian(lengthMem.Span);
|
||||||
|
using var rentedMsgMem = _memoryPool.Rent(messageLength);
|
||||||
|
var msgMem = rentedMsgMem.Memory.Slice(0, messageLength);
|
||||||
|
read = await _sourceStream.ReadAsync(msgMem, token);
|
||||||
|
if (read != messageLength)
|
||||||
{
|
{
|
||||||
using var rentedLengthMem = _memoryPool.Rent(4);
|
return null;
|
||||||
var lengthMem = rentedLengthMem.Memory.Slice(0, 4);
|
|
||||||
var read = await _sourceStream.ReadAsync(lengthMem, token);
|
|
||||||
if (read != 4)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var messageLength = BinaryPrimitives.ReadInt32BigEndian(lengthMem.Span);
|
|
||||||
using var rentedMsgMem = _memoryPool.Rent(messageLength);
|
|
||||||
var msgMem = rentedMsgMem.Memory.Slice(0, messageLength);
|
|
||||||
read = await _sourceStream.ReadAsync(msgMem, token);
|
|
||||||
if (read != messageLength)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var entity = new EventEntity();
|
|
||||||
entity.MergeFrom(msgMem.ToArray());
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
var entity = new EventEntity();
|
||||||
{
|
entity.MergeFrom(msgMem.ToArray());
|
||||||
if (!_keepStreamOpen)
|
|
||||||
{
|
|
||||||
await _sourceStream.DisposeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
_memoryPool.Dispose();
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (!_keepStreamOpen)
|
||||||
|
{
|
||||||
|
await _sourceStream.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
_memoryPool.Dispose();
|
||||||
{
|
}
|
||||||
if (!_keepStreamOpen)
|
|
||||||
{
|
|
||||||
_sourceStream?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_memoryPool?.Dispose();
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_keepStreamOpen)
|
||||||
|
{
|
||||||
|
_sourceStream?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_memoryPool?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,77 +3,80 @@ using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace INetMock.Client.Audit.Serialization
|
namespace INetMock.Client.Audit.Serialization;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures how the TypedReader proceeds with mismatching entities.
|
||||||
|
/// Given a TypedReader<HttpDetails> it can't guarantee that every Event<T> contains a HttpDetails entity.
|
||||||
|
/// Therefore different dropping strategies can be configured.
|
||||||
|
/// </summary>
|
||||||
|
public enum DropMode
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures how the TypedReader proceeds with mismatching entities.
|
/// Drops the whole entity.
|
||||||
/// Given a TypedReader<HttpDetails> it can't guarantee that every Event<T> contains a HttpDetails entity.
|
/// This filters the actual stream for entities supporting the details in question.
|
||||||
/// Therefore different dropping strategies can be configured.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum DropMode
|
DropEntity,
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Drops the whole entity.
|
|
||||||
/// This filters the actual stream for entities supporting the details in question.
|
|
||||||
/// </summary>
|
|
||||||
DropEntity,
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drops only the mismatching details but keeps the event
|
/// Drops only the mismatching details but keeps the event
|
||||||
/// e.g. a TypedReader<HttpDetails> would still contain DNS events but no details about them
|
/// e.g. a TypedReader<HttpDetails> would still contain DNS events but no details about them
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DropDetails
|
DropDetails
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TypedReader<T> : IEventReader<T> where T : EventDetails, new()
|
public sealed class TypedReader<T> : IEventReader<T> where T : EventDetails, new()
|
||||||
{
|
{
|
||||||
private readonly IProtoEventReader _reader;
|
private readonly IProtoEventReader _reader;
|
||||||
private readonly DropMode _dropMode;
|
private readonly DropMode _dropMode;
|
||||||
|
|
||||||
public TypedReader(IProtoEventReader reader, DropMode dropMode = DropMode.DropDetails)
|
public TypedReader(IProtoEventReader reader, DropMode dropMode = DropMode.DropDetails)
|
||||||
{
|
{
|
||||||
_reader = reader;
|
_reader = reader;
|
||||||
_dropMode = dropMode;
|
_dropMode = dropMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<Event<T>> ReadAllAsync([EnumeratorCancellation] CancellationToken token = default)
|
public async IAsyncEnumerable<Event<T>> ReadAllAsync([EnumeratorCancellation] CancellationToken token = default)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var ev = await ReadAsync(token);
|
var ev = await ReadAsync(token);
|
||||||
if (ev == null)
|
if (ev == null)
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return ev;
|
yield return ev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Event<T>?> ReadAsync(CancellationToken token = default)
|
public async Task<Event<T>?> ReadAsync(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var entity = await _reader.ReadAsync(token);
|
var entity = await _reader.ReadAsync(token);
|
||||||
if (entity == null) return null;
|
if (entity == null)
|
||||||
var parsed = new Event<T>(entity);
|
{
|
||||||
|
return null;
|
||||||
var canReturn = (_dropMode, parsed.Details) switch
|
}
|
||||||
{
|
|
||||||
(DropMode.DropDetails, null) => true,
|
var parsed = new Event<T>(entity);
|
||||||
(DropMode.DropEntity, null) => false,
|
|
||||||
(_, _) => true
|
var canReturn = (_dropMode, parsed.Details) switch
|
||||||
};
|
{
|
||||||
|
(DropMode.DropDetails, null) => true,
|
||||||
if (canReturn)
|
(DropMode.DropEntity, null) => false,
|
||||||
{
|
(_, _) => true
|
||||||
return parsed;
|
};
|
||||||
}
|
|
||||||
} while (true);
|
if (canReturn)
|
||||||
}
|
{
|
||||||
|
return parsed;
|
||||||
public void Dispose() => _reader.Dispose();
|
}
|
||||||
|
} while (true);
|
||||||
public ValueTask DisposeAsync() => _reader.DisposeAsync();
|
}
|
||||||
}
|
|
||||||
|
public void Dispose() => _reader.Dispose();
|
||||||
|
|
||||||
|
public ValueTask DisposeAsync() => _reader.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,38 +3,37 @@ using System.Net.Http;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
|
|
||||||
namespace INetMock.Client.Grpc
|
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)
|
internal static class ChannelFactory
|
||||||
|
{
|
||||||
|
internal static GrpcChannel ForAddress(Uri uri, GrpcChannelOptions options) =>
|
||||||
|
uri.Scheme.ToLowerInvariant() switch
|
||||||
{
|
{
|
||||||
var endpoint = new UnixDomainSocketEndPoint(path);
|
"unix" => ForUnixSocket(uri.AbsolutePath, options),
|
||||||
options.HttpHandler = new SocketsHttpHandler
|
_ => GrpcChannel.ForAddress(uri, options)
|
||||||
|
};
|
||||||
|
|
||||||
|
private static GrpcChannel ForUnixSocket(string path, GrpcChannelOptions options)
|
||||||
|
{
|
||||||
|
var endpoint = new UnixDomainSocketEndPoint(path);
|
||||||
|
options.HttpHandler = new SocketsHttpHandler
|
||||||
|
{
|
||||||
|
ConnectCallback = async (_, cancellationToken) =>
|
||||||
{
|
{
|
||||||
ConnectCallback = async (_, cancellationToken) =>
|
var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified);
|
await socket.ConnectAsync(endpoint, cancellationToken).ConfigureAwait(false);
|
||||||
try
|
return new NetworkStream(socket, true);
|
||||||
{
|
|
||||||
await socket.ConnectAsync(endpoint, cancellationToken).ConfigureAwait(false);
|
|
||||||
return new NetworkStream(socket, true);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
socket.Dispose();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
catch
|
||||||
return GrpcChannel.ForAddress("http://localhost", options);
|
{
|
||||||
}
|
socket.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return GrpcChannel.ForAddress("http://localhost", options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,30 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Google.Protobuf" Version="3.19.0" />
|
<Using Include="Inetmock.Audit.V1" />
|
||||||
<PackageReference Include="Grpc.Net.Client" Version="2.40.0" />
|
<Using Include="Inetmock.Rpc.V1" />
|
||||||
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.40.0" />
|
</ItemGroup>
|
||||||
<PackageReference Include="Grpc.Tools" Version="2.41.1">
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Google.Protobuf" Version="3.19.3" />
|
||||||
|
<PackageReference Include="Grpc.Net.Client" Version="2.42.0" />
|
||||||
|
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.42.0" />
|
||||||
|
<PackageReference Include="Grpc.Tools" Version="2.43.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Protobuf Include="proto/audit/v1/event_entity.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
<Protobuf Include="proto/audit/v1/dhcp_details.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
||||||
<Protobuf Include="proto/audit/v1/dns_details.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
<Protobuf Include="proto/audit/v1/dns_details.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
||||||
|
<Protobuf Include="proto/audit/v1/event_entity.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
||||||
<Protobuf Include="proto/audit/v1/http_details.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
<Protobuf Include="proto/audit/v1/http_details.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
||||||
<Protobuf Include="proto/rpc/v1/audit.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
<Protobuf Include="proto/rpc/v1/audit.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
||||||
<Protobuf Include="proto/rpc/v1/health.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
<Protobuf Include="proto/rpc/v1/endpoint.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
||||||
<Protobuf Include="proto/rpc/v1/pcap.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
<Protobuf Include="proto/rpc/v1/pcap.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
||||||
|
<Protobuf Include="proto/rpc/v1/pprof.proto" GrpcServices="Client" AdditionalImportDirs="./proto/" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -7,82 +7,80 @@ using System.Threading.Tasks;
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
using Grpc.Net.Client;
|
using Grpc.Net.Client;
|
||||||
using INetMock.Client.Grpc;
|
using ChannelFactory = INetMock.Client.Grpc.ChannelFactory;
|
||||||
using INetMock.Client.Rpc;
|
|
||||||
|
|
||||||
namespace INetMock.Client.PCAP.Client
|
namespace INetMock.Client.PCAP.Client;
|
||||||
|
|
||||||
|
public class PcapApiClient : IPcapApiClient
|
||||||
{
|
{
|
||||||
public class PcapApiClient : IPcapApiClient
|
private readonly PCAPService.PCAPServiceClient _pcapServiceClient;
|
||||||
|
|
||||||
|
public PcapApiClient(string address, GrpcChannelOptions? options = null)
|
||||||
|
: this(new Uri(address), options)
|
||||||
{
|
{
|
||||||
private readonly PCAPService.PCAPServiceClient _pcapServiceClient;
|
}
|
||||||
|
|
||||||
public PcapApiClient(string address, GrpcChannelOptions? options = null)
|
public PcapApiClient(Uri address, GrpcChannelOptions? options = null)
|
||||||
: this(new Uri(address), options)
|
: this(ChannelFactory.ForAddress(address, options ?? new GrpcChannelOptions()))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public PcapApiClient(Uri address, GrpcChannelOptions? options = null)
|
public PcapApiClient(ChannelBase channel)
|
||||||
: this(ChannelFactory.ForAddress(address, options ?? new GrpcChannelOptions()))
|
{
|
||||||
{
|
_pcapServiceClient = new PCAPService.PCAPServiceClient(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PcapApiClient(ChannelBase channel)
|
public async Task<IReadOnlyList<RecordingDevice>> ListAvailableDevicesAsync(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
_pcapServiceClient = new PCAPService.PCAPServiceClient(channel);
|
var devices = await _pcapServiceClient.ListAvailableDevicesAsync(new(), Metadata.Empty, null, token);
|
||||||
}
|
return devices.AvailableDevices
|
||||||
|
.Select(d => new RecordingDevice(
|
||||||
public async Task<IReadOnlyList<RecordingDevice>> ListAvailableDevicesAsync(CancellationToken token = default)
|
d.Name,
|
||||||
{
|
d.Addresses
|
||||||
var devices = await _pcapServiceClient.ListAvailableDevicesAsync(new(), Metadata.Empty, null, token);
|
.Select(addr => new IPAddress(addr.Span))
|
||||||
return devices.AvailableDevices
|
.ToList()
|
||||||
.Select(d => new RecordingDevice(
|
|
||||||
d.Name,
|
|
||||||
d.Addresses
|
|
||||||
.Select(addr => new IPAddress(addr.Span))
|
|
||||||
.ToList()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.ToList();
|
)
|
||||||
}
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<Subscription>> ListActiveRecordingsAsync(CancellationToken token = default)
|
public async Task<IReadOnlyList<Subscription>> ListActiveRecordingsAsync(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var recordings = await _pcapServiceClient.ListActiveRecordingsAsync(new(), Metadata.Empty, null, token);
|
||||||
|
return recordings.Subscriptions
|
||||||
|
.Select(consumerKey => new Subscription(consumerKey))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> StartPcapFileRecordingAsync(RecordingRequest request,
|
||||||
|
CancellationToken token = default)
|
||||||
|
{
|
||||||
|
var clientRequest = new StartPCAPFileRecordingRequest
|
||||||
{
|
{
|
||||||
var recordings = await _pcapServiceClient.ListActiveRecordingsAsync(new(), Metadata.Empty, null, token);
|
Device = request.Device,
|
||||||
return recordings.Subscriptions
|
Promiscuous = request.Promiscuous,
|
||||||
.Select(consumerKey => new Subscription(consumerKey))
|
TargetPath = request.TargetPath,
|
||||||
.ToList();
|
ReadTimeout = Duration.FromTimeSpan(request.ReadTimeout)
|
||||||
}
|
};
|
||||||
|
|
||||||
public async Task<string> StartPcapFileRecordingAsync(RecordingRequest request,
|
var result = await _pcapServiceClient.StartPCAPFileRecordingAsync(
|
||||||
CancellationToken token = default)
|
clientRequest,
|
||||||
{
|
Metadata.Empty,
|
||||||
var clientRequest = new StartPCAPFileRecordingRequest
|
null,
|
||||||
{
|
token);
|
||||||
Device = request.Device,
|
|
||||||
Promiscuous = request.Promiscuous,
|
|
||||||
TargetPath = request.TargetPath,
|
|
||||||
ReadTimeout = Duration.FromTimeSpan(request.ReadTimeout)
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = await _pcapServiceClient.StartPCAPFileRecordingAsync(
|
return result.ResolvedPath;
|
||||||
clientRequest,
|
}
|
||||||
Metadata.Empty,
|
|
||||||
null,
|
|
||||||
token);
|
|
||||||
|
|
||||||
return result.ResolvedPath;
|
public async Task<bool> StopPcapFileRecordingAsync(string consumerKey, CancellationToken token = default)
|
||||||
}
|
{
|
||||||
|
var clientRequest = new StopPCAPFileRecordingRequest { ConsumerKey = consumerKey };
|
||||||
public async Task<bool> StopPcapFileRecording(string consumerKey, CancellationToken token = default)
|
var result = await _pcapServiceClient.StopPCAPFileRecordingAsync(
|
||||||
{
|
clientRequest,
|
||||||
var clientRequest = new StopPCAPFileRecordingRequest {ConsumerKey = consumerKey};
|
Metadata.Empty,
|
||||||
var result = await _pcapServiceClient.StopPCAPFileRecordingAsync(
|
null,
|
||||||
clientRequest,
|
token
|
||||||
Metadata.Empty,
|
);
|
||||||
null,
|
return result.Removed;
|
||||||
token
|
|
||||||
);
|
|
||||||
return result.Removed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,12 @@ using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace INetMock.Client.PCAP
|
namespace INetMock.Client.PCAP;
|
||||||
|
|
||||||
|
public interface IPcapApiClient
|
||||||
{
|
{
|
||||||
public interface IPcapApiClient
|
Task<IReadOnlyList<RecordingDevice>> ListAvailableDevicesAsync(CancellationToken token = default);
|
||||||
{
|
Task<IReadOnlyList<Subscription>> ListActiveRecordingsAsync(CancellationToken token = default);
|
||||||
Task<IReadOnlyList<RecordingDevice>> ListAvailableDevicesAsync(CancellationToken token = default);
|
Task<string> StartPcapFileRecordingAsync(RecordingRequest request, CancellationToken token = default);
|
||||||
Task<IReadOnlyList<Subscription>> ListActiveRecordingsAsync(CancellationToken token = default);
|
Task<bool> StopPcapFileRecordingAsync(string consumerKey, CancellationToken token = default);
|
||||||
Task<string> StartPcapFileRecordingAsync(RecordingRequest request, CancellationToken token = default);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,52 +2,51 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
namespace INetMock.Client.PCAP
|
namespace INetMock.Client.PCAP;
|
||||||
|
|
||||||
|
public record RecordingDevice(string Name, IReadOnlyList<IPAddress> Addresses);
|
||||||
|
|
||||||
|
public record Subscription
|
||||||
{
|
{
|
||||||
public record RecordingDevice(string Name, IReadOnlyList<IPAddress> Addresses);
|
public Subscription(string consumerKey)
|
||||||
|
|
||||||
public record Subscription
|
|
||||||
{
|
{
|
||||||
public Subscription(string consumerKey)
|
ConsumerKey = consumerKey;
|
||||||
{
|
(ConsumerName, Device) = SplitConsumerKey(consumerKey);
|
||||||
ConsumerKey = consumerKey;
|
|
||||||
(ConsumerName, Device) = SplitConsumerKey(consumerKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Subscription(string consumerKey, string consumerName, string device)
|
|
||||||
{
|
|
||||||
ConsumerKey = consumerKey;
|
|
||||||
ConsumerName = consumerName;
|
|
||||||
Device = device;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ConsumerKey { get; init; }
|
|
||||||
public string ConsumerName { get; init; }
|
|
||||||
public string Device { get; init; }
|
|
||||||
|
|
||||||
private static (string name, string key) SplitConsumerKey(string consumerKey)
|
|
||||||
{
|
|
||||||
var splitIndex = consumerKey.IndexOf(':');
|
|
||||||
if (splitIndex < 0)
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException(
|
|
||||||
nameof(consumerKey),
|
|
||||||
"The given consumer key could not be split into components"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (consumerKey[(splitIndex + 1)..], consumerKey[..splitIndex]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public record RecordingRequest(string Device, string TargetPath, bool Promiscuous = false)
|
public Subscription(string consumerKey, string consumerName, string device)
|
||||||
{
|
{
|
||||||
public RecordingRequest(string device, string targetPath, bool promiscuous, TimeSpan readTimeout) : this(device,
|
ConsumerKey = consumerKey;
|
||||||
targetPath, promiscuous)
|
ConsumerName = consumerName;
|
||||||
|
Device = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ConsumerKey { get; init; }
|
||||||
|
public string ConsumerName { get; init; }
|
||||||
|
public string Device { get; init; }
|
||||||
|
|
||||||
|
private static (string name, string key) SplitConsumerKey(string consumerKey)
|
||||||
|
{
|
||||||
|
var splitIndex = consumerKey.IndexOf(':');
|
||||||
|
if (splitIndex < 0)
|
||||||
{
|
{
|
||||||
ReadTimeout = readTimeout;
|
throw new ArgumentOutOfRangeException(
|
||||||
|
nameof(consumerKey),
|
||||||
|
"The given consumer key could not be split into components"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSpan ReadTimeout { get; } = TimeSpan.FromSeconds(30);
|
return (consumerKey[(splitIndex + 1)..], consumerKey[..splitIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record RecordingRequest(string Device, string TargetPath, bool Promiscuous = false)
|
||||||
|
{
|
||||||
|
public RecordingRequest(string device, string targetPath, bool promiscuous, TimeSpan readTimeout) : this(device,
|
||||||
|
targetPath, promiscuous)
|
||||||
|
{
|
||||||
|
ReadTimeout = readTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan ReadTimeout { get; } = TimeSpan.FromSeconds(30);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using INetMock.Client.Audit;
|
||||||
|
using INetMock.Client.Audit.Serialization;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace INetMock.Client.IntegrationTest.Audit.Serialization;
|
||||||
|
|
||||||
|
public class GenericReaderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Test_ReadAllAsync_AuditFile()
|
||||||
|
{
|
||||||
|
await using var auditFileStream = File.OpenRead(Path.Join("testdata", "test.ima"));
|
||||||
|
await using IEventReader reader = new GenericReader(new ProtoReader(auditFileStream));
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
await foreach (var ev in reader.ReadAllAsync())
|
||||||
|
{
|
||||||
|
Assert.NotNull(ev);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.True(count > 0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using INetMock.Client.Audit;
|
||||||
|
using INetMock.Client.Audit.Serialization;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace INetMock.Client.IntegrationTest.Audit.Serialization;
|
||||||
|
|
||||||
|
public class TypedReaderTest
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public async Task Test_ReadAllAsync_DropDetails_AuditFile_HTTPEvents()
|
||||||
|
{
|
||||||
|
await using var fileStream = File.OpenRead(Path.Join("testdata", "test.ima"));
|
||||||
|
await using IEventReader<HttpDetails> httpReader = new TypedReader<HttpDetails>(new ProtoReader(fileStream));
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
await foreach (var ev in httpReader.ReadAllAsync())
|
||||||
|
{
|
||||||
|
Assert.NotNull(ev);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
Assert.True(count > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Test_ReadAllAsync_DropEntity_AuditFile_HTTPEvents()
|
||||||
|
{
|
||||||
|
await using var fileStream = File.OpenRead(Path.Join("testdata", "test.ima"));
|
||||||
|
await using IEventReader<HttpDetails> httpReader = new TypedReader<HttpDetails>(new ProtoReader(fileStream), DropMode.DropEntity);
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
await foreach (var ev in httpReader.ReadAllAsync())
|
||||||
|
{
|
||||||
|
Assert.NotNull(ev);
|
||||||
|
Assert.NotNull(ev.Details);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
Assert.True(count > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Test_ReadAllAsync_DropDetails_AuditFile_DNSEvents()
|
||||||
|
{
|
||||||
|
await using var fileStream = File.OpenRead(Path.Join("testdata", "test.ima"));
|
||||||
|
await using IEventReader<DnsDetails> httpReader = new TypedReader<DnsDetails>(new ProtoReader(fileStream));
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
await foreach (var ev in httpReader.ReadAllAsync())
|
||||||
|
{
|
||||||
|
Assert.NotNull(ev);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
Assert.True(count > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Test_ReadAllAsync_DropEntity_AuditFile_DNSEvents()
|
||||||
|
{
|
||||||
|
await using var fileStream = File.OpenRead(Path.Join("testdata", "test.ima"));
|
||||||
|
await using IEventReader<DnsDetails> httpReader = new TypedReader<DnsDetails>(new ProtoReader(fileStream), DropMode.DropEntity);
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
await foreach (var ev in httpReader.ReadAllAsync())
|
||||||
|
{
|
||||||
|
Assert.NotNull(ev);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
Assert.True(count > 0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,36 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace INetMock.Client.IntegrationTest
|
|
||||||
{
|
|
||||||
internal static class DockerEndpoint
|
|
||||||
{
|
|
||||||
private const string DockerHostEnvName = "DOCKER_HOST";
|
|
||||||
private const string DockerTlsVerifyEnvName = "DOCKER_TLS_VERIFY";
|
|
||||||
|
|
||||||
internal static string DetermineFromEnv() => DetermineEndpoint(
|
|
||||||
Environment.GetEnvironmentVariable(DockerHostEnvName),
|
|
||||||
Environment.GetEnvironmentVariable(DockerTlsVerifyEnvName)
|
|
||||||
);
|
|
||||||
|
|
||||||
internal static string DetermineEndpoint(string? dockerHost, string? tlsVerify)
|
|
||||||
{
|
|
||||||
dockerHost ??= "";
|
|
||||||
var dockerTlsVerify = int.TryParse(tlsVerify, out var verify) && verify == 1;
|
|
||||||
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
|
||||||
return (dockerHost, dockerTlsVerify, isWindows) switch
|
|
||||||
{
|
|
||||||
("", _, true) => "npipe://./pipe/docker_engine",
|
|
||||||
("", _, false) => "unix:/var/run/docker.sock",
|
|
||||||
(_, false, _) => dockerHost,
|
|
||||||
(var h, true, _) when h.StartsWith("tcp") => h.Replace(
|
|
||||||
"tcp",
|
|
||||||
"https",
|
|
||||||
true,
|
|
||||||
CultureInfo.InvariantCulture),
|
|
||||||
_ => "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace INetMock.Client.IntegrationTest
|
|
||||||
{
|
|
||||||
public class DockerEndpointTests
|
|
||||||
{
|
|
||||||
[Theory]
|
|
||||||
[InlineData(null, null, "unix:/var/run/docker.sock")]
|
|
||||||
[InlineData("tcp://docker:2375", null, "tcp://docker:2375")]
|
|
||||||
[InlineData("tcp://docker:2375", "0", "tcp://docker:2375")]
|
|
||||||
[InlineData("http://docker:2375", null, "http://docker:2375")]
|
|
||||||
[InlineData("http://docker:2375", "0", "http://docker:2375")]
|
|
||||||
[InlineData("tcp://docker:2376", "1", "https://docker:2376")]
|
|
||||||
[InlineData("https://docker:2376", "0", "https://docker:2376")]
|
|
||||||
public void DetermineDockerEndpoint_Input_ExpectedOutput(string? dockerHost, string? tlsVerify, string expected)
|
|
||||||
{
|
|
||||||
var actual = DockerEndpoint.DetermineEndpoint(dockerHost, tlsVerify);
|
|
||||||
|
|
||||||
Assert.Equal(expected, actual);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +1,23 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<LangVersion>10</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DotNet.Testcontainers" Version="1.4.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="coverlet.collector" Version="1.3.0">
|
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
@ -26,4 +27,10 @@
|
||||||
<ProjectReference Include="..\..\src\INetMock.Client\INetMock.Client.csproj" />
|
<ProjectReference Include="..\..\src\INetMock.Client\INetMock.Client.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="testdata\test.ima">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
19
tests/INetMock.Client.IntegrationTest/INetMockFixture.cs
Normal file
19
tests/INetMock.Client.IntegrationTest/INetMockFixture.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace INetMock.Client.IntegrationTest;
|
||||||
|
|
||||||
|
public class INetMockFixture
|
||||||
|
{
|
||||||
|
private const string DefaultINetMockSocketPath = "unix:///var/run/inetmock/inetmock.sock";
|
||||||
|
|
||||||
|
public INetMockFixture()
|
||||||
|
{
|
||||||
|
INetMockSocketPath = Environment.GetEnvironmentVariable("INETMOCK_SOCKET") ?? DefaultINetMockSocketPath;
|
||||||
|
if (INetMockSocketPath.StartsWith("http:"))
|
||||||
|
{
|
||||||
|
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string INetMockSocketPath { get; }
|
||||||
|
}
|
|
@ -1,42 +0,0 @@
|
||||||
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<TestcontainersContainer>()
|
|
||||||
.WithImage("inetmock-root:latest")
|
|
||||||
.WithCommand("serve")
|
|
||||||
.WithPortBinding(80, true)
|
|
||||||
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(80))
|
|
||||||
.WithDockerEndpoint(DockerEndpoint.DetermineFromEnv())
|
|
||||||
.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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +1,63 @@
|
||||||
|
using System.Linq;
|
||||||
using INetMock.Client.PCAP;
|
using INetMock.Client.PCAP;
|
||||||
using INetMock.Client.PCAP.Client;
|
using INetMock.Client.PCAP.Client;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace INetMock.Client.IntegrationTest.PCAP.Client
|
namespace INetMock.Client.IntegrationTest.PCAP.Client;
|
||||||
|
|
||||||
|
public class PcapApiClientTests : IClassFixture<INetMockFixture>
|
||||||
{
|
{
|
||||||
public class PcapApiClientTests : IClassFixture<INetMockServerFixture>
|
private readonly ITestOutputHelper _outputHelper;
|
||||||
|
private readonly IPcapApiClient _apiClient;
|
||||||
|
|
||||||
|
public PcapApiClientTests(ITestOutputHelper testOutputHelper, INetMockFixture inetMockFixture)
|
||||||
{
|
{
|
||||||
private readonly ITestOutputHelper _outputHelper;
|
_outputHelper = testOutputHelper;
|
||||||
private readonly IPcapApiClient _apiClient;
|
_apiClient = new PcapApiClient(inetMockFixture.INetMockSocketPath);
|
||||||
|
}
|
||||||
|
|
||||||
public PcapApiClientTests(ITestOutputHelper testOutputHelper, INetMockServerFixture inetmockFixture)
|
[Fact]
|
||||||
|
public async void ListAvailableDevicesAsync_RunningContainer_AtLeastLoopbackDevice()
|
||||||
|
{
|
||||||
|
var devs = await _apiClient.ListAvailableDevicesAsync();
|
||||||
|
|
||||||
|
foreach (var (name, _) in devs)
|
||||||
{
|
{
|
||||||
_outputHelper = testOutputHelper;
|
_outputHelper.WriteLine(name);
|
||||||
_apiClient = new PcapApiClient(inetmockFixture.SocketPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
Assert.Contains(devs, device => device.Name.Equals("lo"));
|
||||||
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()
|
||||||
|
{
|
||||||
|
var recordingDevice = (await _apiClient.ListAvailableDevicesAsync()).FirstOrDefault();
|
||||||
|
if (recordingDevice == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
var targetPath = $"/tmp/{recordingDevice.Name}_record.pcap";
|
||||||
public async void ListActiveRecordingsAsync_NoRecordingsRunning_EmptyResult()
|
await _apiClient.StartPcapFileRecordingAsync(new(recordingDevice.Name, targetPath));
|
||||||
|
|
||||||
|
var subscriptions = await _apiClient.ListActiveRecordingsAsync();
|
||||||
|
Assert.Contains(subscriptions, subscription =>
|
||||||
|
subscription.Device.Equals(recordingDevice.Name) &&
|
||||||
|
subscription.ConsumerName.Equals(targetPath)
|
||||||
|
);
|
||||||
|
foreach (var subscription in subscriptions)
|
||||||
{
|
{
|
||||||
var recordings = await _apiClient.ListActiveRecordingsAsync();
|
await _apiClient.StopPcapFileRecordingAsync(subscription.ConsumerKey);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
tests/INetMock.Client.IntegrationTest/testdata/test.ima
vendored
Normal file
BIN
tests/INetMock.Client.IntegrationTest/testdata/test.ima
vendored
Normal file
Binary file not shown.
|
@ -4,55 +4,56 @@ using INetMock.Client.Audit.Serialization;
|
||||||
using INetMock.Client.Test.Hex;
|
using INetMock.Client.Test.Hex;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace INetMock.Client.Test.Audit.Serialization
|
namespace INetMock.Client.Test.Audit.Serialization;
|
||||||
|
|
||||||
|
public class GenericReaderTest
|
||||||
{
|
{
|
||||||
public class GenericReaderTest
|
private const string HttpEventPayload = "000000a7120b088092b8c398feffffff01180120022a047f00000132047f00000138d8fc0140504a3308041224544c535f45434448455f45434453415f574954485f4145535f3235365f4342435f5348411a096c6f63616c686f7374a2014c080112096c6f63616c686f73741a15687474703a2f2f6c6f63616c686f73742f6173646622084854545020312e312a1c0a0641636365707412120a106170706c69636174696f6e2f6a736f6e";
|
||||||
|
private const string DnsEventPayload = "0000004e120b088092b8c398feffffff01180220012a100000000000000000000000000000000132100000000000000000000000000000000138d8fc014050aa0110120e0801120a6769746c61622e636f6d";
|
||||||
|
|
||||||
|
private readonly byte[] _httpEventPayloadBytes;
|
||||||
|
private readonly byte[] _dnsEventPayloadBytes;
|
||||||
|
|
||||||
|
public GenericReaderTest()
|
||||||
{
|
{
|
||||||
private const string HttpEventPayload = "000000e2120b088092b8c398feffffff01180120022a047f00000132047f00000138d8fc0140504a3308041224544c535f45434448455f45434453415f574954485f4145535f3235365f4342435f5348411a096c6f63616c686f73745287010a37747970652e676f6f676c65617069732e636f6d2f696e65746d6f636b2e61756469742e76312e4854545044657461696c73456e74697479124c080112096c6f63616c686f73741a15687474703a2f2f6c6f63616c686f73742f6173646622084854545020312e312a1c0a0641636365707412120a106170706c69636174696f6e2f6a736f6e";
|
_httpEventPayloadBytes = HttpEventPayload.HexToByteArray();
|
||||||
private const string DnsEventPayload = "0000003b120b088092b8c398feffffff01180220012a100000000000000000000000000000000132100000000000000000000000000000000138d8fc014050";
|
_dnsEventPayloadBytes = DnsEventPayload.HexToByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
private readonly byte[] _httpEventPayloadBytes;
|
[Fact]
|
||||||
private readonly byte[] _dnsEventPayloadBytes;
|
public async void TestRead_HttpEvent_Success()
|
||||||
|
{
|
||||||
public GenericReaderTest()
|
await using var protoReader = new ProtoReader(new MemoryStream(_httpEventPayloadBytes));
|
||||||
|
await using IEventReader reader = new GenericReader(protoReader);
|
||||||
|
await foreach (var ev in reader.ReadAllAsync())
|
||||||
{
|
{
|
||||||
_httpEventPayloadBytes = HttpEventPayload.HexToByteArray();
|
Assert.NotNull(ev);
|
||||||
_dnsEventPayloadBytes = DnsEventPayload.HexToByteArray();
|
Assert.NotNull(ev.Details);
|
||||||
|
|
||||||
|
Assert.NotNull(ev.DetailsAs<HttpDetails>());
|
||||||
|
Assert.Null(ev.DetailsAs<DnsDetails>());
|
||||||
|
|
||||||
|
var httpEvent = (Event<HttpDetails>)ev;
|
||||||
|
Assert.NotNull(httpEvent);
|
||||||
|
Assert.NotNull(httpEvent.Details);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void TestRead_HttpEvent_Success()
|
public async void TestRead_DnsEvent_Success()
|
||||||
|
{
|
||||||
|
await using var memStream = new MemoryStream(_dnsEventPayloadBytes);
|
||||||
|
await using var protoReader = new ProtoReader(new MemoryStream(_dnsEventPayloadBytes));
|
||||||
|
await using IEventReader reader = new GenericReader(protoReader);
|
||||||
|
await foreach (var ev in reader.ReadAllAsync())
|
||||||
{
|
{
|
||||||
await using var protoReader = new ProtoReader(new MemoryStream(_httpEventPayloadBytes));
|
Assert.NotNull(ev);
|
||||||
await using IEventReader<GenericDetails> reader = new GenericReader(protoReader);
|
Assert.NotNull(ev.Details);
|
||||||
await foreach (var ev in reader.ReadAllAsync())
|
Assert.NotNull(ev.DetailsAs<DnsDetails>());
|
||||||
{
|
|
||||||
Assert.NotNull(ev);
|
|
||||||
|
|
||||||
Assert.True(ev.CanConvert<HttpDetails>());
|
var dnsEvent = (Event<DnsDetails>)ev;
|
||||||
Assert.False(ev.CanConvert<DnsDetails>());
|
Assert.NotNull(dnsEvent);
|
||||||
|
Assert.NotNull(dnsEvent.Details);
|
||||||
var httpEvent = (Event<HttpDetails>?) ev;
|
|
||||||
var dnsEvent = (Event<DnsDetails>?) ev;
|
|
||||||
|
|
||||||
Assert.NotNull(httpEvent);
|
|
||||||
Assert.Null(dnsEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async void TestRead_DnsEvent_Success()
|
|
||||||
{
|
|
||||||
await using var memStream = new MemoryStream(_dnsEventPayloadBytes);
|
|
||||||
await using var protoReader = new ProtoReader(new MemoryStream(_dnsEventPayloadBytes));
|
|
||||||
await using IEventReader<GenericDetails> reader = new GenericReader(protoReader);
|
|
||||||
await foreach (var ev in reader.ReadAllAsync())
|
|
||||||
{
|
|
||||||
Assert.NotNull(ev);
|
|
||||||
Assert.True(ev.CanConvert<DnsDetails>());
|
|
||||||
var dnsEvent = (Event<DnsDetails>?) ev;
|
|
||||||
Assert.NotNull(dnsEvent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,42 +4,41 @@ using INetMock.Client.Audit.Serialization;
|
||||||
using INetMock.Client.Test.Hex;
|
using INetMock.Client.Test.Hex;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace INetMock.Client.Test.Audit.Serialization
|
namespace INetMock.Client.Test.Audit.Serialization;
|
||||||
|
|
||||||
|
public class TypedReaderTest
|
||||||
{
|
{
|
||||||
public class TypedReaderTest
|
private const string HttpEventPayload = "000000e5120b088092b8c398feffffff01180120022a047f00000132047f00000138d8fc0140504a3308041224544c535f45434448455f45434453415f574954485f4145535f3235365f4342435f5348411a096c6f63616c686f7374528a010a3c747970652e676f6f676c65617069732e636f6d2f696e65746d6f636b2e61756469742e64657461696c732e4854545044657461696c73456e74697479124a12096c6f63616c686f73741a15687474703a2f2f6c6f63616c686f73742f6173646622084854545020312e312a1c0a0641636365707412120a106170706c69636174696f6e2f6a736f6e";
|
||||||
|
private const string DnsEventPayload = "0000003b120b088092b8c398feffffff01180120012a100000000000000000000000000000000132100000000000000000000000000000000138d8fc014050";
|
||||||
|
|
||||||
|
private readonly byte[] _httpEventPayloadBytes;
|
||||||
|
private readonly byte[] _dnsEventPayloadBytes;
|
||||||
|
|
||||||
|
public TypedReaderTest()
|
||||||
{
|
{
|
||||||
private const string HttpEventPayload = "000000e5120b088092b8c398feffffff01180120022a047f00000132047f00000138d8fc0140504a3308041224544c535f45434448455f45434453415f574954485f4145535f3235365f4342435f5348411a096c6f63616c686f7374528a010a3c747970652e676f6f676c65617069732e636f6d2f696e65746d6f636b2e61756469742e64657461696c732e4854545044657461696c73456e74697479124a12096c6f63616c686f73741a15687474703a2f2f6c6f63616c686f73742f6173646622084854545020312e312a1c0a0641636365707412120a106170706c69636174696f6e2f6a736f6e";
|
_httpEventPayloadBytes = HttpEventPayload.HexToByteArray();
|
||||||
private const string DnsEventPayload = "0000003b120b088092b8c398feffffff01180120012a100000000000000000000000000000000132100000000000000000000000000000000138d8fc014050";
|
_dnsEventPayloadBytes = DnsEventPayload.HexToByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
private readonly byte[] _httpEventPayloadBytes;
|
[Fact]
|
||||||
private readonly byte[] _dnsEventPayloadBytes;
|
public async void TestRead_HttpEvent_Success()
|
||||||
|
{
|
||||||
public TypedReaderTest()
|
await using var protoReader = new ProtoReader(new MemoryStream(_httpEventPayloadBytes));
|
||||||
|
await using IEventReader<HttpDetails> reader = new TypedReader<HttpDetails>(protoReader, DropMode.DropEntity);
|
||||||
|
await foreach (var ev in reader.ReadAllAsync())
|
||||||
{
|
{
|
||||||
_httpEventPayloadBytes = HttpEventPayload.HexToByteArray();
|
Assert.NotNull(ev);
|
||||||
_dnsEventPayloadBytes = DnsEventPayload.HexToByteArray();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async void TestRead_HttpEvent_Success()
|
public async void TestRead_DnsEvent_Success()
|
||||||
|
{
|
||||||
|
await using var protoReader = new ProtoReader(new MemoryStream(_dnsEventPayloadBytes));
|
||||||
|
await using IEventReader<DnsDetails> reader = new TypedReader<DnsDetails>(protoReader, DropMode.DropEntity);
|
||||||
|
await foreach (var ev in reader.ReadAllAsync())
|
||||||
{
|
{
|
||||||
await using var protoReader = new ProtoReader(new MemoryStream(_httpEventPayloadBytes));
|
Assert.NotNull(ev);
|
||||||
await using IEventReader<HttpDetails> reader = new TypedReader<HttpDetails>(protoReader, DropMode.DropEntity);
|
|
||||||
await foreach (var ev in reader.ReadAllAsync())
|
|
||||||
{
|
|
||||||
Assert.NotNull(ev);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async void TestRead_DnsEvent_Success()
|
|
||||||
{
|
|
||||||
await using var protoReader = new ProtoReader(new MemoryStream(_dnsEventPayloadBytes));
|
|
||||||
await using IEventReader<DnsDetails> reader = new TypedReader<DnsDetails>(protoReader, DropMode.DropEntity);
|
|
||||||
await foreach (var ev in reader.ReadAllAsync())
|
|
||||||
{
|
|
||||||
Assert.NotNull(ev);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace INetMock.Client.Test.Hex
|
namespace INetMock.Client.Test.Hex;
|
||||||
|
|
||||||
|
public static class Converter
|
||||||
{
|
{
|
||||||
public static class Converter
|
public static byte[] HexToByteArray(this string hex)
|
||||||
{
|
{
|
||||||
public static byte[] HexToByteArray(this string hex) {
|
return Enumerable.Range(0, hex.Length)
|
||||||
return Enumerable.Range(0, hex.Length)
|
.Where(x => x % 2 == 0)
|
||||||
.Where(x => x % 2 == 0)
|
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
|
||||||
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
|
.ToArray();
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="coverlet.collector" Version="3.0.2">
|
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
using INetMock.Client.PCAP;
|
using INetMock.Client.PCAP;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace INetMock.Client.Test.PCAP
|
namespace INetMock.Client.Test.PCAP;
|
||||||
{
|
|
||||||
public class SubscriptionTests
|
|
||||||
{
|
|
||||||
[Theory]
|
|
||||||
[InlineData("lo:test.pcap", "test.pcap", "lo")]
|
|
||||||
public void Constructor_ConsumerKey_SplitIntoComponents(string key, string expectedName, string expectedDevice)
|
|
||||||
{
|
|
||||||
var sub = new Subscription(key);
|
|
||||||
|
|
||||||
Assert.Equal(sub, new Subscription(key, expectedName, expectedDevice));
|
public class SubscriptionTests
|
||||||
}
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("lo:test.pcap", "test.pcap", "lo")]
|
||||||
|
public void Constructor_ConsumerKey_SplitIntoComponents(string key, string expectedName, string expectedDevice)
|
||||||
|
{
|
||||||
|
var sub = new Subscription(key);
|
||||||
|
|
||||||
|
Assert.Equal(sub, new Subscription(key, expectedName, expectedDevice));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue