Skip to content

Commit

Permalink
feat: Add DynamoDB module (#768)
Browse files Browse the repository at this point in the history
  • Loading branch information
dominikus1993 authored Feb 10, 2023
1 parent 07b5c8c commit 06bb7e0
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 0 deletions.
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Minio", "src
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Minio.Tests", "tests\Testcontainers.Minio.Tests\Testcontainers.Minio.Tests.csproj", "{5DB1F35F-B714-4B62-84BE-16A33084D3E1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DynamoDb.Tests", "tests\Testcontainers.DynamoDb.Tests\Testcontainers.DynamoDb.Tests.csproj", "{101515E6-74C1-40F9-85C8-871F742A378D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.DynamoDb", "src\Testcontainers.DynamoDb\Testcontainers.DynamoDb.csproj", "{2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -149,6 +153,14 @@ Global
{5DB1F35F-B714-4B62-84BE-16A33084D3E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5DB1F35F-B714-4B62-84BE-16A33084D3E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5DB1F35F-B714-4B62-84BE-16A33084D3E1}.Release|Any CPU.Build.0 = Release|Any CPU
{101515E6-74C1-40F9-85C8-871F742A378D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{101515E6-74C1-40F9-85C8-871F742A378D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{101515E6-74C1-40F9-85C8-871F742A378D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{101515E6-74C1-40F9-85C8-871F742A378D}.Release|Any CPU.Build.0 = Release|Any CPU
{2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{EC76857B-A3B8-4B7A-A1B0-8D867A4D1733} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
Expand All @@ -173,5 +185,7 @@ Global
{27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{1266E1E6-5CEF-4161-8B45-83282455746E} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{5DB1F35F-B714-4B62-84BE-16A33084D3E1} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{101515E6-74C1-40F9-85C8-871F742A378D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{2EAFA567-9F68-4C52-9DBC-8F3EC11BB2CE} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions src/Testcontainers.DynamoDb/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
71 changes: 71 additions & 0 deletions src/Testcontainers.DynamoDb/DynamoDbBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
namespace Testcontainers.DynamoDb;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class DynamoDbBuilder : ContainerBuilder<DynamoDbBuilder, DynamoDbContainer, DynamoDbConfiguration>
{
public const string DynamoDbImage = "amazon/dynamodb-local:1.21.0";

public const ushort DynamoDbPort = 8000;

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

/// <summary>
/// Initializes a new instance of the <see cref="DynamoDbBuilder" /> class.
/// </summary>
/// <param name="dockerResourceConfiguration">The Docker resource configuration.</param>
private DynamoDbBuilder(DynamoDbConfiguration dockerResourceConfiguration)
: base(dockerResourceConfiguration)
{
DockerResourceConfiguration = dockerResourceConfiguration;
}

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


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

/// <inheritdoc />
protected override DynamoDbBuilder Init()
{
return base.Init()
.WithImage(DynamoDbImage)
.WithPortBinding(DynamoDbPort, true)
.WithWaitStrategy(Wait.ForUnixContainer()
.UntilHttpRequestIsSucceeded(request: req =>
req.ForPath("/")
.ForStatusCode(HttpStatusCode.BadRequest)
.ForPort(DynamoDbPort)));
}

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

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

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

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class DynamoDbConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="DynamoDbConfiguration" /> class.
/// </summary>
public DynamoDbConfiguration()
{
}

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

/// <summary>
/// Initializes a new instance of the <see cref="DynamoDbConfiguration" /> class.
/// </summary>
/// <param name="oldValue">The old Docker resource configuration.</param>
/// <param name="newValue">The new Docker resource configuration.</param>
public DynamoDbConfiguration(DynamoDbConfiguration oldValue, DynamoDbConfiguration newValue)
: base(oldValue, newValue)
{
}
}
25 changes: 25 additions & 0 deletions src/Testcontainers.DynamoDb/DynamoDbContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Testcontainers.DynamoDb;

/// <inheritdoc cref="DockerContainer" />
[PublicAPI]
public sealed class DynamoDbContainer : DockerContainer
{
/// <summary>
/// Initializes a new instance of the <see cref="DynamoDbContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
/// <param name="logger">The logger.</param>
public DynamoDbContainer(DynamoDbConfiguration configuration, ILogger logger)
: base(configuration, logger)
{
}

/// <summary>
/// Gets the DynamoDB endpoint.
/// </summary>
/// <returns>The DynamoDB endpoint.</returns>
public string GetEndpoint()
{
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(DynamoDbBuilder.DynamoDbPort)).ToString();
}
}
12 changes: 12 additions & 0 deletions src/Testcontainers.DynamoDb/Testcontainers.DynamoDb.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="2022.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)src/Testcontainers/Testcontainers.csproj" />
</ItemGroup>
</Project>
11 changes: 11 additions & 0 deletions src/Testcontainers.DynamoDb/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
global using System;
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;
global using System.Threading.Tasks;
global using System.Collections.Generic;
global using System.Net;
81 changes: 81 additions & 0 deletions tests/Testcontainers.DynamoDb.Tests/DynamoDbContainerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
namespace Testcontainers.DynamoDb;

public sealed class DynamoDbContainerTest : IAsyncLifetime
{
private readonly DynamoDbContainer dynamoDbContainer = new DynamoDbBuilder().Build();

public Task InitializeAsync()
{
return this.dynamoDbContainer.StartAsync();
}

public Task DisposeAsync()
{
return this.dynamoDbContainer.DisposeAsync().AsTask();
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task CreateTableReturnsCorrectTableDescription()
{
// Given
const string tableName = "TestDynamoDbTable";
var clientConfig = new AmazonDynamoDBConfig();
clientConfig.ServiceURL = this.dynamoDbContainer.GetEndpoint();
clientConfig.UseHttp = true;
using var client = new AmazonDynamoDBClient(new BasicAWSCredentials("dummy", "dummy"), clientConfig);

// When
_ = await client.CreateTableAsync(new CreateTableRequest()
{
TableName = tableName,
AttributeDefinitions = new List<AttributeDefinition>() { new AttributeDefinition("Id", ScalarAttributeType.S), new AttributeDefinition("Name", ScalarAttributeType.S), },
KeySchema = new List<KeySchemaElement>() { new KeySchemaElement("Id", KeyType.HASH), new KeySchemaElement("Name", KeyType.RANGE), },
ProvisionedThroughput = new ProvisionedThroughput(1, 1),
TableClass = TableClass.STANDARD,
})
.ConfigureAwait(false);

var tableDescription = await client.DescribeTableAsync(tableName).ConfigureAwait(false);

// Then
Assert.NotNull(tableDescription);
Assert.Equal(HttpStatusCode.OK, tableDescription.HttpStatusCode);
Assert.Equal(tableName, tableDescription.Table.TableName);
Assert.Equal("Id", tableDescription.Table.KeySchema[0].AttributeName);
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task InsertElementToTableReturnsHttpStatusCodeOk()
{
// Given
var tableName = $"TestDynamoDbTable-{Guid.NewGuid():D}";
var itemId = Guid.NewGuid().ToString("D");
var itemName = Guid.NewGuid().ToString("D");

var clientConfig = new AmazonDynamoDBConfig();
clientConfig.ServiceURL = this.dynamoDbContainer.GetEndpoint();
clientConfig.UseHttp = true;
using var client = new AmazonDynamoDBClient(new BasicAWSCredentials("dummy", "dummy"), clientConfig);

// When
_ = await client.CreateTableAsync(new CreateTableRequest()
{
TableName = tableName,
AttributeDefinitions = new List<AttributeDefinition>() { new AttributeDefinition("Id", ScalarAttributeType.S), new AttributeDefinition("Name", ScalarAttributeType.S), },
KeySchema = new List<KeySchemaElement>() { new KeySchemaElement("Id", KeyType.HASH), new KeySchemaElement("Name", KeyType.RANGE), },
ProvisionedThroughput = new ProvisionedThroughput(1, 1),
TableClass = TableClass.STANDARD,
})
.ConfigureAwait(false);

_ = await client.PutItemAsync(new PutItemRequest(tableName, new Dictionary<string, AttributeValue>() { { "Id", new AttributeValue() { S = itemId } }, { "Name", new AttributeValue() { S = itemName } } })).ConfigureAwait(false);

var getItemResponse = await client.GetItemAsync(new GetItemRequest(tableName, new Dictionary<string, AttributeValue>() { { "Id", new AttributeValue() { S = itemId } }, { "Name", new AttributeValue() { S = itemName } } }))
.ConfigureAwait(false);

// Then
Assert.Equal(HttpStatusCode.OK, getItemResponse.HttpStatusCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsPublishable>false</IsPublishable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.101.42" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="coverlet.msbuild" Version="3.2.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
<PackageReference Include="xunit" Version="2.4.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)src/Testcontainers.DynamoDb/Testcontainers.DynamoDb.csproj" />
<ProjectReference Include="$(SolutionDir)tests/Testcontainers.Commons/Testcontainers.Commons.csproj" />
</ItemGroup>
</Project>
10 changes: 10 additions & 0 deletions tests/Testcontainers.DynamoDb.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Net;
global using System.Threading.Tasks;
global using Amazon.DynamoDBv2;
global using Amazon.DynamoDBv2.Model;
global using Amazon.Runtime;
global using DotNet.Testcontainers.Commons;
global using Xunit;

0 comments on commit 06bb7e0

Please sign in to comment.