Skip to content

Commit

Permalink
Use TestContainers to allow for local testing. (#76)
Browse files Browse the repository at this point in the history
* Use Testcontainers for easier to run tests.

* Adjust Workflow script.
This is not compatible with Clusters at this time.

* Tentative support for Clusters down the line.
It does not look like a Cluster can be tested with the base image for ArangoDB.

* Don't start so many containers please.

* Be explicit about Cluster vs Single, so it's ready to go for later down the line.
Edit Build.yml to run against 3.11, which was the original tested-against server.

* Minor cleanup items.

* Readme.

* Remove unnecessary items.
  • Loading branch information
HarryCordewener authored Nov 7, 2024
1 parent ef20175 commit 094ae50
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 55 deletions.
21 changes: 7 additions & 14 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,17 @@ jobs:
- 8.x
arango:
- "arangodb:3.11"
# - "arangodb:3.10"
# - "arangodb/enterprise:3.9"
- "arangodb:latest"
# - "arangodb/enterprise:3.9"
topology:
- single
# - cluster

steps:
- uses: actions/checkout@v3

- name: chmod
run: find ./docker -name '*.sh' | xargs chmod +x

- name: arango
run: ./docker/start_db_${{ matrix.topology }}_retry_fail.sh ${{ matrix.arango }}
env:
ARANGO_LICENSE_KEY: ${{ secrets.ARANGO_LICENSE_KEY }}

- uses: actions/checkout@v4

- name: dotnet
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet }}

Expand All @@ -49,4 +41,5 @@ jobs:
- name: test
run: dotnet test --configuration Release
env:
ARANGODB_CONNECTION: "Server=http://172.28.3.1:8529;Realm=CI-{UUID};User=root;Password=test;"
ARANGODB_VERSION: ${{ matrix.arango }}
ARANGODB_TOPOLOGY: ${{ matrix.topology }}
11 changes: 6 additions & 5 deletions Core.Arango.Tests/Core.Arango.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,22 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="Testcontainers.ArangoDb" Version="3.10.0" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="Xunit.Priority" Version="1.1.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
Expand Down
157 changes: 138 additions & 19 deletions Core.Arango.Tests/Core/TestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,148 @@
using Newtonsoft.Json;
using Xunit;
using Xunit.Abstractions;
using Testcontainers.ArangoDb;
using System.Collections.Generic;
using System.IO;
using DotNet.Testcontainers.Builders;

namespace Core.Arango.Tests.Core
{
public abstract class TestBase : IAsyncLifetime
{
private const string ARANGO_LICENSE_KEY_ENVAR = "ARANGO_LICENSE_KEY";
private const string ARANGODB_VERSION_ENVAR = "ARANGODB_VERSION";
private const string ARANGODB_TOPOLOGY_ENVAR = "ARANGODB_TOPOLOGY";
private const string DefaultImage = "arangodb:latest";
private const string DefaultImagePassword = "password";
private const string DefaultImageUser = "root";
private const string ClusterValue = "cluster";

public IArangoContext Arango { get; protected set; }
private readonly static Lazy<Task<IEnumerable<ArangoDbContainer>>> Containers = new(async () =>
Environment.GetEnvironmentVariable(ARANGODB_TOPOLOGY_ENVAR) == ClusterValue
? await SetupClusterServer()
: await SetupSingleServer());

public virtual Task InitializeAsync()
public virtual async Task InitializeAsync()
{
return Task.CompletedTask;
foreach (var container in await Containers.Value)
{
await container.StartAsync()
.ConfigureAwait(false);
}
}

public async Task DisposeAsync()
private static (Dictionary<string, string> Environment, string Version) GetVersionAndEnvironment()
{
try
var environment = new Dictionary<string, string>();

if (string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(ARANGO_LICENSE_KEY_ENVAR)))
{
foreach (var db in await Arango.Database.ListAsync())
await Arango.Database.DropAsync(db);
environment.Add(ARANGO_LICENSE_KEY_ENVAR, Environment.GetEnvironmentVariable(ARANGO_LICENSE_KEY_ENVAR));
}
catch

var version = string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(ARANGODB_VERSION_ENVAR))
? DefaultImage
: Environment.GetEnvironmentVariable(ARANGODB_VERSION_ENVAR);

return (environment, version);
}

private static Task<List<ArangoDbContainer>> SetupSingleServer()
{
var (environment, version) = GetVersionAndEnvironment();
return Task.FromResult(new List<ArangoDbContainer>{new ArangoDbBuilder()
.WithImage(version)
.WithEnvironment(environment)
.WithPassword(DefaultImagePassword)
.Build() });
}

private static async Task<List<ArangoDbContainer>> SetupClusterServer()
{
var (environment, version) = GetVersionAndEnvironment();

// This is not yet tested. But is a largely compatible replication of the start_db_cluster script.
// 2024-10-29 20:11:32 Error while processing command-line options for arangod:
// 2024 - 10 - 29 20:11:32 unknown option '--cluster.start-dbserver false'
// I suspect that's because Cluster is only available for Enterprise?
var arangoDBNetwork = new NetworkBuilder()
.WithName($"ArangoDB-{Guid.NewGuid():D}")
.Build();

var jwt = Path.GetTempFileName();
File.Move(jwt, jwt + ".jwt");
jwt += ".jwt";
File.AppendAllText(jwt, "Averysecretword");

var leadAgent = $"leadagent-{Guid.NewGuid():N}";
var extraAgents = Enumerable.Range(0, 2);
var dbs = Enumerable.Range(0, 2);
var coordinators = Enumerable.Range(0, 2);

var containers = new List<ArangoDbContainer>();

var DefaultContainerConfiguration = new ArangoDbBuilder()
.WithImage(version)
.WithResourceMapping(jwt, "/jwtSecret")
.WithEnvironment(environment)
.WithPassword(DefaultImagePassword)
.WithNetwork(arangoDBNetwork);

containers.Add(DefaultContainerConfiguration
.WithName(leadAgent)
.WithCommand(
"--cluster.start-dbserver", "false",
"--cluster.start-coordinator", "false",
"--auth.jwt-secret", "/jwtSecret")
.Build());

foreach (var _ in extraAgents)
{
//
containers.Add(DefaultContainerConfiguration
.WithName($"{extraAgents}-{Guid.NewGuid():N}")
.WithCommand(
"--cluster.start-dbserver", "false",
"--cluster.start-coordinator", "false",
"--auth.jwt-secret", "/jwtSecret",
"--starter.join", leadAgent)
.Build());
}

foreach (var _ in dbs)
{
containers.Add(DefaultContainerConfiguration
.WithName($"{dbs}-{Guid.NewGuid():N}")
.WithCommand(
"--cluster.start-dbserver", "true",
"--cluster.start-coordinator", "false",
"--auth.jwt-secret", "/jwtSecret",
"--starter.join", leadAgent)
.Build());
}

foreach (var _ in coordinators)
{
containers.Add(DefaultContainerConfiguration
.WithName($"{coordinators}-{Guid.NewGuid():N}")
.WithCommand(
"--cluster.start-dbserver", "false",
"--cluster.start-coordinator", "true",
"--auth.jwt-secret", "/jwtSecret",
"--starter.join", leadAgent)
.Build());
}

await arangoDBNetwork.CreateAsync()
.ConfigureAwait(false);

return containers;
}

public async Task DisposeAsync()
{
await Task.CompletedTask;
}

public async Task SetupAsync(string serializer, string createDatabase = "test")
Expand All @@ -47,20 +166,20 @@ public async Task SetupAsync(string serializer, string createDatabase = "test")
});

if (!string.IsNullOrEmpty(createDatabase))
await Arango.Database.CreateAsync("test");
{
var databaseCreateSuccessful = await Arango.Database.CreateAsync("test");
if (databaseCreateSuccessful == false)
{
throw new Exception("Database creation failed");
}
}
}

protected string UniqueTestRealm()
{
var cs = Environment.GetEnvironmentVariable("ARANGODB_CONNECTION");

if (string.IsNullOrWhiteSpace(cs))
cs = "Server=http://localhost:8529;Realm=CI-{UUID};User=root;Password=;";

return cs.Replace("{UUID}", Guid.NewGuid().ToString("D"));
}
protected static string UniqueTestRealm()
// Last to get the Coordinators for clusters, or the only existing one for a single server.
=> $"Server={Containers.Value.Result.Last().GetTransportAddress()};User={DefaultImageUser};Realm=CI-{Guid.NewGuid():D};Password={DefaultImagePassword};";

protected void PrintQuery<T>(IQueryable<T> query, ITestOutputHelper output)
protected static void PrintQuery<T>(IQueryable<T> query, ITestOutputHelper output)
{
var aql = query.ToAql();
output.WriteLine("QUERY:");
Expand Down
2 changes: 1 addition & 1 deletion Core.Arango.Tests/IndexTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public async Task DropAll(string serializer)

await Arango.Index.CreateAsync("test", "test", new ArangoIndex
{
Fields = new List<string> {"test"},
Fields = ["test"],
Type = ArangoIndexType.Hash
});

Expand Down
20 changes: 15 additions & 5 deletions Core.Arango.Tests/LinqTest.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Core.Arango.Protocol;
Expand All @@ -10,7 +8,6 @@
using Newtonsoft.Json;
using Xunit;
using Xunit.Abstractions;
using System.Linq.Expressions;

namespace Core.Arango.Tests
{
Expand Down Expand Up @@ -70,7 +67,7 @@ await Arango.Query<Project>("test")
}

[Fact]
public async Task MultiFilter()
public Task MultiFilter()
{
var q = Arango.Query<Project>("test")
.Where(x => x.Name == "Project A")
Expand All @@ -80,16 +77,20 @@ public async Task MultiFilter()
.Skip(0).Take(1);

Assert.Equal("FOR `x` IN `Project` FILTER ( `x`.`Name` == @P1 ) SORT `x`.`Name` ASC FILTER ( `x`.`Name` == @P2 ) SORT `x`.`Name` DESC LIMIT @P3 , @P4 RETURN `x`", q.ToAql().aql.Trim());

return Task.CompletedTask;
}


[Fact]
public async Task FilterOrder()
public Task FilterOrder()
{
var q = Arango.Query<Project>("test")
.Where(x => (x.Name == "Project A" || x.Name == "Project B") && x.Budget <= 0);

Assert.Equal("FOR `x` IN `Project` FILTER ( ( ( `x`.`Name` == @P1 ) OR ( `x`.`Name` == @P2 ) ) AND ( `x`.`Budget` <= @P3 ) ) RETURN `x`", q.ToAql().aql.Trim());

return Task.CompletedTask;
}

[Fact]
Expand Down Expand Up @@ -117,6 +118,7 @@ public async Task GroupBy()

_output.WriteLine(q.ToAql().aql);
_output.WriteLine("");
await Task.CompletedTask;
//_output.WriteLine(JsonConvert.SerializeObject(await q.ToListAsync(), Formatting.Indented));
}

Expand All @@ -132,6 +134,7 @@ public async Task MathAbs()

_output.WriteLine(q.ToAql().aql);
_output.WriteLine("");
await Task.CompletedTask;
//_output.WriteLine(JsonConvert.SerializeObject(await q.ToListAsync(), Formatting.Indented));
}

Expand All @@ -147,6 +150,7 @@ public async Task Ternary()

_output.WriteLine(q.ToAql().aql);
_output.WriteLine("");
await Task.CompletedTask;
//_output.WriteLine(JsonConvert.SerializeObject(await q.ToListAsync(), Formatting.Indented));
}

Expand Down Expand Up @@ -174,6 +178,7 @@ public async Task Remove()
.Remove().In<Project>().Select(x => x.Key);

_output.WriteLine(q.ToAql().aql);
await Task.CompletedTask;
}

[Fact]
Expand Down Expand Up @@ -217,10 +222,13 @@ public async Task QueryableContains()
.Contains("CB");

Assert.True(q);
await Task.CompletedTask;
}

public override async Task InitializeAsync()
{
await base.InitializeAsync();

Arango = new ArangoContext(UniqueTestRealm());
await Arango.Database.CreateAsync(D);
await Arango.Collection.CreateAsync(D, nameof(Client), ArangoCollectionType.Document);
Expand Down Expand Up @@ -300,6 +308,7 @@ public async Task StringContains()
var aql = q.ToAql().aql.Trim();

Assert.Equal("FOR `x` IN `Project` FILTER CONTAINS( `x`.`Name` , @P1 ) RETURN `x`", aql);
await Task.CompletedTask;
}

[Fact]
Expand All @@ -309,6 +318,7 @@ public async Task StringConcat()
var aql = q.ToAql().aql.Trim();

Assert.Equal("FOR `x` IN `Project` FILTER ( CONCAT( `x`.`Name` , @P1 ) == @P2 ) RETURN `x`", aql);
await Task.CompletedTask;
}
}
}
Loading

0 comments on commit 094ae50

Please sign in to comment.