Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 43 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,27 +69,53 @@ jobs:
- name: Build solution
run: dotnet build ./src/NEventStore.Persistence.MongoDB.Core.sln -c Release --no-restore /p:ContinuousIntegrationBuild=True

- name: Test dependency verification guards
shell: pwsh
run: ./scripts/test-verify-driver-dependencies.ps1

- name: Verify production and default test restore
shell: pwsh
run: ./scripts/verify-driver-dependencies.ps1 -TestDriverVersion '3.13.0'

- name: Pack and verify published dependency contract
if: runner.os == 'Windows'
shell: pwsh
run: |
nuget pack ./src/.nuget/NEventStore.Persistence.MongoDB.nuspec -properties "version=${{ steps.gitversion.outputs.semver }};configuration=Release" -OutputDirectory artifacts
if ($LASTEXITCODE -ne 0) { throw 'NuGet pack failed.' }
$package = Get-ChildItem artifacts -Filter '*.nupkg' | Select-Object -First 1
./scripts/verify-driver-dependencies.ps1 -PackagePath $package.FullName -RestoreConsumer

# Tests run on Linux because this repository needs a live MongoDB instance and
# GitHub Actions service containers are only available on Linux runners.
# The matrix is therefore per target framework instead of per OS.
# Each framework/driver pair restores and builds in a separate job.
test-modern-tfm-linux:
name: Test (Linux, ${{ matrix.tfm }})
name: Test (Linux, ${{ matrix.tfm }}, Driver ${{ matrix.driver }}, MongoDB ${{ matrix.mongodb }})
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Run each modern TFM independently so failures are isolated and easy to read.
# Pin both lanes; update the current 3.x pin in reviewed changes.
driver: ["3.0.0", "3.13.0"]
mongodb: ["7.0.45"]
tfm:
- net8.0
- net9.0
- net10.0
include:
# Retain current-server coverage with the current driver only.
- tfm: net10.0
driver: "3.13.0"
mongodb: "9.0.4"

services:
# The test project reads NEventStore.MongoDB from the environment and expects
# a real MongoDB server. This sidecar provides that dependency for each matrix leg.
mongodb:
image: mongo:9.0.4
# MongoDB 7 supports both lanes and avoids the MongoDB 8/Linux 6.19
# startup incompatibility: https://jira.mongodb.org/browse/SERVER-121912
image: mongo:${{ matrix.mongodb }}
ports:
- 27017:27017
options: >-
Expand Down Expand Up @@ -127,15 +153,22 @@ jobs:
restore-keys: |
nuget-${{ runner.os }}-

- name: Run tests for ${{ matrix.tfm }}
# Only the test project is executed here. It pulls in the production project and
# linked acceptance coverage from the NEventStore submodule.
run: dotnet test ./src/NEventStore.Persistence.MongoDB.Tests/NEventStore.Persistence.MongoDB.Core.Tests.csproj -c Release -f ${{ matrix.tfm }} --logger "trx;LogFileName=test-results-${{ matrix.tfm }}.trx"
- name: Restore selected test driver
run: dotnet restore ./src/NEventStore.Persistence.MongoDB.Tests/NEventStore.Persistence.MongoDB.Core.Tests.csproj -p:MongoDbDriverTestVersion=${{ matrix.driver }}

- name: Verify restored driver and BSON versions
shell: pwsh
run: ./scripts/verify-driver-dependencies.ps1 -TestDriverVersion '${{ matrix.driver }}'

- name: Run tests for ${{ matrix.tfm }} with Driver ${{ matrix.driver }}
# Both driver lanes execute the same complete acceptance and regression suite.
run: dotnet test ./src/NEventStore.Persistence.MongoDB.Tests/NEventStore.Persistence.MongoDB.Core.Tests.csproj -c Release -f ${{ matrix.tfm }} --no-restore -p:MongoDbDriverTestVersion=${{ matrix.driver }} --logger "trx;LogFileName=test-results-${{ matrix.tfm }}-${{ matrix.driver }}-${{ matrix.mongodb }}.trx"

- name: Upload test results
if: ${{ always() }}
uses: actions/upload-artifact@v7.0.2
with:
name: test-results-${{ matrix.tfm }}
path: "**/test-results-${{ matrix.tfm }}.trx"
name: test-results-${{ matrix.tfm }}-${{ matrix.driver }}-${{ matrix.mongodb }}
path: "**/test-results-${{ matrix.tfm }}-${{ matrix.driver }}-${{ matrix.mongodb }}.trx"
if-no-files-found: error
retention-days: 14
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Run the same acceptance and regression suite with pinned MongoDB.Driver 3.0.0 and 3.13.0 test dependencies in isolated CI jobs on MongoDB 7.0.45, retaining a net10.0/current-driver lane on MongoDB 9.0.4. Verify restored Driver/BSON versions and the packed dependency contract across supported frameworks. Production dependencies and the published `[3.0.0,4.0.0)` range are unchanged; no API, schema or migration change. [#93](https://github.com/NEventStore/NEventStore.Persistence.MongoDB/issues/93)

- Guard recycle-bin checkpoint reads and deletion together in synchronous and asynchronous cleanup. Disposed engines fail before collection access, failed reads never reach deletion, and acquired cursors are disposed. Preserve async cancellation, query shape and the strict checkpoint cutoff. No public API, stored-schema or MongoDB minimum-version change; no data migration required. [#107](https://github.com/NEventStore/NEventStore.Persistence.MongoDB/issues/107)

- Translate MongoDB driver failures from all nine lazy synchronous reads at cursor acquisition and subsequent batches, preserving streaming, cursor disposal, and serializer exception identity. Async reads, public signatures, stored data, CI, and the MongoDB 4.2 minimum are unchanged. [#91](https://github.com/NEventStore/NEventStore.Persistence.MongoDB/issues/91)
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,31 @@ To build the project locally on a Windows Machine:
NEventStore.MongoDB="mongodb://localhost:50002/NEventStore"
```

### MongoDB driver coverage

The production reference stays at MongoDB.Driver 3.0.0 and the published range stays
`[3.0.0,4.0.0)`. Only the test project accepts `MongoDbDriverTestVersion`; its default
is the pinned current stable 3.x release, 3.13.0. CI runs the same full suite for
3.0.0 and 3.13.0 on net8.0, net9.0 and net10.0 in separate jobs with MongoDB 7.0.45. One additional net10.0/3.13.0 job retains
MongoDB 9.0.4 coverage without pairing that server with the baseline driver.
MongoDB 7 also avoids the [MongoDB 8/Linux 6.19 startup incompatibility](https://jira.mongodb.org/browse/SERVER-121912).
.NET Framework 4.7.2 is compiled but not executed. This matrix does not establish
compatibility with every MongoDB server or every intervening driver release.

Run either lane locally (do not reuse `--no-restore` or `--no-build` outputs when
switching versions):

```powershell
dotnet test ./src/NEventStore.Persistence.MongoDB.Tests/NEventStore.Persistence.MongoDB.Core.Tests.csproj -c Release -f net8.0 -p:MongoDbDriverTestVersion=3.0.0
dotnet test ./src/NEventStore.Persistence.MongoDB.Tests/NEventStore.Persistence.MongoDB.Core.Tests.csproj -c Release -f net8.0 -p:MongoDbDriverTestVersion=3.13.0
```

When a new stable 3.x driver is released, update the test-project default and the
CI current-lane/verification pins together, then review both lanes' results. Keep
the baseline and production package contract unchanged. Do not use floating versions.
CI checks the resolved Driver and BSON packages for every target and verifies the
packed package's dependency ranges and consumer restore without publishing it.

## Run Benchmarks (locally)

- Build benchmark project:
Expand Down
82 changes: 82 additions & 0 deletions scripts/test-verify-driver-dependencies.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Synthetic regression checks; no MongoDB, NuGet restore, or Pester required.
$ErrorActionPreference = 'Stop'
$verify = Join-Path $PSScriptRoot 'verify-driver-dependencies.ps1'
$temp = Join-Path ([IO.Path]::GetTempPath()) ([guid]::NewGuid().ToString())
New-Item -ItemType Directory $temp | Out-Null
$testPath = Join-Path $temp 'tests.json'
$libraryPath = Join-Path $temp 'library.json'
$packagePath = Join-Path $temp 'fixture.nupkg'
$testTargets = @('.NETFramework,Version=v4.7.2', 'net8.0', 'net9.0', 'net10.0')
$libraryTargets = $testTargets + @('netstandard2.1', 'net6.0')
function Write-Assets($path, $targets, $version) {
$data = @{ targets = @{} }
foreach ($target in $targets) {
$data.targets[$target] = @{
"MongoDB.Driver/$version" = @{ type = 'package' }
"MongoDB.Bson/$version" = @{ type = 'package' }
}
}
$data | ConvertTo-Json -Depth 10 | Set-Content $path
}
function Assert-Fails($label, [scriptblock]$action, $message) {
try { & $action } catch {
if ($_.Exception.Message -notlike "*$message*") { throw }
Write-Host "PASS: $label"
return
}
throw "Expected failure: $label"
}
function Check-Assets($version) {
& $verify -TestDriverVersion $version -TestAssetsPath $testPath -LibraryAssetsPath $libraryPath
}
function Write-Package($targets, $range) {
$groups = foreach ($target in $targets) {
"<group targetFramework='$target'><dependency id='MongoDB.Driver' version='$range'/></group>"
}
$xml = "<package xmlns='http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd'><metadata><id>NEventStore.Persistence.MongoDB</id><version>1.0.0</version><dependencies>$($groups -join '')</dependencies></metadata></package>"
$zip = [IO.Compression.ZipFile]::Open($packagePath, 'Create')
try {
$writer = [IO.StreamWriter]::new($zip.CreateEntry('NEventStore.Persistence.MongoDB.nuspec').Open())
try { $writer.Write($xml) } finally { $writer.Dispose() }
} finally { $zip.Dispose() }
}
try {
Write-Assets $libraryPath $libraryTargets '3.0.0'
foreach ($version in @('3.0.0', '3.9.0')) {
Write-Assets $testPath $testTargets $version
Check-Assets $version
}
Assert-Fails 'test downgrade' { Check-Assets '3.0.0' } 'MongoDB.Driver'
$data = Get-Content $testPath -Raw | ConvertFrom-Json -AsHashtable
$data.targets['net10.0'].Remove('MongoDB.Bson/3.9.0')
$data | ConvertTo-Json -Depth 10 | Set-Content $testPath
Assert-Fails 'missing BSON' { Check-Assets '3.9.0' } 'MongoDB.Bson'
Write-Assets $testPath @('net8.0') '3.9.0'
Assert-Fails 'missing test frameworks' { Check-Assets '3.9.0' } 'target frameworks'
Write-Assets $testPath ($testTargets + 'net8.0/linux-x64') '3.9.0'
Check-Assets '3.9.0'
$data = Get-Content $testPath -Raw | ConvertFrom-Json -AsHashtable
$data.targets['net8.0/linux-x64'].Remove('MongoDB.Bson/3.9.0')
$data.targets['net8.0/linux-x64']['MongoDB.Bson/3.0.0'] = @{ type = 'package' }
$data | ConvertTo-Json -Depth 10 | Set-Content $testPath
Assert-Fails 'BSON skew in RID target' { Check-Assets '3.9.0' } 'MongoDB.Bson'
Write-Assets $testPath $testTargets '3.9.0'
Write-Assets $libraryPath $libraryTargets '3.9.0'
Assert-Fails 'production upgraded' { Check-Assets '3.9.0' } 'MongoDB.Driver'
Write-Assets $libraryPath @() '3.0.0'
Assert-Fails 'empty production targets' { Check-Assets '3.9.0' } 'target frameworks'
Write-Package @('.NETFramework4.7.2', '.NETStandard2.1', 'net6.0', 'net8.0', 'net9.0', 'net10.0') '[3.0.0,4.0.0)'
& $verify -PackagePath $packagePath
Remove-Item $packagePath
Write-Package $libraryTargets '[3.0.0, 4.0.0)'
& $verify -PackagePath $packagePath
Remove-Item $packagePath
Write-Package $libraryTargets '[3.9.0,4.0.0)'
Assert-Fails 'raised package floor' { & $verify -PackagePath $packagePath } 'MongoDB.Driver'
Remove-Item $packagePath
Write-Package @('net8.0') '[3.0.0,4.0.0)'
Assert-Fails 'missing package frameworks' { & $verify -PackagePath $packagePath } 'target frameworks'
Write-Host 'All dependency verifier regression checks passed.'
} finally {
Remove-Item $temp -Recurse -Force
}
113 changes: 113 additions & 0 deletions scripts/verify-driver-dependencies.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
[CmdletBinding(DefaultParameterSetName = 'Assets')]
param(
[Parameter(Mandatory, ParameterSetName = 'Assets')]
[string]$TestDriverVersion,
[Parameter(ParameterSetName = 'Assets')]
[string]$TestAssetsPath = "$PSScriptRoot/../src/NEventStore.Persistence.MongoDB.Tests/obj/project.assets.json",
[Parameter(ParameterSetName = 'Assets')]
[string]$LibraryAssetsPath = "$PSScriptRoot/../src/NEventStore.Persistence.MongoDB/obj/project.assets.json",
[Parameter(Mandatory, ParameterSetName = 'Package')]
[string]$PackagePath,
[Parameter(ParameterSetName = 'Package')]
[switch]$RestoreConsumer
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Explicit compatibility contract: changing supported targets requires updating this guard.
$testFrameworks = @('net472', 'net8.0', 'net9.0', 'net10.0')
$libraryFrameworks = $testFrameworks + @('netstandard2.1', 'net6.0')

function Normalize-Framework([string]$framework) {
$framework = $framework.Split('/')[0].ToLowerInvariant().Replace(',version=v', '').TrimStart('.')
if ($framework.StartsWith('netframework')) { return 'net' + $framework.Substring(12).Replace('.', '') }
return $framework.Replace('netcoreapp', 'net')
}

function Assert-Frameworks($actual, $expected, $source) {
$normalized = @($actual | ForEach-Object { Normalize-Framework $_ } | Sort-Object -Unique)
if (Compare-Object $normalized ($expected | Sort-Object -Unique)) {
throw "$source target frameworks: expected $($expected -join ', '); found $($normalized -join ', ')"
}
}

function Assert-Assets($path, $version, $frameworks) {
$assets = Get-Content -LiteralPath $path -Raw | ConvertFrom-Json -AsHashtable
if (-not $assets.targets -or $assets.targets.Count -eq 0) { throw "$path has no target frameworks" }
Assert-Frameworks @($assets.targets.Keys) $frameworks $path
foreach ($target in $assets.targets.Keys) {
foreach ($package in @('MongoDB.Driver', 'MongoDB.Bson')) {
$matches = @($assets.targets[$target].Keys | Where-Object { $_.StartsWith("$package/", [StringComparison]::OrdinalIgnoreCase) })
if ($matches.Count -ne 1 -or $matches[0] -cne "$package/$version" -or $assets.targets[$target][$matches[0]].type -ne 'package') {
throw "$path [$target]: expected $package/$version; found $($matches -join ', ')"
}
}
}
Write-Host "Verified $path`: MongoDB.Driver and MongoDB.Bson $version in every target."
}

if ($PSCmdlet.ParameterSetName -eq 'Assets') {
Assert-Assets $TestAssetsPath $TestDriverVersion $testFrameworks
Assert-Assets $LibraryAssetsPath '3.0.0' $libraryFrameworks
} else {
$zip = [IO.Compression.ZipFile]::OpenRead((Resolve-Path -LiteralPath $PackagePath).Path)
try {
$entries = @($zip.Entries | Where-Object { $_.FullName -like '*.nuspec' })
if ($entries.Count -ne 1) { throw "$PackagePath must contain exactly one nuspec" }
$reader = [IO.StreamReader]::new($entries[0].Open())
try { [xml]$nuspec = $reader.ReadToEnd() } finally { $reader.Dispose() }
if ($nuspec.package.metadata.id -ne 'NEventStore.Persistence.MongoDB') { throw 'Unexpected package identity' }
$groups = @($nuspec.package.metadata.dependencies.group)
Assert-Frameworks @($groups | ForEach-Object { $_.targetFramework }) $libraryFrameworks $PackagePath
foreach ($group in $groups) {
$drivers = @($group.dependency | Where-Object { $_.id -eq 'MongoDB.Driver' })
if ($drivers.Count -ne 1 -or ($drivers[0].version -replace '\s', '') -ne '[3.0.0,4.0.0)') {
throw "$PackagePath [$($group.targetFramework)]: expected MongoDB.Driver [3.0.0,4.0.0)"
}
}
Write-Host "Verified $PackagePath`: MongoDB.Driver [3.0.0,4.0.0) for all supported frameworks."
} finally { $zip.Dispose() }
}

# Restore the actual package as a consumer, isolated from repository MSBuild props
# and the global package cache so a previously published package cannot mask it.
if ($RestoreConsumer) {
$temp = Join-Path ([IO.Path]::GetTempPath()) ([guid]::NewGuid().ToString())
New-Item -ItemType Directory $temp | Out-Null
try {
$project = Join-Path $temp 'Consumer.csproj'
$version = [Security.SecurityElement]::Escape($nuspec.package.metadata.version)
if ([string]::IsNullOrWhiteSpace($version)) { throw 'Packed nuspec has no version' }
@"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><TargetFrameworks>$($libraryFrameworks -join ';')</TargetFrameworks></PropertyGroup>
<ItemGroup><PackageReference Include="NEventStore.Persistence.MongoDB" Version="[$version]" /></ItemGroup>
</Project>
"@ | Set-Content $project
$source = New-Item -ItemType Directory "$temp/feed"
Copy-Item -LiteralPath $PackagePath -Destination $source.FullName
$escapedSource = [Security.SecurityElement]::Escape($source.FullName)
$config = Join-Path $temp 'NuGet.Config'
# Exact ID mapping takes precedence over the wildcard, so the adapter
# can only come from this build, never the public feed.
@"
<configuration>
<packageSources>
<clear />
<add key="local" value="$escapedSource" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<clear />
<packageSource key="local"><package pattern="NEventStore.Persistence.MongoDB" /></packageSource>
<packageSource key="nuget.org"><package pattern="*" /></packageSource>
</packageSourceMapping>
</configuration>
"@ | Set-Content $config
& dotnet restore $project --configfile $config --packages "$temp/packages" --verbosity minimal
if ($LASTEXITCODE -ne 0) { throw "Packed consumer restore failed with exit code $LASTEXITCODE" }
Assert-Assets "$temp/obj/project.assets.json" '3.0.0' $libraryFrameworks
} finally {
Remove-Item $temp -Recurse -Force
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<RootNamespace>NEventStore.Persistence.MongoDB.Tests</RootNamespace>
<TestTfmsInParallel>false</TestTfmsInParallel>
<IsTestProject>true</IsTestProject>
<!-- Test-only override. Keep the production project and published range unchanged. -->
<MongoDbDriverTestVersion Condition="'$(MongoDbDriverTestVersion)' == ''">3.13.0</MongoDbDriverTestVersion>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -22,7 +24,7 @@
<PackageReference Include="FakeItEasy" Version="9.0.1" />
<PackageReference Include="FluentAssertions" Version="8.10.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageReference Include="MongoDB.Driver" Version="3.9.0" />
<PackageReference Include="MongoDB.Driver" Version="[$(MongoDbDriverTestVersion)]" />
<PackageReference Include="MSTest.TestAdapter" Version="4.2.3" />
<PackageReference Include="MSTest.TestFramework" Version="4.2.3" />
<PackageReference Include="NUnit" Version="4.6.1" />
Expand Down
Loading