public async Task Should_execute_operations_with_same_partition_key_together()
        {
            var fakeContainer    = new FakeContainer();
            var fakeCosmosClient = new FakeCosmosClient(fakeContainer);
            var containerHolderHolderResolver = new ContainerHolderResolver(new FakeProvider(fakeCosmosClient),
                                                                            new ContainerInformation("fakeContainer", new PartitionKeyPath("/deep/down")), "fakeDatabase");

            var storageSession = new StorageSession(containerHolderHolderResolver, new ContextBag(), true);
            var firstOperation = new FakeOperation
            {
                PartitionKey = new PartitionKey("PartitionKey1")
            };

            storageSession.AddOperation(firstOperation);
            var secondOperation = new FakeOperation
            {
                PartitionKey = new PartitionKey("PartitionKey1")
            };

            storageSession.AddOperation(secondOperation);

            await((ICompletableSynchronizedStorageSession)storageSession).CompleteAsync();

            Assert.That(firstOperation.WasApplied, Is.True);
            Assert.That(secondOperation.WasApplied, Is.True);
            Assert.That(firstOperation.AppliedBatch, Is.EqualTo(secondOperation.AppliedBatch), "Operations with the same partition key must be in the same batch");
        }
        public async Task Should_not_dispose_release_operations_when_operations_not_successful()
        {
            var fakeContainer    = new FakeContainer();
            var fakeCosmosClient = new FakeCosmosClient(fakeContainer);
            var containerHolderHolderResolver = new ContainerHolderResolver(new FakeProvider(fakeCosmosClient),
                                                                            new ContainerInformation("fakeContainer", new PartitionKeyPath("/deep/down")), "fakeDatabase");

            var storageSession = new StorageSession(containerHolderHolderResolver, new ContextBag(), true);
            var operation      = new ThrowOnApplyOperation
            {
                PartitionKey = new PartitionKey("PartitionKey1")
            };

            storageSession.AddOperation(operation);
            var releaseOperation = new ReleaseLockOperation
            {
                PartitionKey = new PartitionKey("PartitionKey1")
            };

            storageSession.AddOperation(releaseOperation);

            try
            {
                await((ICompletableSynchronizedStorageSession)storageSession).CompleteAsync();
            }
            catch
            {
                // ignored
            }

            Assert.That(releaseOperation.WasApplied, Is.False);
            Assert.That(releaseOperation.WasDisposed, Is.False);
        }
        public void Should_execute_and_dispose_release_operations_with_different_partition_key_distinct_when_not_completed()
        {
            var fakeContainer    = new FakeContainer();
            var fakeCosmosClient = new FakeCosmosClient(fakeContainer);
            var containerHolderHolderResolver = new ContainerHolderResolver(new FakeProvider(fakeCosmosClient),
                                                                            new ContainerInformation("fakeContainer", new PartitionKeyPath("/deep/down")), "fakeDatabase");

            var storageSession        = new StorageSession(containerHolderHolderResolver, new ContextBag(), true);
            var firstReleaseOperation = new ReleaseLockOperation
            {
                PartitionKey = new PartitionKey("PartitionKey1")
            };

            storageSession.AddOperation(firstReleaseOperation);

            var secondReleaseOperation = new ReleaseLockOperation
            {
                PartitionKey = new PartitionKey("PartitionKey2")
            };

            storageSession.AddOperation(secondReleaseOperation);

            storageSession.Dispose();

            Assert.That(firstReleaseOperation.WasApplied, Is.True);
            Assert.That(secondReleaseOperation.WasApplied, Is.True);
            Assert.That(firstReleaseOperation.WasDisposed, Is.True);
            Assert.That(secondReleaseOperation.WasDisposed, Is.True);
            Assert.That(firstReleaseOperation.AppliedBatch, Is.Not.EqualTo(secondReleaseOperation.AppliedBatch), "Release operations with the different partition keys must be in different batches.");
        }
        public void Should_execute_and_dispose_release_operations_as_best_effort()
        {
            var fakeContainer = new FakeContainer
            {
                TransactionalBatchFactory = () => new ThrowsOnExecuteAsyncTransactionalBatch()
            };
            var fakeCosmosClient = new FakeCosmosClient(fakeContainer);
            var containerHolderHolderResolver = new ContainerHolderResolver(new FakeProvider(fakeCosmosClient),
                                                                            new ContainerInformation("fakeContainer", new PartitionKeyPath("/deep/down")), "fakeDatabase");

            var storageSession        = new StorageSession(containerHolderHolderResolver, new ContextBag(), true);
            var firstReleaseOperation = new ReleaseLockOperation
            {
                PartitionKey = new PartitionKey("PartitionKey1")
            };

            storageSession.AddOperation(firstReleaseOperation);

            var secondReleaseOperation = new ReleaseLockOperation
            {
                PartitionKey = new PartitionKey("PartitionKey2")
            };

            storageSession.AddOperation(secondReleaseOperation);

            Assert.DoesNotThrow(() => storageSession.Dispose());

            Assert.That(firstReleaseOperation.WasApplied, Is.True);
            Assert.That(secondReleaseOperation.WasApplied, Is.True);
            Assert.That(firstReleaseOperation.WasDisposed, Is.True);
            Assert.That(secondReleaseOperation.WasDisposed, Is.True);
        }
        public void Should_throw_when_no_container_available()
        {
            var fakeCosmosClient = new FakeCosmosClient(null);
            var containerHolderHolderResolver = new ContainerHolderResolver(new FakeProvider(fakeCosmosClient), null, "fakeDatabase");

            var storageSession = new StorageSession(containerHolderHolderResolver, new ContextBag(), true);
            var operation      = new FakeOperation();

            storageSession.AddOperation(operation);

            var exception = Assert.ThrowsAsync <Exception>(async() => await((ICompletableSynchronizedStorageSession)storageSession).CompleteAsync());

            Assert.That(exception.Message, Is.EqualTo("Unable to retrieve the container name and the partition key during processing. Make sure that either `persistence.Container()` is used or the relevant container information is available on the message handling pipeline."));
        }
        public async Task Should_clear_added_pending_operations_and_restore_ones_from_outbox_record()
        {
            var messageId = Guid.NewGuid().ToString();

            var transportOperations = new[]
            {
                new TransportOperation(
                    messageId: "42",
                    properties: new DispatchProperties {
                    { "Destination", "somewhere" }
                },
                    body: Array.Empty <byte>(),
                    headers: new Dictionary <string, string>()),
            };

            var fakeCosmosClient = new FakeCosmosClient(new FakeContainer
            {
                ReadItemStreamOutboxRecord = (id, key) => new OutboxRecord
                {
                    Dispatched          = false,
                    Id                  = messageId,
                    TransportOperations = transportOperations.Select(op => new StorageTransportOperation(op))
                                          .ToArray()
                }
            });

            var containerHolderHolderResolver = new ContainerHolderResolver(new FakeProvider(fakeCosmosClient),
                                                                            new ContainerInformation("fakeContainer", new PartitionKeyPath("")), "fakeDatabase");

            var behavior = new OutboxBehavior(containerHolderHolderResolver, new JsonSerializer());

            var testableContext = new TestableIncomingLogicalMessageContext();

            testableContext.Extensions.Set(new PartitionKey(""));
            testableContext.Extensions.Set(new SetAsDispatchedHolder());

            testableContext.Extensions.Set <IOutboxTransaction>(new CosmosOutboxTransaction(containerHolderHolderResolver, testableContext.Extensions));

            var pendingTransportOperations = new PendingTransportOperations();

            pendingTransportOperations.Add(new Transport.TransportOperation(new OutgoingMessage(null, null, null), null));
            testableContext.Extensions.Set(pendingTransportOperations);

            await behavior.Invoke(testableContext, c => Task.CompletedTask);

            Assert.IsTrue(pendingTransportOperations.HasOperations, "Should have exactly one operation added found on the outbox record");
            Assert.AreEqual("42", pendingTransportOperations.Operations.ElementAt(0).Message.MessageId, "Should have exactly one operation added found on the outbox record");
        }
        public void Should_dispose_operations()
        {
            var fakeCosmosClient = new FakeCosmosClient(null);
            var containerHolderHolderResolver = new ContainerHolderResolver(new FakeProvider(fakeCosmosClient), null, "fakeDatabase");

            var storageSession = new StorageSession(containerHolderHolderResolver, new ContextBag(), true);
            var firstOperation = new FakeOperation();

            storageSession.AddOperation(firstOperation);
            var secondOperation = new FakeOperation();

            storageSession.AddOperation(secondOperation);

            storageSession.Dispose();

            Assert.That(firstOperation.WasDisposed, Is.True);
            Assert.That(firstOperation.WasApplied, Is.False);
            Assert.That(secondOperation.WasDisposed, Is.True);
            Assert.That(secondOperation.WasApplied, Is.False);
        }
        public async Task Should_not_complete_when_marked_as_do_not_complete()
        {
            var fakeCosmosClient = new FakeCosmosClient(null);
            var containerHolderHolderResolver = new ContainerHolderResolver(new FakeProvider(fakeCosmosClient),
                                                                            new ContainerInformation("fakeContainer", new PartitionKeyPath("")), "fakeDatabase");

            var storageSession = new StorageSession(containerHolderHolderResolver, new ContextBag(), false);
            var firstOperation = new FakeOperation();

            storageSession.AddOperation(firstOperation);
            var secondOperation = new FakeOperation();

            storageSession.AddOperation(secondOperation);

            await((ICompletableSynchronizedStorageSession)storageSession).CompleteAsync();

            Assert.That(firstOperation.WasDisposed, Is.False);
            Assert.That(firstOperation.WasApplied, Is.False);
            Assert.That(secondOperation.WasDisposed, Is.False);
            Assert.That(secondOperation.WasApplied, Is.False);
        }
        public Task Configure(CancellationToken cancellationToken = default)
        {
            // with this we have a partition key per run which makes things naturally isolated
            partitionKey = Guid.NewGuid().ToString();

            var serializer = new JsonSerializer
            {
                ContractResolver = new UpperCaseIdIntoLowerCaseIdContractResolver(),
                Converters       = { new ReadOnlyMemoryConverter() }
            };

            var persistenceConfiguration = (PersistenceConfiguration)Variant.Values[0];

            var sagaPersistenceConfiguration = new SagaPersistenceConfiguration();

            if (persistenceConfiguration.UsePessimisticLocking)
            {
                var pessimisticLockingConfiguration = sagaPersistenceConfiguration.UsePessimisticLocking();
                if (SessionTimeout.HasValue)
                {
                    pessimisticLockingConfiguration.SetLeaseLockAcquisitionTimeout(SessionTimeout.Value);
                }
                SupportsPessimisticConcurrency = true;
            }

            var partitionKeyPath = new PartitionKeyPath(SetupFixture.PartitionPathKey);
            var resolver         = new ContainerHolderResolver(this, new ContainerInformation(SetupFixture.ContainerName, partitionKeyPath), SetupFixture.DatabaseName);

            SynchronizedStorage = new StorageSessionFactory(resolver, null);
            SagaStorage         = new SagaPersister(serializer, sagaPersistenceConfiguration);
            OutboxStorage       = new OutboxPersister(resolver, serializer, OutboxTimeToLiveInSeconds);

            GetContextBagForSagaStorage = () =>
            {
                var contextBag = new ContextBag();
                // This populates the partition key required to participate in a shared transaction
                var setAsDispatchedHolder = new SetAsDispatchedHolder
                {
                    PartitionKey    = new PartitionKey(partitionKey),
                    ContainerHolder = resolver.ResolveAndSetIfAvailable(contextBag)
                };
                contextBag.Set(setAsDispatchedHolder);
                contextBag.Set(new PartitionKey(partitionKey));
                return(contextBag);
            };

            GetContextBagForOutbox = () =>
            {
                var contextBag = new ContextBag();
                // This populates the partition key required to participate in a shared transaction
                var setAsDispatchedHolder = new SetAsDispatchedHolder
                {
                    PartitionKey    = new PartitionKey(partitionKey),
                    ContainerHolder = resolver.ResolveAndSetIfAvailable(contextBag)
                };
                contextBag.Set(setAsDispatchedHolder);
                contextBag.Set(new PartitionKey(partitionKey));
                return(contextBag);
            };

            return(Task.CompletedTask);
        }