Skip to content

Commit

Permalink
feat: Add Firebird SQL module (#1073)
Browse files Browse the repository at this point in the history
Co-authored-by: Jasper Park <[email protected]>
Co-authored-by: Andre Hofmeister <[email protected]>
  • Loading branch information
3 people authored Jan 2, 2024
1 parent 1e88fe9 commit 8857c60
Show file tree
Hide file tree
Showing 11 changed files with 420 additions and 0 deletions.
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.EventStoreDb
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.FakeGcsServer", "src\Testcontainers.FakeGcsServer\Testcontainers.FakeGcsServer.csproj", "{FF86B509-2F9E-4269-ABC2-912B3339DE29}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.FirebirdSql", "src\Testcontainers.FirebirdSql\Testcontainers.FirebirdSql.csproj", "{31BAF2C4-0608-4C0F-845A-14FE7C0A1670}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Firestore", "src\Testcontainers.Firestore\Testcontainers.Firestore.csproj", "{B3CC460D-0DFD-48A8-9502-54E9828B7B05}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.InfluxDb", "src\Testcontainers.InfluxDb\Testcontainers.InfluxDb.csproj", "{8F483B83-7BD4-4BD5-9F03-DFC26E1CE678}"
Expand Down Expand Up @@ -125,6 +127,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.EventStoreDb
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.FakeGcsServer.Tests", "tests\Testcontainers.FakeGcsServer.Tests\Testcontainers.FakeGcsServer.Tests.csproj", "{9F27AA1B-C25D-400C-BCB0-6B0BF1A1DCEA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.FirebirdSql.Tests", "tests\Testcontainers.FirebirdSql.Tests\Testcontainers.FirebirdSql.Tests.csproj", "{E39095AC-9B34-4178-A486-04C902B6FD33}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Firestore.Tests", "tests\Testcontainers.Firestore.Tests\Testcontainers.Firestore.Tests.csproj", "{2F0D7CD6-7EA9-46FC-B8F2-25D55699525F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.InfluxDb.Tests", "tests\Testcontainers.InfluxDb.Tests\Testcontainers.InfluxDb.Tests.csproj", "{B45B0EF2-5852-4ED3-904A-8FC62A3253D7}"
Expand Down Expand Up @@ -248,6 +252,10 @@ Global
{FF86B509-2F9E-4269-ABC2-912B3339DE29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FF86B509-2F9E-4269-ABC2-912B3339DE29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FF86B509-2F9E-4269-ABC2-912B3339DE29}.Release|Any CPU.Build.0 = Release|Any CPU
{31BAF2C4-0608-4C0F-845A-14FE7C0A1670}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31BAF2C4-0608-4C0F-845A-14FE7C0A1670}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31BAF2C4-0608-4C0F-845A-14FE7C0A1670}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31BAF2C4-0608-4C0F-845A-14FE7C0A1670}.Release|Any CPU.Build.0 = Release|Any CPU
{B3CC460D-0DFD-48A8-9502-54E9828B7B05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B3CC460D-0DFD-48A8-9502-54E9828B7B05}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3CC460D-0DFD-48A8-9502-54E9828B7B05}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -416,6 +424,10 @@ Global
{9F27AA1B-C25D-400C-BCB0-6B0BF1A1DCEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F27AA1B-C25D-400C-BCB0-6B0BF1A1DCEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F27AA1B-C25D-400C-BCB0-6B0BF1A1DCEA}.Release|Any CPU.Build.0 = Release|Any CPU
{E39095AC-9B34-4178-A486-04C902B6FD33}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E39095AC-9B34-4178-A486-04C902B6FD33}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E39095AC-9B34-4178-A486-04C902B6FD33}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E39095AC-9B34-4178-A486-04C902B6FD33}.Release|Any CPU.Build.0 = Release|Any CPU
{2F0D7CD6-7EA9-46FC-B8F2-25D55699525F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F0D7CD6-7EA9-46FC-B8F2-25D55699525F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F0D7CD6-7EA9-46FC-B8F2-25D55699525F}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -548,6 +560,7 @@ Global
{641DDEA5-B6E0-41E6-BA11-7A28C0913127} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{84D707E0-C9FA-4327-85DC-0AFEBEA73572} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{FF86B509-2F9E-4269-ABC2-912B3339DE29} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{31BAF2C4-0608-4C0F-845A-14FE7C0A1670} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{B3CC460D-0DFD-48A8-9502-54E9828B7B05} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{8F483B83-7BD4-4BD5-9F03-DFC26E1CE678} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{C5AF86A8-2F11-41B6-BB01-325AD9016B94} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
Expand Down Expand Up @@ -590,6 +603,7 @@ Global
{DD5B3678-468F-4D73-AECE-705E3D66CD43} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{64F8E9B9-78FD-4E13-BDDF-0340E2D4E1D0} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{9F27AA1B-C25D-400C-BCB0-6B0BF1A1DCEA} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{E39095AC-9B34-4178-A486-04C902B6FD33} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{2F0D7CD6-7EA9-46FC-B8F2-25D55699525F} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{B45B0EF2-5852-4ED3-904A-8FC62A3253D7} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{BEFC4109-4511-4FBD-AC4F-3D3B388B8CAD} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
Expand Down
1 change: 1 addition & 0 deletions src/Testcontainers.FirebirdSql/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
124 changes: 124 additions & 0 deletions src/Testcontainers.FirebirdSql/FirebirdSqlBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
namespace Testcontainers.FirebirdSql;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class FirebirdSqlBuilder : ContainerBuilder<FirebirdSqlBuilder, FirebirdSqlContainer, FirebirdSqlConfiguration>
{
public const string FirebirdSqlImage = "jacobalberty/firebird:v4.0";

public const ushort FirebirdSqlPort = 3050;

public const string DefaultDatabase = "test";

public const string DefaultUsername = "test";

public const string DefaultPassword = "test";

public const string DefaultSysdbaPassword = "masterkey";

private const string TestQueryString = "SELECT 1 FROM RDB$DATABASE;";

/// <summary>
/// Initializes a new instance of the <see cref="FirebirdSqlBuilder" /> class.
/// </summary>
public FirebirdSqlBuilder()
: this(new FirebirdSqlConfiguration())
{
DockerResourceConfiguration = Init().DockerResourceConfiguration;
}

/// <summary>
/// Initializes a new instance of the <see cref="FirebirdSqlBuilder" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public FirebirdSqlBuilder(FirebirdSqlConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
DockerResourceConfiguration = resourceConfiguration;
}

/// <inheritdoc />
protected override FirebirdSqlConfiguration DockerResourceConfiguration { get; }

/// <summary>
/// Sets the FirebirdSql database.
/// </summary>
/// <param name="database">The FirebirdSql database.</param>
/// <returns>A configured instance of <see cref="FirebirdSqlBuilder" />.</returns>
public FirebirdSqlBuilder WithDatabase(string database)
{
return Merge(DockerResourceConfiguration, new FirebirdSqlConfiguration(database: database))
.WithEnvironment("FIREBIRD_DATABASE", database);
}

/// <summary>
/// Sets the FirebirdSql username.
/// </summary>
/// <param name="username">The FirebirdSql username.</param>
/// <returns>A configured instance of <see cref="FirebirdSqlBuilder" />.</returns>
public FirebirdSqlBuilder WithUsername(string username)
{
return Merge(DockerResourceConfiguration, new FirebirdSqlConfiguration(username: username))
.WithEnvironment("FIREBIRD_USER", "sysdba".Equals(username, StringComparison.OrdinalIgnoreCase) ? string.Empty : username);
}

/// <summary>
/// Sets the FirebirdSql password.
/// </summary>
/// <param name="password">The FirebirdSql password.</param>
/// <returns>A configured instance of <see cref="FirebirdSqlBuilder" />.</returns>
public FirebirdSqlBuilder WithPassword(string password)
{
return Merge(DockerResourceConfiguration, new FirebirdSqlConfiguration(password: password))
.WithEnvironment("FIREBIRD_PASSWORD", password)
.WithEnvironment("ISC_PASSWORD", password);
}

/// <inheritdoc />
public override FirebirdSqlContainer Build()
{
Validate();
return new FirebirdSqlContainer(DockerResourceConfiguration, TestcontainersSettings.Logger);
}

/// <inheritdoc />
protected override FirebirdSqlBuilder Init()
{
return base.Init()
.WithImage(FirebirdSqlImage)
.WithPortBinding(FirebirdSqlPort, true)
.WithDatabase(DefaultDatabase)
.WithUsername(DefaultUsername)
.WithPassword(DefaultPassword)
.WithResourceMapping(Encoding.Default.GetBytes(TestQueryString), "/home/firebird_check.sql")
.WithWaitStrategy(Wait.ForUnixContainer().UntilContainerIsHealthy());
}

/// <inheritdoc />
protected override void Validate()
{
base.Validate();

_ = Guard.Argument(DockerResourceConfiguration.Password, nameof(DockerResourceConfiguration.Password))
.NotNull()
.NotEmpty();
}

/// <inheritdoc />
protected override FirebirdSqlBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new FirebirdSqlConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override FirebirdSqlBuilder Clone(IContainerConfiguration resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new FirebirdSqlConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override FirebirdSqlBuilder Merge(FirebirdSqlConfiguration oldValue, FirebirdSqlConfiguration newValue)
{
return new FirebirdSqlBuilder(new FirebirdSqlConfiguration(oldValue, newValue));
}
}
82 changes: 82 additions & 0 deletions src/Testcontainers.FirebirdSql/FirebirdSqlConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
namespace Testcontainers.FirebirdSql;

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class FirebirdSqlConfiguration : ContainerConfiguration
{
private readonly string _database;

/// <summary>
/// Initializes a new instance of the <see cref="FirebirdSqlConfiguration" /> class.
/// </summary>
/// <param name="database">The FirebirdSql database.</param>
/// <param name="username">The FirebirdSql username.</param>
/// <param name="password">The FirebirdSql password.</param>
public FirebirdSqlConfiguration(
string database = null,
string username = null,
string password = null)
{
_database = database;
Username = username;
Password = password;
}

/// <summary>
/// Initializes a new instance of the <see cref="FirebirdSqlConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public FirebirdSqlConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="FirebirdSqlConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public FirebirdSqlConfiguration(IContainerConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="FirebirdSqlConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public FirebirdSqlConfiguration(FirebirdSqlConfiguration resourceConfiguration)
: this(new FirebirdSqlConfiguration(), resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="FirebirdSqlConfiguration" /> class.
/// </summary>
/// <param name="oldValue">The old Docker resource configuration.</param>
/// <param name="newValue">The new Docker resource configuration.</param>
public FirebirdSqlConfiguration(FirebirdSqlConfiguration oldValue, FirebirdSqlConfiguration newValue)
: base(oldValue, newValue)
{
_database = BuildConfiguration.Combine(oldValue._database, newValue._database);
Username = BuildConfiguration.Combine(oldValue.Username, newValue.Username);
Password = BuildConfiguration.Combine(oldValue.Password, newValue.Password);
}

/// <summary>
/// Gets the FirebirdSql database.
/// </summary>
public string Database => Image.Tag.StartsWith("2.5") || Image.Tag.StartsWith("v2.5") ? string.Join("/", "/firebird/data", _database) : _database;

/// <summary>
/// Gets the FirebirdSql username.
/// </summary>
public string Username { get; }

/// <summary>
/// Gets the FirebirdSql password.
/// </summary>
public string Password { get; }
}
51 changes: 51 additions & 0 deletions src/Testcontainers.FirebirdSql/FirebirdSqlContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
namespace Testcontainers.FirebirdSql;

/// <inheritdoc cref="DockerContainer" />
[PublicAPI]
public sealed class FirebirdSqlContainer : DockerContainer, IDatabaseContainer
{
private readonly FirebirdSqlConfiguration _configuration;

/// <summary>
/// Initializes a new instance of the <see cref="FirebirdSqlContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
/// <param name="logger">The logger.</param>
public FirebirdSqlContainer(FirebirdSqlConfiguration configuration, ILogger logger)
: base(configuration, logger)
{
_configuration = configuration;
}

/// <summary>
/// Gets the FirebirdSql connection string.
/// </summary>
/// <returns>The FirebirdSql connection string.</returns>
public string GetConnectionString()
{
var properties = new Dictionary<string, string>();
properties.Add("DataSource", Hostname);
properties.Add("Port", GetMappedPublicPort(FirebirdSqlBuilder.FirebirdSqlPort).ToString());
properties.Add("Database", _configuration.Database);
properties.Add("User", _configuration.Username);
properties.Add("Password", _configuration.Password);
return string.Join(";", properties.Select(property => string.Join("=", property.Key, property.Value)));
}

/// <summary>
/// Executes the SQL script in the FirebirdSql container.
/// </summary>
/// <param name="scriptContent">The content of the SQL script to execute.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Task that completes when the SQL script has been executed.</returns>
public async Task<ExecResult> ExecScriptAsync(string scriptContent, CancellationToken ct = default)
{
var scriptFilePath = string.Join("/", string.Empty, "tmp", Guid.NewGuid().ToString("D"), Path.GetRandomFileName());

await CopyAsync(Encoding.Default.GetBytes(scriptContent), scriptFilePath, Unix.FileMode644, ct)
.ConfigureAwait(false);

return await ExecAsync(new[] { "/usr/local/firebird/bin/isql", "-i", scriptFilePath, "-user", _configuration.Username, "-pass", _configuration.Password, _configuration.Database }, ct)
.ConfigureAwait(false);
}
}
12 changes: 12 additions & 0 deletions src/Testcontainers.FirebirdSql/Testcontainers.FirebirdSql.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" PrivateAssets="All"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)src/Testcontainers/Testcontainers.csproj"/>
</ItemGroup>
</Project>
14 changes: 14 additions & 0 deletions src/Testcontainers.FirebirdSql/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
global using System;
global using System.Collections.Generic;
global using System.IO;
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;
1 change: 1 addition & 0 deletions tests/Testcontainers.FirebirdSql.Tests/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
Loading

0 comments on commit 8857c60

Please sign in to comment.