diff --git a/.github/workflows/post-integration.yml b/.github/workflows/post-integration.yml
index 088efb0..749c8a5 100644
--- a/.github/workflows/post-integration.yml
+++ b/.github/workflows/post-integration.yml
@@ -11,6 +11,7 @@ env:
ATC_EMAIL: 'atcnet.org@gmail.com'
ATC_NAME: 'Atc-Net'
NUGET_REPO_URL: 'https://nuget.pkg.github.com/atc-net/index.json'
+ VERSION: 1.1.0.${{ github.run_number }}
jobs:
merge-to-stable:
@@ -43,14 +44,14 @@ jobs:
- name: ๐งน Clean
run: dotnet clean -c Release && dotnet nuget locals all --clear
- - name: ๐ Restore packages
- run: dotnet restore
+ - name: ๐ ๏ธ Building library in release mode
+ run: dotnet pack -c Release -o packages -p:UseSourceLink=true -p:Version=${{ env.VERSION }} -p:PackageVersion=${{ env.VERSION }}
- - name: ๐ ๏ธ Build
- run: dotnet build -c Release --no-restore /p:UseSourceLink=true
+ - name: ๐ ๏ธ Building preview library in release mode
+ run: dotnet pack -c Release -o packages -p:UseSourceLink=true -p:Version=${{ env.VERSION }} -p:PackageVersion=${{ env.VERSION }}-preview -p:DefineConstants=PREVIEW
- name: ๐งช Run unit tests
- run: dotnet test -c Release --no-build
+ run: dotnet test -c Release -p:DefineConstants=PREVIEW
- name: ๐ฉ๏ธ SonarCloud install scanner
run: dotnet tool install --global dotnet-sonarscanner
@@ -60,6 +61,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
shell: pwsh
+ continue-on-error: true
run: |
dotnet sonarscanner begin /k:"atc-cosmos" /o:"atc-net" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io"
dotnet build -c Release /p:UseSourceLink=true --no-restore
@@ -73,8 +75,14 @@ jobs:
git merge --ff-only main
git push origin stable
- - name: ๐ณ๏ธ Creating library package for pre-release
- run: dotnet pack -c Release --no-restore -o ${GITHUB_WORKSPACE}/packages -p:RepositoryBranch=$BRANCH_NAME
-
- name: ๐ฆ Push packages to GitHub Package Registry
- run: dotnet nuget push ${GITHUB_WORKSPACE}/packages/'Atc.Cosmos.'${NBGV_NuGetPackageVersion}'.nupkg' -k ${{ secrets.GITHUB_TOKEN }} -s ${{ env.NUGET_REPO_URL }} --skip-duplicate --no-symbols
\ No newline at end of file
+ continue-on-error: true
+ run: dotnet nuget push **/*.nupkg -k ${{ secrets.GITHUB_TOKEN }} -s ${{ env.NUGET_REPO_URL }} --skip-duplicate --no-symbols
+
+ - name: ๐ณ๏ธ Upload build artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: Packages
+ path: |
+ packages/*.nupkg
+ README.md
\ No newline at end of file
diff --git a/.github/workflows/pre-integration.yml b/.github/workflows/pre-integration.yml
index 57d7b09..d19df7d 100644
--- a/.github/workflows/pre-integration.yml
+++ b/.github/workflows/pre-integration.yml
@@ -1,18 +1,15 @@
name: "Pre-Integration"
on:
- pull_request:
- types:
- - opened
- - synchronize
- - reopened
+ push:
+ workflow_dispatch:
+
+env:
+ VERSION: 1.1.0.${{ github.run_number }}
jobs:
- dotnet5-build:
- strategy:
- matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
- runs-on: ${{ matrix.os }}
+ build:
+ runs-on: ubuntu-latest
steps:
- name: ๐ Checkout repository
uses: actions/checkout@v2
@@ -27,16 +24,24 @@ jobs:
- name: ๐งน Clean
run: dotnet clean -c Release && dotnet nuget locals all --clear
- - name: ๐ Restore packages
- run: dotnet restore
-
- name: ๐ ๏ธ Building library in release mode
- run: dotnet build -c Release --no-restore
+ run: dotnet pack -c Release -o packages -p:UseSourceLink=true -p:Version=${{ env.VERSION }} -p:PackageVersion=${{ env.VERSION }}
- dotnet-test:
+ - name: ๐ ๏ธ Building preview library in release mode
+ run: dotnet pack -c Release -o packages -p:UseSourceLink=true -p:Version=${{ env.VERSION }} -p:PackageVersion=${{ env.VERSION }}-preview -p:DefineConstants=PREVIEW
+
+ - name: ๐ณ๏ธ Upload build artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: Packages
+ path: |
+ packages/*.nupkg
+ README.md
+
+ test:
runs-on: ubuntu-latest
needs:
- - dotnet5-build
+ - build
steps:
- name: ๐ Checkout repository
uses: actions/checkout@v2
@@ -51,8 +56,5 @@ jobs:
- name: ๐ Restore packages
run: dotnet restore
- - name: ๐ ๏ธ Build
- run: dotnet build -c Release --no-restore /p:UseSourceLink=true
-
- name: ๐งช Run unit tests
- run: dotnet test -c Release --no-build
\ No newline at end of file
+ run: dotnet test -c Release -p:DefineConstants=PREVIEW
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index bb4912b..01708b4 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -7,6 +7,7 @@ env:
ATC_EMAIL: 'atcnet.org@gmail.com'
ATC_NAME: 'Atc-Net'
NUGET_REPO_URL: 'https://api.nuget.org/v3/index.json'
+ VERSION: 1.1.${{ github.run_number }}
jobs:
release:
@@ -19,14 +20,6 @@ jobs:
fetch-depth: 0
token: ${{ secrets.PAT_WORKFLOWS }}
- - name: โ๏ธ Sets environment variables - branch-name
- uses: nelonoel/branch-name@v1.0.1
-
- - name: โ๏ธ Sets environment variables - Nerdbank.GitVersioning
- uses: dotnet/nbgv@master
- with:
- setAllVars: true
-
- name: โ๏ธ Setup dotnet 6.0.x
uses: actions/setup-dotnet@v1
with:
@@ -35,11 +28,19 @@ jobs:
- name: ๐งน Clean
run: dotnet clean -c Release && dotnet nuget locals all --clear
- - name: ๐ Restore packages
- run: dotnet restore
-
- name: ๐ ๏ธ Building library in release mode
- run: dotnet build -c Release --no-restore /p:UseSourceLink=true
+ run: dotnet pack -c Release -o packages -p:UseSourceLink=true -p:Version=${{ env.VERSION }} -p:PackageVersion=${{ env.VERSION }}
+
+ - name: ๐ ๏ธ Building preview library in release mode
+ run: dotnet pack -c Release -o packages -p:UseSourceLink=true -p:Version=${{ env.VERSION }} -p:PackageVersion=${{ env.VERSION }}-preview -p:DefineConstants=PREVIEW
+
+ - name: ๐ณ๏ธ Upload build artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: Packages
+ path: |
+ packages/*.nupkg
+ README.md
- name: โฉ Merge to release-branch
run: |
@@ -49,8 +50,5 @@ jobs:
git merge --ff-only stable
git push origin release
- - name: ๐ณ๏ธ Creating library package for release
- run: dotnet pack -c Release --no-restore -o ${GITHUB_WORKSPACE}/packages -p:RepositoryBranch=$BRANCH_NAME /p:PublicRelease=true
-
- name: ๐ฆ Push packages to NuGet
- run: dotnet nuget push ${GITHUB_WORKSPACE}/packages/'Atc.Cosmos.'${NBGV_NuGetPackageVersion}'.nupkg' -k ${{ secrets.NUGET_KEY }} -s ${{ env.NUGET_REPO_URL }} --skip-duplicate --no-symbols
\ No newline at end of file
+ run: dotnet nuget push **/*.nupkg -k ${{ secrets.NUGET_KEY }} -s ${{ env.NUGET_REPO_URL }} --skip-duplicate --no-symbols
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..8226ed0
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "dotnet.defaultSolution": "Atc.Cosmos.sln"
+}
\ No newline at end of file
diff --git a/Atc.Cosmos.sln b/Atc.Cosmos.sln
index 13aa2dd..98a44c0 100644
--- a/Atc.Cosmos.sln
+++ b/Atc.Cosmos.sln
@@ -11,16 +11,26 @@ Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
+ Debug Preview|Any CPU = Debug Preview|Any CPU
+ Release Preview|Any CPU = Release Preview|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AD8BA566-1E47-4E9E-BEBA-985DCA7A0DB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD8BA566-1E47-4E9E-BEBA-985DCA7A0DB5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD8BA566-1E47-4E9E-BEBA-985DCA7A0DB5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD8BA566-1E47-4E9E-BEBA-985DCA7A0DB5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AD8BA566-1E47-4E9E-BEBA-985DCA7A0DB5}.Debug Preview|Any CPU.ActiveCfg = Debug Preview|Any CPU
+ {AD8BA566-1E47-4E9E-BEBA-985DCA7A0DB5}.Debug Preview|Any CPU.Build.0 = Debug Preview|Any CPU
+ {AD8BA566-1E47-4E9E-BEBA-985DCA7A0DB5}.Release Preview|Any CPU.ActiveCfg = Release Preview|Any CPU
+ {AD8BA566-1E47-4E9E-BEBA-985DCA7A0DB5}.Release Preview|Any CPU.Build.0 = Release Preview|Any CPU
{16FE83BC-DF0D-493D-8EE0-A78006A07EFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{16FE83BC-DF0D-493D-8EE0-A78006A07EFF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16FE83BC-DF0D-493D-8EE0-A78006A07EFF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16FE83BC-DF0D-493D-8EE0-A78006A07EFF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {16FE83BC-DF0D-493D-8EE0-A78006A07EFF}.Debug Preview|Any CPU.ActiveCfg = Debug Preview|Any CPU
+ {16FE83BC-DF0D-493D-8EE0-A78006A07EFF}.Debug Preview|Any CPU.Build.0 = Debug Preview|Any CPU
+ {16FE83BC-DF0D-493D-8EE0-A78006A07EFF}.Release Preview|Any CPU.ActiveCfg = Release Preview|Any CPU
+ {16FE83BC-DF0D-493D-8EE0-A78006A07EFF}.Release Preview|Any CPU.Build.0 = Release Preview|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Directory.Build.props b/Directory.Build.props
index 558e2c4..c52e122 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,9 +1,25 @@
-
+
+
+
+ Debug;Release;Debug Preview;Release Preview
+ AnyCPU
+
+
+
+ TRACE;PREVIEW;
+
+
+
+ TRACE;PREVIEW;
+ true
+ true
+
atc-net
atc-cosmos
+ $(DefineConstants.Contains('PREVIEW'))
diff --git a/src/Atc.Cosmos/Atc.Cosmos.csproj b/src/Atc.Cosmos/Atc.Cosmos.csproj
index 2181ab9..33396f1 100644
--- a/src/Atc.Cosmos/Atc.Cosmos.csproj
+++ b/src/Atc.Cosmos/Atc.Cosmos.csproj
@@ -4,15 +4,17 @@
Atc.Cosmos
cosmos;cosmos-sql;netcore;repository
Library for configuring containers in Cosmos and providing an easy way to read and write document resources.
+ preview
-
+
+
@@ -22,8 +24,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/src/Atc.Cosmos/DependencyInjection/ServiceCollectionExtensions.cs b/src/Atc.Cosmos/DependencyInjection/ServiceCollectionExtensions.cs
index f495053..dd5cfdc 100644
--- a/src/Atc.Cosmos/DependencyInjection/ServiceCollectionExtensions.cs
+++ b/src/Atc.Cosmos/DependencyInjection/ServiceCollectionExtensions.cs
@@ -61,12 +61,21 @@ public static IServiceCollection ConfigureCosmos(
services.AddSingleton();
services.AddSingleton(typeof(ICosmosReader<>), typeof(CosmosReader<>));
services.AddSingleton(typeof(ICosmosWriter<>), typeof(CosmosWriter<>));
+ services.AddSingleton(typeof(ICosmosBulkReader<>), typeof(CosmosBulkReader<>));
services.AddSingleton(typeof(ICosmosBulkWriter<>), typeof(CosmosBulkWriter<>));
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+#if PREVIEW
+ services.AddSingleton(typeof(ILowPriorityCosmosReader<>), typeof(LowPriorityCosmosReader<>));
+ services.AddSingleton(typeof(ILowPriorityCosmosWriter<>), typeof(LowPriorityCosmosWriter<>));
+ services.AddSingleton(typeof(ILowPriorityCosmosBulkReader<>), typeof(LowPriorityCosmosBulkReader<>));
+ services.AddSingleton(typeof(ILowPriorityCosmosBulkWriter<>), typeof(LowPriorityCosmosBulkWriter<>));
+ services.AddSingleton();
+ services.AddSingleton();
+#endif
builder(new CosmosBuilder(services, registry, null));
return services;
diff --git a/src/Atc.Cosmos/ILowPriorityCosmosBulkReader.cs b/src/Atc.Cosmos/ILowPriorityCosmosBulkReader.cs
new file mode 100644
index 0000000..1efde5f
--- /dev/null
+++ b/src/Atc.Cosmos/ILowPriorityCosmosBulkReader.cs
@@ -0,0 +1,17 @@
+#if PREVIEW
+namespace Atc.Cosmos
+{
+ ///
+ /// Represents a reader that can perform bulk reads on Cosmos resources using the PriorityLevel Low.
+ ///
+ ///
+ /// The type of
+ /// to be read by this reader.
+ ///
+ public interface ILowPriorityCosmosBulkReader
+ : ICosmosBulkReader
+ where T : class, ICosmosResource
+ {
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Atc.Cosmos/ILowPriorityCosmosBulkWriter.cs b/src/Atc.Cosmos/ILowPriorityCosmosBulkWriter.cs
new file mode 100644
index 0000000..9970998
--- /dev/null
+++ b/src/Atc.Cosmos/ILowPriorityCosmosBulkWriter.cs
@@ -0,0 +1,17 @@
+#if PREVIEW
+namespace Atc.Cosmos
+{
+ ///
+ /// Represents a reader that can perform bulk reads on Cosmos resources using the PriorityLevel Low.
+ ///
+ ///
+ /// The type of
+ /// to be read by this reader.
+ ///
+ public interface ILowPriorityCosmosBulkWriter
+ : ICosmosBulkWriter
+ where T : class, ICosmosResource
+ {
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Atc.Cosmos/ILowPriorityCosmosReader.cs b/src/Atc.Cosmos/ILowPriorityCosmosReader.cs
new file mode 100644
index 0000000..d94ce1a
--- /dev/null
+++ b/src/Atc.Cosmos/ILowPriorityCosmosReader.cs
@@ -0,0 +1,17 @@
+#if PREVIEW
+namespace Atc.Cosmos
+{
+ ///
+ /// Represents a reader that can read Cosmos resources using the PriorityLevel Low.
+ ///
+ ///
+ /// The type of
+ /// to be read by this reader.
+ ///
+ public interface ILowPriorityCosmosReader
+ : ICosmosReader
+ where T : class, ICosmosResource
+ {
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Atc.Cosmos/ILowPriorityCosmosReaderFactory.cs b/src/Atc.Cosmos/ILowPriorityCosmosReaderFactory.cs
new file mode 100644
index 0000000..1e898f6
--- /dev/null
+++ b/src/Atc.Cosmos/ILowPriorityCosmosReaderFactory.cs
@@ -0,0 +1,26 @@
+#if PREVIEW
+namespace Atc.Cosmos
+{
+ ///
+ /// Represents a factory for creating instances.
+ ///
+ public interface ILowPriorityCosmosReaderFactory
+ {
+ ///
+ /// Create a .
+ ///
+ /// The for the .
+ /// A .
+ ILowPriorityCosmosReader CreateReader()
+ where TResource : class, ICosmosResource;
+
+ ///
+ /// Create a .
+ ///
+ /// The for the .
+ /// A .
+ ILowPriorityCosmosBulkReader CreateBulkReader()
+ where TResource : class, ICosmosResource;
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Atc.Cosmos/ILowPriorityCosmosWriter.cs b/src/Atc.Cosmos/ILowPriorityCosmosWriter.cs
new file mode 100644
index 0000000..8e2805b
--- /dev/null
+++ b/src/Atc.Cosmos/ILowPriorityCosmosWriter.cs
@@ -0,0 +1,17 @@
+#if PREVIEW
+namespace Atc.Cosmos
+{
+ ///
+ /// Represents a writer that can write Cosmos resources using the PriorityLevel Low.
+ ///
+ ///
+ /// The type of
+ /// to be read by this reader.
+ ///
+ public interface ILowPriorityCosmosWriter
+ : ICosmosWriter
+ where T : class, ICosmosResource
+ {
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Atc.Cosmos/ILowPriorityCosmosWriterFactory.cs b/src/Atc.Cosmos/ILowPriorityCosmosWriterFactory.cs
new file mode 100644
index 0000000..ca05a44
--- /dev/null
+++ b/src/Atc.Cosmos/ILowPriorityCosmosWriterFactory.cs
@@ -0,0 +1,26 @@
+#if PREVIEW
+namespace Atc.Cosmos
+{
+ ///
+ /// Represents a factory for creating instances.
+ ///
+ public interface ILowPriorityCosmosWriterFactory
+ {
+ ///
+ /// Create a .
+ ///
+ /// The for the .
+ /// A .
+ ILowPriorityCosmosWriter CreateWriter()
+ where TResource : class, ICosmosResource;
+
+ ///
+ /// Create a .
+ ///
+ /// The for the .
+ /// A .
+ ILowPriorityCosmosBulkWriter CreateBulkWriter()
+ where TResource : class, ICosmosResource;
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Atc.Cosmos/Internal/CosmosBulkReader.cs b/src/Atc.Cosmos/Internal/CosmosBulkReader.cs
index d4f03f2..492139f 100644
--- a/src/Atc.Cosmos/Internal/CosmosBulkReader.cs
+++ b/src/Atc.Cosmos/Internal/CosmosBulkReader.cs
@@ -18,6 +18,10 @@ public CosmosBulkReader(ICosmosContainerProvider containerProvider)
this.container = containerProvider.GetContainer(allowBulk: true);
}
+#if PREVIEW
+ protected virtual PriorityLevel PriorityLevel => PriorityLevel.High;
+#endif
+
public async Task ReadAsync(
string documentId,
string partitionKey,
@@ -27,6 +31,12 @@ public async Task ReadAsync(
.ReadItemAsync(
documentId,
new PartitionKey(partitionKey),
+ new ItemRequestOptions
+ {
+#if PREVIEW
+ PriorityLevel = PriorityLevel,
+#endif
+ },
cancellationToken: cancellationToken)
.ConfigureAwait(false);
@@ -62,6 +72,9 @@ public async IAsyncEnumerable ReadAllAsync(
requestOptions: new QueryRequestOptions
{
PartitionKey = new PartitionKey(partitionKey),
+#if PREVIEW
+ PriorityLevel = PriorityLevel,
+#endif
});
while (reader.HasMoreResults && !cancellationToken.IsCancellationRequested)
@@ -92,6 +105,9 @@ public async IAsyncEnumerable QueryAsync(
requestOptions: new QueryRequestOptions
{
PartitionKey = new PartitionKey(partitionKey),
+#if PREVIEW
+ PriorityLevel = PriorityLevel,
+#endif
});
while (reader.HasMoreResults && !cancellationToken.IsCancellationRequested)
@@ -133,6 +149,9 @@ public async Task> PagedQueryAsync(
{
PartitionKey = new PartitionKey(partitionKey),
MaxItemCount = pageSize,
+#if PREVIEW
+ PriorityLevel = PriorityLevel,
+#endif
});
if (!reader.HasMoreResults)
@@ -193,6 +212,9 @@ public async Task> CrossPartitionPagedQueryAsync(
requestOptions: new QueryRequestOptions
{
MaxItemCount = pageSize,
+#if PREVIEW
+ PriorityLevel = PriorityLevel,
+#endif
});
if (!reader.HasMoreResults)
@@ -220,6 +242,9 @@ public async IAsyncEnumerable> BatchReadAllAsync(
requestOptions: new QueryRequestOptions
{
PartitionKey = new PartitionKey(partitionKey),
+#if PREVIEW
+ PriorityLevel = PriorityLevel,
+#endif
});
while (reader.HasMoreResults && !cancellationToken.IsCancellationRequested)
@@ -248,6 +273,9 @@ public async IAsyncEnumerable> BatchQueryAsync(
requestOptions: new QueryRequestOptions
{
PartitionKey = new PartitionKey(partitionKey),
+#if PREVIEW
+ PriorityLevel = PriorityLevel,
+#endif
});
while (reader.HasMoreResults && !cancellationToken.IsCancellationRequested)
diff --git a/src/Atc.Cosmos/Internal/CosmosBulkWriter.cs b/src/Atc.Cosmos/Internal/CosmosBulkWriter.cs
index 8195b65..b90f9b9 100644
--- a/src/Atc.Cosmos/Internal/CosmosBulkWriter.cs
+++ b/src/Atc.Cosmos/Internal/CosmosBulkWriter.cs
@@ -1,6 +1,5 @@
using System.Threading;
using System.Threading.Tasks;
-using Atc.Cosmos.Serialization;
using Microsoft.Azure.Cosmos;
namespace Atc.Cosmos.Internal
@@ -9,16 +8,17 @@ public class CosmosBulkWriter : ICosmosBulkWriter
where T : class, ICosmosResource
{
private readonly Container container;
- private readonly IJsonCosmosSerializer serializer;
public CosmosBulkWriter(
- ICosmosContainerProvider containerProvider,
- IJsonCosmosSerializer serializer)
+ ICosmosContainerProvider containerProvider)
{
this.container = containerProvider.GetContainer(allowBulk: true);
- this.serializer = serializer;
}
+#if PREVIEW
+ protected virtual PriorityLevel PriorityLevel => PriorityLevel.High;
+#endif
+
public Task CreateAsync(
T document,
CancellationToken cancellationToken = default)
@@ -26,7 +26,13 @@ public Task CreateAsync(
.CreateItemAsync
diff --git a/test/Atc.Cosmos.Tests/CosmosBulkReaderTests.cs b/test/Atc.Cosmos.Tests/CosmosBulkReaderTests.cs
index f50e879..62c1a68 100644
--- a/test/Atc.Cosmos.Tests/CosmosBulkReaderTests.cs
+++ b/test/Atc.Cosmos.Tests/CosmosBulkReaderTests.cs
@@ -87,7 +87,11 @@ public async Task ReadAsync_Reads_Item_In_Container(
.ReadItemAsync(
documentId,
new PartitionKey(partitionKey),
+#if PREVIEW
+ Arg.Is(c => c.PriorityLevel == PriorityLevel.High),
+#else
Arg.Any(),
+#endif
cancellationToken);
}
diff --git a/test/Atc.Cosmos.Tests/CosmosBulkWriterTests.cs b/test/Atc.Cosmos.Tests/CosmosBulkWriterTests.cs
index a4c8365..0025b98 100644
--- a/test/Atc.Cosmos.Tests/CosmosBulkWriterTests.cs
+++ b/test/Atc.Cosmos.Tests/CosmosBulkWriterTests.cs
@@ -47,7 +47,7 @@ public CosmosBulkWriterTests()
.FromString(default)
.ReturnsForAnyArgs(new Fixture().Create());
- sut = new CosmosBulkWriter(containerProvider, serializer);
+ sut = new CosmosBulkWriter(containerProvider);
}
[Fact]
diff --git a/test/Atc.Cosmos.Tests/CosmosReaderBatchTests.cs b/test/Atc.Cosmos.Tests/CosmosReaderBatchTests.cs
index cc14c09..5e86e73 100644
--- a/test/Atc.Cosmos.Tests/CosmosReaderBatchTests.cs
+++ b/test/Atc.Cosmos.Tests/CosmosReaderBatchTests.cs
@@ -184,7 +184,8 @@ public async Task QueryAsync_Returns_Empty_No_More_Result(
{
feedIterator.HasMoreResults.Returns(false);
- var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+ var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
_ = feedIterator
.Received(1)
@@ -207,7 +208,8 @@ public async Task QueryAsync_Returns_Empty_When_Query_Matches_Non(
{
feedIterator.HasMoreResults.Returns(true, false);
- var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+ var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
_ = feedIterator
.Received(2)
@@ -237,7 +239,8 @@ public async Task QueryAsync_Returns_Items_When_Query_Matches(
.GetEnumerator()
.Returns(new List { record }.GetEnumerator());
- var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+ var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
_ = feedIterator
.Received(2)
@@ -294,7 +297,8 @@ public async Task QueryAsync_With_Custom_Returns_Empty_No_More_Result(
{
feedIterator.HasMoreResults.Returns(false);
- var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+ var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
_ = feedIterator
.Received(1)
@@ -317,7 +321,8 @@ public async Task QueryAsync_With_Custom_Returns_Empty_When_Query_Matches_Non(
{
feedIterator.HasMoreResults.Returns(true, false);
- var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+ var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
_ = feedIterator
.Received(2)
@@ -347,7 +352,8 @@ public async Task QueryAsync_With_Custom_Returns_Items_When_Query_Matches(
.GetEnumerator()
.Returns(new List { record }.GetEnumerator());
- var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+ var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
_ = feedIterator
.Received(2)
@@ -380,7 +386,7 @@ public void CrossPartitionQueryAsync_Uses_The_Right_Container(
}
[Theory, AutoNSubstituteData]
- public void CrossPartitionQueryAsync_Does_Not_Specify_QueryRequestOptions(
+ public void CrossPartitionQueryAsync_Uses_QueryRequestOptions_With_PriorityLevel_High(
QueryDefinition query,
CancellationToken cancellationToken)
{
@@ -388,7 +394,13 @@ public void CrossPartitionQueryAsync_Does_Not_Specify_QueryRequestOptions(
container
.Received(1)
- .GetItemQueryIterator(query, requestOptions: null);
+ .GetItemQueryIterator(
+ query,
+#if PREVIEW
+ requestOptions: Arg.Is(c => c.PriorityLevel == PriorityLevel.High));
+#else
+ requestOptions: Arg.Any());
+#endif
}
[Theory, AutoNSubstituteData]
@@ -398,7 +410,8 @@ public async Task CrossPartitionQueryAsync_Returns_Empty_No_More_Result(
{
feedIterator.HasMoreResults.Returns(false);
- var response = await sut.BatchCrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+ var response = await sut.BatchCrossPartitionQueryAsync(query, cancellationToken)
+ .ToListAsync(cancellationToken);
_ = feedIterator
.Received(1)
@@ -420,7 +433,8 @@ public async Task CrossPartitionQueryAsync_Returns_Empty_When_Query_Matches_Non(
{
feedIterator.HasMoreResults.Returns(true, false);
- var response = await sut.BatchCrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+ var response = await sut.BatchCrossPartitionQueryAsync(query, cancellationToken)
+ .ToListAsync(cancellationToken);
_ = feedIterator
.Received(2)
@@ -449,7 +463,8 @@ public async Task CrossPartitionQueryAsync_Returns_Items_When_Query_Matches(
.GetEnumerator()
.Returns(new List { record }.GetEnumerator());
- var response = await sut.BatchCrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+ var response = await sut.BatchCrossPartitionQueryAsync(query, cancellationToken)
+ .ToListAsync(cancellationToken);
_ = feedIterator
.Received(2)
diff --git a/test/Atc.Cosmos.Tests/CosmosWriterTests.cs b/test/Atc.Cosmos.Tests/CosmosWriterTests.cs
index 8011071..2f4b432 100644
--- a/test/Atc.Cosmos.Tests/CosmosWriterTests.cs
+++ b/test/Atc.Cosmos.Tests/CosmosWriterTests.cs
@@ -92,7 +92,11 @@ await container
.UpsertItemAsync(
record,
new PartitionKey(record.Pk),
+#if PREVIEW
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.High),
+#else
Arg.Any(),
+#endif
cancellationToken);
}
@@ -110,13 +114,17 @@ await container
.UpsertItemAsync(
record,
new PartitionKey(record.Pk),
+#if PREVIEW
+ Arg.Is(p => p.EnableContentResponseOnWrite == false && p.PriorityLevel == PriorityLevel.High),
+#else
Arg.Is(p => p.EnableContentResponseOnWrite == false),
+#endif
cancellationToken);
}
[Theory, AutoNSubstituteData]
public async Task CreateAsync_Calls_CreateItem_On_Container(
- CancellationToken cancellationToken)
+ CancellationToken cancellationToken)
{
await sut.CreateAsync(record, cancellationToken);
_ = container
@@ -124,13 +132,17 @@ public async Task CreateAsync_Calls_CreateItem_On_Container(
.CreateItemAsync(
record,
new PartitionKey(record.Pk),
+#if PREVIEW
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.High),
+#else
Arg.Any(),
+#endif
cancellationToken);
}
[Theory, AutoNSubstituteData]
public async Task CreateWithNoResponseAsync_Calls_CreateItem_On_Container(
- CancellationToken cancellationToken)
+ CancellationToken cancellationToken)
{
await sut.CreateWithNoResponseAsync(record, cancellationToken);
_ = container
@@ -138,13 +150,17 @@ public async Task CreateWithNoResponseAsync_Calls_CreateItem_On_Container(
.CreateItemAsync(
record,
new PartitionKey(record.Pk),
+#if PREVIEW
+ Arg.Is(p => p.EnableContentResponseOnWrite == false && p.PriorityLevel == PriorityLevel.High),
+#else
Arg.Is(p => p.EnableContentResponseOnWrite == false),
+#endif
cancellationToken);
}
[Theory, AutoNSubstituteData]
public async Task ReplaceAsync_Calls_ReplaceItemAsync_On_Container(
- CancellationToken cancellationToken)
+ CancellationToken cancellationToken)
{
await sut.ReplaceAsync(record, cancellationToken);
_ = container
@@ -153,13 +169,17 @@ public async Task ReplaceAsync_Calls_ReplaceItemAsync_On_Container(
record,
record.Id,
new PartitionKey(record.Pk),
- Arg.Is(o => o.IfMatchEtag == ((ICosmosResource)record).ETag),
+#if PREVIEW
+ Arg.Is(o => o.IfMatchEtag == record.ETag && o.PriorityLevel == PriorityLevel.High),
+#else
+ Arg.Is(o => o.IfMatchEtag == record.ETag),
+#endif
cancellationToken);
}
[Theory, AutoNSubstituteData]
public async Task ReplaceWithNoResponseAsync_Calls_ReplaceItemAsync_On_Container(
- CancellationToken cancellationToken)
+ CancellationToken cancellationToken)
{
await sut.ReplaceWithNoResponseAsync(record, cancellationToken);
_ = container
@@ -168,8 +188,12 @@ public async Task ReplaceWithNoResponseAsync_Calls_ReplaceItemAsync_On_Container
record,
record.Id,
new PartitionKey(record.Pk),
- Arg.Is(o => o.IfMatchEtag == ((ICosmosResource)record).ETag
- && o.EnableContentResponseOnWrite == false),
+ Arg.Is(
+ o => o.IfMatchEtag == record.ETag
+#if PREVIEW
+ && o.PriorityLevel == PriorityLevel.High
+#endif
+ && o.EnableContentResponseOnWrite == false),
cancellationToken);
}
@@ -192,7 +216,7 @@ public void Multiple_Operations_Uses_Same_Container(
[Theory, AutoNSubstituteData]
public async Task DeleteAsync_Calls_DeleteItemAsync_On_Container(
- CancellationToken cancellationToken)
+ CancellationToken cancellationToken)
{
await sut.DeleteAsync(record.Id, record.Pk, cancellationToken);
_ = container
@@ -200,13 +224,17 @@ public async Task DeleteAsync_Calls_DeleteItemAsync_On_Container(
.DeleteItemAsync(
record.Id,
new PartitionKey(record.Pk),
+#if PREVIEW
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.High),
+#else
Arg.Any(),
+#endif
cancellationToken: cancellationToken);
}
[Theory, AutoNSubstituteData]
public async Task Should_Return_True_When_Trying_To_Delete_Existing_Resource(
- CancellationToken cancellationToken)
+ CancellationToken cancellationToken)
{
var deleted = await sut.TryDeleteAsync(
record.Id,
@@ -222,13 +250,17 @@ public async Task Should_Return_True_When_Trying_To_Delete_Existing_Resource(
.DeleteItemAsync(
record.Id,
new PartitionKey(record.Pk),
+#if PREVIEW
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.High),
+#else
Arg.Any(),
+#endif
cancellationToken: cancellationToken);
}
[Theory, AutoNSubstituteData]
public async Task Should_Return_False_When_Trying_To_Delete_NonExisting_Resource(
- CancellationToken cancellationToken)
+ CancellationToken cancellationToken)
{
container
.DeleteItemAsync(default, default, default, default)
@@ -249,7 +281,11 @@ public async Task Should_Return_False_When_Trying_To_Delete_NonExisting_Resource
.DeleteItemAsync(
record.Id,
new PartitionKey(record.Pk),
+#if PREVIEW
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.High),
+#else
Arg.Any(),
+#endif
cancellationToken: cancellationToken);
}
@@ -317,16 +353,21 @@ await sut.UpdateAsync(
record,
record.Id,
new PartitionKey(record.Pk),
- Arg.Is(o => o.IfMatchEtag == ((ICosmosResource)record).ETag),
+#if PREVIEW
+ Arg.Is(
+ o => o.IfMatchEtag == record.ETag && o.PriorityLevel == PriorityLevel.High),
+#else
+ Arg.Is(o => o.IfMatchEtag == record.ETag),
+#endif
cancellationToken);
}
[Theory, AutoNSubstituteData]
public async Task UpdateOrCreateAsync_Finds_The_Resource(
- Action updateDocument,
- int retries,
- Record defaultDocument,
- CancellationToken cancellationToken)
+ Action updateDocument,
+ int retries,
+ Record defaultDocument,
+ CancellationToken cancellationToken)
{
await sut.UpdateOrCreateAsync(
() => defaultDocument,
@@ -432,7 +473,11 @@ await sut.UpdateOrCreateAsync(
.CreateItemAsync(
defaultDocument,
new PartitionKey(defaultDocument.Pk),
+#if PREVIEW
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.High),
+#else
Arg.Any(),
+#endif
cancellationToken);
}
@@ -455,7 +500,11 @@ await sut.PatchAsync(
record.Id,
new PartitionKey(record.Pk),
patchOperations,
+#if PREVIEW
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.High),
+#else
Arg.Any(),
+#endif
cancellationToken);
}
@@ -478,7 +527,11 @@ await sut.PatchWithNoResponseAsync(
record.Id,
new PartitionKey(record.Pk),
patchOperations,
+#if PREVIEW
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.High),
+#else
Arg.Any(),
+#endif
cancellationToken);
}
}
diff --git a/test/Atc.Cosmos.Tests/LowPriorityCosmosBulkReaderTests.cs b/test/Atc.Cosmos.Tests/LowPriorityCosmosBulkReaderTests.cs
new file mode 100644
index 0000000..0b78205
--- /dev/null
+++ b/test/Atc.Cosmos.Tests/LowPriorityCosmosBulkReaderTests.cs
@@ -0,0 +1,805 @@
+#if PREVIEW
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Atc.Cosmos.Internal;
+using Atc.Test;
+using AutoFixture;
+using Dasync.Collections;
+using FluentAssertions;
+using Microsoft.Azure.Cosmos;
+using NSubstitute;
+using NSubstitute.ExceptionExtensions;
+using Xunit;
+
+namespace Atc.Cosmos.Tests
+{
+ public class LowPriorityCosmosBulkReaderTests
+ {
+ private readonly ItemResponse itemResponse;
+ private readonly FeedIterator feedIterator;
+ private readonly FeedResponse feedResponse;
+ private readonly Record record;
+ private readonly Container container;
+ private readonly ICosmosContainerProvider containerProvider;
+ private readonly LowPriorityCosmosBulkReader sut;
+
+ public LowPriorityCosmosBulkReaderTests()
+ {
+ record = new Fixture().Create();
+ itemResponse = Substitute.For>();
+ itemResponse
+ .Resource
+ .Returns(record);
+
+ feedResponse = Substitute.For>();
+ feedIterator = Substitute.For>();
+ feedIterator
+ .ReadNextAsync(default)
+ .ReturnsForAnyArgs(feedResponse);
+
+ container = Substitute.For();
+ container
+ .ReadItemAsync(default, default, default)
+ .ReturnsForAnyArgs(itemResponse);
+
+ container
+ .GetItemQueryIterator(default(QueryDefinition), default)
+ .ReturnsForAnyArgs(feedIterator);
+
+ container
+ .GetItemQueryIterator(default(string), default)
+ .ReturnsForAnyArgs(feedIterator);
+
+ containerProvider = Substitute.For();
+ containerProvider
+ .GetContainer(allowBulk: true)
+ .Returns(container, null);
+ sut = new LowPriorityCosmosBulkReader(containerProvider);
+ }
+
+ [Fact]
+ public void Implements_Interface()
+ => sut.Should().BeAssignableTo>();
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAsync_Uses_The_Right_Container(
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ await sut.ReadAsync(documentId, partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer(allowBulk: true);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAsync_Reads_Item_In_Container(
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ await sut.ReadAsync(documentId, partitionKey, cancellationToken);
+
+ _ = container
+ .Received(1)
+ .ReadItemAsync(
+ documentId,
+ new PartitionKey(partitionKey),
+ Arg.Is(c => c.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAsync_Returns_Item_Read_From_Container(
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ var result = await sut.ReadAsync(documentId, partitionKey, cancellationToken);
+ result
+ .Should()
+ .Be(itemResponse.Resource);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void ReadAsync_Throws_Expection_When_Record_IsNot_Found(
+ CosmosException exception,
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ container
+ .ReadItemAsync(default, default, default, default)
+ .ThrowsForAnyArgs(exception);
+
+ FluentActions
+ .Awaiting(() => sut.ReadAsync(documentId, partitionKey, cancellationToken))
+ .Should()
+ .ThrowAsync();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task FindAsync_Uses_The_Right_Container(
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ await sut.FindAsync(documentId, partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer(allowBulk: true);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task FindAsync_Return_Default_When_Record_IsNot_Found(
+ CosmosException exception,
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ container
+ .ReadItemAsync(default, default, default, default)
+ .ThrowsForAnyArgs(exception);
+
+ var response = await sut.FindAsync(documentId, partitionKey, cancellationToken);
+
+ response
+ .Should()
+ .BeNull();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task FindAsync_Returns_Record_When_Successful(
+ string partitionKey,
+ string documentId,
+ CancellationToken cancellationToken)
+ {
+ var result = await sut.FindAsync(documentId, partitionKey, cancellationToken);
+ result
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void ReadAllAsync_Uses_The_Right_Container(
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.ReadAllAsync(partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer(allowBulk: true);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAllAsync_Returns_Empty_No_More_Result(
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut
+ .ReadAllAsync(partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAllAsync_Returns_Empty_When_Query_Matches_Non(
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(true, false);
+
+ var response = await sut
+ .ReadAllAsync(partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAllAsync_Returns_All_Items(
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true, false);
+
+ feedResponse
+ .GetEnumerator()
+ .Returns(new List { record }.GetEnumerator());
+
+ var response = await sut
+ .ReadAllAsync(partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .NotBeEmpty();
+
+ response[0]
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void QueryAsync_Uses_The_Right_Container(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.QueryAsync(query, partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer(allowBulk: true);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_Returns_Empty_No_More_Result(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_Returns_Empty_When_Query_Matches_Non(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(true, false);
+
+ var response = await sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true, false);
+
+ feedResponse
+ .GetEnumerator()
+ .Returns(new List { record }.GetEnumerator());
+
+ var response = await sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .NotBeEmpty();
+
+ response[0]
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task Should_Have_ETag_From_ItemResponse(
+ string etag,
+ string partitionKey,
+ string documentId,
+ CancellationToken cancellationToken)
+ {
+ itemResponse
+ .ETag
+ .Returns(etag);
+ itemResponse
+ .Resource
+ .Returns(record);
+
+ var result = await sut.FindAsync(documentId, partitionKey, cancellationToken);
+
+ var resource = result as ICosmosResource;
+ resource
+ .Should()
+ .NotBeNull();
+
+ resource
+ .ETag
+ .Should()
+ .NotBeNullOrWhiteSpace();
+
+ resource
+ .ETag
+ .Should()
+ .Be(etag);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void Multiple_Operations_Uses_Same_Container(
+ QueryDefinition query,
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.ReadAsync(documentId, partitionKey, cancellationToken);
+ _ = sut.ReadAsync(documentId, partitionKey, cancellationToken);
+ _ = sut.FindAsync(documentId, partitionKey, cancellationToken);
+ _ = sut.FindAsync(documentId, partitionKey, cancellationToken);
+ _ = sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+ _ = sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ container
+ .ReceivedCalls()
+ .Should()
+ .HaveCount(6);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void QueryAsync_With_Custom_Result_Uses_The_Right_Container(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.QueryAsync(query, partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer(allowBulk: true);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_With_Custom_Returns_Empty_No_More_Result(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_With_Custom_Returns_Empty_When_Query_Matches_Non(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(true, false);
+
+ var response = await sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_With_Custom_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true, false);
+
+ feedResponse
+ .GetEnumerator()
+ .Returns(new List { record }.GetEnumerator());
+
+ var response = await sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .NotBeEmpty();
+
+ response[0]
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void CrossPartitionQueryAsync_Uses_The_Right_Container(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.CrossPartitionQueryAsync(query, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer(allowBulk: true);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void CrossPartitionQueryAsync_Does_Not_Specify_QueryRequestOptions(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.CrossPartitionQueryAsync(query, cancellationToken).ToArrayAsync(cancellationToken);
+
+ container
+ .Received(1)
+ .GetItemQueryIterator(query, requestOptions: null);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionQueryAsync_Returns_Empty_No_More_Result(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut.CrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionQueryAsync_Returns_Empty_When_Query_Matches_Non(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(true, false);
+
+ var response = await sut.CrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionQueryAsync_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true, false);
+
+ feedResponse
+ .GetEnumerator()
+ .Returns(new List { record }.GetEnumerator());
+
+ var response = await sut.CrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .NotBeEmpty();
+
+ response[0]
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void CrossPartitionPagedQueryAsync_Uses_The_Right_Container(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer(allowBulk: true);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void CrossPartitionPagedQueryAsync_Gets_ItemQueryIterator(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ container
+ .Received(1)
+ .GetItemQueryIterator(
+ query,
+ continuationToken,
+ requestOptions: Arg.Is(o
+ => o.PartitionKey == null
+ && o.MaxItemCount == pageSize));
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionPagedQueryAsync_Returns_Empty_When_No_More_Result(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut
+ .CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response.Items
+ .Should()
+ .BeEmpty();
+ response.ContinuationToken
+ .Should()
+ .BeNull();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionPagedQueryAsync_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ List records,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true);
+ feedResponse
+ .ContinuationToken
+ .Returns(continuationToken);
+ feedResponse
+ .GetEnumerator()
+ .Returns(records.GetEnumerator());
+
+ var response = await sut
+ .CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ null,
+ cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response.Items
+ .Should()
+ .BeEquivalentTo(records);
+
+ response.ContinuationToken
+ .Should()
+ .Be(continuationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void CrossPartitionPagedQueryAsync_With_Custom_Uses_The_Right_Container(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer(allowBulk: true);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionPagedQueryAsync_With_Custom_Returns_Empty_No_More_Result(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut
+ .CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response.Items
+ .Should()
+ .BeEmpty();
+ response.ContinuationToken
+ .Should()
+ .BeNull();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionPagedQueryAsync_With_Custom_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ List records,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true);
+ feedResponse
+ .ContinuationToken
+ .Returns(continuationToken);
+ feedResponse
+ .GetEnumerator()
+ .Returns(records.GetEnumerator());
+
+ var response = await sut
+ .CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ null,
+ cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response.Items
+ .Should()
+ .BeEquivalentTo(records);
+
+ response.ContinuationToken
+ .Should()
+ .Be(continuationToken);
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/Atc.Cosmos.Tests/LowPriorityCosmosReaderBatchTests.cs b/test/Atc.Cosmos.Tests/LowPriorityCosmosReaderBatchTests.cs
new file mode 100644
index 0000000..a98c0e4
--- /dev/null
+++ b/test/Atc.Cosmos.Tests/LowPriorityCosmosReaderBatchTests.cs
@@ -0,0 +1,475 @@
+#if PREVIEW
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Atc.Cosmos.Internal;
+using Atc.Test;
+using AutoFixture;
+using Dasync.Collections;
+using FluentAssertions;
+using Microsoft.Azure.Cosmos;
+using NSubstitute;
+using Xunit;
+
+namespace Atc.Cosmos.Tests
+{
+ public class LowPriorityCosmosReaderBatchTests
+ {
+ private readonly CosmosOptions options;
+ private readonly ItemResponse itemResponse;
+ private readonly FeedIterator feedIterator;
+ private readonly FeedResponse feedResponse;
+ private readonly Record record;
+ private readonly Container container;
+ private readonly ICosmosContainerProvider containerProvider;
+ private readonly LowPriorityCosmosReader sut;
+
+ public LowPriorityCosmosReaderBatchTests()
+ {
+ var fixture = FixtureFactory.Create();
+ options = fixture.Create();
+ record = fixture.Create();
+ itemResponse = Substitute.For>();
+ itemResponse
+ .Resource
+ .Returns(record);
+
+ feedResponse = Substitute.For>();
+ feedIterator = Substitute.For>();
+ feedIterator
+ .ReadNextAsync(default)
+ .ReturnsForAnyArgs(feedResponse);
+
+ container = Substitute.For();
+ container
+ .ReadItemAsync(default, default, default)
+ .ReturnsForAnyArgs(itemResponse);
+
+ container
+ .GetItemQueryIterator(default(QueryDefinition), default)
+ .ReturnsForAnyArgs(feedIterator);
+
+ container
+ .GetItemQueryIterator(default(string), default)
+ .ReturnsForAnyArgs(feedIterator);
+
+ containerProvider = Substitute.For();
+ containerProvider
+ .GetContainer()
+ .Returns(container, null);
+
+ sut = new LowPriorityCosmosReader(containerProvider);
+ }
+
+ [Fact]
+ public void Implements_Interface()
+ => sut.Should().BeAssignableTo>();
+
+ [Theory, AutoNSubstituteData]
+ public void ReadAllAsync_Uses_The_Right_Container(
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.BatchReadAllAsync(partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAllAsync_Returns_Empty_No_More_Result(
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut
+ .BatchReadAllAsync(partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAllAsync_Returns_Empty_When_Query_Matches_Non(
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(true, false);
+
+ var response = await sut
+ .BatchReadAllAsync(partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .First()
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAllAsync_Returns_All_Items(
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true, false);
+
+ feedResponse
+ .GetEnumerator()
+ .Returns(new List { record }.GetEnumerator());
+
+ var response = await sut
+ .BatchReadAllAsync(partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .NotBeEmpty();
+
+ response[0]
+ .First()
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void QueryAsync_Uses_The_Right_Container(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.BatchQueryAsync(query, partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_Returns_Empty_No_More_Result(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_Returns_Empty_When_Query_Matches_Non(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(true, false);
+
+ var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .First()
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true, false);
+
+ feedResponse
+ .GetEnumerator()
+ .Returns(new List { record }.GetEnumerator());
+
+ var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .NotBeEmpty();
+
+ response[0]
+ .First()
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void Multiple_Operations_Uses_Same_Container(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.BatchReadAllAsync(partitionKey, cancellationToken).ToArrayAsync(cancellationToken);
+ _ = sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+ _ = sut.BatchCrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+
+ container
+ .ReceivedCalls()
+ .Should()
+ .HaveCount(3);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void QueryAsync_With_Custom_Result_Uses_The_Right_Container(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.BatchQueryAsync(query, partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_With_Custom_Returns_Empty_No_More_Result(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_With_Custom_Returns_Empty_When_Query_Matches_Non(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(true, false);
+
+ var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .First()
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_With_Custom_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true, false);
+
+ feedResponse
+ .GetEnumerator()
+ .Returns(new List { record }.GetEnumerator());
+
+ var response = await sut.BatchQueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .NotBeEmpty();
+
+ response[0]
+ .First()
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void CrossPartitionQueryAsync_Uses_The_Right_Container(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.BatchCrossPartitionQueryAsync(query, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void CrossPartitionQueryAsync_Uses_QueryRequestOptions_With_PriorityLevel_Low(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.BatchCrossPartitionQueryAsync(query, cancellationToken).ToArrayAsync(cancellationToken);
+
+ container
+ .Received(1)
+ .GetItemQueryIterator(
+ query,
+ requestOptions: Arg.Is(
+ c => c.PriorityLevel == PriorityLevel.Low));
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionQueryAsync_Returns_Empty_No_More_Result(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut.BatchCrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionQueryAsync_Returns_Empty_When_Query_Matches_Non(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(true, false);
+
+ var response = await sut.BatchCrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .First()
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionQueryAsync_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true, false);
+
+ feedResponse
+ .GetEnumerator()
+ .Returns(new List { record }.GetEnumerator());
+
+ var response = await sut.BatchCrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .NotBeEmpty();
+
+ response[0]
+ .First()
+ .Should()
+ .Be(record);
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/Atc.Cosmos.Tests/LowPriorityCosmosReaderTests.cs b/test/Atc.Cosmos.Tests/LowPriorityCosmosReaderTests.cs
new file mode 100644
index 0000000..5878fc0
--- /dev/null
+++ b/test/Atc.Cosmos.Tests/LowPriorityCosmosReaderTests.cs
@@ -0,0 +1,1035 @@
+#if PREVIEW
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Atc.Cosmos.Internal;
+using Atc.Test;
+using AutoFixture;
+using Dasync.Collections;
+using FluentAssertions;
+using Microsoft.Azure.Cosmos;
+using NSubstitute;
+using NSubstitute.ExceptionExtensions;
+using Xunit;
+
+namespace Atc.Cosmos.Tests
+{
+ public class LowPriorityCosmosReaderTests
+ {
+ private readonly CosmosOptions options;
+ private readonly ItemResponse itemResponse;
+ private readonly FeedIterator feedIterator;
+ private readonly FeedResponse feedResponse;
+ private readonly Record record;
+ private readonly Container container;
+ private readonly ICosmosContainerProvider containerProvider;
+ private readonly LowPriorityCosmosReader sut;
+
+ public LowPriorityCosmosReaderTests()
+ {
+ var fixture = FixtureFactory.Create();
+ options = fixture.Create();
+ record = fixture.Create();
+ itemResponse = Substitute.For>();
+ itemResponse
+ .Resource
+ .Returns(record);
+
+ feedResponse = Substitute.For>();
+ feedIterator = Substitute.For>();
+ feedIterator
+ .ReadNextAsync(default)
+ .ReturnsForAnyArgs(feedResponse);
+
+ container = Substitute.For();
+ container
+ .ReadItemAsync(default, default, default)
+ .ReturnsForAnyArgs(itemResponse);
+
+ container
+ .GetItemQueryIterator(default(QueryDefinition), default)
+ .ReturnsForAnyArgs(feedIterator);
+
+ container
+ .GetItemQueryIterator(default(string), default)
+ .ReturnsForAnyArgs(feedIterator);
+
+ containerProvider = Substitute.For();
+ containerProvider
+ .GetContainer()
+ .Returns(container, null);
+ containerProvider
+ .GetCosmosOptions()
+ .Returns(options);
+
+ sut = new LowPriorityCosmosReader(containerProvider);
+ }
+
+ [Fact]
+ public void Implements_Interface()
+ => sut.Should().BeAssignableTo>();
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAsync_Uses_The_Right_Container(
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ await sut.ReadAsync(documentId, partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAsync_Reads_Item_In_Container_Using_PriorityLevel_Low(
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ await sut.ReadAsync(documentId, partitionKey, cancellationToken);
+
+ _ = container
+ .Received(1)
+ .ReadItemAsync(
+ documentId,
+ new PartitionKey(partitionKey),
+ Arg.Is(c => c.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAsync_Returns_Item_Read_From_Container(
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ var result = await sut.ReadAsync(documentId, partitionKey, cancellationToken);
+ result
+ .Should()
+ .Be(itemResponse.Resource);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void ReadAsync_Throws_Expection_When_Record_IsNot_Found(
+ CosmosException exception,
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ container
+ .ReadItemAsync(default, default, default, default)
+ .ThrowsForAnyArgs(exception);
+
+ FluentActions
+ .Awaiting(() => sut.ReadAsync(documentId, partitionKey, cancellationToken))
+ .Should()
+ .ThrowAsync();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task FindAsync_Uses_The_Right_Container(
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ await sut.FindAsync(documentId, partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task FindAsync_Return_Default_When_Record_IsNot_Found(
+ CosmosException exception,
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ container
+ .ReadItemAsync(default, default, default, default)
+ .ThrowsForAnyArgs(exception);
+
+ var response = await sut.FindAsync(documentId, partitionKey, cancellationToken);
+
+ response
+ .Should()
+ .BeNull();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task FindAsync_Returns_Record_When_Successful(
+ string partitionKey,
+ string documentId,
+ CancellationToken cancellationToken)
+ {
+ var result = await sut.FindAsync(documentId, partitionKey, cancellationToken);
+ result
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void ReadAllAsync_Uses_The_Right_Container(
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.ReadAllAsync(partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAllAsync_Returns_Empty_No_More_Result(
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut
+ .ReadAllAsync(partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAllAsync_Returns_Empty_When_Query_Matches_Non(
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(true, false);
+
+ var response = await sut
+ .ReadAllAsync(partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReadAllAsync_Returns_All_Items(
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true, false);
+
+ feedResponse
+ .GetEnumerator()
+ .Returns(new List { record }.GetEnumerator());
+
+ var response = await sut
+ .ReadAllAsync(partitionKey, cancellationToken)
+ .ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .NotBeEmpty();
+
+ response[0]
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void QueryAsync_Uses_The_Right_Container(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.QueryAsync(query, partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_Returns_Empty_No_More_Result(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_Returns_Empty_When_Query_Matches_Non(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(true, false);
+
+ var response = await sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true, false);
+
+ feedResponse
+ .GetEnumerator()
+ .Returns(new List { record }.GetEnumerator());
+
+ var response = await sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .NotBeEmpty();
+
+ response[0]
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task Should_Have_ETag_From_ItemResponse(
+ string etag,
+ string partitionKey,
+ string documentId,
+ CancellationToken cancellationToken)
+ {
+ itemResponse
+ .ETag
+ .Returns(etag);
+ itemResponse
+ .Resource
+ .Returns(record);
+
+ var result = await sut.FindAsync(documentId, partitionKey, cancellationToken);
+
+ var resource = result as ICosmosResource;
+ resource
+ .Should()
+ .NotBeNull();
+
+ resource
+ .ETag
+ .Should()
+ .NotBeNullOrWhiteSpace();
+
+ resource
+ .ETag
+ .Should()
+ .Be(etag);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void Multiple_Operations_Uses_Same_Container(
+ QueryDefinition query,
+ string documentId,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.ReadAsync(documentId, partitionKey, cancellationToken);
+ _ = sut.ReadAsync(documentId, partitionKey, cancellationToken);
+ _ = sut.FindAsync(documentId, partitionKey, cancellationToken);
+ _ = sut.FindAsync(documentId, partitionKey, cancellationToken);
+ _ = sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+ _ = sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ container
+ .ReceivedCalls()
+ .Should()
+ .HaveCount(6);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void QueryAsync_With_Custom_Result_Uses_The_Right_Container(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.QueryAsync(query, partitionKey, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_With_Custom_Returns_Empty_No_More_Result(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_With_Custom_Returns_Empty_When_Query_Matches_Non(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(true, false);
+
+ var response = await sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task QueryAsync_With_Custom_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ string partitionKey,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true, false);
+
+ feedResponse
+ .GetEnumerator()
+ .Returns(new List { record }.GetEnumerator());
+
+ var response = await sut.QueryAsync(query, partitionKey, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .NotBeEmpty();
+
+ response[0]
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void PagedQueryAsync_Uses_The_Right_Container(
+ QueryDefinition query,
+ string partitionKey,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.PagedQueryAsync(
+ query,
+ partitionKey,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void PagedQueryAsync_Gets_ItemQueryIterator(
+ QueryDefinition query,
+ string partitionKey,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.PagedQueryAsync(
+ query,
+ partitionKey,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ container
+ .Received(1)
+ .GetItemQueryIterator(
+ query,
+ continuationToken,
+ requestOptions: Arg.Is(o
+ => o.PartitionKey == new PartitionKey(partitionKey)
+ && o.MaxItemCount == pageSize
+ && o.ResponseContinuationTokenLimitInKb == options.ContinuationTokenLimitInKb));
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task PagedQueryAsync_Returns_Empty_When_No_More_Result(
+ QueryDefinition query,
+ string partitionKey,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut
+ .PagedQueryAsync(
+ query,
+ partitionKey,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response.Items
+ .Should()
+ .BeEmpty();
+ response.ContinuationToken
+ .Should()
+ .BeNull();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task PagedQueryAsync_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ string partitionKey,
+ int pageSize,
+ string continuationToken,
+ List records,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true);
+ feedResponse
+ .ContinuationToken
+ .Returns(continuationToken);
+ feedResponse
+ .GetEnumerator()
+ .Returns(records.GetEnumerator());
+
+ var response = await sut
+ .PagedQueryAsync(
+ query,
+ partitionKey,
+ pageSize,
+ null,
+ cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response.Items
+ .Should()
+ .BeEquivalentTo(records);
+
+ response.ContinuationToken
+ .Should()
+ .Be(continuationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void PagedQueryAsync_With_Custom_Uses_The_Right_Container(
+ QueryDefinition query,
+ string partitionKey,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.PagedQueryAsync(
+ query,
+ partitionKey,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task PagedQueryAsync_With_Custom_Returns_Empty_No_More_Result(
+ QueryDefinition query,
+ string partitionKey,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut
+ .PagedQueryAsync(
+ query,
+ partitionKey,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response.Items
+ .Should()
+ .BeEmpty();
+ response.ContinuationToken
+ .Should()
+ .BeNull();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task PagedQueryAsync_With_Custom_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ string partitionKey,
+ int pageSize,
+ string continuationToken,
+ List records,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true);
+ feedResponse
+ .ContinuationToken
+ .Returns(continuationToken);
+ feedResponse
+ .GetEnumerator()
+ .Returns(records.GetEnumerator());
+
+ var response = await sut
+ .PagedQueryAsync(
+ query,
+ partitionKey,
+ pageSize,
+ null,
+ cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response.Items
+ .Should()
+ .BeEquivalentTo(records);
+
+ response.ContinuationToken
+ .Should()
+ .Be(continuationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void CrossPartitionQueryAsync_Uses_The_Right_Container(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.CrossPartitionQueryAsync(query, cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void CrossPartitionQueryAsync_Does_Not_Specify_QueryRequestOptions(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.CrossPartitionQueryAsync(query, cancellationToken).ToArrayAsync(cancellationToken);
+
+ container
+ .Received(1)
+ .GetItemQueryIterator(query, requestOptions: null);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionQueryAsync_Returns_Empty_No_More_Result(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut.CrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionQueryAsync_Returns_Empty_When_Query_Matches_Non(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(true, false);
+
+ var response = await sut.CrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .BeEmpty();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionQueryAsync_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true, false);
+
+ feedResponse
+ .GetEnumerator()
+ .Returns(new List { record }.GetEnumerator());
+
+ var response = await sut.CrossPartitionQueryAsync(query, cancellationToken).ToListAsync(cancellationToken);
+
+ _ = feedIterator
+ .Received(2)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response
+ .Should()
+ .NotBeEmpty();
+
+ response[0]
+ .Should()
+ .Be(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void CrossPartitionPagedQueryAsync_Uses_The_Right_Container(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void CrossPartitionPagedQueryAsync_Gets_ItemQueryIterator(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ container
+ .Received(1)
+ .GetItemQueryIterator(
+ query,
+ continuationToken,
+ requestOptions: Arg.Is(o
+ => o.PartitionKey == null
+ && o.MaxItemCount == pageSize
+ && o.ResponseContinuationTokenLimitInKb == options.ContinuationTokenLimitInKb));
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionPagedQueryAsync_Returns_Empty_When_No_More_Result(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut
+ .CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response.Items
+ .Should()
+ .BeEmpty();
+ response.ContinuationToken
+ .Should()
+ .BeNull();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionPagedQueryAsync_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ List records,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true);
+ feedResponse
+ .ContinuationToken
+ .Returns(continuationToken);
+ feedResponse
+ .GetEnumerator()
+ .Returns(records.GetEnumerator());
+
+ var response = await sut
+ .CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ null,
+ cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response.Items
+ .Should()
+ .BeEquivalentTo(records);
+
+ response.ContinuationToken
+ .Should()
+ .Be(continuationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void CrossPartitionPagedQueryAsync_With_Custom_Uses_The_Right_Container(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ _ = sut.CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ containerProvider
+ .Received(1)
+ .GetContainer();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionPagedQueryAsync_With_Custom_Returns_Empty_No_More_Result(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ CancellationToken cancellationToken)
+ {
+ feedIterator.HasMoreResults.Returns(false);
+
+ var response = await sut
+ .CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ continuationToken,
+ cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(0)
+ .ReadNextAsync(default);
+
+ response.Items
+ .Should()
+ .BeEmpty();
+ response.ContinuationToken
+ .Should()
+ .BeNull();
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CrossPartitionPagedQueryAsync_With_Custom_Returns_Items_When_Query_Matches(
+ QueryDefinition query,
+ int pageSize,
+ string continuationToken,
+ List records,
+ CancellationToken cancellationToken)
+ {
+ feedIterator
+ .HasMoreResults
+ .Returns(true);
+ feedResponse
+ .ContinuationToken
+ .Returns(continuationToken);
+ feedResponse
+ .GetEnumerator()
+ .Returns(records.GetEnumerator());
+
+ var response = await sut
+ .CrossPartitionPagedQueryAsync(
+ query,
+ pageSize,
+ null,
+ cancellationToken);
+
+ _ = feedIterator
+ .Received(1)
+ .HasMoreResults;
+
+ _ = feedIterator
+ .Received(1)
+ .ReadNextAsync(default);
+
+ response.Items
+ .Should()
+ .BeEquivalentTo(records);
+
+ response.ContinuationToken
+ .Should()
+ .Be(continuationToken);
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/Atc.Cosmos.Tests/LowPriorityCosmosWriterTests.cs b/test/Atc.Cosmos.Tests/LowPriorityCosmosWriterTests.cs
new file mode 100644
index 0000000..6d7f70c
--- /dev/null
+++ b/test/Atc.Cosmos.Tests/LowPriorityCosmosWriterTests.cs
@@ -0,0 +1,490 @@
+#if PREVIEW
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Atc.Cosmos.Internal;
+using Atc.Cosmos.Serialization;
+using Atc.Test;
+using AutoFixture;
+using AutoFixture.AutoNSubstitute;
+using FluentAssertions;
+using Microsoft.Azure.Cosmos;
+using NSubstitute;
+using Xunit;
+
+namespace Atc.Cosmos.Tests
+{
+ public class LowPriorityCosmosWriterTests
+ {
+ private readonly Record record;
+ private readonly Container container;
+ private readonly ICosmosContainerProvider containerProvider;
+ private readonly ILowPriorityCosmosReader reader;
+ private readonly IJsonCosmosSerializer serializer;
+ private readonly LowPriorityCosmosWriter sut;
+
+ public LowPriorityCosmosWriterTests()
+ {
+ record = new Fixture().Create();
+
+ container = Substitute.For();
+
+ containerProvider = Substitute.For();
+ containerProvider
+ .GetContainer()
+ .ReturnsForAnyArgs(container, null);
+
+ var response = Substitute.For>();
+ response.Resource.Returns(new Fixture().Create());
+ container
+ .CreateItemAsync(default, default, default, default)
+ .ReturnsForAnyArgs(response);
+ container
+ .ReplaceItemAsync(default, default, default, default, default)
+ .ReturnsForAnyArgs(response);
+ container
+ .UpsertItemAsync(default, default, default, default)
+ .ReturnsForAnyArgs(response);
+ container
+ .PatchItemAsync(default, default, default, default)
+ .ReturnsForAnyArgs(response);
+
+ reader = Substitute.For>();
+ reader
+ .ReadAsync(default, default, default)
+ .ReturnsForAnyArgs(record);
+
+ serializer = Substitute.For();
+ serializer
+ .FromString(default)
+ .ReturnsForAnyArgs(new Fixture().Create());
+
+ sut = new LowPriorityCosmosWriter(containerProvider, reader, serializer);
+ }
+
+ [Fact]
+ public void Implements_Interface()
+ => sut.Should().BeAssignableTo>();
+
+ [Theory, AutoNSubstituteData]
+ public async Task WriteAsync_Uses_The_Right_Container(
+ CancellationToken cancellationToken)
+ {
+ await sut.WriteAsync(record, cancellationToken);
+ containerProvider
+ .Received(1)
+ .GetContainer(
+ allowBulk: false);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task WriteAsync_UpsertItem_In_Container(
+ CancellationToken cancellationToken)
+ {
+ containerProvider
+ .GetContainer()
+ .ReturnsForAnyArgs(container);
+
+ await sut.WriteAsync(record, cancellationToken);
+ await container
+ .Received(1)
+ .UpsertItemAsync(
+ record,
+ new PartitionKey(record.Pk),
+ Arg.Is(c => c.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task WriteWithNoResponseAsync_UpsertItem_In_Container(
+ CancellationToken cancellationToken)
+ {
+ containerProvider
+ .GetContainer()
+ .ReturnsForAnyArgs(container);
+
+ await sut.WriteWithNoResponseAsync(record, cancellationToken);
+ await container
+ .Received(1)
+ .UpsertItemAsync(
+ record,
+ new PartitionKey(record.Pk),
+ Arg.Is(
+ p => p.EnableContentResponseOnWrite == false && p.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CreateAsync_Calls_CreateItem_On_Container(
+ CancellationToken cancellationToken)
+ {
+ await sut.CreateAsync(record, cancellationToken);
+ _ = container
+ .Received(1)
+ .CreateItemAsync(
+ record,
+ new PartitionKey(record.Pk),
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task CreateWithNoResponseAsync_Calls_CreateItem_On_Container(
+ CancellationToken cancellationToken)
+ {
+ await sut.CreateWithNoResponseAsync(record, cancellationToken);
+ _ = container
+ .Received(1)
+ .CreateItemAsync(
+ record,
+ new PartitionKey(record.Pk),
+ Arg.Is(p => p.EnableContentResponseOnWrite == false && p.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReplaceAsync_Calls_ReplaceItemAsync_On_Container(
+ CancellationToken cancellationToken)
+ {
+ await sut.ReplaceAsync(record, cancellationToken);
+ _ = container
+ .Received(1)
+ .ReplaceItemAsync(
+ record,
+ record.Id,
+ new PartitionKey(record.Pk),
+ Arg.Is(
+ o => o.IfMatchEtag == record.ETag && o.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task ReplaceWithNoResponseAsync_Calls_ReplaceItemAsync_On_Container(
+ CancellationToken cancellationToken)
+ {
+ await sut.ReplaceWithNoResponseAsync(record, cancellationToken);
+ _ = container
+ .Received(1)
+ .ReplaceItemAsync(
+ record,
+ record.Id,
+ new PartitionKey(record.Pk),
+ Arg.Is(o => o.IfMatchEtag == record.ETag
+ && o.EnableContentResponseOnWrite == false
+ && o.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public void Multiple_Operations_Uses_Same_Container(
+ CancellationToken cancellationToken)
+ {
+ _ = sut.WriteAsync(record, cancellationToken);
+ _ = sut.WriteAsync(record, cancellationToken);
+ _ = sut.CreateAsync(record, cancellationToken);
+ _ = sut.CreateAsync(record, cancellationToken);
+ _ = sut.ReplaceAsync(record, cancellationToken);
+ _ = sut.ReplaceAsync(record, cancellationToken);
+
+ container
+ .ReceivedCalls()
+ .Should()
+ .HaveCount(6);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task DeleteAsync_Calls_DeleteItemAsync_On_Container(
+ CancellationToken cancellationToken)
+ {
+ await sut.DeleteAsync(record.Id, record.Pk, cancellationToken);
+ _ = container
+ .Received(1)
+ .DeleteItemAsync(
+ record.Id,
+ new PartitionKey(record.Pk),
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.Low),
+ cancellationToken: cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task Should_Return_True_When_Trying_To_Delete_Existing_Resource(
+ CancellationToken cancellationToken)
+ {
+ var deleted = await sut.TryDeleteAsync(
+ record.Id,
+ record.Pk,
+ cancellationToken);
+
+ deleted
+ .Should()
+ .BeTrue();
+
+ _ = container
+ .Received(1)
+ .DeleteItemAsync(
+ record.Id,
+ new PartitionKey(record.Pk),
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.Low),
+ cancellationToken: cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task Should_Return_False_When_Trying_To_Delete_NonExisting_Resource(
+ CancellationToken cancellationToken)
+ {
+ container
+ .DeleteItemAsync(default, default, default, default)
+ .ReturnsForAnyArgs>(
+ r => throw new CosmosException("fake", HttpStatusCode.NotFound, 0, "1", 1));
+
+ var deleted = await sut.TryDeleteAsync(
+ record.Id,
+ record.Pk,
+ cancellationToken);
+
+ deleted
+ .Should()
+ .BeFalse();
+
+ _ = container
+ .Received(1)
+ .DeleteItemAsync(
+ record.Id,
+ new PartitionKey(record.Pk),
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.Low),
+ cancellationToken: cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task UpdateAsync_Reads_The_Resource(
+ string documentId,
+ string partitionKey,
+ Action updateDocument,
+ int retries,
+ CancellationToken cancellationToken)
+ {
+ await sut.UpdateAsync(
+ documentId,
+ partitionKey,
+ updateDocument,
+ retries,
+ cancellationToken);
+
+ _ = reader
+ .Received(1)
+ .ReadAsync(
+ documentId,
+ partitionKey,
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task UpdateAsync_Calls_UpdateDocument_With_Read_Resource(
+ string documentId,
+ string partitionKey,
+ [Substitute] Action updateDocument,
+ int retries,
+ CancellationToken cancellationToken)
+ {
+ await sut.UpdateAsync(
+ documentId,
+ partitionKey,
+ updateDocument,
+ retries,
+ cancellationToken);
+
+ updateDocument
+ .Received(1)
+ .Invoke(record);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task UpdateAsync_Calls_ReplaceItem_With_Updated_Resource(
+ string documentId,
+ string partitionKey,
+ [Substitute] Action updateDocument,
+ int retries,
+ CancellationToken cancellationToken)
+ {
+ await sut.UpdateAsync(
+ documentId,
+ partitionKey,
+ updateDocument,
+ retries,
+ cancellationToken);
+
+ _ = container
+ .Received(1)
+ .ReplaceItemAsync(
+ record,
+ record.Id,
+ new PartitionKey(record.Pk),
+ Arg.Is(o => o.IfMatchEtag == record.ETag && o.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task UpdateOrCreateAsync_Finds_The_Resource(
+ Action updateDocument,
+ int retries,
+ Record defaultDocument,
+ CancellationToken cancellationToken)
+ {
+ await sut.UpdateOrCreateAsync(
+ () => defaultDocument,
+ updateDocument,
+ retries,
+ cancellationToken);
+
+ _ = reader
+ .Received(1)
+ .FindAsync(
+ defaultDocument.Id,
+ defaultDocument.Pk,
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task UpdateAsync_Calls_UpdateDocument_With_Found_Resource(
+ [Substitute] Action updateDocument,
+ int retries,
+ Record defaultDocument,
+ Record foundResource,
+ CancellationToken cancellationToken)
+ {
+ reader
+ .FindAsync(default, default, default)
+ .ReturnsForAnyArgs(foundResource);
+
+ await sut.UpdateOrCreateAsync(
+ () => defaultDocument,
+ updateDocument,
+ retries,
+ cancellationToken);
+
+ updateDocument
+ .Received(1)
+ .Invoke(foundResource);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task UpdateAsync_Calls_UpdateDocument_With_Default_Document_If_Not_Found(
+ [Substitute] Action updateDocument,
+ int retries,
+ Record defaultDocument,
+ CancellationToken cancellationToken)
+ {
+ await sut.UpdateOrCreateAsync(
+ () => defaultDocument,
+ updateDocument,
+ retries,
+ cancellationToken);
+
+ updateDocument
+ .Received(1)
+ .Invoke(defaultDocument);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task UpdateOrCreateAsync_Calls_ReplaceItem_If_Resource_Has_ETag(
+ [Substitute] Action updateDocument,
+ int retries,
+ Record defaultDocument,
+ Record foundResource,
+ string etag,
+ CancellationToken cancellationToken)
+ {
+ ((ICosmosResource)foundResource).ETag = etag;
+ reader
+ .FindAsync(default, default, default)
+ .ReturnsForAnyArgs(foundResource);
+
+ await sut.UpdateOrCreateAsync(
+ () => defaultDocument,
+ updateDocument,
+ retries,
+ cancellationToken);
+
+ _ = container
+ .Received(1)
+ .ReplaceItemAsync(
+ foundResource,
+ foundResource.Id,
+ new PartitionKey(foundResource.Pk),
+ Arg.Is(o => o.IfMatchEtag == foundResource.ETag && o.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task UpdateOrCreateAsync_Calls_CreateItem_If_Resource_Has_No_ETag(
+ [Substitute] Action updateDocument,
+ int retries,
+ Record defaultDocument,
+ CancellationToken cancellationToken)
+ {
+ defaultDocument.ETag = null;
+ await sut.UpdateOrCreateAsync(
+ () => defaultDocument,
+ updateDocument,
+ retries,
+ cancellationToken);
+
+ _ = container
+ .Received(1)
+ .CreateItemAsync(
+ defaultDocument,
+ new PartitionKey(defaultDocument.Pk),
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task PatchAsync_Calls_PatchItemAsync_On_Container(
+ IReadOnlyList patchOperations,
+ string filterPredicate,
+ CancellationToken cancellationToken)
+ {
+ await sut.PatchAsync(
+ record.Id,
+ record.Pk,
+ patchOperations,
+ filterPredicate,
+ cancellationToken);
+
+ _ = container
+ .Received(1)
+ .PatchItemAsync(
+ record.Id,
+ new PartitionKey(record.Pk),
+ patchOperations,
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+
+ [Theory, AutoNSubstituteData]
+ public async Task PatchWithNoResponseAsync_Calls_PatchItemAsync_On_Container(
+ IReadOnlyList patchOperations,
+ string filterPredicate,
+ CancellationToken cancellationToken)
+ {
+ await sut.PatchWithNoResponseAsync(
+ record.Id,
+ record.Pk,
+ patchOperations,
+ filterPredicate,
+ cancellationToken);
+
+ _ = container
+ .Received(1)
+ .PatchItemAsync(
+ record.Id,
+ new PartitionKey(record.Pk),
+ patchOperations,
+ Arg.Is(o => o.PriorityLevel == PriorityLevel.Low),
+ cancellationToken);
+ }
+ }
+}
+#endif
\ No newline at end of file