second first commit

This commit is contained in:
Zakaria
2026-09-22 08:57:00 -04:00
parent e88d7e2ff3
commit 0aef7fb2d8
4766 changed files with 1236986 additions and 0 deletions
+244
View File
@@ -0,0 +1,244 @@
# Testcontainers for .NET
The `Testcontainers.Floci` package integrates Floci with [Testcontainers for .NET](https://dotnet.testcontainers.org/). It starts a real Floci container before your tests and disposes it after, and gives you a typed, per-service configuration API over Floci's environment-variable surface.
Targets `net8.0`, `net9.0`, `net10.0`, `netstandard2.0` and `netstandard2.1`.
## Installation
```bash
dotnet add package Testcontainers.Floci
```
!!! note "Published to GitHub Packages, not nuget.org"
The package is served from the [floci-io GitHub Packages feed](https://github.com/floci-io/testcontainers-floci-dotnet/pkgs/nuget/Testcontainers.Floci). A package with the same id, `Testcontainers.Floci`, also exists on nuget.org, published by the Testcontainers project. Without package source mapping, NuGet may resolve that one instead of this module, so the mapping block below is required, not optional. Add a `nuget.config` next to your solution:
```xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="github-floci-io" value="https://nuget.pkg.github.com/floci-io/index.json" />
</packageSources>
<packageSourceCredentials>
<github-floci-io>
<add key="Username" value="%GITHUB_ACTOR%" />
<add key="ClearTextPassword" value="%GITHUB_PACKAGES_PAT%" />
</github-floci-io>
</packageSourceCredentials>
<packageSourceMapping>
<!-- Route only this package to the floci-io feed; everything else stays on nuget.org. -->
<packageSource key="github-floci-io">
<package pattern="Testcontainers.Floci" />
</packageSource>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
```
GitHub Packages requires authentication even for public packages. `GITHUB_PACKAGES_PAT` is a personal access token with the `read:packages` scope, and `GITHUB_ACTOR` is your GitHub username. The `dotnet nuget add source` command can register the feed and credentials for you, but it does not create the `packageSourceMapping` entry, so the config file above is the canonical setup.
## Basic usage with xUnit
The container implements the Testcontainers builder pattern. Use `IAsyncLifetime` so it starts once per test class:
```csharp
using Amazon.S3;
using Amazon.S3.Model;
using Testcontainers.Floci;
using Xunit;
public sealed class S3Tests : IAsyncLifetime
{
private readonly FlociContainer _floci = new FlociBuilder("floci/floci:latest").Build();
public Task InitializeAsync() => _floci.StartAsync();
public Task DisposeAsync() => _floci.DisposeAsync().AsTask();
[Fact]
public async Task CreatesAndListsBucket()
{
using var s3 = new AmazonS3Client(
_floci.AccessKey,
_floci.SecretKey,
new AmazonS3Config
{
ServiceURL = _floci.GetEndpoint(),
AuthenticationRegion = _floci.Region,
ForcePathStyle = true,
});
await s3.PutBucketAsync(new PutBucketRequest { BucketName = "my-bucket" });
var buckets = await s3.ListBucketsAsync();
Assert.Contains(buckets.Buckets, b => b.BucketName == "my-bucket");
}
}
```
`GetEndpoint()` returns `http://localhost:<mapped-port>/`. `Region`, `AccountId`, `AvailabilityZone`, `AccessKey` and `SecretKey` expose the container's identity defaults.
## SQS example
```csharp
using Amazon.SQS;
using Amazon.SQS.Model;
using Testcontainers.Floci;
using Xunit;
public sealed class SqsTests : IAsyncLifetime
{
private readonly FlociContainer _floci = new FlociBuilder("floci/floci:latest").Build();
public Task InitializeAsync() => _floci.StartAsync();
public Task DisposeAsync() => _floci.DisposeAsync().AsTask();
private AmazonSQSClient CreateClient() => new(
_floci.AccessKey,
_floci.SecretKey,
new AmazonSQSConfig
{
ServiceURL = _floci.GetEndpoint(),
AuthenticationRegion = _floci.Region,
});
[Fact]
public async Task SendsAndReceivesMessage()
{
using var sqs = CreateClient();
var queueUrl = (await sqs.CreateQueueAsync(
new CreateQueueRequest { QueueName = "orders" })).QueueUrl;
await sqs.SendMessageAsync(new SendMessageRequest
{
QueueUrl = queueUrl,
MessageBody = """{"event":"order.placed"}""",
});
var received = await sqs.ReceiveMessageAsync(new ReceiveMessageRequest
{
QueueUrl = queueUrl,
MaxNumberOfMessages = 1,
});
Assert.Single(received.Messages);
}
}
```
## DynamoDB example
```csharp
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.Model;
using Testcontainers.Floci;
using Xunit;
public sealed class DynamoDbTests : IAsyncLifetime
{
private readonly FlociContainer _floci = new FlociBuilder("floci/floci:latest").Build();
public Task InitializeAsync() => _floci.StartAsync();
public Task DisposeAsync() => _floci.DisposeAsync().AsTask();
[Fact]
public async Task PutsAndGetsItem()
{
using var dynamo = new AmazonDynamoDBClient(
_floci.AccessKey,
_floci.SecretKey,
new AmazonDynamoDBConfig
{
ServiceURL = _floci.GetEndpoint(),
AuthenticationRegion = _floci.Region,
});
await dynamo.CreateTableAsync(new CreateTableRequest
{
TableName = "Orders",
AttributeDefinitions = [new AttributeDefinition("id", ScalarAttributeType.S)],
KeySchema = [new KeySchemaElement("id", KeyType.HASH)],
BillingMode = BillingMode.PAY_PER_REQUEST,
});
await dynamo.PutItemAsync(new PutItemRequest
{
TableName = "Orders",
Item = new()
{
["id"] = new AttributeValue { S = "order-1" },
["status"] = new AttributeValue { S = "placed" },
},
});
var response = await dynamo.GetItemAsync(new GetItemRequest
{
TableName = "Orders",
Key = new() { ["id"] = new AttributeValue { S = "order-1" } },
});
Assert.Equal("placed", response.Item["status"].S);
}
}
```
## Configuring services
Each service has a `record` config with `init` properties. Pass it to the matching `With…` method on the builder; only the settings you set differ from Floci's defaults.
```csharp
var floci = new FlociBuilder("floci/floci:latest")
.WithRegion("eu-west-2")
.WithAccountId("123456789012")
.WithSqs(new SqsConfig { VisibilityTimeout = 60, MaxMessageSize = 131072 })
.WithS3(new S3Config { Enabled = true })
.Build();
```
Configs map onto Floci's `FLOCI_SERVICES_<SERVICE>_<SETTING>` environment variables. `ENABLED` is always sent; the rest only when the service is enabled.
## Container-based services
RDS, Lambda, ElastiCache, ECS, EC2 and ECR make Floci spawn real sibling containers through the Docker daemon. Their configs opt into mounting the Docker socket and into publishing ports 1:1, because Floci returns `endpoint=localhost:<port>` literally.
```csharp
var floci = new FlociBuilder("floci/floci:latest")
.WithRds(new RdsConfig { Enabled = true, ProxyBasePort = 7010 })
.Build();
```
A few things to know:
- Sibling containers are managed by Floci, not by Ryuk. They are named after the resource, so delete the resource in teardown (`DeleteDBInstanceAsync`, `DeleteFunctionAsync`, `DeleteReplicationGroupAsync`) or the container is left behind.
- **On macOS, avoid the default RDS `ProxyBasePort` of 7000**: AirPlay Receiver listens on `*:7000`. Use 7010, as above.
- Connect to `127.0.0.1` rather than `localhost` so the client does not resolve to IPv6, and disable SSL (`SSL Mode=Disable`): Floci's RDS proxy does not negotiate TLS.
- The first Lambda invocation pulls the AWS runtime image and cold-starts (roughly 8–10s). Give the client a generous timeout.
## Running the tests
Integration tests need a running Docker daemon.
```bash
dotnet build # must stay warning-free
dotnet test # requires Docker
```
Under [Colima](https://github.com/abiosoft/colima) on macOS, the host socket path cannot be bind-mounted into a container, which breaks Ryuk. Point Testcontainers at the in-VM path instead, with no code change required:
```bash
export DOCKER_HOST="unix:///Users/<you>/.colima/default/docker.sock"
export TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE="/var/run/docker.sock"
```
Container-backed tests can flake when many Floci containers start at once, so the suite disables xUnit's test parallelization.
## Source and changelog
[github.com/floci-io/testcontainers-floci-dotnet](https://github.com/floci-io/testcontainers-floci-dotnet)
+287
View File
@@ -0,0 +1,287 @@
# Testcontainers — Go
The `testcontainers-floci-go` module integrates Floci with [Testcontainers for Go](https://golang.testcontainers.org/). It starts a real Floci container before your tests and shuts it down after, with no extra setup.
## Installation
```bash
go get github.com/floci-io/testcontainers-floci-go
```
Requires Go 1.25+ and Testcontainers for Go v0.42+.
## Basic usage
```go
package myservice_test
import (
"context"
"testing"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
floci "github.com/floci-io/testcontainers-floci-go"
)
func TestS3CreateBucket(t *testing.T) {
ctx := context.Background()
container, err := floci.NewFlociContainer().Start(ctx)
if err != nil {
t.Fatal(err)
}
defer container.Stop(ctx)
cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion(container.GetRegion()),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
container.GetAccessKey(), container.GetSecretKey(), "",
)),
config.WithBaseEndpoint(container.GetEndpoint()),
)
if err != nil {
t.Fatal(err)
}
client := s3.NewFromConfig(cfg, func(o *s3.Options) {
o.UsePathStyle = true
})
_, err = client.CreateBucket(ctx, &s3.CreateBucketInput{
Bucket: aws.String("my-bucket"),
})
if err != nil {
t.Fatal(err)
}
out, err := client.ListBuckets(ctx, &s3.ListBucketsInput{})
if err != nil {
t.Fatal(err)
}
var found bool
for _, b := range out.Buckets {
if aws.ToString(b.Name) == "my-bucket" {
found = true
break
}
}
if !found {
t.Error("bucket not found after create")
}
}
```
## SQS example
```go
func TestSqsSendReceive(t *testing.T) {
ctx := context.Background()
container, err := floci.NewFlociContainer().Start(ctx)
if err != nil {
t.Fatal(err)
}
defer container.Stop(ctx)
cfg, _ := config.LoadDefaultConfig(ctx,
config.WithRegion(container.GetRegion()),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
container.GetAccessKey(), container.GetSecretKey(), "",
)),
config.WithBaseEndpoint(container.GetEndpoint()),
)
client := sqs.NewFromConfig(cfg)
queue, err := client.CreateQueue(ctx, &sqs.CreateQueueInput{
QueueName: aws.String("orders"),
})
if err != nil {
t.Fatal(err)
}
_, err = client.SendMessage(ctx, &sqs.SendMessageInput{
QueueUrl: queue.QueueUrl,
MessageBody: aws.String(`{"event":"order.placed"}`),
})
if err != nil {
t.Fatal(err)
}
out, err := client.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{
QueueUrl: queue.QueueUrl,
MaxNumberOfMessages: 1,
})
if err != nil {
t.Fatal(err)
}
if len(out.Messages) != 1 {
t.Fatalf("expected 1 message, got %d", len(out.Messages))
}
}
```
## DynamoDB example
```go
func TestDynamoDBPutGet(t *testing.T) {
ctx := context.Background()
container, err := floci.NewFlociContainer().Start(ctx)
if err != nil {
t.Fatal(err)
}
defer container.Stop(ctx)
cfg, _ := config.LoadDefaultConfig(ctx,
config.WithRegion(container.GetRegion()),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
container.GetAccessKey(), container.GetSecretKey(), "",
)),
config.WithBaseEndpoint(container.GetEndpoint()),
)
client := dynamodb.NewFromConfig(cfg)
_, err = client.CreateTable(ctx, &dynamodb.CreateTableInput{
TableName: aws.String("Orders"),
BillingMode: types.BillingModePayPerRequest,
AttributeDefinitions: []types.AttributeDefinition{
{AttributeName: aws.String("id"), AttributeType: types.ScalarAttributeTypeS},
},
KeySchema: []types.KeySchemaElement{
{AttributeName: aws.String("id"), KeyType: types.KeyTypeHash},
},
})
if err != nil {
t.Fatal(err)
}
_, err = client.PutItem(ctx, &dynamodb.PutItemInput{
TableName: aws.String("Orders"),
Item: map[string]types.AttributeValue{
"id": &types.AttributeValueMemberS{Value: "order-1"},
"status": &types.AttributeValueMemberS{Value: "placed"},
},
})
if err != nil {
t.Fatal(err)
}
result, err := client.GetItem(ctx, &dynamodb.GetItemInput{
TableName: aws.String("Orders"),
Key: map[string]types.AttributeValue{
"id": &types.AttributeValueMemberS{Value: "order-1"},
},
})
if err != nil {
t.Fatal(err)
}
status := result.Item["status"].(*types.AttributeValueMemberS).Value
if status != "placed" {
t.Errorf("expected status 'placed', got '%s'", status)
}
}
```
## Reusing the container across tests
Start the container once with `TestMain` and share it across all tests in the package:
```go
var sharedContainer *floci.StartedFlociContainer
func TestMain(m *testing.M) {
ctx := context.Background()
var err error
sharedContainer, err = floci.NewFlociContainer().Start(ctx)
if err != nil {
log.Fatalf("failed to start floci: %v", err)
}
code := m.Run()
sharedContainer.Stop(ctx)
os.Exit(code)
}
func awsCfg(ctx context.Context) aws.Config {
cfg, _ := config.LoadDefaultConfig(ctx,
config.WithRegion(sharedContainer.GetRegion()),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
sharedContainer.GetAccessKey(), sharedContainer.GetSecretKey(), "",
)),
config.WithBaseEndpoint(sharedContainer.GetEndpoint()),
)
return cfg
}
```
## Options
Options are chained on the builder before calling `Start`:
```go
container, err := floci.NewFlociContainer().
WithRegion("eu-west-1").
WithAccountID("123456789012").
WithImage("floci/floci:1.6.0").
WithDedicatedNetwork().
Start(ctx)
```
| Method | Default | Description |
|---|---|---|
| `WithImage(image string)` | `floci/floci:latest` | Docker image to use |
| `WithRegion(region string)` | `us-east-1` | AWS region set in Floci and returned by `GetRegion()` |
| `WithAccountID(id string)` | `000000000000` | Default AWS account ID used in ARNs |
| `WithAvailabilityZone(az string)` | `us-east-1a` | Availability zone reported by Floci |
| `WithDedicatedNetwork()` | _(none)_ | Create a dedicated Docker network for container-backed services (ElastiCache, RDS, OpenSearch, MSK) |
## Service configuration
Each service exposes a typed config struct. Pass it with the matching `With<Service>Config` method:
```go
container, err := floci.NewFlociContainer().
WithS3Config(floci.S3Config{
Enabled: true,
DefaultPresignExpirySeconds: 7200,
}).
WithSqsConfig(floci.SqsConfig{
Enabled: true,
DefaultVisibilityTimeout: 60,
MaxMessageSize: 131072,
}).
WithDynamoDbConfig(floci.DynamoDbConfig{
Enabled: true,
}).
Start(ctx)
```
Available config methods: `WithAcmConfig`, `WithApiGatewayConfig`, `WithAppConfigConfig`, `WithAthenaConfig`, `WithBackupConfig`, `WithBedrockRuntimeConfig`, `WithCloudFormationConfig`, `WithCloudWatchLogsConfig`, `WithCloudWatchMetricsConfig`, `WithCodeBuildConfig`, `WithCodeDeployConfig`, `WithCognitoConfig`, `WithDynamoDbConfig`, `WithEc2Config`, `WithEcrConfig`, `WithEcsConfig`, `WithEksConfig`, `WithElastiCacheConfig`, `WithElbConfig`, `WithEventBridgeConfig`, `WithFirehoseConfig`, `WithGlueConfig`, `WithIamConfig`, `WithKinesisConfig`, `WithKmsConfig`, `WithLambdaConfig`, `WithMskConfig`, `WithOpenSearchConfig`, `WithRdsConfig`, `WithRoute53Config`, `WithS3Config`, `WithSchedulerConfig`, `WithSecretsManagerConfig`, `WithSesConfig`, `WithSnsConfig`, `WithSqsConfig`, `WithSsmConfig`, `WithStepFunctionsConfig`, `WithStsConfig`, `WithTextractConfig`, `WithTransferConfig`.
## `StartedFlociContainer` API
| Method | Returns | Description |
|---|---|---|
| `GetEndpoint()` | `string` | Full HTTP endpoint, e.g. `http://localhost:32768` |
| `GetRegion()` | `string` | AWS region configured at start |
| `GetAccountID()` | `string` | AWS account ID configured at start |
| `GetAccessKey()` | `string` | Always `"test"` |
| `GetSecretKey()` | `string` | Always `"test"` |
| `GetAvailabilityZone()` | `string` | Availability zone configured at start |
| `GetDedicatedNetworkName()` | `string` | Docker network name, or empty string if not using a dedicated network |
| `GetMappedPort(ctx, port int)` | `(int, error)` | Host port mapped to the given container port |
| `Stop(ctx)` | `error` | Terminate the container and clean up |
## Source
[github.com/floci-io/testcontainers-floci-go](https://github.com/floci-io/testcontainers-floci-go)
+38
View File
@@ -0,0 +1,38 @@
# Testcontainers
Floci has first-class Testcontainers modules for every major SDK language. Each module starts a real Floci container before your tests run and tears it down after — no running daemon, no shared state, no port conflicts.
## Available modules
For Testcontainers 1.x, use the versions as indicated in the table below.
| Language | Package | Version | Registry | Source |
|---|---|---|---|---|
| Java | `io.floci:testcontainers-floci` | `1.14.0` | [Maven Central](https://mvnrepository.com/artifact/io.floci/testcontainers-floci) | [GitHub](https://github.com/floci-io/testcontainers-floci) |
| Node.js | `@floci/testcontainers` | `0.1.0` | [npm](https://www.npmjs.com/package/@floci/testcontainers) | [GitHub](https://github.com/floci-io/testcontainers-floci-node) |
| Python | `testcontainers-floci` | `0.1.1` | [PyPI](https://pypi.org/project/testcontainers-floci/) | [GitHub](https://github.com/floci-io/testcontainers-floci-python) |
| .NET | `Testcontainers.Floci` | see releases | [GitHub Packages](https://github.com/orgs/floci-io/packages?repo_name=testcontainers-floci-dotnet) | [GitHub](https://github.com/floci-io/testcontainers-floci-dotnet) |
| Go | — | 🚧 In progress | — | [GitHub](https://github.com/floci-io/testcontainers-floci-go) |
For Testcontainers 2.x / Spring Boot 4.x, use version `2.15.0`.
## How it works
Every module exposes a `FlociContainer` class that wraps the official `floci/floci:latest` Docker image. When the container starts it waits for port 4566 to be ready, then exposes:
| Method | Returns |
|---|---|
| `getEndpoint()` | `http://localhost:<mapped-port>` |
| `getRegion()` | `us-east-1` (default) |
| `getAccessKey()` | `test` |
| `getSecretKey()` | `test` |
You pass these values directly into any AWS SDK client — no manual configuration, no environment variables.
## Language guides
- [Java](java.md) — JUnit 5, Spring Boot `@ServiceConnection`
- [Node.js / TypeScript](nodejs.md) — Jest, Vitest
- [Python](python.md) — pytest
- [.NET](dotnet.md): xUnit
- [Go](go.md) — in progress
+232
View File
@@ -0,0 +1,232 @@
# Testcontainers — Java
The `testcontainers-floci` library integrates Floci with [Testcontainers for Java](https://java.testcontainers.org/). It starts a real Floci container before your tests and shuts it down after, with no extra setup.
Two artifact lines are published to keep in sync with the Testcontainers major version:
| Testcontainers version | Spring Boot | Artifact version |
|---|---|---|
| 1.x | 3.x | `1.14.0` |
| 2.x | 4.x | `2.15.0` |
## Installation
=== "Maven"
```xml
<dependency>
<groupId>io.floci</groupId>
<artifactId>testcontainers-floci</artifactId>
<version>1.14.0</version>
<scope>test</scope>
</dependency>
```
=== "Gradle"
```groovy
testImplementation 'io.floci:testcontainers-floci:1.14.0'
```
## Basic usage — JUnit 5
Annotate the class with `@Testcontainers` and declare a static `FlociContainer` field with `@Container`. Testcontainers handles the lifecycle automatically.
```java
import io.floci.testcontainers.FlociContainer;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import java.net.URI;
import static org.assertj.core.api.Assertions.assertThat;
@Testcontainers
class S3IntegrationTest {
@Container
static FlociContainer floci = new FlociContainer();
@Test
void shouldCreateBucket() {
S3Client s3 = S3Client.builder()
.endpointOverride(URI.create(floci.getEndpoint()))
.region(Region.of(floci.getRegion()))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(floci.getAccessKey(), floci.getSecretKey())))
.forcePathStyle(true)
.build();
s3.createBucket(b -> b.bucket("my-bucket"));
assertThat(s3.listBuckets().buckets())
.anyMatch(b -> b.name().equals("my-bucket"));
}
}
```
## SQS example
```java
@Testcontainers
class SqsIntegrationTest {
@Container
static FlociContainer floci = new FlociContainer();
@Test
void shouldSendAndReceiveMessage() {
SqsClient sqs = SqsClient.builder()
.endpointOverride(URI.create(floci.getEndpoint()))
.region(Region.of(floci.getRegion()))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(floci.getAccessKey(), floci.getSecretKey())))
.build();
String queueUrl = sqs.createQueue(b -> b.queueName("orders")).queueUrl();
sqs.sendMessage(b -> b.queueUrl(queueUrl).messageBody("{\"event\":\"order.placed\"}"));
var messages = sqs.receiveMessage(b -> b.queueUrl(queueUrl).maxNumberOfMessages(1)).messages();
assertThat(messages).hasSize(1);
assertThat(messages.get(0).body()).contains("order.placed");
}
}
```
## DynamoDB example
```java
@Testcontainers
class DynamoDbIntegrationTest {
@Container
static FlociContainer floci = new FlociContainer();
@Test
void shouldCreateTableAndPutItem() {
DynamoDbClient dynamo = DynamoDbClient.builder()
.endpointOverride(URI.create(floci.getEndpoint()))
.region(Region.of(floci.getRegion()))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(floci.getAccessKey(), floci.getSecretKey())))
.build();
dynamo.createTable(b -> b
.tableName("Orders")
.attributeDefinitions(a -> a.attributeName("id").attributeType(ScalarAttributeType.S))
.keySchema(k -> k.attributeName("id").keyType(KeyType.HASH))
.billingMode(BillingMode.PAY_PER_REQUEST));
dynamo.putItem(b -> b
.tableName("Orders")
.item(Map.of("id", AttributeValue.fromS("order-1"),
"status", AttributeValue.fromS("placed"))));
var item = dynamo.getItem(b -> b
.tableName("Orders")
.key(Map.of("id", AttributeValue.fromS("order-1")))).item();
assertThat(item.get("status").s()).isEqualTo("placed");
}
}
```
## Spring Boot — `@ServiceConnection`
Add the Spring Boot companion artifact for zero-config auto-wiring. The `@ServiceConnection` annotation registers the container as a `ConnectionDetails` bean and configures all AWS SDK clients automatically.
=== "Maven"
```xml
<dependency>
<groupId>io.floci</groupId>
<artifactId>spring-boot-testcontainers-floci</artifactId>
<version>1.14.0</version>
<scope>test</scope>
</dependency>
```
=== "Gradle"
```groovy
testImplementation 'io.floci:spring-boot-testcontainers-floci:1.14.0'
```
```java
import io.floci.testcontainers.FlociContainer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import software.amazon.awssdk.services.s3.S3Client;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@Testcontainers
class AppIntegrationTest {
@Container
@ServiceConnection
static FlociContainer floci = new FlociContainer();
@Autowired
S3Client s3;
@Test
void shouldCreateBucket() {
s3.createBucket(b -> b.bucket("my-bucket"));
assertThat(s3.listBuckets().buckets())
.anyMatch(b -> b.name().equals("my-bucket"));
}
}
```
With `@ServiceConnection`, Spring Boot auto-configures the endpoint URL, region, and credentials for every AWS SDK client bean in the application context — no `application-test.yml` overrides needed.
## Reusing the container across tests
Declare the container in a shared base class or a JUnit 5 extension to start it once per test suite rather than once per class:
```java
abstract class FlociTestBase {
@Container
static FlociContainer floci = new FlociContainer();
static S3Client s3;
@BeforeAll
static void setUpClients() {
s3 = S3Client.builder()
.endpointOverride(URI.create(floci.getEndpoint()))
.region(Region.of(floci.getRegion()))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(floci.getAccessKey(), floci.getSecretKey())))
.forcePathStyle(true)
.build();
}
}
@Testcontainers
class MyServiceTest extends FlociTestBase {
@Test
void myTest() {
s3.createBucket(b -> b.bucket("test-bucket"));
// ...
}
}
```
## Source and changelog
[github.com/floci-io/testcontainers-floci](https://github.com/floci-io/testcontainers-floci)
+270
View File
@@ -0,0 +1,270 @@
# Testcontainers — Node.js / TypeScript
The `@floci/testcontainers` package integrates Floci with [Testcontainers for Node.js](https://node.testcontainers.org/). It works with any test runner that supports `async`/`await` — Jest, Vitest, Mocha, and others.
## Installation
```sh
npm install --save-dev @floci/testcontainers
```
```sh
# yarn
yarn add --dev @floci/testcontainers
# pnpm
pnpm add -D @floci/testcontainers
```
## Basic usage — Jest
```typescript
import { FlociContainer } from "@floci/testcontainers";
import { S3Client, CreateBucketCommand, ListBucketsCommand } from "@aws-sdk/client-s3";
describe("S3", () => {
let floci: FlociContainer;
beforeAll(async () => {
floci = await new FlociContainer().start();
});
afterAll(async () => {
await floci.stop();
});
it("should create and list a bucket", async () => {
const s3 = new S3Client({
endpoint: floci.getEndpoint(),
region: floci.getRegion(),
credentials: {
accessKeyId: floci.getAccessKey(),
secretAccessKey: floci.getSecretKey(),
},
forcePathStyle: true,
});
await s3.send(new CreateBucketCommand({ Bucket: "my-bucket" }));
const { Buckets } = await s3.send(new ListBucketsCommand({}));
expect(Buckets?.some(b => b.Name === "my-bucket")).toBe(true);
});
});
```
## SQS example
```typescript
import { FlociContainer } from "@floci/testcontainers";
import {
SQSClient,
CreateQueueCommand,
SendMessageCommand,
ReceiveMessageCommand,
} from "@aws-sdk/client-sqs";
describe("SQS", () => {
let floci: FlociContainer;
let sqs: SQSClient;
beforeAll(async () => {
floci = await new FlociContainer().start();
sqs = new SQSClient({
endpoint: floci.getEndpoint(),
region: floci.getRegion(),
credentials: {
accessKeyId: floci.getAccessKey(),
secretAccessKey: floci.getSecretKey(),
},
});
});
afterAll(async () => {
await floci.stop();
});
it("should send and receive a message", async () => {
const { QueueUrl } = await sqs.send(
new CreateQueueCommand({ QueueName: "orders" })
);
await sqs.send(
new SendMessageCommand({
QueueUrl,
MessageBody: JSON.stringify({ event: "order.placed" }),
})
);
const { Messages } = await sqs.send(
new ReceiveMessageCommand({ QueueUrl, MaxNumberOfMessages: 1 })
);
expect(Messages).toHaveLength(1);
expect(JSON.parse(Messages![0].Body!).event).toBe("order.placed");
});
});
```
## DynamoDB example
```typescript
import { FlociContainer } from "@floci/testcontainers";
import {
DynamoDBClient,
CreateTableCommand,
PutItemCommand,
GetItemCommand,
} from "@aws-sdk/client-dynamodb";
describe("DynamoDB", () => {
let floci: FlociContainer;
let dynamo: DynamoDBClient;
beforeAll(async () => {
floci = await new FlociContainer().start();
dynamo = new DynamoDBClient({
endpoint: floci.getEndpoint(),
region: floci.getRegion(),
credentials: {
accessKeyId: floci.getAccessKey(),
secretAccessKey: floci.getSecretKey(),
},
});
});
afterAll(async () => {
await floci.stop();
});
it("should put and get an item", async () => {
await dynamo.send(
new CreateTableCommand({
TableName: "Orders",
AttributeDefinitions: [{ AttributeName: "id", AttributeType: "S" }],
KeySchema: [{ AttributeName: "id", KeyType: "HASH" }],
BillingMode: "PAY_PER_REQUEST",
})
);
await dynamo.send(
new PutItemCommand({
TableName: "Orders",
Item: {
id: { S: "order-1" },
status: { S: "placed" },
},
})
);
const { Item } = await dynamo.send(
new GetItemCommand({
TableName: "Orders",
Key: { id: { S: "order-1" } },
})
);
expect(Item?.status?.S).toBe("placed");
});
});
```
## Vitest
The same pattern works with Vitest — replace `describe`/`it`/`expect` with their Vitest equivalents (the API is identical):
```typescript
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { FlociContainer } from "@floci/testcontainers";
import { S3Client, CreateBucketCommand, ListBucketsCommand } from "@aws-sdk/client-s3";
describe("S3", () => {
let floci: FlociContainer;
beforeAll(async () => {
floci = await new FlociContainer().start();
});
afterAll(async () => {
await floci.stop();
});
it("should create a bucket", async () => {
const s3 = new S3Client({
endpoint: floci.getEndpoint(),
region: floci.getRegion(),
credentials: {
accessKeyId: floci.getAccessKey(),
secretAccessKey: floci.getSecretKey(),
},
forcePathStyle: true,
});
await s3.send(new CreateBucketCommand({ Bucket: "vitest-bucket" }));
const { Buckets } = await s3.send(new ListBucketsCommand({}));
expect(Buckets?.some(b => b.Name === "vitest-bucket")).toBe(true);
});
});
```
## Reusing the container across test files
Start the container once in a global setup file and expose the endpoint via an environment variable or a shared module so individual test files don't each start their own container.
=== "Jest — globalSetup"
```typescript
// jest.global-setup.ts
import { FlociContainer } from "@floci/testcontainers";
let floci: FlociContainer;
export async function setup() {
floci = await new FlociContainer().start();
process.env.FLOCI_ENDPOINT = floci.getEndpoint();
}
export async function teardown() {
await floci?.stop();
}
```
```json
// jest.config.json
{
"globalSetup": "./jest.global-setup.ts"
}
```
=== "Vitest — globalSetup"
```typescript
// vitest.global-setup.ts
import { FlociContainer } from "@floci/testcontainers";
let floci: FlociContainer;
export async function setup() {
floci = await new FlociContainer().start();
process.env.FLOCI_ENDPOINT = floci.getEndpoint();
}
export async function teardown() {
await floci?.stop();
}
```
```typescript
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globalSetup: "./vitest.global-setup.ts",
},
});
```
## Source and changelog
[github.com/floci-io/testcontainers-floci-node](https://github.com/floci-io/testcontainers-floci-node)
+229
View File
@@ -0,0 +1,229 @@
# Testcontainers — Python
The `testcontainers-floci` package integrates Floci with [Testcontainers for Python](https://testcontainers-python.readthedocs.io/). It works as a context manager and integrates naturally with pytest fixtures.
## Installation
```sh
pip install testcontainers-floci
```
```sh
# poetry
poetry add --group dev testcontainers-floci
# uv
uv add --dev testcontainers-floci
```
## Basic usage — context manager
```python
import boto3
from floci import FlociContainer
def test_s3_create_bucket():
with FlociContainer() as floci:
s3 = boto3.client(
"s3",
endpoint_url=floci.get_endpoint(),
region_name=floci.get_region(),
aws_access_key_id=floci.get_access_key(),
aws_secret_access_key=floci.get_secret_key(),
)
s3.create_bucket(Bucket="my-bucket")
buckets = [b["Name"] for b in s3.list_buckets()["Buckets"]]
assert "my-bucket" in buckets
```
## Pytest fixture
Use a session-scoped fixture so the container starts once and is shared across all tests in the suite.
```python
import pytest
import boto3
from floci import FlociContainer
@pytest.fixture(scope="session")
def floci():
with FlociContainer() as container:
yield container
@pytest.fixture(scope="session")
def s3_client(floci):
return boto3.client(
"s3",
endpoint_url=floci.get_endpoint(),
region_name=floci.get_region(),
aws_access_key_id=floci.get_access_key(),
aws_secret_access_key=floci.get_secret_key(),
)
def test_create_bucket(s3_client):
s3_client.create_bucket(Bucket="my-bucket")
buckets = [b["Name"] for b in s3_client.list_buckets()["Buckets"]]
assert "my-bucket" in buckets
def test_upload_object(s3_client):
s3_client.create_bucket(Bucket="uploads")
s3_client.put_object(Bucket="uploads", Key="hello.txt", Body=b"hello floci")
body = s3_client.get_object(Bucket="uploads", Key="hello.txt")["Body"].read()
assert body == b"hello floci"
```
## SQS example
```python
import pytest
import boto3
import json
from floci import FlociContainer
@pytest.fixture(scope="session")
def floci():
with FlociContainer() as container:
yield container
@pytest.fixture(scope="session")
def sqs_client(floci):
return boto3.client(
"sqs",
endpoint_url=floci.get_endpoint(),
region_name=floci.get_region(),
aws_access_key_id=floci.get_access_key(),
aws_secret_access_key=floci.get_secret_key(),
)
def test_send_and_receive_message(sqs_client):
queue_url = sqs_client.create_queue(QueueName="orders")["QueueUrl"]
sqs_client.send_message(
QueueUrl=queue_url,
MessageBody=json.dumps({"event": "order.placed"}),
)
response = sqs_client.receive_message(QueueUrl=queue_url, MaxNumberOfMessages=1)
messages = response.get("Messages", [])
assert len(messages) == 1
assert json.loads(messages[0]["Body"])["event"] == "order.placed"
```
## DynamoDB example
```python
import pytest
import boto3
from floci import FlociContainer
@pytest.fixture(scope="session")
def floci():
with FlociContainer() as container:
yield container
@pytest.fixture(scope="session")
def dynamo_client(floci):
return boto3.client(
"dynamodb",
endpoint_url=floci.get_endpoint(),
region_name=floci.get_region(),
aws_access_key_id=floci.get_access_key(),
aws_secret_access_key=floci.get_secret_key(),
)
def test_put_and_get_item(dynamo_client):
dynamo_client.create_table(
TableName="Orders",
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
BillingMode="PAY_PER_REQUEST",
)
dynamo_client.put_item(
TableName="Orders",
Item={"id": {"S": "order-1"}, "status": {"S": "placed"}},
)
item = dynamo_client.get_item(
TableName="Orders",
Key={"id": {"S": "order-1"}},
)["Item"]
assert item["status"]["S"] == "placed"
```
## Secrets Manager example
```python
def test_create_and_get_secret(floci):
sm = boto3.client(
"secretsmanager",
endpoint_url=floci.get_endpoint(),
region_name=floci.get_region(),
aws_access_key_id=floci.get_access_key(),
aws_secret_access_key=floci.get_secret_key(),
)
sm.create_secret(Name="db/password", SecretString="supersecret")
value = sm.get_secret_value(SecretId="db/password")["SecretString"]
assert value == "supersecret"
```
## conftest.py pattern
Place shared fixtures in `conftest.py` so every test module picks them up automatically:
```python
# conftest.py
import pytest
import boto3
from floci import FlociContainer
@pytest.fixture(scope="session")
def floci():
with FlociContainer() as container:
yield container
@pytest.fixture(scope="session")
def aws_clients(floci):
kwargs = dict(
endpoint_url=floci.get_endpoint(),
region_name=floci.get_region(),
aws_access_key_id=floci.get_access_key(),
aws_secret_access_key=floci.get_secret_key(),
)
return {
"s3": boto3.client("s3", **kwargs),
"sqs": boto3.client("sqs", **kwargs),
"dynamodb": boto3.client("dynamodb", **kwargs),
"secretsmanager": boto3.client("secretsmanager", **kwargs),
}
```
```python
# test_my_service.py
def test_something(aws_clients):
s3 = aws_clients["s3"]
s3.create_bucket(Bucket="test")
# ...
```
## Source and changelog
[github.com/floci-io/testcontainers-floci-python](https://github.com/floci-io/testcontainers-floci-python)