From 99373378b40ea49cb7129bce46a5208f975486e0 Mon Sep 17 00:00:00 2001 From: Peet-HD <19489992+Peet-HD@users.noreply.github.com> Date: Mon, 4 Mar 2024 00:44:42 +0000 Subject: [PATCH 1/7] Added Gitlab Container to test against the gitlab API. --- Testcontainers.sln | 9 +- src/Testcontainers.Gitlab/.editorconfig | 1 + src/Testcontainers.Gitlab/GitlabBuilder.cs | 78 +++++++++++++++ .../GitlabConfiguration.cs | 53 +++++++++++ src/Testcontainers.Gitlab/GitlabContainer.cs | 95 +++++++++++++++++++ .../Models/PersonalAccessToken.cs | 11 +++ .../Models/PersonalAccessTokenScopes.cs | 18 ++++ .../RegexPatterns/GitlabRegex.cs | 18 ++++ .../Testcontainers.Gitlab.csproj | 12 +++ src/Testcontainers.Gitlab/Usings.cs | 15 +++ 10 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 src/Testcontainers.Gitlab/.editorconfig create mode 100644 src/Testcontainers.Gitlab/GitlabBuilder.cs create mode 100644 src/Testcontainers.Gitlab/GitlabConfiguration.cs create mode 100644 src/Testcontainers.Gitlab/GitlabContainer.cs create mode 100644 src/Testcontainers.Gitlab/Models/PersonalAccessToken.cs create mode 100644 src/Testcontainers.Gitlab/Models/PersonalAccessTokenScopes.cs create mode 100644 src/Testcontainers.Gitlab/RegexPatterns/GitlabRegex.cs create mode 100644 src/Testcontainers.Gitlab/Testcontainers.Gitlab.csproj create mode 100644 src/Testcontainers.Gitlab/Usings.cs diff --git a/Testcontainers.sln b/Testcontainers.sln index 9595905ed..36d85e98b 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 @@ -195,6 +195,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tests", "tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Tests", "tests\Testcontainers.WebDriver.Tests\Testcontainers.WebDriver.Tests.csproj", "{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Gitlab", "src\Testcontainers.Gitlab\Testcontainers.Gitlab.csproj", "{B3857615-7DD1-41D2-BA74-938DA4469E5E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -568,6 +570,10 @@ Global {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.Build.0 = Release|Any CPU + {B3857615-7DD1-41D2-BA74-938DA4469E5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3857615-7DD1-41D2-BA74-938DA4469E5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3857615-7DD1-41D2-BA74-938DA4469E5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3857615-7DD1-41D2-BA74-938DA4469E5E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} @@ -661,5 +667,6 @@ Global {1A1983E6-5297-435F-B467-E8E1F11277D6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {B3857615-7DD1-41D2-BA74-938DA4469E5E} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} EndGlobalSection EndGlobal diff --git a/src/Testcontainers.Gitlab/.editorconfig b/src/Testcontainers.Gitlab/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/src/Testcontainers.Gitlab/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/src/Testcontainers.Gitlab/GitlabBuilder.cs b/src/Testcontainers.Gitlab/GitlabBuilder.cs new file mode 100644 index 000000000..9996ab4d0 --- /dev/null +++ b/src/Testcontainers.Gitlab/GitlabBuilder.cs @@ -0,0 +1,78 @@ + namespace Testcontainers.Gitlab; + + /// + [PublicAPI] + public sealed class GitlabBuilder : ContainerBuilder + { + /// + /// This is the default image for gitlab community edition. + /// + public const string GitlabImage = "gitlab/gitlab-ce"; + /// + /// This port is used for http communication to gitlab instance. + /// + + public const ushort GitlabHttpPort = 80; + /// + /// This port is used for ssh communication to gitlab instance. + /// + public const ushort GitlabSshPort = 22; + + /// + /// Initializes a new instance of the class. + /// + public GitlabBuilder() + : this(new GitlabConfiguration()) + { + DockerResourceConfiguration = Init().DockerResourceConfiguration; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + private GitlabBuilder(GitlabConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + DockerResourceConfiguration = resourceConfiguration; + } + + /// + protected override GitlabConfiguration DockerResourceConfiguration { get; } + + /// + public override GitlabContainer Build() + { + Validate(); + return new GitlabContainer(DockerResourceConfiguration, TestcontainersSettings.Logger); + } + + /// + protected override GitlabBuilder Init() + { + return base.Init() + .WithImage(GitlabImage) + .WithPortBinding(GitlabHttpPort, true) + .WithPortBinding(GitlabSshPort, true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilFileExists("/etc/gitlab/initial_root_password")) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(80)) + .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(22)) + .WithWaitStrategy(Wait.ForUnixContainer().UntilContainerIsHealthy()) + .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => request.ForPath("/users/sign_in").ForStatusCode(HttpStatusCode.OK))); + } + + /// + protected override void Validate() => base.Validate(); + + /// + protected override GitlabBuilder Clone(IResourceConfiguration resourceConfiguration) + => Merge(DockerResourceConfiguration, new GitlabConfiguration(resourceConfiguration)); + + /// + protected override GitlabBuilder Clone(IContainerConfiguration resourceConfiguration) + => Merge(DockerResourceConfiguration, new GitlabConfiguration(resourceConfiguration)); + + /// + protected override GitlabBuilder Merge(GitlabConfiguration oldValue, GitlabConfiguration newValue) + => new(new GitlabConfiguration(oldValue, newValue)); + } \ No newline at end of file diff --git a/src/Testcontainers.Gitlab/GitlabConfiguration.cs b/src/Testcontainers.Gitlab/GitlabConfiguration.cs new file mode 100644 index 000000000..64e73b523 --- /dev/null +++ b/src/Testcontainers.Gitlab/GitlabConfiguration.cs @@ -0,0 +1,53 @@ +namespace Testcontainers.Gitlab; + + /// + [PublicAPI] + public sealed class GitlabConfiguration : ContainerConfiguration + { + /// + /// Initializes a new instance of the class. + /// + public GitlabConfiguration() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public GitlabConfiguration(IResourceConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public GitlabConfiguration(IContainerConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public GitlabConfiguration(GitlabConfiguration resourceConfiguration) + : this(new GitlabConfiguration(), resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The old Docker resource configuration. + /// The new Docker resource configuration. + public GitlabConfiguration(GitlabConfiguration oldValue, GitlabConfiguration newValue) + : base(oldValue, newValue) + { + } + } \ No newline at end of file diff --git a/src/Testcontainers.Gitlab/GitlabContainer.cs b/src/Testcontainers.Gitlab/GitlabContainer.cs new file mode 100644 index 000000000..0551c58ed --- /dev/null +++ b/src/Testcontainers.Gitlab/GitlabContainer.cs @@ -0,0 +1,95 @@ +using Testcontainers.Gitlab.Models; +using Testcontainers.Gitlab.RegexPatterns; + +namespace Testcontainers.Gitlab +{ + /// + /// + /// Initializes a new instance of the class. + /// + /// The container configuration. + /// The logger. + [PublicAPI] + public sealed class GitlabContainer(GitlabConfiguration configuration, ILogger logger) : DockerContainer(configuration, logger) + { + /// + /// The password for the root user + /// + public string RootPassword { get; private set; } + + public override async Task StartAsync(CancellationToken ct = default) + { + await base.StartAsync(ct); + RootPassword = await GetInitialRootPassword(); + } + + /// + /// Generate a personal access token. + /// + /// The personal access token to create. + /// + /// + public async Task GenerateAccessToken(PersonalAccessToken pat) + { + var scope = "[" + '\'' + pat.Scope.ToString().Replace(", ", "\', \'") + '\'' + "]"; + + var command = $"token = User.find_by_username('{pat.User}')" + + $".personal_access_tokens" + + $".create(name: '{pat.Name}', scopes: {scope}, expires_at: {pat.ExpirationInDays}.days.from_now); " + + $"puts token.cleartext_tokens"; + + var tokenCommand = new List{ + { "gitlab-rails" }, + { "runner" }, + { command } + }; + + ExecResult tokenResult = await ExecAsync(tokenCommand); + + string token = ""; + if (tokenResult.ExitCode == 0) + { + var match = GitlabRegex.GitlabPersonalAccessToken.Match(tokenResult.Stdout); + token = match.Value; + } + else + { + throw new DataMisalignedException("Stderr: " + tokenResult.Stderr + "|" + "Stdout: " + tokenResult.Stdout); + } + pat.TokenInternal = token; + return pat; + } + + /// + /// Generate a personal access token. + /// + /// Name of + /// + /// + /// + /// + public async Task GenerateAccessToken(string user, PersonalAccessTokenScopes scope, string name = "", int expirationInDays = 365) + => await GenerateAccessToken(new PersonalAccessToken + { + Name = string.IsNullOrWhiteSpace(name) ? Guid.NewGuid().ToString() : name, + User = user, + Scope = scope, + ExpirationInDays = expirationInDays + }); + + + /// + /// This method returns the initial root password for the gitlab root user. + /// + /// Returns the initial root password generated by gitlab. + private async Task GetInitialRootPassword() + { + var byteArray = await ReadFileAsync("/etc/gitlab/initial_root_password"); + + string fileContent = Encoding.UTF8.GetString(byteArray); + var match = GitlabRegex.GitlabRootPassword.Match(fileContent); + string[] splits = match.Value.Split(' '); + return splits[1]; + } +} +} \ No newline at end of file diff --git a/src/Testcontainers.Gitlab/Models/PersonalAccessToken.cs b/src/Testcontainers.Gitlab/Models/PersonalAccessToken.cs new file mode 100644 index 000000000..e64e52087 --- /dev/null +++ b/src/Testcontainers.Gitlab/Models/PersonalAccessToken.cs @@ -0,0 +1,11 @@ +namespace Testcontainers.Gitlab.Models; + +public record PersonalAccessToken +{ + public string Name { get; set; } = string.Empty; + public string User { get; set; } = string.Empty; + public PersonalAccessTokenScopes Scope { get; set; } = PersonalAccessTokenScopes.None; + public int ExpirationInDays { get; set; } = 365; + internal string TokenInternal { get; set; } = string.Empty; + public string Token => TokenInternal; +} diff --git a/src/Testcontainers.Gitlab/Models/PersonalAccessTokenScopes.cs b/src/Testcontainers.Gitlab/Models/PersonalAccessTokenScopes.cs new file mode 100644 index 000000000..2bf72803e --- /dev/null +++ b/src/Testcontainers.Gitlab/Models/PersonalAccessTokenScopes.cs @@ -0,0 +1,18 @@ +namespace Testcontainers.Gitlab.Models +{ + [Flags] + public enum PersonalAccessTokenScopes + { + None = 0, + api = 2, + read_api = 4, + read_user = 8, + read_repository = 16, + write_repository = 32, + read_registry = 64, + write_registry = 128, + create_runner = 256, + ai_features = 512, + k8s_proxy = 1024, + } +} \ No newline at end of file diff --git a/src/Testcontainers.Gitlab/RegexPatterns/GitlabRegex.cs b/src/Testcontainers.Gitlab/RegexPatterns/GitlabRegex.cs new file mode 100644 index 000000000..378965f8e --- /dev/null +++ b/src/Testcontainers.Gitlab/RegexPatterns/GitlabRegex.cs @@ -0,0 +1,18 @@ +using System.Text.RegularExpressions; + +namespace Testcontainers.Gitlab.RegexPatterns; + +/// +/// This class contains regex patterns that are used in gitlab. +/// +public static partial class GitlabRegex +{ + /// + /// GitLab Personal Access Token + /// + public static Regex GitlabPersonalAccessToken => new(@"glpat-[0-9a-zA-Z_\-]{20}", RegexOptions.Compiled | RegexOptions.IgnoreCase); + /// + /// Regex Pattern to find the gitlab root password + /// + public static Regex GitlabRootPassword => new(@"Password: .*", RegexOptions.Compiled | RegexOptions.IgnoreCase); +} \ No newline at end of file diff --git a/src/Testcontainers.Gitlab/Testcontainers.Gitlab.csproj b/src/Testcontainers.Gitlab/Testcontainers.Gitlab.csproj new file mode 100644 index 000000000..8b2ed72c6 --- /dev/null +++ b/src/Testcontainers.Gitlab/Testcontainers.Gitlab.csproj @@ -0,0 +1,12 @@ + + + net6.0;net8.0;netstandard2.0;netstandard2.1 + latest + + + + + + + + \ No newline at end of file diff --git a/src/Testcontainers.Gitlab/Usings.cs b/src/Testcontainers.Gitlab/Usings.cs new file mode 100644 index 000000000..0e135aab0 --- /dev/null +++ b/src/Testcontainers.Gitlab/Usings.cs @@ -0,0 +1,15 @@ +global using System; +global using System.Collections.Generic; +global using System.IO; +global using System.Net; +global using System.Linq; +global using System.Text; +global using System.Threading; +global using System.Threading.Tasks; +global using Docker.DotNet.Models; +global using DotNet.Testcontainers; +global using DotNet.Testcontainers.Builders; +global using DotNet.Testcontainers.Configurations; +global using DotNet.Testcontainers.Containers; +global using JetBrains.Annotations; +global using Microsoft.Extensions.Logging; \ No newline at end of file From ef082600d80e1c26e03d5b6aa12ec606dab549d3 Mon Sep 17 00:00:00 2001 From: Peet-HD <19489992+Peet-HD@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:35:57 +0000 Subject: [PATCH 2/7] Improved documentation --- src/Testcontainers.Gitlab/GitlabContainer.cs | 153 +++++++++--------- .../Models/PersonalAccessToken.cs | 25 +++ 2 files changed, 101 insertions(+), 77 deletions(-) diff --git a/src/Testcontainers.Gitlab/GitlabContainer.cs b/src/Testcontainers.Gitlab/GitlabContainer.cs index 0551c58ed..ba9b02e55 100644 --- a/src/Testcontainers.Gitlab/GitlabContainer.cs +++ b/src/Testcontainers.Gitlab/GitlabContainer.cs @@ -1,95 +1,94 @@ using Testcontainers.Gitlab.Models; using Testcontainers.Gitlab.RegexPatterns; -namespace Testcontainers.Gitlab +namespace Testcontainers.Gitlab; + +/// +/// +/// Initializes a new instance of the class. +/// +/// The container configuration. +/// The logger. +[PublicAPI] +public sealed class GitlabContainer(GitlabConfiguration configuration, ILogger logger) : DockerContainer(configuration, logger) { - /// /// - /// Initializes a new instance of the class. + /// The password for the root user /// - /// The container configuration. - /// The logger. - [PublicAPI] - public sealed class GitlabContainer(GitlabConfiguration configuration, ILogger logger) : DockerContainer(configuration, logger) - { - /// - /// The password for the root user - /// - public string RootPassword { get; private set; } + public string RootPassword { get; private set; } - public override async Task StartAsync(CancellationToken ct = default) - { - await base.StartAsync(ct); - RootPassword = await GetInitialRootPassword(); - } + public override async Task StartAsync(CancellationToken ct = default) + { + await base.StartAsync(ct); + RootPassword = await GetInitialRootPassword(); + } - /// - /// Generate a personal access token. - /// - /// The personal access token to create. - /// - /// - public async Task GenerateAccessToken(PersonalAccessToken pat) - { - var scope = "[" + '\'' + pat.Scope.ToString().Replace(", ", "\', \'") + '\'' + "]"; + /// + /// Generate a personal access token. + /// + /// The personal access token to create. + /// + /// + public async Task GenerateAccessToken(PersonalAccessToken pat) + { + var scope = "[" + '\'' + pat.Scope.ToString().Replace(", ", "\', \'") + '\'' + "]"; - var command = $"token = User.find_by_username('{pat.User}')" + - $".personal_access_tokens" + - $".create(name: '{pat.Name}', scopes: {scope}, expires_at: {pat.ExpirationInDays}.days.from_now); " + - $"puts token.cleartext_tokens"; + var command = $"token = User.find_by_username('{pat.User}')" + + $".personal_access_tokens" + + $".create(name: '{pat.Name}', scopes: {scope}, expires_at: {pat.ExpirationInDays}.days.from_now); " + + $"puts token.cleartext_tokens"; - var tokenCommand = new List{ - { "gitlab-rails" }, - { "runner" }, - { command } - }; + var tokenCommand = new List{ + { "gitlab-rails" }, + { "runner" }, + { command } + }; - ExecResult tokenResult = await ExecAsync(tokenCommand); + ExecResult tokenResult = await ExecAsync(tokenCommand); - string token = ""; - if (tokenResult.ExitCode == 0) - { - var match = GitlabRegex.GitlabPersonalAccessToken.Match(tokenResult.Stdout); - token = match.Value; - } - else - { - throw new DataMisalignedException("Stderr: " + tokenResult.Stderr + "|" + "Stdout: " + tokenResult.Stdout); - } - pat.TokenInternal = token; - return pat; + string token = ""; + if (tokenResult.ExitCode == 0) + { + var match = GitlabRegex.GitlabPersonalAccessToken.Match(tokenResult.Stdout); + token = match.Value; } + else + { + throw new DataMisalignedException("Stderr: " + tokenResult.Stderr + "|" + "Stdout: " + tokenResult.Stdout); + } + pat.TokenInternal = token; + return pat; + } - /// - /// Generate a personal access token. - /// - /// Name of - /// - /// - /// - /// - public async Task GenerateAccessToken(string user, PersonalAccessTokenScopes scope, string name = "", int expirationInDays = 365) - => await GenerateAccessToken(new PersonalAccessToken - { - Name = string.IsNullOrWhiteSpace(name) ? Guid.NewGuid().ToString() : name, - User = user, - Scope = scope, - ExpirationInDays = expirationInDays - }); + /// + /// Generate a personal access token. + /// + /// Name of the personal access token. If left empty a GUID will be used. + /// The name of the user that owns this personal access token. + /// The scope that will be given to the token. + /// Days until the tokens expires. + /// + public async Task GenerateAccessToken(string user, PersonalAccessTokenScopes scope, string name = "", int expirationInDays = 365) + => await GenerateAccessToken(new PersonalAccessToken + { + Name = string.IsNullOrWhiteSpace(name) ? Guid.NewGuid().ToString() : name, + User = user, + Scope = scope, + ExpirationInDays = expirationInDays + }); - /// - /// This method returns the initial root password for the gitlab root user. - /// - /// Returns the initial root password generated by gitlab. - private async Task GetInitialRootPassword() - { - var byteArray = await ReadFileAsync("/etc/gitlab/initial_root_password"); + /// + /// This method returns the initial root password for the gitlab root user. + /// + /// Returns the initial root password generated by gitlab. + private async Task GetInitialRootPassword() + { + var byteArray = await ReadFileAsync("/etc/gitlab/initial_root_password"); - string fileContent = Encoding.UTF8.GetString(byteArray); - var match = GitlabRegex.GitlabRootPassword.Match(fileContent); - string[] splits = match.Value.Split(' '); - return splits[1]; - } -} + string fileContent = Encoding.UTF8.GetString(byteArray); + var match = GitlabRegex.GitlabRootPassword.Match(fileContent); + string[] splits = match.Value.Split(' '); + return splits[1]; + } } \ No newline at end of file diff --git a/src/Testcontainers.Gitlab/Models/PersonalAccessToken.cs b/src/Testcontainers.Gitlab/Models/PersonalAccessToken.cs index e64e52087..0c7d53576 100644 --- a/src/Testcontainers.Gitlab/Models/PersonalAccessToken.cs +++ b/src/Testcontainers.Gitlab/Models/PersonalAccessToken.cs @@ -1,11 +1,36 @@ namespace Testcontainers.Gitlab.Models; +/// +/// The personal access token that is used to authenticate against the API from gitlab. +/// public record PersonalAccessToken { + /// Name of the personal access token. If left empty a GUID will be used. + /// The name of the user that owns this personal access token. + /// The scope that will be given to the token. + /// Days until the tokens expires. + /// + /// Name of the personal access token. If left empty a GUID will be used. + /// public string Name { get; set; } = string.Empty; + /// + /// The name of the user that owns this personal access token. + /// public string User { get; set; } = string.Empty; + /// + /// The scope that will be given to the token. + /// public PersonalAccessTokenScopes Scope { get; set; } = PersonalAccessTokenScopes.None; + /// + /// Days until the tokens expires. + /// public int ExpirationInDays { get; set; } = 365; + /// + /// Internal token that is used to set the token publically. + /// internal string TokenInternal { get; set; } = string.Empty; + /// + /// The token that will be generated. + /// public string Token => TokenInternal; } From 5c4e69d56d7cd89fadc634e5fccbc0125e1e6983 Mon Sep 17 00:00:00 2001 From: Peet-HD <19489992+Peet-HD@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:47:58 +0000 Subject: [PATCH 3/7] Add test project for gitlab --- Testcontainers.sln | 7 +++++ .../Testcontainers.Gitlab.Tests/.editorconfig | 1 + .../GitlabContainerTests.cs | 29 +++++++++++++++++++ .../Testcontainers.Gitlab.Tests.csproj | 18 ++++++++++++ tests/Testcontainers.Gitlab.Tests/Usings.cs | 6 ++++ 5 files changed, 61 insertions(+) create mode 100644 tests/Testcontainers.Gitlab.Tests/.editorconfig create mode 100644 tests/Testcontainers.Gitlab.Tests/GitlabContainerTests.cs create mode 100644 tests/Testcontainers.Gitlab.Tests/Testcontainers.Gitlab.Tests.csproj create mode 100644 tests/Testcontainers.Gitlab.Tests/Usings.cs diff --git a/Testcontainers.sln b/Testcontainers.sln index 36d85e98b..e0c1aaee9 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -197,6 +197,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Gitlab", "src\Testcontainers.Gitlab\Testcontainers.Gitlab.csproj", "{B3857615-7DD1-41D2-BA74-938DA4469E5E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Gitlab.Tests", "tests\Testcontainers.Gitlab.Tests\Testcontainers.Gitlab.Tests.csproj", "{185C65BB-0F79-40D0-A940-7CBE7C8E6A30}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -574,6 +576,10 @@ Global {B3857615-7DD1-41D2-BA74-938DA4469E5E}.Debug|Any CPU.Build.0 = Debug|Any CPU {B3857615-7DD1-41D2-BA74-938DA4469E5E}.Release|Any CPU.ActiveCfg = Release|Any CPU {B3857615-7DD1-41D2-BA74-938DA4469E5E}.Release|Any CPU.Build.0 = Release|Any CPU + {185C65BB-0F79-40D0-A940-7CBE7C8E6A30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {185C65BB-0F79-40D0-A940-7CBE7C8E6A30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {185C65BB-0F79-40D0-A940-7CBE7C8E6A30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {185C65BB-0F79-40D0-A940-7CBE7C8E6A30}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} @@ -668,5 +674,6 @@ Global {27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {B3857615-7DD1-41D2-BA74-938DA4469E5E} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} + {185C65BB-0F79-40D0-A940-7CBE7C8E6A30} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} EndGlobalSection EndGlobal diff --git a/tests/Testcontainers.Gitlab.Tests/.editorconfig b/tests/Testcontainers.Gitlab.Tests/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/tests/Testcontainers.Gitlab.Tests/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/tests/Testcontainers.Gitlab.Tests/GitlabContainerTests.cs b/tests/Testcontainers.Gitlab.Tests/GitlabContainerTests.cs new file mode 100644 index 000000000..f1d388c62 --- /dev/null +++ b/tests/Testcontainers.Gitlab.Tests/GitlabContainerTests.cs @@ -0,0 +1,29 @@ +using Testcontainers.Gitlab.Models; +using Testcontainers.Gitlab.RegexPatterns; +using System.Net.Http; +using System; + +namespace Testcontainers.Gitlab; + +public sealed class GitlabContainerTest : IAsyncLifetime +{ + private readonly GitlabContainer _gitlabContainer = new GitlabBuilder().Build(); + + public Task InitializeAsync() + { + return _gitlabContainer.StartAsync(); + } + + public Task DisposeAsync() + { + return _gitlabContainer.DisposeAsync().AsTask(); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task ConnectionStateReturnsOpen() + { + var pat = await _gitlabContainer.GenerateAccessToken("root",PersonalAccessTokenScopes.api); + Assert.True(!string.IsNullOrEmpty(pat.Token)); + } +} \ No newline at end of file diff --git a/tests/Testcontainers.Gitlab.Tests/Testcontainers.Gitlab.Tests.csproj b/tests/Testcontainers.Gitlab.Tests/Testcontainers.Gitlab.Tests.csproj new file mode 100644 index 000000000..d2907edff --- /dev/null +++ b/tests/Testcontainers.Gitlab.Tests/Testcontainers.Gitlab.Tests.csproj @@ -0,0 +1,18 @@ + + + net8.0 + false + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Testcontainers.Gitlab.Tests/Usings.cs b/tests/Testcontainers.Gitlab.Tests/Usings.cs new file mode 100644 index 000000000..02acb4398 --- /dev/null +++ b/tests/Testcontainers.Gitlab.Tests/Usings.cs @@ -0,0 +1,6 @@ +global using System.Data; +global using System.Data.Common; +global using System.Threading.Tasks; +global using DotNet.Testcontainers.Commons; +global using Microsoft.Data.SqlClient; +global using Xunit; \ No newline at end of file From 2fe34db3c4c7fd99bc1847defd4770d59ae3b8d9 Mon Sep 17 00:00:00 2001 From: Peet-HD <19489992+Peet-HD@users.noreply.github.com> Date: Tue, 2 Apr 2024 08:45:34 +0000 Subject: [PATCH 4/7] Remove logger and adjusted test --- src/Testcontainers.Gitlab/GitlabBuilder.cs | 2 +- src/Testcontainers.Gitlab/GitlabContainer.cs | 3 +-- .../GitlabContainerTests.cs | 18 ++++++++++++++++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Testcontainers.Gitlab/GitlabBuilder.cs b/src/Testcontainers.Gitlab/GitlabBuilder.cs index 9996ab4d0..750772552 100644 --- a/src/Testcontainers.Gitlab/GitlabBuilder.cs +++ b/src/Testcontainers.Gitlab/GitlabBuilder.cs @@ -44,7 +44,7 @@ private GitlabBuilder(GitlabConfiguration resourceConfiguration) public override GitlabContainer Build() { Validate(); - return new GitlabContainer(DockerResourceConfiguration, TestcontainersSettings.Logger); + return new GitlabContainer(DockerResourceConfiguration); } /// diff --git a/src/Testcontainers.Gitlab/GitlabContainer.cs b/src/Testcontainers.Gitlab/GitlabContainer.cs index ba9b02e55..2244cc195 100644 --- a/src/Testcontainers.Gitlab/GitlabContainer.cs +++ b/src/Testcontainers.Gitlab/GitlabContainer.cs @@ -8,9 +8,8 @@ namespace Testcontainers.Gitlab; /// Initializes a new instance of the class. /// /// The container configuration. -/// The logger. [PublicAPI] -public sealed class GitlabContainer(GitlabConfiguration configuration, ILogger logger) : DockerContainer(configuration, logger) +public sealed class GitlabContainer(GitlabConfiguration configuration) : DockerContainer(configuration) { /// /// The password for the root user diff --git a/tests/Testcontainers.Gitlab.Tests/GitlabContainerTests.cs b/tests/Testcontainers.Gitlab.Tests/GitlabContainerTests.cs index f1d388c62..fa2a36597 100644 --- a/tests/Testcontainers.Gitlab.Tests/GitlabContainerTests.cs +++ b/tests/Testcontainers.Gitlab.Tests/GitlabContainerTests.cs @@ -19,11 +19,25 @@ public Task DisposeAsync() return _gitlabContainer.DisposeAsync().AsTask(); } + // [Fact] + // [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + // public async Task ConnectionStateReturnsOpen() + // { + // var pat = await _gitlabContainer.GenerateAccessToken("root",PersonalAccessTokenScopes.api); + // Assert.True(!string.IsNullOrEmpty(pat.Token)); + // } + [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public async Task ConnectionStateReturnsOpen() + public async Task GetUser() { var pat = await _gitlabContainer.GenerateAccessToken("root",PersonalAccessTokenScopes.api); - Assert.True(!string.IsNullOrEmpty(pat.Token)); + + using var client = new HttpClient(); + client.DefaultRequestHeaders.Add("PRIVATE-TOKEN", pat.Token); + var port = _gitlabContainer.GetMappedPublicPort(80); + var result = await client.GetAsync($"http://localhost:{port}/api/v4/user"); + + Assert.True(result.IsSuccessStatusCode); } } \ No newline at end of file From aaeca1f211420505d6b94101bfb9a5270b4ea939 Mon Sep 17 00:00:00 2001 From: Peet-HD <19489992+Peet-HD@users.noreply.github.com> Date: Tue, 30 Apr 2024 00:25:42 +0200 Subject: [PATCH 5/7] set specific version for image Set version of docker image to 16.11.1 --- src/Testcontainers.Gitlab/GitlabBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Testcontainers.Gitlab/GitlabBuilder.cs b/src/Testcontainers.Gitlab/GitlabBuilder.cs index 750772552..46b4b6200 100644 --- a/src/Testcontainers.Gitlab/GitlabBuilder.cs +++ b/src/Testcontainers.Gitlab/GitlabBuilder.cs @@ -5,9 +5,9 @@ namespace Testcontainers.Gitlab; public sealed class GitlabBuilder : ContainerBuilder { /// - /// This is the default image for gitlab community edition. + /// This is the default image for gitlab community edition in version 16.11.1 . /// - public const string GitlabImage = "gitlab/gitlab-ce"; + public const string GitlabImage = "gitlab/gitlab-ce:16.11.1-ce.0"; /// /// This port is used for http communication to gitlab instance. /// From 321477f70cf5f8477b2d62e1659617f7d120453a Mon Sep 17 00:00:00 2001 From: Peet-HD <19489992+Peet-HD@users.noreply.github.com> Date: Tue, 30 Apr 2024 00:31:13 +0200 Subject: [PATCH 6/7] Fix WaitStrategy Fixed WaitStrategy so that all conditions are met and are not overwritten by latest condition. --- src/Testcontainers.Gitlab/GitlabBuilder.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Testcontainers.Gitlab/GitlabBuilder.cs b/src/Testcontainers.Gitlab/GitlabBuilder.cs index 46b4b6200..dcd411b86 100644 --- a/src/Testcontainers.Gitlab/GitlabBuilder.cs +++ b/src/Testcontainers.Gitlab/GitlabBuilder.cs @@ -54,11 +54,11 @@ protected override GitlabBuilder Init() .WithImage(GitlabImage) .WithPortBinding(GitlabHttpPort, true) .WithPortBinding(GitlabSshPort, true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilFileExists("/etc/gitlab/initial_root_password")) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(80)) - .WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(22)) - .WithWaitStrategy(Wait.ForUnixContainer().UntilContainerIsHealthy()) - .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => request.ForPath("/users/sign_in").ForStatusCode(HttpStatusCode.OK))); + .WithWaitStrategy(Wait.ForUnixContainer() + .UntilPortIsAvailable(80) + .UntilPortIsAvailable(22) + .UntilContainerIsHealthy() + .UntilHttpRequestIsSucceeded(request => request.ForPath("/users/sign_in").ForStatusCode(HttpStatusCode.OK))); } /// From b8db1fb2a70e4590016452f56049e6ca219399f3 Mon Sep 17 00:00:00 2001 From: Peet-HD <19489992+Peet-HD@users.noreply.github.com> Date: Wed, 1 May 2024 21:07:14 +0200 Subject: [PATCH 7/7] Made root password configurable Add support to set the root password for gitlab --- src/Testcontainers.Gitlab/GitlabBuilder.cs | 147 ++++++++++-------- .../GitlabConfiguration.cs | 118 ++++++++------ src/Testcontainers.Gitlab/GitlabContainer.cs | 26 +--- .../GitlabContainerTests.cs | 12 +- 4 files changed, 151 insertions(+), 152 deletions(-) diff --git a/src/Testcontainers.Gitlab/GitlabBuilder.cs b/src/Testcontainers.Gitlab/GitlabBuilder.cs index dcd411b86..571d75df6 100644 --- a/src/Testcontainers.Gitlab/GitlabBuilder.cs +++ b/src/Testcontainers.Gitlab/GitlabBuilder.cs @@ -1,78 +1,89 @@ - namespace Testcontainers.Gitlab; - - /// - [PublicAPI] - public sealed class GitlabBuilder : ContainerBuilder - { - /// - /// This is the default image for gitlab community edition in version 16.11.1 . - /// - public const string GitlabImage = "gitlab/gitlab-ce:16.11.1-ce.0"; - /// - /// This port is used for http communication to gitlab instance. - /// +namespace Testcontainers.Gitlab; - public const ushort GitlabHttpPort = 80; - /// - /// This port is used for ssh communication to gitlab instance. - /// - public const ushort GitlabSshPort = 22; +/// +[PublicAPI] +public sealed class GitlabBuilder : ContainerBuilder +{ + /// + /// This is the default image for gitlab community edition in version 16.11.1 . + /// + public const string GitlabImage = "gitlab/gitlab-ce:16.11.1-ce.0"; + /// + /// This port is used for http communication to gitlab instance. + /// - /// - /// Initializes a new instance of the class. - /// - public GitlabBuilder() - : this(new GitlabConfiguration()) - { - DockerResourceConfiguration = Init().DockerResourceConfiguration; - } + public const ushort GitlabHttpPort = 80; + /// + /// This port is used for ssh communication to gitlab instance. + /// + public const ushort GitlabSshPort = 22; - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - private GitlabBuilder(GitlabConfiguration resourceConfiguration) - : base(resourceConfiguration) - { - DockerResourceConfiguration = resourceConfiguration; - } + /// + /// Initializes a new instance of the class. + /// + public GitlabBuilder() + : this(new GitlabConfiguration()) + { + DockerResourceConfiguration = Init().DockerResourceConfiguration; + } - /// - protected override GitlabConfiguration DockerResourceConfiguration { get; } + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + private GitlabBuilder(GitlabConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + DockerResourceConfiguration = resourceConfiguration; + } - /// - public override GitlabContainer Build() - { - Validate(); - return new GitlabContainer(DockerResourceConfiguration); - } + /// + protected override GitlabConfiguration DockerResourceConfiguration { get; } - /// - protected override GitlabBuilder Init() - { - return base.Init() - .WithImage(GitlabImage) - .WithPortBinding(GitlabHttpPort, true) - .WithPortBinding(GitlabSshPort, true) - .WithWaitStrategy(Wait.ForUnixContainer() - .UntilPortIsAvailable(80) - .UntilPortIsAvailable(22) - .UntilContainerIsHealthy() - .UntilHttpRequestIsSucceeded(request => request.ForPath("/users/sign_in").ForStatusCode(HttpStatusCode.OK))); - } + /// + public override GitlabContainer Build() + { + Validate(); + return new GitlabContainer(DockerResourceConfiguration); + } - /// - protected override void Validate() => base.Validate(); + /// + protected override GitlabBuilder Init() + { + return base.Init() + .WithImage(GitlabImage) + .WithPortBinding(GitlabHttpPort, true) + .WithPortBinding(GitlabSshPort, true) + .WithEnvironment("GITLAB_ROOT_PASSWORD", DockerResourceConfiguration.Password) + .WithWaitStrategy(Wait.ForUnixContainer() + .UntilPortIsAvailable(80) + .UntilPortIsAvailable(22) + .UntilContainerIsHealthy() + .UntilHttpRequestIsSucceeded(request => request.ForPath("/users/sign_in").ForStatusCode(HttpStatusCode.OK))); + } - /// - protected override GitlabBuilder Clone(IResourceConfiguration resourceConfiguration) - => Merge(DockerResourceConfiguration, new GitlabConfiguration(resourceConfiguration)); + /// + /// Sets the Gitlab password. + /// + /// The Gitlab password. + /// A configured instance of . + public GitlabBuilder WithPassword(string password) + { + return Merge(DockerResourceConfiguration, new GitlabConfiguration(password: password)); + } - /// - protected override GitlabBuilder Clone(IContainerConfiguration resourceConfiguration) - => Merge(DockerResourceConfiguration, new GitlabConfiguration(resourceConfiguration)); + /// + protected override void Validate() => base.Validate(); - /// - protected override GitlabBuilder Merge(GitlabConfiguration oldValue, GitlabConfiguration newValue) - => new(new GitlabConfiguration(oldValue, newValue)); - } \ No newline at end of file + /// + protected override GitlabBuilder Clone(IResourceConfiguration resourceConfiguration) + => Merge(DockerResourceConfiguration, new GitlabConfiguration(resourceConfiguration)); + + /// + protected override GitlabBuilder Clone(IContainerConfiguration resourceConfiguration) + => Merge(DockerResourceConfiguration, new GitlabConfiguration(resourceConfiguration)); + + /// + protected override GitlabBuilder Merge(GitlabConfiguration oldValue, GitlabConfiguration newValue) + => new(new GitlabConfiguration(oldValue, newValue)); +} \ No newline at end of file diff --git a/src/Testcontainers.Gitlab/GitlabConfiguration.cs b/src/Testcontainers.Gitlab/GitlabConfiguration.cs index 64e73b523..12accd51b 100644 --- a/src/Testcontainers.Gitlab/GitlabConfiguration.cs +++ b/src/Testcontainers.Gitlab/GitlabConfiguration.cs @@ -1,53 +1,71 @@ namespace Testcontainers.Gitlab; - /// - [PublicAPI] - public sealed class GitlabConfiguration : ContainerConfiguration +/// +[PublicAPI] +public sealed class GitlabConfiguration : ContainerConfiguration +{ + /// + /// Initializes a new instance of the class. + /// + /// The Gitlab admin password. + public GitlabConfiguration(string password) { - /// - /// Initializes a new instance of the class. - /// - public GitlabConfiguration() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - public GitlabConfiguration(IResourceConfiguration resourceConfiguration) - : base(resourceConfiguration) - { - // Passes the configuration upwards to the base implementations to create an updated immutable copy. - } - - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - public GitlabConfiguration(IContainerConfiguration resourceConfiguration) - : base(resourceConfiguration) - { - // Passes the configuration upwards to the base implementations to create an updated immutable copy. - } - - /// - /// Initializes a new instance of the class. - /// - /// The Docker resource configuration. - public GitlabConfiguration(GitlabConfiguration resourceConfiguration) - : this(new GitlabConfiguration(), resourceConfiguration) - { - // Passes the configuration upwards to the base implementations to create an updated immutable copy. - } - - /// - /// Initializes a new instance of the class. - /// - /// The old Docker resource configuration. - /// The new Docker resource configuration. - public GitlabConfiguration(GitlabConfiguration oldValue, GitlabConfiguration newValue) - : base(oldValue, newValue) - { - } - } \ No newline at end of file + _password = password; + } + + /// + /// Initializes a new instance of the class. + /// + public GitlabConfiguration() + { + _password = Guid.NewGuid().ToString(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public GitlabConfiguration(IResourceConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public GitlabConfiguration(IContainerConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public GitlabConfiguration(GitlabConfiguration resourceConfiguration) + : this(new GitlabConfiguration(), resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The old Docker resource configuration. + /// The new Docker resource configuration. + public GitlabConfiguration(GitlabConfiguration oldValue, GitlabConfiguration newValue) + : base(oldValue, newValue) + { + _password = BuildConfiguration.Combine(oldValue.Password, newValue.Password); + } + + /// + /// Gets the Gitlab admin password. + /// + public string Password => _password; + + private readonly string _password; +} \ No newline at end of file diff --git a/src/Testcontainers.Gitlab/GitlabContainer.cs b/src/Testcontainers.Gitlab/GitlabContainer.cs index 2244cc195..712e0f664 100644 --- a/src/Testcontainers.Gitlab/GitlabContainer.cs +++ b/src/Testcontainers.Gitlab/GitlabContainer.cs @@ -12,15 +12,10 @@ namespace Testcontainers.Gitlab; public sealed class GitlabContainer(GitlabConfiguration configuration) : DockerContainer(configuration) { /// - /// The password for the root user + /// Gets the root password. /// - public string RootPassword { get; private set; } + public string Password => configuration.Password; - public override async Task StartAsync(CancellationToken ct = default) - { - await base.StartAsync(ct); - RootPassword = await GetInitialRootPassword(); - } /// /// Generate a personal access token. @@ -45,7 +40,7 @@ public async Task GenerateAccessToken(PersonalAccessToken p ExecResult tokenResult = await ExecAsync(tokenCommand); - string token = ""; + string token; if (tokenResult.ExitCode == 0) { var match = GitlabRegex.GitlabPersonalAccessToken.Match(tokenResult.Stdout); @@ -75,19 +70,4 @@ public async Task GenerateAccessToken(string user, Personal Scope = scope, ExpirationInDays = expirationInDays }); - - - /// - /// This method returns the initial root password for the gitlab root user. - /// - /// Returns the initial root password generated by gitlab. - private async Task GetInitialRootPassword() - { - var byteArray = await ReadFileAsync("/etc/gitlab/initial_root_password"); - - string fileContent = Encoding.UTF8.GetString(byteArray); - var match = GitlabRegex.GitlabRootPassword.Match(fileContent); - string[] splits = match.Value.Split(' '); - return splits[1]; - } } \ No newline at end of file diff --git a/tests/Testcontainers.Gitlab.Tests/GitlabContainerTests.cs b/tests/Testcontainers.Gitlab.Tests/GitlabContainerTests.cs index fa2a36597..da19a02b6 100644 --- a/tests/Testcontainers.Gitlab.Tests/GitlabContainerTests.cs +++ b/tests/Testcontainers.Gitlab.Tests/GitlabContainerTests.cs @@ -1,7 +1,5 @@ using Testcontainers.Gitlab.Models; -using Testcontainers.Gitlab.RegexPatterns; using System.Net.Http; -using System; namespace Testcontainers.Gitlab; @@ -19,19 +17,11 @@ public Task DisposeAsync() return _gitlabContainer.DisposeAsync().AsTask(); } - // [Fact] - // [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - // public async Task ConnectionStateReturnsOpen() - // { - // var pat = await _gitlabContainer.GenerateAccessToken("root",PersonalAccessTokenScopes.api); - // Assert.True(!string.IsNullOrEmpty(pat.Token)); - // } - [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public async Task GetUser() { - var pat = await _gitlabContainer.GenerateAccessToken("root",PersonalAccessTokenScopes.api); + var pat = await _gitlabContainer.GenerateAccessToken("root", PersonalAccessTokenScopes.api); using var client = new HttpClient(); client.DefaultRequestHeaders.Add("PRIVATE-TOKEN", pat.Token);