Files
Abdullah Atta 99536de56d api: create MongoDB indexes at startup
Self-hosted and new deployments got none of the API's indexes, and without unique ones concurrent upserts can duplicate sync items. A background service creates any missing index in the notesnook database, including unique UserId + ItemId on sync collections and unique sync_devices and user_settings keys. Existing indexes are never changed: one with the same name but a different definition is logged. Each index is created on its own, so existing duplicates only skip that index, and no failure stops the host.
2026-10-22 13:13:09 +05:00

68 lines
2.6 KiB
C#

using Microsoft.Extensions.Logging.Abstractions;
using MongoDB.Bson;
using MongoDB.Driver;
using Notesnook.API.Services;
namespace Notesnook.API.Tests;
public class MongoIndexesTests(TestDatabase db) : IClassFixture<TestDatabase>
{
private readonly MongoIndexesHostedService service = new(db.Client, NullLogger<MongoIndexesHostedService>.Instance);
private IMongoDatabase Database => db.Client.GetDatabase(db.Name);
private static async Task<BsonDocument?> IndexAsync(IMongoDatabase database, string collection, string name)
{
var indexes = await (await database.GetCollection<BsonDocument>(collection).Indexes.ListAsync()).ToListAsync();
return indexes.FirstOrDefault((index) => index["name"] == name);
}
[Fact]
public async Task Creates_unique_sync_indexes_and_is_idempotent()
{
await service.EnsureIndexesAsync(Database, CancellationToken.None);
await service.EnsureIndexesAsync(Database, CancellationToken.None);
var index = await IndexAsync(Database, "content", "UserId_1_ItemId_1");
Assert.NotNull(index);
Assert.True(index["unique"].ToBoolean());
Assert.NotNull(await IndexAsync(Database, "sync_devices", "UserId_1_DeviceId_1"));
}
[Fact]
public async Task Existing_duplicates_dont_stop_the_other_indexes()
{
var database = db.Client.GetDatabase(db.Name + "_duplicates");
try
{
await database.GetCollection<BsonDocument>("tags").InsertManyAsync(
[new BsonDocument { ["UserId"] = "u", ["ItemId"] = "i" }, new BsonDocument { ["UserId"] = "u", ["ItemId"] = "i" }]);
await service.EnsureIndexesAsync(database, CancellationToken.None);
Assert.Null(await IndexAsync(database, "tags", "UserId_1_ItemId_1"));
Assert.NotNull(await IndexAsync(database, "tags", "UserId_1__id_1"));
Assert.NotNull(await IndexAsync(database, "vaults", "UserId_1_ItemId_1"));
}
finally
{
await db.Client.DropDatabaseAsync(database.DatabaseNamespace.DatabaseName);
}
}
[Fact]
public async Task An_unreachable_database_doesnt_stop_the_host()
{
var unreachable = new MongoClient(new MongoClientSettings
{
Server = new MongoServerAddress("127.0.0.1", 1),
ServerSelectionTimeout = TimeSpan.FromMilliseconds(200)
});
using var hosted = new MongoIndexesHostedService(unreachable, NullLogger<MongoIndexesHostedService>.Instance);
await hosted.StartAsync(CancellationToken.None);
await hosted.ExecuteTask!;
Assert.True(hosted.ExecuteTask.IsCompletedSuccessfully);
}
}