integration tests

This commit is contained in:
Trevi Awater
2026-03-30 21:31:01 +02:00
parent 3473744e71
commit d4949c7503
9 changed files with 564 additions and 0 deletions

View File

@@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Neo4j.iOS", "Xamari
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Neo4j.Tests", "Xamarin.Neo4j\Xamarin.Neo4j.Tests\Xamarin.Neo4j.Tests.csproj", "{A9874913-14B2-4BED-A838-89697258405A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Neo4j.IntegrationTests", "Xamarin.Neo4j\Xamarin.Neo4j.IntegrationTests\Xamarin.Neo4j.IntegrationTests.csproj", "{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|iPhone = Ad-Hoc|iPhone
@@ -100,5 +102,21 @@ Global
{A9874913-14B2-4BED-A838-89697258405A}.Release|iPhone.Build.0 = Release|Any CPU
{A9874913-14B2-4BED-A838-89697258405A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{A9874913-14B2-4BED-A838-89697258405A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.AppStore|iPhone.Build.0 = Debug|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Debug|iPhone.Build.0 = Debug|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Release|Any CPU.Build.0 = Release|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Release|iPhone.ActiveCfg = Release|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Release|iPhone.Build.0 = Release|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{B3E7D2A1-5C4F-4A8B-9D6E-1F2A3B4C5D6E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,71 @@
using System;
using System.Threading.Tasks;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using Neo4j.Driver;
namespace Xamarin.Neo4j.IntegrationTests.Fixtures
{
public class MemgraphFixture : IAsyncDisposable
{
private IContainer _container;
public string BoltUri { get; private set; }
public async Task StartAsync()
{
_container = new ContainerBuilder()
.WithImage("memgraph/memgraph:latest")
.WithPortBinding(7687, true)
.WithCommand("--bolt-server-name-for-init=Neo4j/5.0.0", "--also-log-to-stderr")
.WithWaitStrategy(Wait.ForUnixContainer()
.UntilPortIsAvailable(7687))
.Build();
await _container.StartAsync();
var boltPort = _container.GetMappedPublicPort(7687);
BoltUri = $"bolt://localhost:{boltPort}";
Console.Error.WriteLine($"[MemgraphFixture] Container started, BoltUri={BoltUri}");
await WaitForReady();
Console.Error.WriteLine("[MemgraphFixture] Ready!");
}
private async Task WaitForReady()
{
for (var i = 0; i < 30; i++)
{
IDriver driver = null;
try
{
driver = GraphDatabase.Driver(BoltUri);
await using var session = driver.AsyncSession();
var cursor = await session.RunAsync("RETURN 1 AS n");
await cursor.ConsumeAsync();
return;
}
catch (Exception ex)
{
var inner = ex.InnerException != null ? $" -> {ex.InnerException.GetType().Name}: {ex.InnerException.Message}" : "";
Console.Error.WriteLine($"[Memgraph] Attempt {i}: {ex.GetType().Name}: {ex.Message}{inner}");
await Task.Delay(2000);
}
finally
{
if (driver != null) await driver.DisposeAsync();
}
}
throw new TimeoutException("Memgraph container did not become ready in time.");
}
public async ValueTask DisposeAsync()
{
if (_container != null)
await _container.DisposeAsync();
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Threading.Tasks;
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using Neo4j.Driver;
namespace Xamarin.Neo4j.IntegrationTests.Fixtures
{
public class Neo4jFixture : IAsyncDisposable
{
private IContainer _container;
public string BoltUri { get; private set; }
public string Username => "neo4j";
public string Password => "testpassword";
public async Task StartAsync()
{
_container = new ContainerBuilder()
.WithImage("neo4j:latest")
.WithPortBinding(7474, true)
.WithPortBinding(7687, true)
.WithEnvironment("NEO4J_AUTH", $"{Username}/{Password}")
.WithWaitStrategy(Wait.ForUnixContainer()
.UntilPortIsAvailable(7687))
.Build();
await _container.StartAsync();
var boltPort = _container.GetMappedPublicPort(7687);
BoltUri = $"bolt://localhost:{boltPort}";
await WaitForReady();
}
private async Task WaitForReady()
{
var driver = GraphDatabase.Driver(BoltUri, AuthTokens.Basic(Username, Password));
for (var i = 0; i < 30; i++)
{
try
{
await driver.VerifyConnectivityAsync();
await driver.DisposeAsync();
return;
}
catch
{
await Task.Delay(1000);
}
}
await driver.DisposeAsync();
throw new TimeoutException("Neo4j container did not become ready in time.");
}
public async ValueTask DisposeAsync()
{
if (_container != null)
await _container.DisposeAsync();
}
}
}

View File

@@ -0,0 +1,78 @@
using System.Threading.Tasks;
using Neo4j.Driver;
using NUnit.Framework;
using Xamarin.Neo4j.IntegrationTests.Fixtures;
namespace Xamarin.Neo4j.IntegrationTests
{
[TestFixture]
public class MemgraphConnectivityTests
{
private static MemgraphFixture _fixture;
private IDriver _driver;
[OneTimeSetUp]
public async Task OneTimeSetUp()
{
_fixture = new MemgraphFixture();
await _fixture.StartAsync();
}
[OneTimeTearDown]
public async Task OneTimeTearDown()
{
if (_fixture != null)
await _fixture.DisposeAsync();
}
[SetUp]
public void SetUp()
{
_driver = GraphDatabase.Driver(_fixture.BoltUri);
}
[TearDown]
public async Task TearDown()
{
if (_driver != null)
await _driver.DisposeAsync();
}
[Test]
public async Task CanEstablishConnection()
{
await using var session = _driver.AsyncSession();
var result = await session.RunAsync("RETURN 1 AS n");
var record = await result.SingleAsync();
Assert.AreEqual(1L, record["n"].As<long>());
}
[Test]
public async Task CanExecuteCypherQuery()
{
await using var session = _driver.AsyncSession();
var result = await session.RunAsync("CREATE (n:Test {name: 'hello'}) RETURN n.name AS name");
var record = await result.SingleAsync();
Assert.AreEqual("hello", record["name"].As<string>());
}
[Test]
public async Task CanQueryNodesAndRelationships()
{
await using var session = _driver.AsyncSession();
await session.RunAsync(
"CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})");
var cursor = await session.RunAsync(
"MATCH (a:Person)-[r:KNOWS]->(b:Person) RETURN a.name AS from, b.name AS to, type(r) AS rel");
var record = await cursor.SingleAsync();
Assert.AreEqual("Alice", record["from"].As<string>());
Assert.AreEqual("Bob", record["to"].As<string>());
Assert.AreEqual("KNOWS", record["rel"].As<string>());
}
}
}

View File

@@ -0,0 +1,126 @@
using System.Threading.Tasks;
using Neo4j.Driver;
using NUnit.Framework;
using Xamarin.Neo4j.IntegrationTests.Fixtures;
namespace Xamarin.Neo4j.IntegrationTests
{
[TestFixture]
public class Neo4jConnectivityTests
{
private static Neo4jFixture _fixture;
private IDriver _driver;
[OneTimeSetUp]
public async Task OneTimeSetUp()
{
_fixture = new Neo4jFixture();
await _fixture.StartAsync();
}
[OneTimeTearDown]
public async Task OneTimeTearDown()
{
if (_fixture != null)
await _fixture.DisposeAsync();
}
[SetUp]
public void SetUp()
{
_driver = GraphDatabase.Driver(
_fixture.BoltUri,
AuthTokens.Basic(_fixture.Username, _fixture.Password));
}
[TearDown]
public async Task TearDown()
{
if (_driver != null)
await _driver.DisposeAsync();
}
[Test]
public async Task CanEstablishConnection()
{
await _driver.VerifyConnectivityAsync();
}
[Test]
public async Task CanExecuteCypherQuery()
{
await using var session = _driver.AsyncSession();
var result = await session.RunAsync("CREATE (n:Test {name: 'hello'}) RETURN n.name AS name");
var record = await result.SingleAsync();
Assert.AreEqual("hello", record["name"].As<string>());
}
[Test]
public async Task CanQueryNodesAndRelationships()
{
await using var session = _driver.AsyncSession();
await session.RunAsync(
"CREATE (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person {name: 'Bob'})");
var cursor = await session.RunAsync(
"MATCH (a:Person)-[r:KNOWS]->(b:Person) RETURN a.name AS from, b.name AS to, type(r) AS rel");
var record = await cursor.SingleAsync();
Assert.AreEqual("Alice", record["from"].As<string>());
Assert.AreEqual("Bob", record["to"].As<string>());
Assert.AreEqual("KNOWS", record["rel"].As<string>());
}
[Test]
public async Task CanExpandNode()
{
await using var session = _driver.AsyncSession();
var setupResult = await session.RunAsync(
"CREATE (c:Hub {name: 'Center'})" +
"CREATE (c)-[:LINK]->(a:Leaf {name: 'A'})" +
"CREATE (c)-[:LINK]->(b:Leaf {name: 'B'})" +
"CREATE (c)-[:LINK]->(d:Leaf {name: 'C'})" +
"RETURN id(c) AS centerId");
var setupRecord = await setupResult.SingleAsync();
var centerId = setupRecord["centerId"].As<long>();
var cursor = await session.RunAsync(
"MATCH (n)-[r]-(m) WHERE id(n) = $nodeId RETURN n, r, m",
new { nodeId = centerId });
var records = await cursor.ToListAsync();
Assert.AreEqual(3, records.Count);
foreach (var record in records)
{
Assert.IsInstanceOf<INode>(record["n"]);
Assert.IsInstanceOf<IRelationship>(record["r"]);
Assert.IsInstanceOf<INode>(record["m"]);
}
}
[Test]
public void CanParseConnectionString()
{
var connectionString = new Models.Neo4jConnectionString
{
Scheme = "bolt://",
Host = "localhost:7687",
Username = _fixture.Username,
Password = _fixture.Password,
Database = "neo4j"
};
var result = connectionString.ParseHost();
Assert.AreEqual("bolt://localhost:7687", result.Item1);
Assert.IsFalse(result.Item2);
Assert.IsFalse(result.Item3);
}
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Testcontainers" Version="4.3.0" />
<PackageReference Include="Neo4j.Driver" Version="5.28.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Xamarin.Neo4j\Models\Neo4jConnectionString.cs" />
</ItemGroup>
</Project>