diff --git a/Datasync.Toolkit.sln b/Datasync.Toolkit.sln
index ecba367..c823206 100644
--- a/Datasync.Toolkit.sln
+++ b/Datasync.Toolkit.sln
@@ -64,6 +64,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{75F7
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample.Datasync.Server", "samples\datasync-server\src\Sample.Datasync.Server\Sample.Datasync.Server.csproj", "{A9967817-2A2C-4C6D-A133-967A6062E9B3}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.MongoDB", "src\CommunityToolkit.Datasync.Server.MongoDB\CommunityToolkit.Datasync.Server.MongoDB.csproj", "{DC20ACF9-12E9-41D9-B672-CB5FD85548E9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Datasync.Server.MongoDB.Test", "tests\CommunityToolkit.Datasync.Server.MongoDB.Test\CommunityToolkit.Datasync.Server.MongoDB.Test.csproj", "{4FC45D20-0BA9-484B-9040-641687659AF6}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -154,6 +158,14 @@ Global
{A9967817-2A2C-4C6D-A133-967A6062E9B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9967817-2A2C-4C6D-A133-967A6062E9B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9967817-2A2C-4C6D-A133-967A6062E9B3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DC20ACF9-12E9-41D9-B672-CB5FD85548E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DC20ACF9-12E9-41D9-B672-CB5FD85548E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DC20ACF9-12E9-41D9-B672-CB5FD85548E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DC20ACF9-12E9-41D9-B672-CB5FD85548E9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4FC45D20-0BA9-484B-9040-641687659AF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4FC45D20-0BA9-484B-9040-641687659AF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4FC45D20-0BA9-484B-9040-641687659AF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4FC45D20-0BA9-484B-9040-641687659AF6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -180,6 +192,8 @@ Global
{D3B72031-D4BD-44D3-973C-2752AB1570F6} = {84AD662A-4B9E-4E64-834D-72529FB7FCE5}
{2889E6B2-9CD1-437C-A43C-98CFAFF68B99} = {D59F1489-5D74-4F52-B78B-88037EAB2838}
{A9967817-2A2C-4C6D-A133-967A6062E9B3} = {75F709FD-8CC2-4558-A802-FE57086167C2}
+ {DC20ACF9-12E9-41D9-B672-CB5FD85548E9} = {84AD662A-4B9E-4E64-834D-72529FB7FCE5}
+ {4FC45D20-0BA9-484B-9040-641687659AF6} = {D59F1489-5D74-4F52-B78B-88037EAB2838}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {78A935E9-8F14-448A-BEDF-360FB742F14E}
diff --git a/Directory.Packages.props b/Directory.Packages.props
index e316022..f759986 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -24,6 +24,7 @@
+
diff --git a/infra/modules/cosmos-mongodb.bicep b/infra/modules/cosmos-mongodb.bicep
index fa44521..2b546cf 100644
--- a/infra/modules/cosmos-mongodb.bicep
+++ b/infra/modules/cosmos-mongodb.bicep
@@ -1,182 +1,75 @@
targetScope = 'resourceGroup'
-@minLength(1)
-@description('The name of the test container to create')
-param containerName string = 'Movies'
+@secure()
+@description('The password for the administrator')
+param administratorPassword string
-@minLength(1)
-@description('The name of the test database to create')
-param databaseName string = 'unittests'
+@description('The username for the administrator')
+param administratorUsername string = 'tester'
+
+@description('The list of firewall rules to install')
+param firewallRules FirewallRule[] = [
+ { startIpAddress: '0.0.0.0', endIpAddress: '0.0.0.0' }
+]
@minLength(1)
@description('Primary location for all resources')
param location string = resourceGroup().location
-@allowed(['3.2', '3.6', '4.0', '4.2'])
-@description('Specifies the MongoDB server version to use.')
-param mongoVersion string = '4.2'
-
@description('The name of the Mongo Server to create.')
param serverName string
@description('The list of tags to apply to all resources.')
param tags object = {}
-/*********************************************************************************/
-
-var compositeIndices = [
- [
- { path: '/BestPictureWinner', order: 'ascending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/BestPictureWinner', order: 'descending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/Duration', order: 'ascending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/Duration', order: 'descending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/Rating', order: 'ascending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/Rating', order: 'descending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/ReleaseDate', order: 'ascending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/ReleaseDate', order: 'descending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/Title', order: 'ascending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/Title', order: 'descending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/UpdatedAt', order: 'ascending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/UpdatedAt', order: 'descending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/Year', order: 'ascending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/Year', order: 'descending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/Year', order: 'ascending' }
- { path: '/Title', order: 'ascending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/Year', order: 'descending' }
- { path: '/Title', order: 'ascending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/Year', order: 'ascending' }
- { path: '/Title', order: 'descending' }
- { path: '/id', order: 'ascending' }
- ]
- [
- { path: '/Year', order: 'descending' }
- { path: '/Title', order: 'descending' }
- { path: '/id', order: 'ascending' }
- ]
-]
+@description('The tier to use for compute')
+@allowed([ 'Free', 'M10', 'M20', 'M25', 'M30', 'M40', 'M50', 'M60', 'M80', 'M200', 'M200-Autoscale'])
+param tier string = 'M10'
/*********************************************************************************/
-resource account 'Microsoft.DocumentDB/databaseAccounts@2022-05-15' = {
+resource cluster 'Microsoft.DocumentDB/mongoClusters@2024-07-01' = {
name: toLower(serverName)
location: location
- kind: 'MongoDB'
tags: tags
properties: {
- apiProperties: {
- serverVersion: mongoVersion
+ administrator: {
+ userName: administratorUsername
+ password: administratorPassword
}
- capabilities: [
- {
- name: 'DisableRateLimitingResponses'
- }
- ]
- consistencyPolicy: {
- defaultConsistencyLevel: 'Session'
+ compute: { tier: tier }
+ highAvailability: {
+ targetMode: 'Disabled'
}
- databaseAccountOfferType: 'Standard'
- disableLocalAuth: false
- locations: [
- {
- locationName: location
- isZoneRedundant: false
- }
- ]
- }
-}
-
-resource database 'Microsoft.DocumentDB/databaseAccounts/mongodbDatabases@2022-05-15' = {
- parent: account
- name: databaseName
- tags: tags
- properties: {
- resource: {
- id: databaseName
- }
- options: {
- throughput: 400
+ publicNetworkAccess: 'Enabled'
+ serverVersion: '7.0'
+ sharding: {
+ shardCount: 1
}
+ storage: { sizeGb: 32 }
}
}
-resource collection 'Microsoft.DocumentDb/databaseAccounts/mongodbDatabases/collections@2022-05-15' = {
- parent: database
- name: containerName
- tags: tags
- properties: {
- resource: {
- id: containerName
- shardKey: {
- _id: 'Hash'
- }
- indexes: [
- {
- key: {
- keys: [
- '_id'
- ]
- }
- }
- {
- key: {
- keys: [
- '$**'
- ]
- }
- }
- ]
+resource mongoFirewallRule 'Microsoft.DocumentDB/mongoClusters/firewallRules@2024-07-01' = [
+ for (fwRule, index) in firewallRules: {
+ name: fwRule.?name ?? 'rule-${index}'
+ parent: cluster
+ properties: {
+ startIpAddress: fwRule.startIpAddress
+ endIpAddress: fwRule.endIpAddress
}
}
-}
+]
+
/*********************************************************************************/
#disable-next-line outputs-should-not-contain-secrets
-output MONGODB_CONNECTIONSTRING string = account.listConnectionStrings().connectionStrings[1].connectionString
+output MONGO_CONNECTIONSTRING string = replace(replace(cluster.listConnectionStrings().connectionStrings[0].connectionString, '', administratorUsername), '', administratorPassword)
+
+/*********************************************************************************/
+
+type FirewallRule = {
+ name: string?
+ startIpAddress: string
+ endIpAddress: string
+}
diff --git a/infra/resources.bicep b/infra/resources.bicep
index 7aa8242..3be157e 100644
--- a/infra/resources.bicep
+++ b/infra/resources.bicep
@@ -110,9 +110,9 @@ module mongodb './modules/cosmos-mongodb.bicep' = {
params: {
location: location
tags: tags
- databaseName: testDatabaseName
- containerName: cosmosContainerName
serverName: mongoServerName
+ administratorPassword: sqlAdminPassword
+ administratorUsername: sqlAdminUsername
}
}
@@ -148,7 +148,7 @@ module app_service './modules/appservice.bicep' = {
output AZSQL_CONNECTIONSTRING string = azuresql.outputs.AZSQL_CONNECTIONSTRING
output COSMOS_CONNECTIONSTRING string = cosmos.outputs.COSMOS_CONNECTIONSTRING
-output MONGO_CONNECTIONSTRING string = mongodb.outputs.MONGODB_CONNECTIONSTRING
+output MONGO_CONNECTIONSTRING string = mongodb.outputs.MONGO_CONNECTIONSTRING
output MONGOACI_CONNECTIONSTRING string = mongoaci.outputs.MONGO_CONNECTIONSTRING
output MYSQL_CONNECTIONSTRING string = mysql.outputs.MYSQL_CONNECTIONSTRING
output PGSQL_CONNECTIONSTRING string = pgsql.outputs.PGSQL_CONNECTIONSTRING
diff --git a/src/CommunityToolkit.Datasync.Server.MongoDB/CommunityToolkit.Datasync.Server.MongoDB.csproj b/src/CommunityToolkit.Datasync.Server.MongoDB/CommunityToolkit.Datasync.Server.MongoDB.csproj
new file mode 100644
index 0000000..92b855e
--- /dev/null
+++ b/src/CommunityToolkit.Datasync.Server.MongoDB/CommunityToolkit.Datasync.Server.MongoDB.csproj
@@ -0,0 +1,18 @@
+
+
+ A repository for the server-side of the Datasync Toolkit that uses MongoDB for storage.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/CommunityToolkit.Datasync.Server.MongoDB/MongoDBRepository.cs b/src/CommunityToolkit.Datasync.Server.MongoDB/MongoDBRepository.cs
new file mode 100644
index 0000000..331ea80
--- /dev/null
+++ b/src/CommunityToolkit.Datasync.Server.MongoDB/MongoDBRepository.cs
@@ -0,0 +1,166 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Datasync.Server.Abstractions.Http;
+using MongoDB.Bson;
+using MongoDB.Driver;
+
+namespace CommunityToolkit.Datasync.Server.MongoDB;
+
+///
+/// A repository implementation that stored data in a LiteDB database.
+///
+/// The entity type to store in the database.
+public class MongoDBRepository : IRepository where TEntity : MongoTableData
+{
+ ///
+ /// Creates a new using the provided MongoDB database.
+ ///
+ ///
+ /// The collection name is based on the entity type.
+ ///
+ /// The to use for storing entities.
+ public MongoDBRepository(IMongoDatabase database) : this(database.GetCollection(typeof(TEntity).Name.ToLowerInvariant() + "s"))
+ {
+ }
+
+ ///
+ /// Creates a new using the provided database connection
+ /// and collection name.
+ ///
+ /// The to use for storing entities.
+ public MongoDBRepository(IMongoCollection collection)
+ {
+ Collection = collection;
+ // TODO: Ensure that there is an index on the right properties.
+ }
+
+ ///
+ /// The collection within the LiteDb database that stores the entities.
+ ///
+ public virtual IMongoCollection Collection { get; }
+
+ ///
+ /// The mechanism by which an Id is generated when one is not provided.
+ ///
+ public Func IdGenerator { get; set; } = _ => Guid.NewGuid().ToString("N");
+
+ ///
+ /// The mechanism by which a new version byte array is generated.
+ ///
+ public Func VersionGenerator { get; set; } = () => Guid.NewGuid().ToByteArray();
+
+ ///
+ /// Updates the system properties for the provided entity on write.
+ ///
+ /// The entity to update.
+ protected void UpdateEntity(TEntity entity)
+ {
+ entity.UpdatedAt = DateTimeOffset.UtcNow;
+ entity.Version = VersionGenerator.Invoke();
+ }
+
+ ///
+ /// Checks that the provided ID is valid.
+ ///
+ /// The ID of the entity to check.
+ /// Thrown if the ID is not valid.
+ protected static void CheckIdIsValid(string id)
+ {
+ if (string.IsNullOrEmpty(id))
+ {
+ throw new HttpException(HttpStatusCodes.Status400BadRequest);
+ }
+ }
+
+ ///
+ /// Returns a filter definition for finding a single document.
+ ///
+ /// The ID of the document to find.
+ /// The filter definition to find the document.
+ protected FilterDefinition GetFilterById(string id)
+ => Builders.Filter.Eq(x => x.Id, id);
+
+ ///
+ /// Returns the document with the provided ID, or null if it doesn't exist.
+ ///
+ /// The ID of the document to find.
+ /// A to observe.
+ /// The document, or null if not found.
+ protected async ValueTask FindDocumentByIdAsync(string id, CancellationToken cancellationToken = default)
+ {
+ return await Collection.Find(GetFilterById(id)).FirstOrDefaultAsync(cancellationToken);
+ }
+
+ ///
+ public virtual ValueTask> AsQueryableAsync(CancellationToken cancellationToken = default)
+ => ValueTask.FromResult(Collection.AsQueryable());
+
+ ///
+ public virtual async ValueTask CreateAsync(TEntity entity, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(entity.Id))
+ {
+ entity.Id = IdGenerator.Invoke(entity);
+ }
+
+ TEntity? existingEntity = await FindDocumentByIdAsync(entity.Id, cancellationToken).ConfigureAwait(false);
+ if (existingEntity is not null)
+ {
+ throw new HttpException(HttpStatusCodes.Status409Conflict) { Payload = existingEntity };
+ }
+
+ UpdateEntity(entity);
+ await Collection.InsertOneAsync(entity, options: null, cancellationToken);
+ }
+
+ ///
+ public virtual async ValueTask DeleteAsync(string id, byte[]? version = null, CancellationToken cancellationToken = default)
+ {
+ CheckIdIsValid(id);
+
+ TEntity storedEntity = await FindDocumentByIdAsync(id, cancellationToken).ConfigureAwait(false)
+ ?? throw new HttpException(HttpStatusCodes.Status404NotFound);
+ if (version?.Length > 0 && !storedEntity.Version.SequenceEqual(version))
+ {
+ throw new HttpException(HttpStatusCodes.Status412PreconditionFailed) { Payload = storedEntity };
+ }
+
+ DeleteResult result = await Collection.DeleteOneAsync(GetFilterById(id), cancellationToken);
+ if (result.DeletedCount == 0)
+ {
+ throw new HttpException(HttpStatusCodes.Status404NotFound);
+ }
+ }
+
+ ///
+ public virtual async ValueTask ReadAsync(string id, CancellationToken cancellationToken = default)
+ {
+ CheckIdIsValid(id);
+
+ return await FindDocumentByIdAsync(id, cancellationToken).ConfigureAwait(false)
+ ?? throw new HttpException(HttpStatusCodes.Status404NotFound);
+ }
+
+ ///
+ public virtual async ValueTask ReplaceAsync(TEntity entity, byte[]? version = null, CancellationToken cancellationToken = default)
+ {
+ CheckIdIsValid(entity.Id);
+
+ TEntity storedEntity = await FindDocumentByIdAsync(entity.Id, cancellationToken).ConfigureAwait(false)
+ ?? throw new HttpException(HttpStatusCodes.Status404NotFound);
+ if (version?.Length > 0 && !storedEntity.Version.SequenceEqual(version))
+ {
+ throw new HttpException(HttpStatusCodes.Status412PreconditionFailed) { Payload = storedEntity };
+ }
+
+ UpdateEntity(entity);
+ ReplaceOptions options = new() { IsUpsert = false };
+ ReplaceOneResult result = await Collection.ReplaceOneAsync(GetFilterById(entity.Id), entity, options, cancellationToken);
+ if (result.IsModifiedCountAvailable && result.ModifiedCount == 0)
+ {
+ throw new HttpException(HttpStatusCodes.Status404NotFound);
+ }
+ }
+}
diff --git a/src/CommunityToolkit.Datasync.Server.MongoDB/MongoTableData.cs b/src/CommunityToolkit.Datasync.Server.MongoDB/MongoTableData.cs
new file mode 100644
index 0000000..818f7e0
--- /dev/null
+++ b/src/CommunityToolkit.Datasync.Server.MongoDB/MongoTableData.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace CommunityToolkit.Datasync.Server.MongoDB;
+
+///
+/// An implementation of the interface for
+/// handling entities in a MongoDB database.
+///
+public class MongoTableData : ITableData
+{
+ ///
+ [BsonId]
+ public string Id { get; set; } = string.Empty;
+
+ ///
+ public bool Deleted { get; set; } = false;
+
+ ///
+ public DateTimeOffset? UpdatedAt { get; set; } = DateTimeOffset.UnixEpoch;
+
+ ///
+ public byte[] Version { get; set; } = [];
+
+ ///
+ public bool Equals(ITableData? other)
+ => other != null && Id == other.Id && Version.SequenceEqual(other.Version);
+}
+
diff --git a/tests/CommunityToolkit.Datasync.Server.MongoDB.Test/CommunityToolkit.Datasync.Server.MongoDB.Test.csproj b/tests/CommunityToolkit.Datasync.Server.MongoDB.Test/CommunityToolkit.Datasync.Server.MongoDB.Test.csproj
new file mode 100644
index 0000000..fbfe531
--- /dev/null
+++ b/tests/CommunityToolkit.Datasync.Server.MongoDB.Test/CommunityToolkit.Datasync.Server.MongoDB.Test.csproj
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
diff --git a/tests/CommunityToolkit.Datasync.Server.MongoDB.Test/CosmosMongoRepository_Tests.cs b/tests/CommunityToolkit.Datasync.Server.MongoDB.Test/CosmosMongoRepository_Tests.cs
new file mode 100644
index 0000000..cf84b07
--- /dev/null
+++ b/tests/CommunityToolkit.Datasync.Server.MongoDB.Test/CosmosMongoRepository_Tests.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Datasync.TestCommon;
+using CommunityToolkit.Datasync.TestCommon.Databases;
+using Microsoft.EntityFrameworkCore;
+using MongoDB.Bson;
+using MongoDB.Driver;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace CommunityToolkit.Datasync.Server.MongoDB.Test;
+
+[ExcludeFromCodeCoverage]
+public class CosmosMongoRepository_Tests(ITestOutputHelper output) : RepositoryTests(), IAsyncLifetime
+{
+ #region Setup
+ private readonly Random random = new();
+ private List movies = [];
+
+ public async Task InitializeAsync()
+ {
+ if (!string.IsNullOrEmpty(ConnectionStrings.CosmosMongo))
+ {
+ Context = await MongoDBContext.CreateContextAsync(ConnectionStrings.CosmosMongo, output);
+ this.movies = await Context.Movies.Find(new BsonDocument()).ToListAsync();
+ }
+ }
+
+ public async Task DisposeAsync()
+ {
+ if (Context is not null)
+ {
+ await Context.DisposeAsync();
+ }
+ }
+
+ public MongoDBContext Context { get; set; }
+
+ protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(ConnectionStrings.CosmosMongo);
+
+ protected override async Task GetEntityAsync(string id)
+ => await Context.Movies.Find(Builders.Filter.Eq(x => x.Id, id)).FirstOrDefaultAsync();
+
+ protected override async Task GetEntityCountAsync()
+ => (int)(await Context.Movies.CountDocumentsAsync(Builders.Filter.Empty));
+
+ protected override Task> GetPopulatedRepositoryAsync()
+ => Task.FromResult>(new MongoDBRepository(Context.Movies));
+
+ protected override Task GetRandomEntityIdAsync(bool exists)
+ => Task.FromResult(exists ? this.movies[this.random.Next(this.movies.Count)].Id : Guid.NewGuid().ToString());
+ #endregion
+}
diff --git a/tests/CommunityToolkit.Datasync.Server.MongoDB.Test/MongoDBRepository_Tests.cs b/tests/CommunityToolkit.Datasync.Server.MongoDB.Test/MongoDBRepository_Tests.cs
new file mode 100644
index 0000000..6bb294b
--- /dev/null
+++ b/tests/CommunityToolkit.Datasync.Server.MongoDB.Test/MongoDBRepository_Tests.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Datasync.TestCommon;
+using CommunityToolkit.Datasync.TestCommon.Databases;
+using Microsoft.EntityFrameworkCore;
+using MongoDB.Bson;
+using MongoDB.Driver;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace CommunityToolkit.Datasync.Server.MongoDB.Test;
+
+[ExcludeFromCodeCoverage]
+public class MongoDBRepository_Tests(ITestOutputHelper output) : RepositoryTests(), IAsyncLifetime
+{
+ #region Setup
+ private readonly Random random = new();
+ private List movies = [];
+
+ public async Task InitializeAsync()
+ {
+ if (!string.IsNullOrEmpty(ConnectionStrings.MongoCommunity))
+ {
+ Context = await MongoDBContext.CreateContextAsync(ConnectionStrings.MongoCommunity, output);
+ this.movies = await Context.Movies.Find(new BsonDocument()).ToListAsync();
+ }
+ }
+
+ public async Task DisposeAsync()
+ {
+ if (Context is not null)
+ {
+ await Context.DisposeAsync();
+ }
+ }
+
+ public MongoDBContext Context { get; set; }
+
+ protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(ConnectionStrings.CosmosDb);
+
+ protected override async Task GetEntityAsync(string id)
+ => await Context.Movies.Find(Builders.Filter.Eq(x => x.Id, id)).FirstOrDefaultAsync();
+
+ protected override async Task GetEntityCountAsync()
+ => (int)(await Context.Movies.CountDocumentsAsync(Builders.Filter.Empty));
+
+ protected override Task> GetPopulatedRepositoryAsync()
+ => Task.FromResult>(new MongoDBRepository(Context.Movies));
+
+ protected override Task GetRandomEntityIdAsync(bool exists)
+ => Task.FromResult(exists ? this.movies[this.random.Next(this.movies.Count)].Id : Guid.NewGuid().ToString());
+ #endregion
+}
\ No newline at end of file
diff --git a/tests/CommunityToolkit.Datasync.Server.MongoDB.Test/MongoTableData_Tests.cs b/tests/CommunityToolkit.Datasync.Server.MongoDB.Test/MongoTableData_Tests.cs
new file mode 100644
index 0000000..ca81a34
--- /dev/null
+++ b/tests/CommunityToolkit.Datasync.Server.MongoDB.Test/MongoTableData_Tests.cs
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Datasync.Common.Test;
+using CommunityToolkit.Datasync.TestCommon;
+using FluentAssertions;
+using Xunit;
+
+namespace CommunityToolkit.Datasync.Server.MongoDB.Test;
+
+[ExcludeFromCodeCoverage]
+public class MongoTableData_Tests
+{
+ [Theory, ClassData(typeof(ITableData_TestData))]
+ public void Equals_Works(ITableData a, ITableData b, bool expected)
+ {
+ MongoTableData entity_a = a.ToTableEntity();
+ MongoTableData entity_b = b.ToTableEntity();
+
+ entity_a.Equals(entity_b).Should().Be(expected);
+ entity_b.Equals(entity_a).Should().Be(expected);
+
+ entity_a.Equals(null).Should().BeFalse();
+ entity_b.Equals(null).Should().BeFalse();
+ }
+}
diff --git a/tests/CommunityToolkit.Datasync.Server.Test/Helpers/LiveControllerTests.cs b/tests/CommunityToolkit.Datasync.Server.Test/Helpers/LiveControllerTests.cs
index cd434b4..102a137 100644
--- a/tests/CommunityToolkit.Datasync.Server.Test/Helpers/LiveControllerTests.cs
+++ b/tests/CommunityToolkit.Datasync.Server.Test/Helpers/LiveControllerTests.cs
@@ -174,6 +174,7 @@ public async Task Query_Test_003()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=((year div 1000.5) eq 2) and (rating eq 'R')",
@@ -203,6 +204,7 @@ public async Task Query_Test_005()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=(year div 1000.5) eq 2",
@@ -260,6 +262,7 @@ public async Task Query_Test_009()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=bestPictureWinner eq true and ceiling(duration div 60.0) eq 2",
@@ -275,6 +278,7 @@ public async Task Query_Test_010()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=bestPictureWinner eq true and floor(duration div 60.0) eq 2",
@@ -290,6 +294,7 @@ public async Task Query_Test_011()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=bestPictureWinner eq true and round(duration div 60.0) eq 2",
@@ -347,6 +352,7 @@ public async Task Query_Test_015()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=ceiling(duration div 60.0) eq 2",
@@ -362,6 +368,7 @@ public async Task Query_Test_016()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=day(releaseDate) eq 1",
@@ -433,6 +440,7 @@ public async Task Query_Test_021()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=floor(duration div 60.0) eq 2",
@@ -448,6 +456,7 @@ public async Task Query_Test_022()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=month(releaseDate) eq 11",
@@ -561,6 +570,7 @@ public async Task Query_Test_030()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=releaseDate eq cast(1994-10-14,Edm.Date)",
@@ -576,6 +586,7 @@ public async Task Query_Test_031()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=releaseDate ge cast(1999-12-31,Edm.Date)",
@@ -591,6 +602,7 @@ public async Task Query_Test_032()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=releaseDate gt cast(1999-12-31,Edm.Date)",
@@ -606,6 +618,7 @@ public async Task Query_Test_033()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=releaseDate le cast(2000-01-01,Edm.Date)",
@@ -621,6 +634,7 @@ public async Task Query_Test_034()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=releaseDate lt cast(2000-01-01,Edm.Date)",
@@ -636,6 +650,7 @@ public async Task Query_Test_035()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=round(duration div 60.0) eq 2",
@@ -777,6 +792,7 @@ public async Task Query_Test_046()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$filter=year(releaseDate) eq 1994",
@@ -1016,6 +1032,7 @@ public async Task Query_Test_063()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=((year div 1000.5) eq 2) and (rating eq 'R')",
@@ -1045,6 +1062,7 @@ public async Task Query_Test_065()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=(year div 1000.5) eq 2",
@@ -1102,6 +1120,7 @@ public async Task Query_Test_069()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=bestPictureWinner eq true and ceiling(duration div 60.0) eq 2",
@@ -1117,6 +1136,7 @@ public async Task Query_Test_070()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=bestPictureWinner eq true and floor(duration div 60.0) eq 2",
@@ -1132,6 +1152,7 @@ public async Task Query_Test_071()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=bestPictureWinner eq true and round(duration div 60.0) eq 2",
@@ -1189,6 +1210,7 @@ public async Task Query_Test_075()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=ceiling(duration div 60.0) eq 2",
@@ -1204,6 +1226,7 @@ public async Task Query_Test_076()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=day(releaseDate) eq 1",
@@ -1275,6 +1298,7 @@ public async Task Query_Test_081()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=floor(duration div 60.0) eq 2",
@@ -1290,6 +1314,7 @@ public async Task Query_Test_082()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=month(releaseDate) eq 11",
@@ -1403,6 +1428,7 @@ public async Task Query_Test_090()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=releaseDate eq cast(1994-10-14,Edm.Date)",
@@ -1418,6 +1444,7 @@ public async Task Query_Test_091()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=releaseDate ge cast(1999-12-31,Edm.Date)",
@@ -1433,6 +1460,7 @@ public async Task Query_Test_092()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=releaseDate gt cast(1999-12-31,Edm.Date)",
@@ -1448,6 +1476,7 @@ public async Task Query_Test_093()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=releaseDate le cast(2000-01-01,Edm.Date)",
@@ -1463,6 +1492,7 @@ public async Task Query_Test_094()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=releaseDate lt cast(2000-01-01,Edm.Date)",
@@ -1478,6 +1508,7 @@ public async Task Query_Test_095()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=round(duration div 60.0) eq 2",
@@ -1619,6 +1650,7 @@ public async Task Query_Test_106()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$count=true&$top=125&$filter=year(releaseDate) eq 1994",
@@ -1858,6 +1890,7 @@ public async Task Query_Test_123()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=((year div 1000.5) eq 2) and (rating eq 'R')",
@@ -1887,6 +1920,7 @@ public async Task Query_Test_125()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=(year div 1000.5) eq 2",
@@ -1944,6 +1978,7 @@ public async Task Query_Test_129()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=bestPictureWinner eq true and ceiling(duration div 60.0) eq 2",
@@ -1959,6 +1994,7 @@ public async Task Query_Test_130()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=bestPictureWinner eq true and floor(duration div 60.0) eq 2",
@@ -1974,6 +2010,7 @@ public async Task Query_Test_131()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=bestPictureWinner eq true and round(duration div 60.0) eq 2",
@@ -2031,6 +2068,7 @@ public async Task Query_Test_135()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=ceiling(duration div 60.0) eq 2",
@@ -2046,6 +2084,7 @@ public async Task Query_Test_136()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=day(releaseDate) eq 1",
@@ -2117,6 +2156,7 @@ public async Task Query_Test_141()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=floor(duration div 60.0) eq 2",
@@ -2132,6 +2172,7 @@ public async Task Query_Test_142()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=month(releaseDate) eq 11",
@@ -2245,6 +2286,7 @@ public async Task Query_Test_150()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=releaseDate eq cast(1994-10-14,Edm.Date)",
@@ -2260,6 +2302,7 @@ public async Task Query_Test_151()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=releaseDate ge cast(1999-12-31,Edm.Date)",
@@ -2275,6 +2318,7 @@ public async Task Query_Test_152()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=releaseDate gt cast(1999-12-31,Edm.Date)",
@@ -2290,6 +2334,7 @@ public async Task Query_Test_153()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=releaseDate le cast(2000-01-01,Edm.Date)",
@@ -2305,6 +2350,7 @@ public async Task Query_Test_154()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=releaseDate lt cast(2000-01-01,Edm.Date)",
@@ -2320,6 +2366,7 @@ public async Task Query_Test_155()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=round(duration div 60.0) eq 2",
@@ -2461,6 +2508,7 @@ public async Task Query_Test_166()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=year(releaseDate) eq 1994",
@@ -2700,6 +2748,7 @@ public async Task Query_Test_183()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=((year div 1000.5) eq 2) and (rating eq 'R')&$skip=5",
@@ -2728,6 +2777,7 @@ public async Task Query_Test_185()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=(year div 1000.5) eq 2&$skip=5",
@@ -2785,6 +2835,7 @@ public async Task Query_Test_189()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=bestPictureWinner eq true and ceiling(duration div 60.0) eq 2&$skip=5",
@@ -2800,6 +2851,7 @@ public async Task Query_Test_190()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=bestPictureWinner eq true and floor(duration div 60.0) eq 2&$skip=5",
@@ -2815,6 +2867,7 @@ public async Task Query_Test_191()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=bestPictureWinner eq true and round(duration div 60.0) eq 2&$skip=5",
@@ -2872,6 +2925,7 @@ public async Task Query_Test_195()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=ceiling(duration div 60.0) eq 2&$skip=5",
@@ -2887,6 +2941,7 @@ public async Task Query_Test_196()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=day(releaseDate) eq 1&$skip=5",
@@ -2958,6 +3013,7 @@ public async Task Query_Test_201()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=floor(duration div 60.0) eq 2&$skip=5",
@@ -2973,6 +3029,7 @@ public async Task Query_Test_202()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=month(releaseDate) eq 11&$skip=5",
@@ -3086,6 +3143,7 @@ public async Task Query_Test_210()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=releaseDate eq cast(1994-10-14,Edm.Date)&$skip=5",
@@ -3100,6 +3158,7 @@ public async Task Query_Test_211()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=releaseDate ge cast(1999-12-31,Edm.Date)&$skip=5",
@@ -3115,6 +3174,7 @@ public async Task Query_Test_212()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=releaseDate gt cast(1999-12-31,Edm.Date)&$skip=5",
@@ -3130,6 +3190,7 @@ public async Task Query_Test_213()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=releaseDate le cast(2000-01-01,Edm.Date)&$skip=5",
@@ -3145,6 +3206,7 @@ public async Task Query_Test_214()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=releaseDate lt cast(2000-01-01,Edm.Date)&$skip=5",
@@ -3160,6 +3222,7 @@ public async Task Query_Test_215()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=round(duration div 60.0) eq 2&$skip=5",
@@ -3300,6 +3363,7 @@ public async Task Query_Test_226()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$filter=year(releaseDate) eq 1994&$skip=5",
@@ -3594,6 +3658,7 @@ public async Task Query_Test_247()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=((year div 1000.5) eq 2) and (rating eq 'R')",
@@ -3623,6 +3688,7 @@ public async Task Query_Test_249()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=(year div 1000.5) eq 2",
@@ -3680,6 +3746,7 @@ public async Task Query_Test_253()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=bestPictureWinner eq true and ceiling(duration div 60.0) eq 2",
@@ -3695,6 +3762,7 @@ public async Task Query_Test_254()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=bestPictureWinner eq true and floor(duration div 60.0) eq 2",
@@ -3710,6 +3778,7 @@ public async Task Query_Test_255()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=bestPictureWinner eq true and round(duration div 60.0) eq 2",
@@ -3767,6 +3836,7 @@ public async Task Query_Test_259()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=ceiling(duration div 60.0) eq 2",
@@ -3782,6 +3852,7 @@ public async Task Query_Test_260()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=day(releaseDate) eq 1",
@@ -3853,6 +3924,7 @@ public async Task Query_Test_265()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Complex math is not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=floor(duration div 60.0) eq 2",
@@ -3868,6 +3940,7 @@ public async Task Query_Test_266()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=month(releaseDate) eq 11",
@@ -3981,6 +4054,7 @@ public async Task Query_Test_274()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=releaseDate eq cast(1994-10-14,Edm.Date)",
@@ -3996,6 +4070,7 @@ public async Task Query_Test_275()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=releaseDate ge cast(1999-12-31,Edm.Date)",
@@ -4011,6 +4086,7 @@ public async Task Query_Test_276()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=releaseDate gt cast(1999-12-31,Edm.Date)",
@@ -4026,6 +4102,7 @@ public async Task Query_Test_277()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=releaseDate le cast(2000-01-01,Edm.Date)",
@@ -4041,6 +4118,7 @@ public async Task Query_Test_278()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=releaseDate lt cast(2000-01-01,Edm.Date)",
@@ -4056,6 +4134,7 @@ public async Task Query_Test_279()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=round(duration div 60.0) eq 2",
@@ -4197,6 +4276,7 @@ public async Task Query_Test_290()
{
Skip.IfNot(CanRunLiveTests());
Skip.If(DriverName == "Cosmos", "Date components are not supported");
+ Skip.If(DriverName == "MongoDB", "Not supported by MongoDB");
await MovieQueryTest(
$"{MovieEndpoint}?$top=5&$filter=year(releaseDate) eq 1994",
diff --git a/tests/CommunityToolkit.Datasync.Server.Test/Helpers/LiveTestsCollection.cs b/tests/CommunityToolkit.Datasync.Server.Test/Helpers/LiveTestsCollection.cs
index d2c8f79..7273f90 100644
--- a/tests/CommunityToolkit.Datasync.Server.Test/Helpers/LiveTestsCollection.cs
+++ b/tests/CommunityToolkit.Datasync.Server.Test/Helpers/LiveTestsCollection.cs
@@ -12,6 +12,8 @@ public class DatabaseFixture
{
public bool AzureSqlIsInitialized { get; set; } = false;
public bool CosmosIsInitialized { get; set; } = false;
+ public bool CosmosMongoIsInitialized { get; set; } = false;
+ public bool MongoCommunityIsInitialized { get; set; } = false;
public bool MysqlIsInitialized { get; set; } = false;
public bool PgIsInitialized { get; set; } = false;
}
diff --git a/tests/CommunityToolkit.Datasync.Server.Test/Live/CosmosMongo_Controller_Tests.cs b/tests/CommunityToolkit.Datasync.Server.Test/Live/CosmosMongo_Controller_Tests.cs
new file mode 100644
index 0000000..29d2eb2
--- /dev/null
+++ b/tests/CommunityToolkit.Datasync.Server.Test/Live/CosmosMongo_Controller_Tests.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Datasync.Server.MongoDB;
+using CommunityToolkit.Datasync.Server.Test.Helpers;
+using CommunityToolkit.Datasync.TestCommon.Databases;
+using MongoDB.Bson;
+using MongoDB.Driver;
+using Xunit.Abstractions;
+
+namespace CommunityToolkit.Datasync.Server.Test.Live;
+
+[ExcludeFromCodeCoverage]
+[Collection("LiveTestsCollection")]
+public class CosmosMongo_Controller_Tests(DatabaseFixture fixture, ITestOutputHelper output) : LiveControllerTests(), IAsyncLifetime
+{
+ #region Setup
+ private readonly Random random = new();
+ private List movies = [];
+
+ public async Task InitializeAsync()
+ {
+ if (!string.IsNullOrEmpty(ConnectionStrings.CosmosMongo))
+ {
+ // Note: we don't clear entities on every run to speed up the test runs. This can only be done because
+ // the tests are read-only (associated with the query and get capabilities). If the test being run writes
+ // to the database then change clearEntities to true.
+ output.WriteLine($"CosmosMongoIsInitialized = {fixture.CosmosMongoIsInitialized}");
+ Context = await MongoDBContext.CreateContextAsync(ConnectionStrings.CosmosMongo, output, clearEntities: !fixture.CosmosMongoIsInitialized);
+ this.movies = await Context.Movies.Find(new BsonDocument()).ToListAsync();
+ fixture.CosmosMongoIsInitialized = true;
+ }
+ }
+
+ public async Task DisposeAsync()
+ {
+ if (Context is not null)
+ {
+ await Context.DisposeAsync();
+ }
+ }
+
+ private MongoDBContext Context { get; set; }
+
+ protected override string DriverName { get; } = "MongoDB";
+
+ protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(ConnectionStrings.CosmosMongo);
+
+ protected override async Task GetEntityAsync(string id)
+ => await Context.Movies.Find(Builders.Filter.Eq(x => x.Id, id)).FirstOrDefaultAsync();
+
+ protected override async Task GetEntityCountAsync()
+ => (int)(await Context.Movies.CountDocumentsAsync(Builders.Filter.Empty));
+
+ protected override Task> GetPopulatedRepositoryAsync()
+ => Task.FromResult>(new MongoDBRepository(Context.Movies));
+
+ protected override Task GetRandomEntityIdAsync(bool exists)
+ => Task.FromResult(exists ? this.movies[this.random.Next(this.movies.Count)].Id : Guid.NewGuid().ToString());
+ #endregion
+}
diff --git a/tests/CommunityToolkit.Datasync.Server.Test/Live/MongoDB_Controller_Tests.cs b/tests/CommunityToolkit.Datasync.Server.Test/Live/MongoDB_Controller_Tests.cs
new file mode 100644
index 0000000..69d9b09
--- /dev/null
+++ b/tests/CommunityToolkit.Datasync.Server.Test/Live/MongoDB_Controller_Tests.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Datasync.Server.MongoDB;
+using CommunityToolkit.Datasync.Server.Test.Helpers;
+using CommunityToolkit.Datasync.TestCommon.Databases;
+using MongoDB.Bson;
+using MongoDB.Driver;
+using Xunit.Abstractions;
+
+namespace CommunityToolkit.Datasync.Server.Test.Live;
+
+[ExcludeFromCodeCoverage]
+[Collection("LiveTestsCollection")]
+public class MongoDB_Controller_Tests(DatabaseFixture fixture, ITestOutputHelper output) : LiveControllerTests(), IAsyncLifetime
+{
+ #region Setup
+ private readonly Random random = new();
+ private List movies = [];
+
+ public async Task InitializeAsync()
+ {
+ if (!string.IsNullOrEmpty(ConnectionStrings.MongoCommunity))
+ {
+ // Note: we don't clear entities on every run to speed up the test runs. This can only be done because
+ // the tests are read-only (associated with the query and get capabilities). If the test being run writes
+ // to the database then change clearEntities to true.
+ output.WriteLine($"MongoCommunityIsInitialized = {fixture.MongoCommunityIsInitialized}");
+ Context = await MongoDBContext.CreateContextAsync(ConnectionStrings.MongoCommunity, output, clearEntities: !fixture.MongoCommunityIsInitialized);
+ this.movies = await Context.Movies.Find(new BsonDocument()).ToListAsync();
+ fixture.MongoCommunityIsInitialized = true;
+ }
+ }
+
+ public async Task DisposeAsync()
+ {
+ if (Context is not null)
+ {
+ await Context.DisposeAsync();
+ }
+ }
+
+ private MongoDBContext Context { get; set; }
+
+ protected override string DriverName { get; } = "MongoDB";
+
+ protected override bool CanRunLiveTests() => !string.IsNullOrEmpty(ConnectionStrings.MongoCommunity);
+
+ protected override async Task GetEntityAsync(string id)
+ => await Context.Movies.Find(Builders.Filter.Eq(x => x.Id, id)).FirstOrDefaultAsync();
+
+ protected override async Task GetEntityCountAsync()
+ => (int)(await Context.Movies.CountDocumentsAsync(Builders.Filter.Empty));
+
+ protected override Task> GetPopulatedRepositoryAsync()
+ => Task.FromResult>(new MongoDBRepository(Context.Movies));
+
+ protected override Task GetRandomEntityIdAsync(bool exists)
+ => Task.FromResult(exists ? this.movies[this.random.Next(this.movies.Count)].Id : Guid.NewGuid().ToString());
+ #endregion
+}
diff --git a/tests/CommunityToolkit.Datasync.TestCommon/CommunityToolkit.Datasync.TestCommon.csproj b/tests/CommunityToolkit.Datasync.TestCommon/CommunityToolkit.Datasync.TestCommon.csproj
index ed10564..6c0154d 100644
--- a/tests/CommunityToolkit.Datasync.TestCommon/CommunityToolkit.Datasync.TestCommon.csproj
+++ b/tests/CommunityToolkit.Datasync.TestCommon/CommunityToolkit.Datasync.TestCommon.csproj
@@ -27,5 +27,6 @@
+
diff --git a/tests/CommunityToolkit.Datasync.TestCommon/Databases/MongoDB/MongoDBContext.cs b/tests/CommunityToolkit.Datasync.TestCommon/Databases/MongoDB/MongoDBContext.cs
new file mode 100644
index 0000000..2acd0c8
--- /dev/null
+++ b/tests/CommunityToolkit.Datasync.TestCommon/Databases/MongoDB/MongoDBContext.cs
@@ -0,0 +1,148 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using MongoDB.Bson;
+using MongoDB.Driver;
+using MongoDB.Driver.Core.Events;
+using Xunit.Abstractions;
+
+namespace CommunityToolkit.Datasync.TestCommon.Databases;
+
+[ExcludeFromCodeCoverage]
+public class MongoDBContext(MongoClient client, IMongoDatabase database) : IAsyncDisposable
+{
+ public static async Task CreateContextAsync(string connectionString, ITestOutputHelper output, bool clearEntities = true)
+ {
+ MongoClientSettings settings = MongoClientSettings.FromConnectionString(connectionString);
+ if (output is not null)
+ {
+ settings.ClusterConfigurator = cb => cb.Subscribe(e => output.WriteLine($"{e.CommandName} - {e.Command.ToJson()}"));
+ }
+
+ MongoClient client = new(settings);
+ IMongoDatabase database = client.GetDatabase("unittests");
+ MongoDBContext context = new(client, database);
+
+ await context.InitializeDatabaseAsync(clearEntities);
+ await context.PopulateDatabaseAsync();
+
+ return context;
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ client.Dispose();
+ GC.SuppressFinalize(this);
+ return ValueTask.CompletedTask;
+ }
+
+ public MongoClient Client => client;
+ public IMongoDatabase Database => database;
+ public IMongoCollection Movies => database.GetCollection("movies");
+
+ public async Task InitializeDatabaseAsync(bool clearEntities)
+ {
+ if (clearEntities)
+ {
+ FilterDefinition filter = Builders.Filter.Empty;
+ await Movies.DeleteManyAsync(filter);
+ }
+ }
+
+ public async Task PopulateDatabaseAsync()
+ {
+ FilterDefinition filter = Builders.Filter.Empty;
+ bool hasEntities = await Movies.CountDocumentsAsync(filter) > 0;
+ if (hasEntities)
+ {
+ return;
+ }
+
+ // Create the indices required for all the tests.
+ //List> indices = [];
+ //string[] props = ["BestPictureWinner", "Duration", "Rating", "ReleaseDate", "Title", "Year", "UpdatedAt", "Deleted"];
+ //foreach (string prop in props)
+ //{
+ // indices.AddRange(GetCompoundIndexDefinitions(prop));
+ //}
+
+ //indices.AddRange(GetCompoundIndexDefinitions("UpdatedAt", "Deleted", includeId: false));
+ //indices.AddRange(GetCompoundIndexDefinitions("Title", "Year"));
+ //indices.AddRange(GetCompoundIndexDefinitions("Year", "Title"));
+ //await Movies.Indexes.CreateManyAsync(indices);
+
+ // Now populate the database with the test data, after the indices are defined.
+ foreach (MongoDBMovie movie in TestData.Movies.OfType())
+ {
+ movie.UpdatedAt = DateTimeOffset.UtcNow;
+ movie.Version = Guid.NewGuid().ToByteArray();
+ InsertOneOptions options = new();
+ await Movies.InsertOneAsync(movie, options);
+ }
+ }
+
+ private static IEnumerable> GetCompoundIndexDefinitions(string field)
+ {
+ return [
+ new CreateIndexModel(Builders.IndexKeys.Combine(
+ Builders.IndexKeys.Ascending(field),
+ Builders.IndexKeys.Ascending("_id")
+ )),
+ new CreateIndexModel(Builders.IndexKeys.Combine(
+ Builders.IndexKeys.Descending(field),
+ Builders.IndexKeys.Ascending("_id")
+ ))
+ ];
+ }
+
+ private static IEnumerable> GetCompoundIndexDefinitions(string field1, string field2, bool includeId = true)
+ {
+ if (includeId)
+ {
+ return [
+ new CreateIndexModel(Builders.IndexKeys.Combine(
+ Builders.IndexKeys.Ascending(field1),
+ Builders.IndexKeys.Ascending(field2),
+ Builders.IndexKeys.Ascending("_id")
+ )),
+ new CreateIndexModel(Builders.IndexKeys.Combine(
+ Builders.IndexKeys.Ascending(field1),
+ Builders.IndexKeys.Descending(field2),
+ Builders.IndexKeys.Ascending("_id")
+ )),
+ new CreateIndexModel(Builders.IndexKeys.Combine(
+ Builders.IndexKeys.Descending(field1),
+ Builders.IndexKeys.Ascending(field2),
+ Builders.IndexKeys.Ascending("_id")
+ )),
+ new CreateIndexModel(Builders.IndexKeys.Combine(
+ Builders.IndexKeys.Descending(field1),
+ Builders.IndexKeys.Descending(field2),
+ Builders.IndexKeys.Ascending("_id")
+ )),
+ ];
+ }
+ else
+ {
+ return [
+ new CreateIndexModel(Builders.IndexKeys.Combine(
+ Builders.IndexKeys.Ascending(field1),
+ Builders.IndexKeys.Ascending(field2)
+ )),
+ new CreateIndexModel(Builders.IndexKeys.Combine(
+ Builders.IndexKeys.Ascending(field1),
+ Builders.IndexKeys.Descending(field2)
+ )),
+ new CreateIndexModel(Builders.IndexKeys.Combine(
+ Builders.IndexKeys.Descending(field1),
+ Builders.IndexKeys.Ascending(field2)
+ )),
+ new CreateIndexModel(Builders.IndexKeys.Combine(
+ Builders.IndexKeys.Descending(field1),
+ Builders.IndexKeys.Descending(field2)
+ )),
+ ];
+ }
+ }
+}
diff --git a/tests/CommunityToolkit.Datasync.TestCommon/Databases/MongoDB/MongoDBMovie.cs b/tests/CommunityToolkit.Datasync.TestCommon/Databases/MongoDB/MongoDBMovie.cs
new file mode 100644
index 0000000..5b38841
--- /dev/null
+++ b/tests/CommunityToolkit.Datasync.TestCommon/Databases/MongoDB/MongoDBMovie.cs
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.Datasync.Server.MongoDB;
+using CommunityToolkit.Datasync.TestCommon.Models;
+using System.ComponentModel.DataAnnotations;
+
+namespace CommunityToolkit.Datasync.TestCommon.Databases;
+
+[ExcludeFromCodeCoverage]
+public class MongoDBMovie : MongoTableData, IMovie, IEquatable
+{
+ ///
+ /// True if the movie won the oscar for Best Picture
+ ///
+ public bool BestPictureWinner { get; set; }
+
+ ///
+ /// The running time of the movie
+ ///
+ [Required]
+ [Range(60, 360)]
+ public int Duration { get; set; }
+
+ ///
+ /// The MPAA rating for the movie, if available.
+ ///
+ public MovieRating Rating { get; set; } = MovieRating.Unrated;
+
+ ///
+ /// The release date of the movie.
+ ///
+ [Required]
+ public DateOnly ReleaseDate { get; set; }
+
+ ///
+ /// The title of the movie.
+ ///
+ [Required]
+ [StringLength(128, MinimumLength = 2)]
+ public string Title { get; set; } = string.Empty;
+
+ ///
+ /// The year that the movie was released.
+ ///
+ [Required]
+ [Range(1920, 2030)]
+ public int Year { get; set; }
+
+ ///
+ /// Determines if this movie has the same content as another movie.
+ ///
+ /// The other movie
+ /// true if the content is the same
+ public bool Equals(IMovie other)
+ => other != null
+ && other.BestPictureWinner == BestPictureWinner
+ && other.Duration == Duration
+ && other.Rating == Rating
+ && other.ReleaseDate == ReleaseDate
+ && other.Title == Title
+ && other.Year == Year;
+}
diff --git a/tests/CommunityToolkit.Datasync.TestCommon/RepositoryTests.cs b/tests/CommunityToolkit.Datasync.TestCommon/RepositoryTests.cs
index ded1444..8aa0917 100644
--- a/tests/CommunityToolkit.Datasync.TestCommon/RepositoryTests.cs
+++ b/tests/CommunityToolkit.Datasync.TestCommon/RepositoryTests.cs
@@ -73,7 +73,28 @@ public async Task AsQueryableAsync_CanRetrieveFilteredLists()
IRepository Repository = await GetPopulatedRepositoryAsync();
int expected = TestData.Movies.Count(m => m.Rating == MovieRating.R);
IQueryable queryable = await Repository.AsQueryableAsync();
- IList actual = await Repository.ToListAsync(queryable.Where(m => m.Rating == MovieRating.R));
+ IQueryable sut = queryable
+ .Where(m => m.Rating == MovieRating.R);
+ IList actual = await Repository.ToListAsync(sut);
+
+ actual.Should().HaveCount(expected);
+ }
+
+ [SkippableFact]
+ public async Task AsQueryableAsync_CanRetrieveOrderedLists()
+ {
+ Skip.IfNot(CanRunLiveTests());
+
+ IRepository Repository = await GetPopulatedRepositoryAsync();
+ int expected = TestData.Movies.Count();
+ IQueryable queryable = await Repository.AsQueryableAsync();
+
+ // We pick this set of orderings because we create a CosmosDB composite index for these already.
+ IQueryable sut = queryable
+ .OrderBy(m => m.ReleaseDate)
+ .ThenBy(m => m.Id);
+
+ IList actual = await Repository.ToListAsync(sut);
actual.Should().HaveCount(expected);
}
@@ -85,7 +106,11 @@ public async Task AsQueryableAsync_CanUseTopAndSkip()
IRepository Repository = await GetPopulatedRepositoryAsync();
IQueryable queryable = await Repository.AsQueryableAsync();
- IList actual = await Repository.ToListAsync(queryable.Where(m => m.Rating == MovieRating.R).Skip(5).Take(20));
+ IQueryable sut = queryable
+ .Where(m => m.Rating == MovieRating.R)
+ .Skip(5)
+ .Take(20);
+ IList actual = await Repository.ToListAsync(sut);
actual.Should().HaveCount(20);
}
@@ -100,7 +125,12 @@ public async Task AsQueryableAsync_CanRetrievePagedDatasyncQuery()
IRepository Repository = await GetPopulatedRepositoryAsync();
IQueryable queryable = await Repository.AsQueryableAsync();
- IList actual = await Repository.ToListAsync(queryable.Where(m => m.UpdatedAt > DateTimeOffset.UnixEpoch && !m.Deleted).OrderBy(m => m.UpdatedAt).Skip(10).Take(10));
+ IQueryable sut = queryable
+ .Where(m => m.UpdatedAt > DateTimeOffset.UnixEpoch && !m.Deleted)
+ .OrderBy(m => m.UpdatedAt)
+ .Skip(10)
+ .Take(10);
+ IList actual = await Repository.ToListAsync(sut);
actual.Should().HaveCount(10);
}