using OpenNest.Data; namespace OpenNest.Tests.Data; public class NestSaveSessionTests { [Fact] public async Task Save_CreatesThenUpdatesAndSaveCopyCreatesNewRecord() { var repository = new FakeRepository(); var session = new NestSaveSession(); var nest = new Nest("Shop job"); var bytes = new byte[] { 1, 2, 3 }; await session.SaveAsync(repository, "http://shop:8090/", nest, bytes); var firstId = session.RemoteId; Assert.NotEqual(Guid.Empty, firstId); Assert.Equal(3, repository.LastRecord!.FileSize); Assert.Equal("Shop job", repository.LastRecord.Name); Assert.Equal(1, repository.Uploads); await session.SaveAsync(repository, "http://shop:8090", nest, bytes); Assert.Equal(firstId, repository.UpdatedId); Assert.Equal(1, repository.Updates); Assert.Equal(1, repository.ExpectedRevision); Assert.Equal(2, session.Revision); await session.SaveAsync(repository, "http://shop:8090", nest, bytes, saveCopy: true); Assert.NotEqual(firstId, session.RemoteId); Assert.Equal(2, repository.Uploads); } [Fact] public async Task FailedSaveAndServerSwitch_NeverOverwritePriorIdentity() { var repository = new FakeRepository(); var session = new NestSaveSession(); var oldId = Guid.NewGuid(); session.Bind(oldId, "http://old:8090"); repository.Fail = true; await Assert.ThrowsAsync(() => session.SaveAsync(repository, "http://new:8090", new Nest("Job"), [1])); Assert.Equal(oldId, session.RemoteId); Assert.Equal("http://old:8090", session.ServerUrl); Assert.NotEqual(Guid.Empty, repository.LastRecord!.Id); Assert.Equal(session.PendingCreateId, repository.LastRecord.Id); Assert.Equal(0, repository.Updates); repository.Fail = false; Assert.Null(await session.ReconcilePendingCreateAsync(repository, "http://new:8090")); await session.SaveAsync(repository, "http://new:8090", new Nest("Job"), [1]); Assert.NotEqual(oldId, session.RemoteId); Assert.Equal("http://new:8090", session.ServerUrl); } [Fact] public async Task FailedUpdate_RetainsIdServerAndExpectedRevision_NoAutomaticRetry() { var repository = new FakeRepository { Fail = true }; var session = new NestSaveSession(); var id = Guid.NewGuid(); session.Bind(id, "http://shop/", 4); await Assert.ThrowsAsync(() => session.SaveAsync(repository, "http://shop", new Nest("Job"), [1])); Assert.Equal(id, session.RemoteId); Assert.Equal("http://shop", session.ServerUrl); Assert.Equal(4, session.Revision); Assert.Equal(4, repository.ExpectedRevision); Assert.Equal(1, repository.Updates); Assert.True(session.RequiresReload); await Assert.ThrowsAsync(() => session.SaveAsync(repository, "http://shop", new Nest("Job"), [1])); Assert.Equal(1, repository.Updates); session.Bind(id, "http://shop", 5); Assert.False(session.RequiresReload); } [Fact] public void LegacyBind_DoesNotFabricateARevision() { var session = new NestSaveSession(); session.Bind(Guid.NewGuid(), "http://shop", 5); session.Bind(Guid.NewGuid(), "http://shop"); Assert.Equal(0, session.Revision); } private sealed class FakeRepository : INestRepository { public int Uploads { get; private set; } public int Updates { get; private set; } public Guid UpdatedId { get; private set; } public NestRecord? LastRecord { get; private set; } public bool Fail { get; set; } public int ExpectedRevision { get; private set; } public Task UploadAsync(byte[] file, NestRecord record, CancellationToken ct = default) { Uploads++; LastRecord = record; if (Fail) throw new IOException("Offline"); if (record.Id == Guid.Empty) record.Id = Guid.NewGuid(); record.Revision = 1; return Task.FromResult(record); } public Task UpdateFileAsync(Guid id, byte[] file, NestRecord record, int expectedRevision, CancellationToken ct = default) { ExpectedRevision = expectedRevision; record.Revision = expectedRevision + 1; return UpdateFileAsync(id, file, record, ct); } public Task UpdateFileAsync(Guid id, byte[] file, NestRecord record, CancellationToken ct = default) { Updates++; UpdatedId = id; LastRecord = record; if (Fail) throw new IOException("Offline"); return Task.FromResult(record); } public Task> ListAsync(CancellationToken ct = default) => throw new NotImplementedException(); public Task QueryAsync(NestQuery query, CancellationToken ct = default) => throw new NotImplementedException(); public Task GetMetadataAsync(Guid id, CancellationToken ct = default) => Task.FromResult(null); public Task GetFileAsync(Guid id, CancellationToken ct = default) => throw new NotImplementedException(); public Task UpdateMetadataAsync(Guid id, NestRecord record, CancellationToken ct = default) => throw new NotImplementedException(); public Task DeleteAsync(Guid id, CancellationToken ct = default) => throw new NotImplementedException(); } }