From 2216d83046548fb14e4981ae415d74df463bf448 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Thu, 1 Aug 2024 10:26:54 -0400 Subject: [PATCH 01/15] checkin in --- .../Resource/FullFidelity/ChangeFeedItem.cs | 5 - .../FullFidelity/ChangeFeedMetadata.cs | 46 +- .../AllVersionsAndDeletes/BuilderTests.cs} | 121 +-- .../BuilderWithCustomSerializerTests.cs | 690 ++++++++++++++++++ .../FeedToken/ChangeFeedIteratorCoreTests.cs | 52 +- .../Contracts/DotNetPreviewSDKAPI.json | 86 +-- 6 files changed, 840 insertions(+), 160 deletions(-) rename Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/{ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs => CFP/AllVersionsAndDeletes/BuilderTests.cs} (85%) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedItem.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedItem.cs index fc0c04ffd0..3794ccbccf 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedItem.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedItem.cs @@ -4,8 +4,6 @@ namespace Microsoft.Azure.Cosmos { - using Newtonsoft.Json; - /// /// The typed response that contains the current, previous, and metadata change feed resource when is initialized to . /// @@ -58,19 +56,16 @@ class ChangeFeedItem /// /// The full fidelity change feed current item. /// - [JsonProperty(PropertyName = "current")] public T Current { get; set; } /// /// The full fidelity change feed metadata. /// - [JsonProperty(PropertyName = "metadata", NullValueHandling = NullValueHandling.Ignore)] public ChangeFeedMetadata Metadata { get; set; } /// /// For delete operations, previous image is always going to be provided. The previous image on replace operations is not going to be exposed by default and requires account-level or container-level opt-in. /// - [JsonProperty(PropertyName = "previous", NullValueHandling = NullValueHandling.Ignore)] public T Previous { get; set; } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs index 38bf63c79b..ead97a348b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs @@ -4,11 +4,6 @@ namespace Microsoft.Azure.Cosmos { - using System; - using Microsoft.Azure.Documents; - using Newtonsoft.Json; - using Newtonsoft.Json.Converters; - /// /// The metadata of a change feed resource with is initialized to . /// @@ -16,39 +11,54 @@ namespace Microsoft.Azure.Cosmos public #else internal -#endif +#endif class ChangeFeedMetadata { + /// + /// New instance of meta data for created. + /// + /// + /// + /// + /// + /// + public ChangeFeedMetadata( + long crts, + long lsn, + ChangeFeedOperationType operationType, + long previousImageLSN, + bool timeToLiveExpired) + { + this.Crts = crts; + this.Lsn = lsn; + this.OperationType = operationType; + this.PreviousImageLSN = previousImageLSN; + this.TimeToLiveExpired = timeToLiveExpired; + } + /// /// The conflict resolution timestamp. /// - [JsonProperty(PropertyName = "crts", NullValueHandling = NullValueHandling.Ignore)] - [JsonConverter(typeof(UnixDateTimeConverter))] - public DateTime ConflictResolutionTimestamp { get; internal set; } + public long Crts { get; } /// /// The current logical sequence number. /// - [JsonProperty(PropertyName = "lsn", NullValueHandling = NullValueHandling.Ignore)] - public long Lsn { get; internal set; } + public long Lsn { get; } /// /// The change feed operation type. /// - [JsonProperty(PropertyName = "operationType")] - [JsonConverter(typeof(StringEnumConverter))] - public ChangeFeedOperationType OperationType { get; internal set; } + public ChangeFeedOperationType OperationType { get; } /// /// The previous logical sequence number. /// - [JsonProperty(PropertyName = "previousImageLSN", NullValueHandling = NullValueHandling.Ignore)] - public long PreviousLsn { get; internal set; } + public long PreviousImageLSN { get; } /// /// Used to distinquish explicit deletes (e.g. via DeleteItem) from deletes caused by TTL expiration (a collection may define time-to-live policy for documents). /// - [JsonProperty(PropertyName = "timeToLiveExpired", NullValueHandling= NullValueHandling.Ignore)] - public bool IsTimeToLiveExpired { get; internal set; } + public bool TimeToLiveExpired { get; } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs similarity index 85% rename from Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs rename to Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs index 0479bbb353..13fe98bacf 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs @@ -2,7 +2,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ -namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.CFP.AllVersionsAndDeletes { using System; using System.Collections.Generic; @@ -10,26 +10,28 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System.Linq; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos; using Microsoft.Azure.Cosmos.ChangeFeed.Utils; + using Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed; using Microsoft.Azure.Cosmos.Services.Management.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using Newtonsoft.Json.Linq; [TestClass] - [TestCategory("ChangeFeedProcessor")] - public class GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests : BaseChangeFeedClientHelper + [TestCategory("ChangeFeedProcessor.AllVersionsAndDeletes")] + public class BuilderTests : BaseChangeFeedClientHelper { [TestInitialize] public async Task TestInitialize() { - await base.ChangeFeedTestInit(); + await this.ChangeFeedTestInit(); } [TestCleanup] public async Task Cleanup() { - await base.TestCleanup(); + await this.TestCleanup(); } [TestMethod] @@ -47,14 +49,13 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA ManualResetEvent allDocsProcessed = new ManualResetEvent(false); ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => { // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. Logger.LogLine($"@ {DateTime.Now}, {nameof(stopwatch)} -> CFP AVAD took '{stopwatch.ElapsedMilliseconds}' to read document CRUD in feed."); - Logger.LogLine($"@ {DateTime.Now}, {nameof(docs)} -> {JsonConvert.SerializeObject(docs)}"); - foreach (ChangeFeedItem change in docs) + foreach (ChangeFeedItem change in docs) { if (change.Metadata.OperationType == ChangeFeedOperationType.Create) { @@ -62,12 +63,12 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA Assert.AreEqual(expected: "1", actual: change.Current.id.ToString()); Assert.AreEqual(expected: "1", actual: change.Current.pk.ToString()); Assert.AreEqual(expected: "Testing TTL on CFP.", actual: change.Current.description.ToString()); - Assert.AreEqual(expected: ttlInSeconds, actual: change.Current.ttl.ToObject()); + Assert.AreEqual(expected: ttlInSeconds, actual: change.Current.ttl); // metadata - Assert.IsTrue(DateTime.TryParse(s: change.Metadata.ConflictResolutionTimestamp.ToString(), out _), message: "Invalid csrt must be a datetime value."); + Assert.IsTrue(DateTime.TryParse(s: change.Metadata.Crts.ToString(), out _), message: "Invalid csrt must be a datetime value."); Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value."); - Assert.IsFalse(change.Metadata.IsTimeToLiveExpired); + Assert.IsFalse(change.Metadata.TimeToLiveExpired); // previous Assert.IsNull(change.Previous); @@ -78,15 +79,15 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA Assert.IsNull(change.Current.id); // metadata - Assert.IsTrue(DateTime.TryParse(s: change.Metadata.ConflictResolutionTimestamp.ToString(), out _), message: "Invalid csrt must be a datetime value."); + Assert.IsTrue(DateTime.TryParse(s: change.Metadata.Crts.ToString(), out _), message: "Invalid csrt must be a datetime value."); Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value."); - Assert.IsTrue(change.Metadata.IsTimeToLiveExpired); + Assert.IsTrue(change.Metadata.TimeToLiveExpired); // previous Assert.AreEqual(expected: "1", actual: change.Previous.id.ToString()); Assert.AreEqual(expected: "1", actual: change.Previous.pk.ToString()); Assert.AreEqual(expected: "Testing TTL on CFP.", actual: change.Previous.description.ToString()); - Assert.AreEqual(expected: ttlInSeconds, actual: change.Previous.ttl.ToObject()); + Assert.AreEqual(expected: ttlInSeconds, actual: change.Previous.ttl); // stop after reading delete since it is the last document in feed. stopwatch.Stop(); @@ -120,8 +121,8 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA try { - await Task.Delay(GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests.ChangeFeedSetupTime); - await monitoredContainer.CreateItemAsync(new { id = "1", pk = "1", description = "Testing TTL on CFP.", ttl = ttlInSeconds }, partitionKey: new PartitionKey("1")); + await Task.Delay(ChangeFeedSetupTime); + await monitoredContainer.CreateItemAsync(new ToDoActivity { id = "1", pk = "1", description = "Testing TTL on CFP.", ttl = ttlInSeconds }, partitionKey: new PartitionKey("1")); // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. @@ -158,6 +159,8 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() string pk = default; string description = default; + Console.WriteLine(JsonConvert.SerializeObject(docs)); + foreach (ChangeFeedItem change in docs) { if (change.Metadata.OperationType != ChangeFeedOperationType.Delete) @@ -174,10 +177,10 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() } ChangeFeedOperationType operationType = change.Metadata.OperationType; - long previousLsn = change.Metadata.PreviousLsn; - DateTime m = change.Metadata.ConflictResolutionTimestamp; + long previousLsn = change.Metadata.PreviousImageLSN; + long m = change.Metadata.Crts; long lsn = change.Metadata.Lsn; - bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; + bool isTimeToLiveExpired = change.Metadata.TimeToLiveExpired; } Assert.IsNotNull(context.LeaseToken); @@ -194,7 +197,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.AreEqual(expected: "1", actual: createChange.Current.pk.ToString()); Assert.AreEqual(expected: "original test", actual: createChange.Current.description.ToString()); Assert.AreEqual(expected: createChange.Metadata.OperationType, actual: ChangeFeedOperationType.Create); - Assert.AreEqual(expected: createChange.Metadata.PreviousLsn, actual: 0); + Assert.AreEqual(expected: createChange.Metadata.PreviousImageLSN, actual: 0); Assert.IsNull(createChange.Previous); ChangeFeedItem replaceChange = docs.ElementAt(1); @@ -203,20 +206,20 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.AreEqual(expected: "1", actual: replaceChange.Current.pk.ToString()); Assert.AreEqual(expected: "test after replace", actual: replaceChange.Current.description.ToString()); Assert.AreEqual(expected: replaceChange.Metadata.OperationType, actual: ChangeFeedOperationType.Replace); - Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLsn); + Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousImageLSN); Assert.IsNull(replaceChange.Previous); ChangeFeedItem deleteChange = docs.ElementAt(2); Assert.IsNull(deleteChange.Current.id); Assert.AreEqual(expected: deleteChange.Metadata.OperationType, actual: ChangeFeedOperationType.Delete); - Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousLsn); + Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousImageLSN); Assert.IsNotNull(deleteChange.Previous); Assert.AreEqual(expected: "1", actual: deleteChange.Previous.id.ToString()); Assert.AreEqual(expected: "1", actual: deleteChange.Previous.pk.ToString()); Assert.AreEqual(expected: "test after replace", actual: deleteChange.Previous.description.ToString()); - Assert.IsTrue(condition: createChange.Metadata.ConflictResolutionTimestamp < replaceChange.Metadata.ConflictResolutionTimestamp, message: "The create operation must happen before the replace operation."); - Assert.IsTrue(condition: replaceChange.Metadata.ConflictResolutionTimestamp < deleteChange.Metadata.ConflictResolutionTimestamp, message: "The replace operation must happen before the delete operation."); + Assert.IsTrue(condition: createChange.Metadata.Crts < replaceChange.Metadata.Crts, message: "The create operation must happen before the replace operation."); + Assert.IsTrue(condition: replaceChange.Metadata.Crts < deleteChange.Metadata.Crts, message: "The replace operation must happen before the delete operation."); Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); @@ -236,7 +239,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() // 1 second delay between operations to get different timestamps. await processor.StartAsync(); - await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + await Task.Delay(ChangeFeedSetupTime); await monitoredContainer.CreateItemAsync(new { id = "1", pk = "1", description = "original test" }, partitionKey: new PartitionKey("1")); await Task.Delay(1000); @@ -246,7 +249,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() await monitoredContainer.DeleteItemAsync(id: "1", partitionKey: new PartitionKey("1")); - bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * ChangeFeedSetupTime); await processor.StopAsync(); @@ -270,16 +273,16 @@ public async Task WhenLatestVersionSwitchToAllVersionsAndDeletesExpectsAexceptio ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); ManualResetEvent allDocsProcessed = new(false); - await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .BuildChangeFeedProcessorWithLatestVersionAsync( + await + BuildChangeFeedProcessorWithLatestVersionAsync( monitoredContainer: monitoredContainer, leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed, withStartFromBeginning: withStartFromBeginning); ArgumentException exception = await Assert.ThrowsExceptionAsync( - () => GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( + () => + BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( monitoredContainer: monitoredContainer, leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed)); @@ -302,8 +305,8 @@ public async Task WhenLegacyLatestVersionSwitchToAllVersionsAndDeletesExpectsAex ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); ManualResetEvent allDocsProcessed = new(false); - await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .BuildChangeFeedProcessorWithLatestVersionAsync( + await + BuildChangeFeedProcessorWithLatestVersionAsync( monitoredContainer: monitoredContainer, leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed, @@ -311,14 +314,14 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests // Read lease documents, remove the Mode, and update the lease documents, so that it mimics a legacy lease document. - await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .RevertLeaseDocumentsToLegacyWithNoMode( + await + RevertLeaseDocumentsToLegacyWithNoMode( leaseContainer: this.LeaseContainer, leaseDocumentCount: 2); ArgumentException exception = await Assert.ThrowsExceptionAsync( - () => GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( + () => + BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( monitoredContainer: monitoredContainer, leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed)); @@ -340,15 +343,15 @@ public async Task WhenAllVersionsAndDeletesSwitchToLatestVersionExpectsAexceptio ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); ManualResetEvent allDocsProcessed = new(false); - await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( + await + BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( monitoredContainer: monitoredContainer, leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed); ArgumentException exception = await Assert.ThrowsExceptionAsync( - () => GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .BuildChangeFeedProcessorWithLatestVersionAsync( + () => + BuildChangeFeedProcessorWithLatestVersionAsync( monitoredContainer: monitoredContainer, leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed, @@ -371,14 +374,14 @@ public async Task WhenNoSwitchAllVersionsAndDeletesFDoesNotExpectAexceptionTestA try { - await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( + await + BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( monitoredContainer: monitoredContainer, leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed); - await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( + await + BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( monitoredContainer: monitoredContainer, leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed); @@ -405,15 +408,15 @@ public async Task WhenNoSwitchLatestVersionDoesNotExpectAexceptionTestAsync(bool try { - await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .BuildChangeFeedProcessorWithLatestVersionAsync( + await + BuildChangeFeedProcessorWithLatestVersionAsync( monitoredContainer: monitoredContainer, leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed, withStartFromBeginning: withStartFromBeginning); - await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .BuildChangeFeedProcessorWithLatestVersionAsync( + await + BuildChangeFeedProcessorWithLatestVersionAsync( monitoredContainer: monitoredContainer, leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed, @@ -439,8 +442,8 @@ public async Task WhenLegacyNoSwitchLatestVersionDoesNotExpectAnExceptionTestAsy ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.LatestVersion); ManualResetEvent allDocsProcessed = new(false); - await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .BuildChangeFeedProcessorWithLatestVersionAsync( + await + BuildChangeFeedProcessorWithLatestVersionAsync( monitoredContainer: monitoredContainer, leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed, @@ -448,13 +451,13 @@ await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests // Read lease documents, remove the Mode, and update the lease documents, so that it mimics a legacy lease document. - await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .RevertLeaseDocumentsToLegacyWithNoMode( + await + RevertLeaseDocumentsToLegacyWithNoMode( leaseContainer: this.LeaseContainer, leaseDocumentCount: 2); - await GetChangeFeedProcessorBuilderWithAllVersionsAndDeletesTests - .BuildChangeFeedProcessorWithLatestVersionAsync( + await + BuildChangeFeedProcessorWithLatestVersionAsync( monitoredContainer: monitoredContainer, leaseContainer: this.LeaseContainer, allDocsProcessed: allDocsProcessed, @@ -495,7 +498,7 @@ private static async Task RevertLeaseDocumentsToLegacyWithNoMode( _ = await leaseContainer.UpsertItemAsync(item: lease); } - + Assert.AreEqual(expected: leaseDocumentCount, actual: counter); } @@ -528,8 +531,8 @@ private static async Task BuildChangeFeedProcessorWithLatestVersionAsync( Interlocked.Exchange(ref latestVersionProcessorAtomic, processor); await processor.StartAsync(); - await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); + await Task.Delay(ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * ChangeFeedSetupTime); if (exception != default) { @@ -561,8 +564,8 @@ private static async Task BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync Interlocked.Exchange(ref allVersionsAndDeletesProcessorAtomic, processor); await processor.StartAsync(); - await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); + await Task.Delay(ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * ChangeFeedSetupTime); if (exception != default) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs new file mode 100644 index 0000000000..68f87276ba --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs @@ -0,0 +1,690 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.CFP.AllVersionsAndDeletes +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos; + using Microsoft.Azure.Cosmos.ChangeFeed.Utils; + using Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed; + using Microsoft.Azure.Cosmos.Services.Management.Tests; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + [TestClass] + [TestCategory("ChangeFeedProcessor.AllVersionsAndDeletes")] + public class BuilderWithCustomSerializerTests : BaseChangeFeedClientHelper + { + private ContainerInternal MonitoredContainer = null; + + [TestInitialize] + public async Task TestInitialize() + { + CosmosClient cosmosClient = TestCommon.CreateCosmosClient((cosmosClientBuilder) => + cosmosClientBuilder.WithSystemTextJsonSerializerOptions( + new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true, + Converters = { new JsonStringEnumConverter() } + }), + useCustomSeralizer: false); + + base.database = await cosmosClient.CreateDatabaseIfNotExistsAsync(id: Guid.NewGuid().ToString()); + this.LeaseContainer = await this.database.CreateContainerIfNotExistsAsync(containerProperties: new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + this.MonitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + } + + [TestCleanup] + public async Task Cleanup() + { + await this.TestCleanup(); + } + + [TestMethod] + [Owner("philipthomas")] + [Description("Validating to deserization of ChangeFeedItem with a Delete payload with TimeToLiveExpired set to true.")] + public void ValidateNSJAndSTJSerializationOfChangeFeedItemDeleteTimeToLiveExpiredIsTrueTest() + { + string json = @"[ + { + ""current"": {}, + ""metadata"": { + ""lsn"": 17, + ""crts"": 1722511591, + ""operationType"": ""delete"", + ""timeToLiveExpired"": true, + ""previousImageLSN"": 16 + }, + ""previous"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""Testing TTL on CFP."", + ""ttl"": 5, + ""_rid"": ""SnxPAOM2VfMBAAAAAAAAAA=="", + ""_self"": ""dbs/SnxPAA==/colls/SnxPAOM2VfM=/docs/SnxPAOM2VfMBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e405-5632b83c01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722511453 + } + } + ]"; + + ValidateSystemTextJsonDeserialization(json); + ValidateNewtonsoftJsonDeserialization(json); + + static void ValidateSystemTextJsonDeserialization(string json) + { + ValidateDeserialization( + System.Text.Json.JsonSerializer.Deserialize>>( + json: json, + options: new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true, + Converters = { new JsonStringEnumConverter(), } + })); + } + + static void ValidateNewtonsoftJsonDeserialization(string json) + { + ValidateDeserialization(JsonConvert.DeserializeObject>>(json)); + } + + static void ValidateDeserialization(List> activities) + { + Assert.IsNotNull(activities); + + ChangeFeedItem deletedChange = activities.ElementAt(0); + Assert.IsNotNull(deletedChange); + Assert.IsNotNull(deletedChange.Current); // Current is not null, but not data. + Assert.AreEqual(expected: default, actual: deletedChange.Current.description); // No current description for Delete + Assert.AreEqual(expected: default, actual: deletedChange.Current.id); // No current id for Delete + Assert.AreEqual(expected: default, actual: deletedChange.Current.ttl); // No current ttl for Delete + Assert.IsNotNull(deletedChange.Metadata); + Assert.AreEqual(expected: 1722511591, actual: deletedChange.Metadata.Crts); + Assert.AreEqual(expected: 17, actual: deletedChange.Metadata.Lsn); + Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deletedChange.Metadata.OperationType); + Assert.AreEqual(expected: 16, actual: deletedChange.Metadata.PreviousImageLSN); + Assert.IsTrue(deletedChange.Metadata.TimeToLiveExpired); + Assert.IsNotNull(deletedChange.Previous); + Assert.AreEqual(expected: "Testing TTL on CFP.", actual: deletedChange.Previous.description); + Assert.AreEqual(expected: "1", actual: deletedChange.Previous.id); + Assert.AreEqual(expected: 5, actual: deletedChange.Previous.ttl); + } + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Validating to deserization using NSJ and STJ of ChangeFeedItem with a Create payload with TTL set to a non-default value.")] + public void ValidateNSJAndSTJSerializationOfChangeFeedItemCreateTTLTest() + { + string json = @"[ + { + ""current"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""Testing TTL on CFP."", + ""ttl"": 5, + ""_rid"": ""SnxPAOM2VfMBAAAAAAAAAA=="", + ""_self"": ""dbs/SnxPAA==/colls/SnxPAOM2VfM=/docs/SnxPAOM2VfMBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e405-5632b83c01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722511453 + }, + ""metadata"": { + ""lsn"": 16, + ""crts"": 1722511453, + ""operationType"": ""create"" + } + } + ]"; + + ValidateSystemTextJsonDeserialization(json); + ValidateNewtonsoftJsonDeserialization(json); + + static void ValidateSystemTextJsonDeserialization(string json) + { + ValidateDeserialization(System.Text.Json.JsonSerializer.Deserialize>>( + json: json, + options: new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true, + Converters = { new JsonStringEnumConverter(), } + })); + } + + static void ValidateNewtonsoftJsonDeserialization(string json) + { + ValidateDeserialization(JsonConvert.DeserializeObject>>(json)); + } + + static void ValidateDeserialization(List> activities) + { + Assert.IsNotNull(activities); + + ChangeFeedItem createdUpdate = activities.ElementAt(0); + Assert.IsNotNull(createdUpdate); + Assert.IsNotNull(createdUpdate.Current); + Assert.AreEqual(expected: "Testing TTL on CFP.", actual: createdUpdate.Current.description); + Assert.AreEqual(expected: "1", actual: createdUpdate.Current.id); + Assert.AreEqual(expected: 5, actual: createdUpdate.Current.ttl); + Assert.IsNotNull(createdUpdate.Metadata); + Assert.AreEqual(expected: 1722511453, actual: createdUpdate.Metadata.Crts); + Assert.AreEqual(expected: 16, actual: createdUpdate.Metadata.Lsn); + Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createdUpdate.Metadata.OperationType); + Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousImageLSN); + Assert.IsFalse(createdUpdate.Metadata.TimeToLiveExpired); + Assert.IsNull(createdUpdate.Previous); // No Previous for a Create change. + } + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Validating to deserization using NSJ and STJ of ChangeFeedItem with a Create, Replace, and Delete payload.")] + public void ValidateNSJAndSTJSerializationOfChangeFeedItemTest() + { + string json = @"[ + { + ""current"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""original test"", + ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", + ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e384-28095c1a01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722455970 + }, + ""metadata"": { + ""crts"": 1722455970, + ""lsn"": 374, + ""operationType"": ""create"", + ""previousImageLSN"": 0, + ""timeToLiveExpired"": false + } + }, + { + ""current"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""test after replace"", + ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", + ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e384-28a5abdd01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722455971 + }, + ""metadata"": { + ""crts"": 1722455971, + ""lsn"": 375, + ""operationType"": ""replace"", + ""previousImageLSN"": 374, + ""timeToLiveExpired"": false + } + }, + { + ""current"": {}, + ""metadata"": { + ""crts"": 1722455972, + ""lsn"": 376, + ""operationType"": ""delete"", + ""previousImageLSN"": 375, + ""timeToLiveExpired"": false + }, + ""previous"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""test after replace"", + ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", + ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e384-28a5abdd01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722455971 + } + } + ]"; + + ValidateSystemTextJsonDeserialization(json); + ValidateNewtonsoftJsonDeserialization(json); + + static void ValidateNewtonsoftJsonDeserialization(string json) + { + ValidateDeserialization(JsonConvert.DeserializeObject>>(json)); + } + + static void ValidateSystemTextJsonDeserialization(string json) + { + ValidateDeserialization(System.Text.Json.JsonSerializer.Deserialize>>( + json: json, + options: new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true, + Converters = { new JsonStringEnumConverter(), } + })); + } + + static void ValidateDeserialization(List> activities) + { + Assert.IsNotNull(activities); + + ChangeFeedItem createdUpdate = activities.ElementAt(0); + Assert.IsNotNull(createdUpdate); + Assert.IsNotNull(createdUpdate.Current); + Assert.AreEqual(expected: "original test", actual: createdUpdate.Current.description); + Assert.AreEqual(expected: "1", actual: createdUpdate.Current.id); + Assert.AreEqual(expected: 0, actual: createdUpdate.Current.ttl); + Assert.IsNotNull(createdUpdate.Metadata); + Assert.AreEqual(expected: 1722455970, actual: createdUpdate.Metadata.Crts); + Assert.AreEqual(expected: 374, actual: createdUpdate.Metadata.Lsn); + Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createdUpdate.Metadata.OperationType); + Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousImageLSN); + Assert.IsFalse(createdUpdate.Metadata.TimeToLiveExpired); + Assert.IsNull(createdUpdate.Previous); // No Previous for a Create change. + + ChangeFeedItem replacedChange = activities.ElementAt(1); + Assert.IsNotNull(replacedChange); + Assert.IsNotNull(replacedChange.Current); + Assert.AreEqual(expected: "test after replace", actual: replacedChange.Current.description); + Assert.AreEqual(expected: "1", actual: replacedChange.Current.id); + Assert.AreEqual(expected: 0, actual: replacedChange.Current.ttl); + Assert.IsNotNull(replacedChange.Metadata); + Assert.AreEqual(expected: 1722455971, actual: replacedChange.Metadata.Crts); + Assert.AreEqual(expected: 375, actual: replacedChange.Metadata.Lsn); + Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replacedChange.Metadata.OperationType); + Assert.AreEqual(expected: 374, actual: replacedChange.Metadata.PreviousImageLSN); + Assert.IsFalse(replacedChange.Metadata.TimeToLiveExpired); + Assert.IsNull(replacedChange.Previous); // No Previous for a Replace change. + + ChangeFeedItem deletedChange = activities.ElementAt(2); + Assert.IsNotNull(deletedChange); + Assert.IsNotNull(deletedChange.Current); // Current is not null, but not data. + Assert.AreEqual(expected: default, actual: deletedChange.Current.description); // No current description for Delete + Assert.AreEqual(expected: default, actual: deletedChange.Current.id); // No current id for Delete + Assert.AreEqual(expected: default, actual: deletedChange.Current.ttl); // No current ttl for Delete + Assert.IsNotNull(deletedChange.Metadata); + Assert.AreEqual(expected: 1722455972, actual: deletedChange.Metadata.Crts); + Assert.AreEqual(expected: 376, actual: deletedChange.Metadata.Lsn); + Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deletedChange.Metadata.OperationType); + Assert.AreEqual(expected: 375, actual: deletedChange.Metadata.PreviousImageLSN); + Assert.IsFalse(deletedChange.Metadata.TimeToLiveExpired); + Assert.IsNotNull(deletedChange.Previous); + Assert.AreEqual(expected: "test after replace", actual: deletedChange.Previous.description); + Assert.AreEqual(expected: "1", actual: deletedChange.Previous.id); + Assert.AreEqual(expected: 0, actual: deletedChange.Previous.ttl); + } + } + + [TestMethod] + [Timeout(300000)] + [TestCategory("LongRunning")] + [Owner("philipthomas-MSFT")] + [Description("Scenario: When a document is created with ttl set, there should be 1 create and 1 delete that will appear for that " + + "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] + public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsAsync() + { + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + Exception exception = default; + int ttlInSeconds = 5; + Stopwatch stopwatch = new(); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + + ChangeFeedProcessor processor = monitoredContainer + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => + { + // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. + + Logger.LogLine($"@ {DateTime.Now}, {nameof(stopwatch)} -> CFP AVAD took '{stopwatch.ElapsedMilliseconds}' to read document CRUD in feed."); + + foreach (ChangeFeedItem change in docs) + { + if (change.Metadata.OperationType == ChangeFeedOperationType.Create) + { + // current + Assert.AreEqual(expected: "1", actual: change.Current.id.ToString()); + Assert.AreEqual(expected: "1", actual: change.Current.pk.ToString()); + Assert.AreEqual(expected: "Testing TTL on CFP.", actual: change.Current.description.ToString()); + Assert.AreEqual(expected: ttlInSeconds, actual: change.Current.ttl); + + // metadata + Assert.IsTrue(DateTime.TryParse(s: change.Metadata.Crts.ToString(), out _), message: "Invalid csrt must be a datetime value."); + Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value."); + Assert.IsFalse(change.Metadata.TimeToLiveExpired); + + // previous + Assert.IsNull(change.Previous); + } + else if (change.Metadata.OperationType == ChangeFeedOperationType.Delete) + { + // current + Assert.IsNull(change.Current.id); + + // metadata + Assert.IsTrue(DateTime.TryParse(s: change.Metadata.Crts.ToString(), out _), message: "Invalid csrt must be a datetime value."); + Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value."); + Assert.IsTrue(change.Metadata.TimeToLiveExpired); + + // previous + Assert.AreEqual(expected: "1", actual: change.Previous.id.ToString()); + Assert.AreEqual(expected: "1", actual: change.Previous.pk.ToString()); + Assert.AreEqual(expected: "Testing TTL on CFP.", actual: change.Previous.description.ToString()); + Assert.AreEqual(expected: ttlInSeconds, actual: change.Previous.ttl); + + // stop after reading delete since it is the last document in feed. + stopwatch.Stop(); + allDocsProcessed.Set(); + } + else + { + Assert.Fail("Invalid operation."); + } + } + + return Task.CompletedTask; + }) + .WithInstanceName(Guid.NewGuid().ToString()) + .WithLeaseContainer(this.LeaseContainer) + .WithErrorNotification((leaseToken, error) => + { + exception = error.InnerException; + + return Task.CompletedTask; + }) + .Build(); + + stopwatch.Start(); + + // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. + + Logger.LogLine($"@ {DateTime.Now}, CFProcessor starting..."); + + await processor.StartAsync(); + + try + { + await Task.Delay(ChangeFeedSetupTime); + await monitoredContainer.CreateItemAsync(new ToDoActivity { id = "1", pk = "1", description = "Testing TTL on CFP.", ttl = ttlInSeconds }, partitionKey: new PartitionKey("1")); + + // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. + + Logger.LogLine($"@ {DateTime.Now}, Document created."); + + bool receivedDelete = allDocsProcessed.WaitOne(250000); + Assert.IsTrue(receivedDelete, "Timed out waiting for docs to process"); + + if (exception != default) + { + Assert.Fail(exception.ToString()); + } + } + finally + { + await processor.StopAsync(); + } + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Scenario: When a document is created, then updated, and finally deleted, there should be 3 changes that will appear for that " + + "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] + public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() + { + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + Exception exception = default; + + ChangeFeedProcessor processor = monitoredContainer + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => + { + Logger.LogLine($"@ {DateTime.Now}, {nameof(docs)} -> {System.Text.Json.JsonSerializer.Serialize(docs)}"); + + string id = default; + string pk = default; + string description = default; + + foreach (ChangeFeedItem change in docs) + { + if (change.Metadata.OperationType != ChangeFeedOperationType.Delete) + { + id = change.Current.id.ToString(); + pk = change.Current.pk.ToString(); + description = change.Current.description.ToString(); + } + else + { + id = change.Previous.id.ToString(); + pk = change.Previous.pk.ToString(); + description = change.Previous.description.ToString(); + } + + ChangeFeedOperationType operationType = change.Metadata.OperationType; + long previousLsn = change.Metadata.PreviousImageLSN; + long m = change.Metadata.Crts; + long lsn = change.Metadata.Lsn; + bool isTimeToLiveExpired = change.Metadata.TimeToLiveExpired; + } + + Assert.IsNotNull(context.LeaseToken); + Assert.IsNotNull(context.Diagnostics); + Assert.IsNotNull(context.Headers); + Assert.IsNotNull(context.Headers.Session); + Assert.IsTrue(context.Headers.RequestCharge > 0); + Assert.IsTrue(context.Diagnostics.ToString().Contains("Change Feed Processor Read Next Async")); + Assert.AreEqual(expected: 3, actual: docs.Count); + + ChangeFeedItem createChange = docs.ElementAt(0); + Assert.IsNotNull(createChange.Current); + Assert.AreEqual(expected: "1", actual: createChange.Current.id.ToString()); + Assert.AreEqual(expected: "1", actual: createChange.Current.pk.ToString()); + Assert.AreEqual(expected: "original test", actual: createChange.Current.description.ToString()); + Assert.AreEqual(expected: createChange.Metadata.OperationType, actual: ChangeFeedOperationType.Create); + Assert.AreEqual(expected: createChange.Metadata.PreviousImageLSN, actual: 0); + Assert.IsNull(createChange.Previous); + + ChangeFeedItem replaceChange = docs.ElementAt(1); + Assert.IsNotNull(replaceChange.Current); + Assert.AreEqual(expected: "1", actual: replaceChange.Current.id.ToString()); + Assert.AreEqual(expected: "1", actual: replaceChange.Current.pk.ToString()); + Assert.AreEqual(expected: "test after replace", actual: replaceChange.Current.description.ToString()); + Assert.AreEqual(expected: replaceChange.Metadata.OperationType, actual: ChangeFeedOperationType.Replace); + Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousImageLSN); + Assert.IsNull(replaceChange.Previous); + + ChangeFeedItem deleteChange = docs.ElementAt(2); + Assert.IsNull(deleteChange.Current.id); + Assert.AreEqual(expected: deleteChange.Metadata.OperationType, actual: ChangeFeedOperationType.Delete); + Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousImageLSN); + Assert.IsNotNull(deleteChange.Previous); + Assert.AreEqual(expected: "1", actual: deleteChange.Previous.id.ToString()); + Assert.AreEqual(expected: "1", actual: deleteChange.Previous.pk.ToString()); + Assert.AreEqual(expected: "test after replace", actual: deleteChange.Previous.description.ToString()); + + Assert.IsTrue(condition: createChange.Metadata.Crts < replaceChange.Metadata.Crts, message: "The create operation must happen before the replace operation."); + Assert.IsTrue(condition: replaceChange.Metadata.Crts < deleteChange.Metadata.Crts, message: "The replace operation must happen before the delete operation."); + Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); + Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); + + return Task.CompletedTask; + }) + .WithInstanceName(Guid.NewGuid().ToString()) + .WithLeaseContainer(this.LeaseContainer) + .WithErrorNotification((leaseToken, error) => + { + exception = error.InnerException; + + return Task.CompletedTask; + }) + .Build(); + + // Start the processor, insert 1 document to generate a checkpoint, modify it, and then delete it. + // 1 second delay between operations to get different timestamps. + + await processor.StartAsync(); + await Task.Delay(ChangeFeedSetupTime); + + await monitoredContainer.CreateItemAsync(new ToDoActivity { id = "1", pk = "1", description = "original test", ttl = -1 }, partitionKey: new PartitionKey("1")); + await Task.Delay(1000); + + await monitoredContainer.UpsertItemAsync(new ToDoActivity { id = "1", pk = "1", description = "test after replace", ttl = -1 }, partitionKey: new PartitionKey("1")); + await Task.Delay(1000); + + await monitoredContainer.DeleteItemAsync(id: "1", partitionKey: new PartitionKey("1")); + + bool isStartOk = allDocsProcessed.WaitOne(10 * ChangeFeedSetupTime); + + await processor.StopAsync(); + + if (exception != default) + { + Assert.Fail(exception.ToString()); + } + } + + private static async Task RevertLeaseDocumentsToLegacyWithNoMode( + Container leaseContainer, + int leaseDocumentCount) + { + FeedIterator iterator = leaseContainer.GetItemQueryStreamIterator( + queryText: "SELECT * FROM c", + continuationToken: null); + + List leases = new List(); + while (iterator.HasMoreResults) + { + using (ResponseMessage responseMessage = await iterator.ReadNextAsync().ConfigureAwait(false)) + { + responseMessage.EnsureSuccessStatusCode(); + leases.AddRange(CosmosFeedResponseSerializer.FromFeedResponseStream( + serializerCore: CosmosContainerExtensions.DefaultJsonSerializer, + streamWithServiceEnvelope: responseMessage.Content)); + } + } + + int counter = 0; + + foreach (JObject lease in leases) + { + if (!lease.ContainsKey("Mode")) + { + continue; + } + + counter++; + lease.Remove("Mode"); + + _ = await leaseContainer.UpsertItemAsync(item: lease); + } + + Assert.AreEqual(expected: leaseDocumentCount, actual: counter); + } + + private static async Task BuildChangeFeedProcessorWithLatestVersionAsync( + ContainerInternal monitoredContainer, + Container leaseContainer, + ManualResetEvent allDocsProcessed, + bool withStartFromBeginning) + { + Exception exception = default; + ChangeFeedProcessor latestVersionProcessorAtomic = null; + + ChangeFeedProcessorBuilder processorBuilder = monitoredContainer + .GetChangeFeedProcessorBuilder(processorName: $"processorName", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection documents, CancellationToken token) => Task.CompletedTask) + .WithInstanceName(Guid.NewGuid().ToString()) + .WithLeaseContainer(leaseContainer) + .WithErrorNotification((leaseToken, error) => + { + exception = error.InnerException; + + return Task.CompletedTask; + }); + + if (withStartFromBeginning) + { + processorBuilder.WithStartFromBeginning(); + } + + ChangeFeedProcessor processor = processorBuilder.Build(); + Interlocked.Exchange(ref latestVersionProcessorAtomic, processor); + + await processor.StartAsync(); + await Task.Delay(ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * ChangeFeedSetupTime); + + if (exception != default) + { + Assert.Fail(exception.ToString()); + } + } + + private static async Task BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( + ContainerInternal monitoredContainer, + Container leaseContainer, + ManualResetEvent allDocsProcessed) + { + Exception exception = default; + ChangeFeedProcessor allVersionsAndDeletesProcessorAtomic = null; + + ChangeFeedProcessorBuilder allVersionsAndDeletesProcessorBuilder = monitoredContainer + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: $"processorName", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> documents, CancellationToken token) => Task.CompletedTask) + .WithInstanceName(Guid.NewGuid().ToString()) + .WithMaxItems(1) + .WithLeaseContainer(leaseContainer) + .WithErrorNotification((leaseToken, error) => + { + exception = error.InnerException; + + return Task.FromResult(exception); + }); + + ChangeFeedProcessor processor = allVersionsAndDeletesProcessorBuilder.Build(); + Interlocked.Exchange(ref allVersionsAndDeletesProcessorAtomic, processor); + + await processor.StartAsync(); + await Task.Delay(ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * ChangeFeedSetupTime); + + if (exception != default) + { + Assert.Fail(exception.ToString()); + } + } + + private async Task CreateMonitoredContainer(ChangeFeedMode changeFeedMode) + { + string PartitionKey = "/pk"; + ContainerProperties properties = new ContainerProperties(id: Guid.NewGuid().ToString(), + partitionKeyPath: PartitionKey); + + if (changeFeedMode == ChangeFeedMode.AllVersionsAndDeletes) + { + properties.ChangeFeedPolicy.FullFidelityRetention = TimeSpan.FromMinutes(5); + properties.DefaultTimeToLive = -1; + } + + ContainerResponse response = await this.database.CreateContainerAsync(properties, + throughput: 10000, + cancellationToken: this.cancellationToken); + + return (ContainerInternal)response; + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] + public class ToDoActivity + { + public string id { get; set; } + + public string pk { get; set; } + + public string description { get; set; } + + public int ttl { get; set; } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs index a43d86faf5..e1cffc23d7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs @@ -972,10 +972,10 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_VerifyingWir Assert.AreEqual(expected: "98052", actual: createOperation.Current.ZipCode); Assert.IsNotNull(createOperation.Metadata); Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); + Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Crts); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); - Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLsn); - Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); + Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousImageLSN); + Assert.IsFalse(createOperation.Metadata.TimeToLiveExpired); ChangeFeedItem replaceOperation = itemChanges.ElementAtOrDefault(1); @@ -986,10 +986,10 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_VerifyingWir Assert.AreEqual(expected: "30363", actual: replaceOperation.Current.ZipCode); Assert.IsNotNull(createOperation.Metadata); Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replaceOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Crts); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLsn); - Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousImageLSN); + Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); break; } @@ -1051,10 +1051,10 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreEqual(expected: "Thailand", actual: firstCreateOperation.Current.State); Assert.AreEqual(expected: "10330", actual: firstCreateOperation.Current.ZipCode); Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: firstCreateOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: firstCreateOperation.Metadata.ConflictResolutionTimestamp); + Assert.AreNotEqual(notExpected: default, actual: firstCreateOperation.Metadata.Crts); Assert.AreNotEqual(notExpected: default, actual: firstCreateOperation.Metadata.Lsn); - Assert.AreEqual(expected: default, actual: firstCreateOperation.Metadata.PreviousLsn); - Assert.IsFalse(firstCreateOperation.Metadata.IsTimeToLiveExpired); + Assert.AreEqual(expected: default, actual: firstCreateOperation.Metadata.PreviousImageLSN); + Assert.IsFalse(firstCreateOperation.Metadata.TimeToLiveExpired); ChangeFeedItem createOperation = resources[1]; @@ -1064,10 +1064,10 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreEqual(expected: "WA", actual: createOperation.Current.State); Assert.AreEqual(expected: "98052", actual: createOperation.Current.ZipCode); Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); + Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Crts); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); - Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLsn); - Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); + Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousImageLSN); + Assert.IsFalse(createOperation.Metadata.TimeToLiveExpired); ChangeFeedItem replaceOperation = resources[2]; @@ -1077,10 +1077,10 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreEqual(expected: "GA", actual: replaceOperation.Current.State); Assert.AreEqual(expected: "30363", actual: replaceOperation.Current.ZipCode); Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replaceOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Crts); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLsn); - Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousImageLSN); + Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); ChangeFeedItem deleteOperation = resources[3]; @@ -1090,9 +1090,9 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.IsNull(deleteOperation.Current.State); Assert.IsNull(deleteOperation.Current.ZipCode); Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deleteOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.ConflictResolutionTimestamp); + Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.Crts); Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousLsn); + Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousImageLSN); Assert.IsNotNull(deleteOperation.Previous); Assert.AreEqual(expected: id, actual: deleteOperation.Previous.Id); Assert.AreEqual(expected: "205 16th St NW", actual: deleteOperation.Previous.Line1); @@ -1160,10 +1160,10 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreEqual(expected: "98052", actual: createOperation.Current.ZipCode); Assert.IsNotNull(createOperation.Metadata); Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); + Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Crts); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); - Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLsn); - Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); + Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousImageLSN); + Assert.IsFalse(createOperation.Metadata.TimeToLiveExpired); ChangeFeedItem replaceOperation = itemChanges[1]; @@ -1174,19 +1174,19 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreEqual(expected: "30363", actual: replaceOperation.Current.ZipCode); Assert.IsNotNull(replaceOperation.Metadata); Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replaceOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Crts); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLsn); - Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousImageLSN); + Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); ChangeFeedItem deleteOperation = itemChanges[2]; Assert.IsNotNull(deleteOperation.Metadata); Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deleteOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.ConflictResolutionTimestamp); + Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.Crts); Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousLsn); - Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); + Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousImageLSN); + Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); break; } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index 4a4b7b08dc..b2b02b8974 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -10,18 +10,14 @@ ], "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedMetadata get_Metadata();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.ChangeFeedMetadata Metadata[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"metadata\")]": { + "Microsoft.Azure.Cosmos.ChangeFeedMetadata Metadata": { "Type": "Property", - "Attributes": [ - "JsonPropertyAttribute" - ], + "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedMetadata Metadata;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.ChangeFeedMetadata get_Metadata();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Metadata(Microsoft.Azure.Cosmos.ChangeFeedMetadata);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "T Current[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"current\")]": { + "T Current": { "Type": "Property", - "Attributes": [ - "JsonPropertyAttribute" - ], + "Attributes": [], "MethodInfo": "T Current;CanRead:True;CanWrite:True;T get_Current();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Current(T);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "T get_Current()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { @@ -38,11 +34,9 @@ ], "MethodInfo": "T get_Previous();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "T Previous[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"previous\")]": { + "T Previous": { "Type": "Property", - "Attributes": [ - "JsonPropertyAttribute" - ], + "Attributes": [], "MethodInfo": "T Previous;CanRead:True;CanWrite:True;T get_Previous();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Previous(T);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "Void .ctor()": { @@ -77,19 +71,29 @@ "Microsoft.Azure.Cosmos.ChangeFeedMetadata;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Boolean get_IsTimeToLiveExpired()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Boolean get_TimeToLiveExpired()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "Boolean get_IsTimeToLiveExpired();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Boolean get_TimeToLiveExpired();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Boolean IsTimeToLiveExpired[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"timeToLiveExpired\")]": { + "Boolean TimeToLiveExpired": { "Type": "Property", + "Attributes": [], + "MethodInfo": "Boolean TimeToLiveExpired;CanRead:True;CanWrite:False;Boolean get_TimeToLiveExpired();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Int64 Crts": { + "Type": "Property", + "Attributes": [], + "MethodInfo": "Int64 Crts;CanRead:True;CanWrite:False;Int64 get_Crts();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Int64 get_Crts()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", "Attributes": [ - "JsonPropertyAttribute" + "CompilerGeneratedAttribute" ], - "MethodInfo": "Boolean IsTimeToLiveExpired;CanRead:True;CanWrite:True;Boolean get_IsTimeToLiveExpired();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Int64 get_Crts();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "Int64 get_Lsn()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", @@ -98,26 +102,22 @@ ], "MethodInfo": "Int64 get_Lsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Int64 get_PreviousLsn()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Int64 get_PreviousImageLSN()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "Int64 get_PreviousLsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Int64 get_PreviousImageLSN();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Int64 Lsn[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"lsn\")]": { + "Int64 Lsn": { "Type": "Property", - "Attributes": [ - "JsonPropertyAttribute" - ], - "MethodInfo": "Int64 Lsn;CanRead:True;CanWrite:True;Int64 get_Lsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "Attributes": [], + "MethodInfo": "Int64 Lsn;CanRead:True;CanWrite:False;Int64 get_Lsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Int64 PreviousLsn[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"previousImageLSN\")]": { + "Int64 PreviousImageLSN": { "Type": "Property", - "Attributes": [ - "JsonPropertyAttribute" - ], - "MethodInfo": "Int64 PreviousLsn;CanRead:True;CanWrite:True;Int64 get_PreviousLsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "Attributes": [], + "MethodInfo": "Int64 PreviousImageLSN;CanRead:True;CanWrite:False;Int64 get_PreviousImageLSN();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", @@ -126,33 +126,15 @@ ], "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"operationType\")]-[Newtonsoft.Json.JsonConverterAttribute(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]": { - "Type": "Property", - "Attributes": [ - "JsonConverterAttribute", - "JsonPropertyAttribute" - ], - "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.DateTime ConflictResolutionTimestamp[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"crts\")]-[Newtonsoft.Json.JsonConverterAttribute(typeof(Microsoft.Azure.Documents.UnixDateTimeConverter))]": { + "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType": { "Type": "Property", - "Attributes": [ - "JsonConverterAttribute", - "JsonPropertyAttribute" - ], - "MethodInfo": "System.DateTime ConflictResolutionTimestamp;CanRead:True;CanWrite:True;System.DateTime get_ConflictResolutionTimestamp();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "System.DateTime get_ConflictResolutionTimestamp()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], - "MethodInfo": "System.DateTime get_ConflictResolutionTimestamp();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType;CanRead:True;CanWrite:False;Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Void .ctor()": { + "Void .ctor(Int64, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean)": { "Type": "Constructor", "Attributes": [], - "MethodInfo": "[Void .ctor(), Void .ctor()]" + "MethodInfo": "[Void .ctor(Int64, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean), Void .ctor(Int64, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean)]" } }, "NestedTypes": {} From 5f7bc3f6e122d547b911ba6657698a29c11c2bca Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 5 Aug 2024 09:25:21 -0400 Subject: [PATCH 02/15] support for both STJ and NSJ --- .../Resource/FullFidelity/ChangeFeedItem.cs | 9 +++ .../FullFidelity/ChangeFeedMetadata.cs | 42 ++++++++++---- .../Converters/STJUnixDateTimeConverter.cs | 46 +++++++++++++++ .../CFP/AllVersionsAndDeletes/BuilderTests.cs | 26 ++++----- .../BuilderWithCustomSerializerTests.cs | 58 +++++++++---------- .../FeedToken/ChangeFeedIteratorCoreTests.cs | 52 ++++++++--------- 6 files changed, 153 insertions(+), 80 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/STJUnixDateTimeConverter.cs diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedItem.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedItem.cs index 3794ccbccf..417cc3b1b6 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedItem.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedItem.cs @@ -4,6 +4,9 @@ namespace Microsoft.Azure.Cosmos { + using System.Text.Json.Serialization; + using Newtonsoft.Json; + /// /// The typed response that contains the current, previous, and metadata change feed resource when is initialized to . /// @@ -56,16 +59,22 @@ class ChangeFeedItem /// /// The full fidelity change feed current item. /// + [JsonProperty(PropertyName = "current")] + [JsonPropertyName("current")] public T Current { get; set; } /// /// The full fidelity change feed metadata. /// + [JsonProperty(PropertyName = "metadata", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("metadata")] public ChangeFeedMetadata Metadata { get; set; } /// /// For delete operations, previous image is always going to be provided. The previous image on replace operations is not going to be exposed by default and requires account-level or container-level opt-in. /// + [JsonProperty(PropertyName = "previous", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("previous")] public T Previous { get; set; } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs index ead97a348b..09b51f72f0 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs @@ -4,6 +4,12 @@ namespace Microsoft.Azure.Cosmos { + using System; + using System.Text.Json; + using System.Text.Json.Serialization; + using Microsoft.Azure.Documents; + using Newtonsoft.Json; + /// /// The metadata of a change feed resource with is initialized to . /// @@ -17,48 +23,60 @@ class ChangeFeedMetadata /// /// New instance of meta data for created. /// - /// + /// /// /// - /// - /// + /// + /// public ChangeFeedMetadata( - long crts, + DateTime conflictResolutionTimestamp, long lsn, ChangeFeedOperationType operationType, - long previousImageLSN, - bool timeToLiveExpired) + long previousLSN, + bool isTimeToLiveExpired) { - this.Crts = crts; + this.ConflictResolutionTimestamp = conflictResolutionTimestamp; this.Lsn = lsn; this.OperationType = operationType; - this.PreviousImageLSN = previousImageLSN; - this.TimeToLiveExpired = timeToLiveExpired; + this.PreviousLSN = previousLSN; + this.IsTimeToLiveExpired = isTimeToLiveExpired; } /// /// The conflict resolution timestamp. /// - public long Crts { get; } + [JsonProperty(PropertyName = "crts", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("crts")] + [Newtonsoft.Json.JsonConverter(typeof(UnixDateTimeConverter))] + [System.Text.Json.Serialization.JsonConverter(typeof(Resource.FullFidelity.Converters.STJUnixDateTimeConverter))] + public DateTime ConflictResolutionTimestamp { get; } /// /// The current logical sequence number. /// + [JsonProperty(PropertyName = "lsn", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("lsn")] public long Lsn { get; } /// /// The change feed operation type. /// + [JsonProperty(PropertyName = "operationType", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("operationType")] public ChangeFeedOperationType OperationType { get; } /// /// The previous logical sequence number. /// - public long PreviousImageLSN { get; } + [Newtonsoft.Json.JsonProperty(PropertyName = "previousImageLSN", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("previousImageLSN")] + public long PreviousLSN { get; } /// /// Used to distinquish explicit deletes (e.g. via DeleteItem) from deletes caused by TTL expiration (a collection may define time-to-live policy for documents). /// - public bool TimeToLiveExpired { get; } + [JsonProperty(PropertyName = "timeToLiveExpired", NullValueHandling = NullValueHandling.Ignore)] + [JsonPropertyName("timeToLiveExpired")] + public bool IsTimeToLiveExpired { get; } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/STJUnixDateTimeConverter.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/STJUnixDateTimeConverter.cs new file mode 100644 index 0000000000..6ac96839ea --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/STJUnixDateTimeConverter.cs @@ -0,0 +1,46 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Resource.FullFidelity.Converters +{ + using System; + using System.Text.Json; + using System.Text.Json.Serialization; + + /// + /// UnixDateTimeConverter for System.Text.Json + /// + public class STJUnixDateTimeConverter : JsonConverter + { + /// + /// Read. + /// + /// + /// + /// + /// DateTime. + public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.Number) + { + throw new System.Text.Json.JsonException("Expected a number representing the Unix timestamp."); + } + + long unixTime = reader.GetInt64(); + return DateTimeOffset.FromUnixTimeSeconds(unixTime).UtcDateTime; + } + + /// + /// Write. + /// + /// + /// + /// + public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) + { + long unixTime = ((DateTimeOffset)value).ToUnixTimeSeconds(); + writer.WriteNumberValue(unixTime); + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs index 13fe98bacf..f50559f17b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs @@ -19,7 +19,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.CFP.AllVersionsAndDeletes using Newtonsoft.Json.Linq; [TestClass] - [TestCategory("ChangeFeedProcessor.AllVersionsAndDeletes")] + [TestCategory("ChangeFeedProcessor")] public class BuilderTests : BaseChangeFeedClientHelper { [TestInitialize] @@ -66,9 +66,9 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA Assert.AreEqual(expected: ttlInSeconds, actual: change.Current.ttl); // metadata - Assert.IsTrue(DateTime.TryParse(s: change.Metadata.Crts.ToString(), out _), message: "Invalid csrt must be a datetime value."); + Assert.IsTrue(DateTime.TryParse(s: change.Metadata.ConflictResolutionTimestamp.ToString(), out _), message: "Invalid csrt must be a datetime value."); Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value."); - Assert.IsFalse(change.Metadata.TimeToLiveExpired); + Assert.IsFalse(change.Metadata.IsTimeToLiveExpired); // previous Assert.IsNull(change.Previous); @@ -79,9 +79,9 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA Assert.IsNull(change.Current.id); // metadata - Assert.IsTrue(DateTime.TryParse(s: change.Metadata.Crts.ToString(), out _), message: "Invalid csrt must be a datetime value."); + Assert.IsTrue(DateTime.TryParse(s: change.Metadata.ConflictResolutionTimestamp.ToString(), out _), message: "Invalid csrt must be a datetime value."); Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value."); - Assert.IsTrue(change.Metadata.TimeToLiveExpired); + Assert.IsTrue(change.Metadata.IsTimeToLiveExpired); // previous Assert.AreEqual(expected: "1", actual: change.Previous.id.ToString()); @@ -177,10 +177,10 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() } ChangeFeedOperationType operationType = change.Metadata.OperationType; - long previousLsn = change.Metadata.PreviousImageLSN; - long m = change.Metadata.Crts; + long previousLsn = change.Metadata.PreviousLSN; + DateTime m = change.Metadata.ConflictResolutionTimestamp; long lsn = change.Metadata.Lsn; - bool isTimeToLiveExpired = change.Metadata.TimeToLiveExpired; + bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; } Assert.IsNotNull(context.LeaseToken); @@ -197,7 +197,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.AreEqual(expected: "1", actual: createChange.Current.pk.ToString()); Assert.AreEqual(expected: "original test", actual: createChange.Current.description.ToString()); Assert.AreEqual(expected: createChange.Metadata.OperationType, actual: ChangeFeedOperationType.Create); - Assert.AreEqual(expected: createChange.Metadata.PreviousImageLSN, actual: 0); + Assert.AreEqual(expected: createChange.Metadata.PreviousLSN, actual: 0); Assert.IsNull(createChange.Previous); ChangeFeedItem replaceChange = docs.ElementAt(1); @@ -206,20 +206,20 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.AreEqual(expected: "1", actual: replaceChange.Current.pk.ToString()); Assert.AreEqual(expected: "test after replace", actual: replaceChange.Current.description.ToString()); Assert.AreEqual(expected: replaceChange.Metadata.OperationType, actual: ChangeFeedOperationType.Replace); - Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousImageLSN); + Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLSN); Assert.IsNull(replaceChange.Previous); ChangeFeedItem deleteChange = docs.ElementAt(2); Assert.IsNull(deleteChange.Current.id); Assert.AreEqual(expected: deleteChange.Metadata.OperationType, actual: ChangeFeedOperationType.Delete); - Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousImageLSN); + Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousLSN); Assert.IsNotNull(deleteChange.Previous); Assert.AreEqual(expected: "1", actual: deleteChange.Previous.id.ToString()); Assert.AreEqual(expected: "1", actual: deleteChange.Previous.pk.ToString()); Assert.AreEqual(expected: "test after replace", actual: deleteChange.Previous.description.ToString()); - Assert.IsTrue(condition: createChange.Metadata.Crts < replaceChange.Metadata.Crts, message: "The create operation must happen before the replace operation."); - Assert.IsTrue(condition: replaceChange.Metadata.Crts < deleteChange.Metadata.Crts, message: "The replace operation must happen before the delete operation."); + Assert.IsTrue(condition: createChange.Metadata.ConflictResolutionTimestamp < replaceChange.Metadata.ConflictResolutionTimestamp, message: "The create operation must happen before the replace operation."); + Assert.IsTrue(condition: replaceChange.Metadata.ConflictResolutionTimestamp < deleteChange.Metadata.ConflictResolutionTimestamp, message: "The replace operation must happen before the delete operation."); Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs index 68f87276ba..3230da542f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs @@ -21,7 +21,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.CFP.AllVersionsAndDeletes using Newtonsoft.Json.Linq; [TestClass] - [TestCategory("ChangeFeedProcessor.AllVersionsAndDeletes")] + [TestCategory("ChangeFeedProcessor")] public class BuilderWithCustomSerializerTests : BaseChangeFeedClientHelper { private ContainerInternal MonitoredContainer = null; @@ -88,7 +88,7 @@ static void ValidateSystemTextJsonDeserialization(string json) json: json, options: new JsonSerializerOptions() { - PropertyNameCaseInsensitive = true, + PropertyNameCaseInsensitive = false, Converters = { new JsonStringEnumConverter(), } })); } @@ -109,11 +109,11 @@ static void ValidateDeserialization(List> activitie Assert.AreEqual(expected: default, actual: deletedChange.Current.id); // No current id for Delete Assert.AreEqual(expected: default, actual: deletedChange.Current.ttl); // No current ttl for Delete Assert.IsNotNull(deletedChange.Metadata); - Assert.AreEqual(expected: 1722511591, actual: deletedChange.Metadata.Crts); + Assert.AreEqual(expected: DateTime.Parse("8/1/2024 11:26:31 AM"), actual: deletedChange.Metadata.ConflictResolutionTimestamp); Assert.AreEqual(expected: 17, actual: deletedChange.Metadata.Lsn); Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deletedChange.Metadata.OperationType); - Assert.AreEqual(expected: 16, actual: deletedChange.Metadata.PreviousImageLSN); - Assert.IsTrue(deletedChange.Metadata.TimeToLiveExpired); + Assert.AreEqual(expected: 16, actual: deletedChange.Metadata.PreviousLSN); + Assert.IsTrue(deletedChange.Metadata.IsTimeToLiveExpired); Assert.IsNotNull(deletedChange.Previous); Assert.AreEqual(expected: "Testing TTL on CFP.", actual: deletedChange.Previous.description); Assert.AreEqual(expected: "1", actual: deletedChange.Previous.id); @@ -177,11 +177,11 @@ static void ValidateDeserialization(List> activitie Assert.AreEqual(expected: "1", actual: createdUpdate.Current.id); Assert.AreEqual(expected: 5, actual: createdUpdate.Current.ttl); Assert.IsNotNull(createdUpdate.Metadata); - Assert.AreEqual(expected: 1722511453, actual: createdUpdate.Metadata.Crts); + Assert.AreEqual(expected: DateTime.Parse("8/1/2024 11:24:13 AM"), actual: createdUpdate.Metadata.ConflictResolutionTimestamp); Assert.AreEqual(expected: 16, actual: createdUpdate.Metadata.Lsn); Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createdUpdate.Metadata.OperationType); - Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousImageLSN); - Assert.IsFalse(createdUpdate.Metadata.TimeToLiveExpired); + Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousLSN); + Assert.IsFalse(createdUpdate.Metadata.IsTimeToLiveExpired); Assert.IsNull(createdUpdate.Previous); // No Previous for a Create change. } } @@ -282,11 +282,11 @@ static void ValidateDeserialization(List> activitie Assert.AreEqual(expected: "1", actual: createdUpdate.Current.id); Assert.AreEqual(expected: 0, actual: createdUpdate.Current.ttl); Assert.IsNotNull(createdUpdate.Metadata); - Assert.AreEqual(expected: 1722455970, actual: createdUpdate.Metadata.Crts); + Assert.AreEqual(expected: DateTime.Parse("7/31/2024 7:59:30 PM"), actual: createdUpdate.Metadata.ConflictResolutionTimestamp); Assert.AreEqual(expected: 374, actual: createdUpdate.Metadata.Lsn); Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createdUpdate.Metadata.OperationType); - Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousImageLSN); - Assert.IsFalse(createdUpdate.Metadata.TimeToLiveExpired); + Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousLSN); + Assert.IsFalse(createdUpdate.Metadata.IsTimeToLiveExpired); Assert.IsNull(createdUpdate.Previous); // No Previous for a Create change. ChangeFeedItem replacedChange = activities.ElementAt(1); @@ -296,11 +296,11 @@ static void ValidateDeserialization(List> activitie Assert.AreEqual(expected: "1", actual: replacedChange.Current.id); Assert.AreEqual(expected: 0, actual: replacedChange.Current.ttl); Assert.IsNotNull(replacedChange.Metadata); - Assert.AreEqual(expected: 1722455971, actual: replacedChange.Metadata.Crts); + Assert.AreEqual(expected: DateTime.Parse("7/31/2024 7:59:31 PM"), actual: replacedChange.Metadata.ConflictResolutionTimestamp); Assert.AreEqual(expected: 375, actual: replacedChange.Metadata.Lsn); Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replacedChange.Metadata.OperationType); - Assert.AreEqual(expected: 374, actual: replacedChange.Metadata.PreviousImageLSN); - Assert.IsFalse(replacedChange.Metadata.TimeToLiveExpired); + Assert.AreEqual(expected: 374, actual: replacedChange.Metadata.PreviousLSN); + Assert.IsFalse(replacedChange.Metadata.IsTimeToLiveExpired); Assert.IsNull(replacedChange.Previous); // No Previous for a Replace change. ChangeFeedItem deletedChange = activities.ElementAt(2); @@ -310,11 +310,11 @@ static void ValidateDeserialization(List> activitie Assert.AreEqual(expected: default, actual: deletedChange.Current.id); // No current id for Delete Assert.AreEqual(expected: default, actual: deletedChange.Current.ttl); // No current ttl for Delete Assert.IsNotNull(deletedChange.Metadata); - Assert.AreEqual(expected: 1722455972, actual: deletedChange.Metadata.Crts); + Assert.AreEqual(expected: DateTime.Parse("7/31/2024 7:59:32 PM"), actual: deletedChange.Metadata.ConflictResolutionTimestamp); Assert.AreEqual(expected: 376, actual: deletedChange.Metadata.Lsn); Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deletedChange.Metadata.OperationType); - Assert.AreEqual(expected: 375, actual: deletedChange.Metadata.PreviousImageLSN); - Assert.IsFalse(deletedChange.Metadata.TimeToLiveExpired); + Assert.AreEqual(expected: 375, actual: deletedChange.Metadata.PreviousLSN); + Assert.IsFalse(deletedChange.Metadata.IsTimeToLiveExpired); Assert.IsNotNull(deletedChange.Previous); Assert.AreEqual(expected: "test after replace", actual: deletedChange.Previous.description); Assert.AreEqual(expected: "1", actual: deletedChange.Previous.id); @@ -354,9 +354,9 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA Assert.AreEqual(expected: ttlInSeconds, actual: change.Current.ttl); // metadata - Assert.IsTrue(DateTime.TryParse(s: change.Metadata.Crts.ToString(), out _), message: "Invalid csrt must be a datetime value."); + Assert.IsTrue(DateTime.TryParse(s: change.Metadata.ConflictResolutionTimestamp.ToString(), out _), message: "Invalid csrt must be a datetime value."); Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value."); - Assert.IsFalse(change.Metadata.TimeToLiveExpired); + Assert.IsFalse(change.Metadata.IsTimeToLiveExpired); // previous Assert.IsNull(change.Previous); @@ -367,9 +367,9 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA Assert.IsNull(change.Current.id); // metadata - Assert.IsTrue(DateTime.TryParse(s: change.Metadata.Crts.ToString(), out _), message: "Invalid csrt must be a datetime value."); + Assert.IsTrue(DateTime.TryParse(s: change.Metadata.ConflictResolutionTimestamp.ToString(), out _), message: "Invalid csrt must be a datetime value."); Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value."); - Assert.IsTrue(change.Metadata.TimeToLiveExpired); + Assert.IsTrue(change.Metadata.IsTimeToLiveExpired); // previous Assert.AreEqual(expected: "1", actual: change.Previous.id.ToString()); @@ -465,10 +465,10 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() } ChangeFeedOperationType operationType = change.Metadata.OperationType; - long previousLsn = change.Metadata.PreviousImageLSN; - long m = change.Metadata.Crts; + long previousLsn = change.Metadata.PreviousLSN; + DateTime m = change.Metadata.ConflictResolutionTimestamp; long lsn = change.Metadata.Lsn; - bool isTimeToLiveExpired = change.Metadata.TimeToLiveExpired; + bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; } Assert.IsNotNull(context.LeaseToken); @@ -485,7 +485,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.AreEqual(expected: "1", actual: createChange.Current.pk.ToString()); Assert.AreEqual(expected: "original test", actual: createChange.Current.description.ToString()); Assert.AreEqual(expected: createChange.Metadata.OperationType, actual: ChangeFeedOperationType.Create); - Assert.AreEqual(expected: createChange.Metadata.PreviousImageLSN, actual: 0); + Assert.AreEqual(expected: createChange.Metadata.PreviousLSN, actual: 0); Assert.IsNull(createChange.Previous); ChangeFeedItem replaceChange = docs.ElementAt(1); @@ -494,20 +494,20 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.AreEqual(expected: "1", actual: replaceChange.Current.pk.ToString()); Assert.AreEqual(expected: "test after replace", actual: replaceChange.Current.description.ToString()); Assert.AreEqual(expected: replaceChange.Metadata.OperationType, actual: ChangeFeedOperationType.Replace); - Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousImageLSN); + Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLSN); Assert.IsNull(replaceChange.Previous); ChangeFeedItem deleteChange = docs.ElementAt(2); Assert.IsNull(deleteChange.Current.id); Assert.AreEqual(expected: deleteChange.Metadata.OperationType, actual: ChangeFeedOperationType.Delete); - Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousImageLSN); + Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousLSN); Assert.IsNotNull(deleteChange.Previous); Assert.AreEqual(expected: "1", actual: deleteChange.Previous.id.ToString()); Assert.AreEqual(expected: "1", actual: deleteChange.Previous.pk.ToString()); Assert.AreEqual(expected: "test after replace", actual: deleteChange.Previous.description.ToString()); - Assert.IsTrue(condition: createChange.Metadata.Crts < replaceChange.Metadata.Crts, message: "The create operation must happen before the replace operation."); - Assert.IsTrue(condition: replaceChange.Metadata.Crts < deleteChange.Metadata.Crts, message: "The replace operation must happen before the delete operation."); + Assert.IsTrue(condition: createChange.Metadata.ConflictResolutionTimestamp < replaceChange.Metadata.ConflictResolutionTimestamp, message: "The create operation must happen before the replace operation."); + Assert.IsTrue(condition: replaceChange.Metadata.ConflictResolutionTimestamp < deleteChange.Metadata.ConflictResolutionTimestamp, message: "The replace operation must happen before the delete operation."); Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs index e1cffc23d7..59f66aac55 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs @@ -972,10 +972,10 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_VerifyingWir Assert.AreEqual(expected: "98052", actual: createOperation.Current.ZipCode); Assert.IsNotNull(createOperation.Metadata); Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Crts); + Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); - Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousImageLSN); - Assert.IsFalse(createOperation.Metadata.TimeToLiveExpired); + Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLSN); + Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem replaceOperation = itemChanges.ElementAtOrDefault(1); @@ -986,10 +986,10 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_VerifyingWir Assert.AreEqual(expected: "30363", actual: replaceOperation.Current.ZipCode); Assert.IsNotNull(createOperation.Metadata); Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replaceOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Crts); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousImageLSN); - Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLSN); + Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); break; } @@ -1051,10 +1051,10 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreEqual(expected: "Thailand", actual: firstCreateOperation.Current.State); Assert.AreEqual(expected: "10330", actual: firstCreateOperation.Current.ZipCode); Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: firstCreateOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: firstCreateOperation.Metadata.Crts); + Assert.AreNotEqual(notExpected: default, actual: firstCreateOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: firstCreateOperation.Metadata.Lsn); - Assert.AreEqual(expected: default, actual: firstCreateOperation.Metadata.PreviousImageLSN); - Assert.IsFalse(firstCreateOperation.Metadata.TimeToLiveExpired); + Assert.AreEqual(expected: default, actual: firstCreateOperation.Metadata.PreviousLSN); + Assert.IsFalse(firstCreateOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem createOperation = resources[1]; @@ -1064,10 +1064,10 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreEqual(expected: "WA", actual: createOperation.Current.State); Assert.AreEqual(expected: "98052", actual: createOperation.Current.ZipCode); Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Crts); + Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); - Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousImageLSN); - Assert.IsFalse(createOperation.Metadata.TimeToLiveExpired); + Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLSN); + Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem replaceOperation = resources[2]; @@ -1077,10 +1077,10 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreEqual(expected: "GA", actual: replaceOperation.Current.State); Assert.AreEqual(expected: "30363", actual: replaceOperation.Current.ZipCode); Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replaceOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Crts); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousImageLSN); - Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLSN); + Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem deleteOperation = resources[3]; @@ -1090,9 +1090,9 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.IsNull(deleteOperation.Current.State); Assert.IsNull(deleteOperation.Current.ZipCode); Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deleteOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.Crts); + Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousImageLSN); + Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousLSN); Assert.IsNotNull(deleteOperation.Previous); Assert.AreEqual(expected: id, actual: deleteOperation.Previous.Id); Assert.AreEqual(expected: "205 16th St NW", actual: deleteOperation.Previous.Line1); @@ -1160,10 +1160,10 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreEqual(expected: "98052", actual: createOperation.Current.ZipCode); Assert.IsNotNull(createOperation.Metadata); Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Crts); + Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); - Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousImageLSN); - Assert.IsFalse(createOperation.Metadata.TimeToLiveExpired); + Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLSN); + Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem replaceOperation = itemChanges[1]; @@ -1174,19 +1174,19 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreEqual(expected: "30363", actual: replaceOperation.Current.ZipCode); Assert.IsNotNull(replaceOperation.Metadata); Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replaceOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Crts); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousImageLSN); - Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLSN); + Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem deleteOperation = itemChanges[2]; Assert.IsNotNull(deleteOperation.Metadata); Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deleteOperation.Metadata.OperationType); - Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.Crts); + Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousImageLSN); - Assert.IsFalse(replaceOperation.Metadata.TimeToLiveExpired); + Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousLSN); + Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); break; } From 3a879b7848cfe72499eeeceff4291216923c7fd7 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 5 Aug 2024 09:45:00 -0400 Subject: [PATCH 03/15] update contracts. --- .../Converters/STJUnixDateTimeConverter.cs | 4 +- .../Contracts/DotNetPreviewSDKAPI.json | 90 ++++++++++++------- 2 files changed, 60 insertions(+), 34 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/STJUnixDateTimeConverter.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/STJUnixDateTimeConverter.cs index 6ac96839ea..db93c62a1c 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/STJUnixDateTimeConverter.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/STJUnixDateTimeConverter.cs @@ -11,7 +11,7 @@ namespace Microsoft.Azure.Cosmos.Resource.FullFidelity.Converters /// /// UnixDateTimeConverter for System.Text.Json /// - public class STJUnixDateTimeConverter : JsonConverter + internal class STJUnixDateTimeConverter : JsonConverter { /// /// Read. @@ -24,7 +24,7 @@ public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, Jso { if (reader.TokenType != JsonTokenType.Number) { - throw new System.Text.Json.JsonException("Expected a number representing the Unix timestamp."); + throw new JsonException("Expected a number representing the Unix timestamp."); } long unixTime = reader.GetInt64(); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index b2b02b8974..0a342e71d6 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -10,14 +10,20 @@ ], "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedMetadata get_Metadata();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.ChangeFeedMetadata Metadata": { + "Microsoft.Azure.Cosmos.ChangeFeedMetadata Metadata[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"metadata\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"metadata\")]": { "Type": "Property", - "Attributes": [], + "Attributes": [ + "JsonPropertyAttribute", + "JsonPropertyNameAttribute" + ], "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedMetadata Metadata;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.ChangeFeedMetadata get_Metadata();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Metadata(Microsoft.Azure.Cosmos.ChangeFeedMetadata);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "T Current": { + "T Current[Newtonsoft.Json.JsonPropertyAttribute(PropertyName = \"current\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"current\")]": { "Type": "Property", - "Attributes": [], + "Attributes": [ + "JsonPropertyAttribute", + "JsonPropertyNameAttribute" + ], "MethodInfo": "T Current;CanRead:True;CanWrite:True;T get_Current();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Current(T);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "T get_Current()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { @@ -34,9 +40,12 @@ ], "MethodInfo": "T get_Previous();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "T Previous": { + "T Previous[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"previous\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"previous\")]": { "Type": "Property", - "Attributes": [], + "Attributes": [ + "JsonPropertyAttribute", + "JsonPropertyNameAttribute" + ], "MethodInfo": "T Previous;CanRead:True;CanWrite:True;T get_Previous();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;Void set_Previous(T);IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "Void .ctor()": { @@ -71,29 +80,20 @@ "Microsoft.Azure.Cosmos.ChangeFeedMetadata;System.Object;IsAbstract:False;IsSealed:False;IsInterface:False;IsEnum:False;IsClass:True;IsValueType:False;IsNested:False;IsGenericType:False;IsSerializable:False": { "Subclasses": {}, "Members": { - "Boolean get_TimeToLiveExpired()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Boolean get_IsTimeToLiveExpired()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "Boolean get_TimeToLiveExpired();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Boolean get_IsTimeToLiveExpired();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Boolean TimeToLiveExpired": { + "Boolean IsTimeToLiveExpired[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"timeToLiveExpired\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"timeToLiveExpired\")]": { "Type": "Property", - "Attributes": [], - "MethodInfo": "Boolean TimeToLiveExpired;CanRead:True;CanWrite:False;Boolean get_TimeToLiveExpired();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Int64 Crts": { - "Type": "Property", - "Attributes": [], - "MethodInfo": "Int64 Crts;CanRead:True;CanWrite:False;Int64 get_Crts();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" - }, - "Int64 get_Crts()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { - "Type": "Method", "Attributes": [ - "CompilerGeneratedAttribute" + "JsonPropertyAttribute", + "JsonPropertyNameAttribute" ], - "MethodInfo": "Int64 get_Crts();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Boolean IsTimeToLiveExpired;CanRead:True;CanWrite:False;Boolean get_IsTimeToLiveExpired();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "Int64 get_Lsn()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", @@ -102,22 +102,28 @@ ], "MethodInfo": "Int64 get_Lsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Int64 get_PreviousImageLSN()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Int64 get_PreviousLSN()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "Int64 get_PreviousImageLSN();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Int64 get_PreviousLSN();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Int64 Lsn": { + "Int64 Lsn[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"lsn\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"lsn\")]": { "Type": "Property", - "Attributes": [], + "Attributes": [ + "JsonPropertyAttribute", + "JsonPropertyNameAttribute" + ], "MethodInfo": "Int64 Lsn;CanRead:True;CanWrite:False;Int64 get_Lsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Int64 PreviousImageLSN": { + "Int64 PreviousLSN[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"previousImageLSN\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"previousImageLSN\")]": { "Type": "Property", - "Attributes": [], - "MethodInfo": "Int64 PreviousImageLSN;CanRead:True;CanWrite:False;Int64 get_PreviousImageLSN();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "Attributes": [ + "JsonPropertyAttribute", + "JsonPropertyNameAttribute" + ], + "MethodInfo": "Int64 PreviousLSN;CanRead:True;CanWrite:False;Int64 get_PreviousLSN();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", @@ -126,15 +132,35 @@ ], "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType": { + "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"operationType\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"operationType\")]": { "Type": "Property", - "Attributes": [], + "Attributes": [ + "JsonPropertyAttribute", + "JsonPropertyNameAttribute" + ], "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType;CanRead:True;CanWrite:False;Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Void .ctor(Int64, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean)": { + "System.DateTime ConflictResolutionTimestamp[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"crts\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"crts\")]-[Newtonsoft.Json.JsonConverterAttribute(typeof(Microsoft.Azure.Documents.UnixDateTimeConverter))]-[System.Text.Json.Serialization.JsonConverterAttribute(typeof(Microsoft.Azure.Cosmos.Resource.FullFidelity.Converters.STJUnixDateTimeConverter))]": { + "Type": "Property", + "Attributes": [ + "JsonConverterAttribute", + "JsonConverterAttribute", + "JsonPropertyAttribute", + "JsonPropertyNameAttribute" + ], + "MethodInfo": "System.DateTime ConflictResolutionTimestamp;CanRead:True;CanWrite:False;System.DateTime get_ConflictResolutionTimestamp();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "System.DateTime get_ConflictResolutionTimestamp()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "System.DateTime get_ConflictResolutionTimestamp();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + }, + "Void .ctor(System.DateTime, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean)": { "Type": "Constructor", "Attributes": [], - "MethodInfo": "[Void .ctor(Int64, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean), Void .ctor(Int64, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean)]" + "MethodInfo": "[Void .ctor(System.DateTime, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean), Void .ctor(System.DateTime, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean)]" } }, "NestedTypes": {} From 23e763120bda5e0c30fb944278df38a3118b7472 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 5 Aug 2024 13:56:50 -0400 Subject: [PATCH 04/15] name change PreviousLsn --- .../FullFidelity/ChangeFeedMetadata.cs | 10 +++++----- .../CFP/AllVersionsAndDeletes/BuilderTests.cs | 8 ++++---- .../BuilderWithCustomSerializerTests.cs | 18 +++++++++--------- .../FeedToken/ChangeFeedIteratorCoreTests.cs | 18 +++++++++--------- .../Contracts/DotNetPreviewSDKAPI.json | 8 ++++---- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs index 09b51f72f0..126317713d 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs @@ -26,19 +26,19 @@ class ChangeFeedMetadata /// /// /// - /// + /// /// public ChangeFeedMetadata( DateTime conflictResolutionTimestamp, long lsn, ChangeFeedOperationType operationType, - long previousLSN, + long previousLsn, bool isTimeToLiveExpired) { this.ConflictResolutionTimestamp = conflictResolutionTimestamp; this.Lsn = lsn; this.OperationType = operationType; - this.PreviousLSN = previousLSN; + this.PreviousLsn = previousLsn; this.IsTimeToLiveExpired = isTimeToLiveExpired; } @@ -68,9 +68,9 @@ public ChangeFeedMetadata( /// /// The previous logical sequence number. /// - [Newtonsoft.Json.JsonProperty(PropertyName = "previousImageLSN", NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty(PropertyName = "previousImageLSN", NullValueHandling = NullValueHandling.Ignore)] [JsonPropertyName("previousImageLSN")] - public long PreviousLSN { get; } + public long PreviousLsn { get; } /// /// Used to distinquish explicit deletes (e.g. via DeleteItem) from deletes caused by TTL expiration (a collection may define time-to-live policy for documents). diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs index 80ac8aa4bf..6ebfd0f8ed 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs @@ -177,7 +177,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() } ChangeFeedOperationType operationType = change.Metadata.OperationType; - long previousLsn = change.Metadata.PreviousLSN; + long previousLsn = change.Metadata.PreviousLsn; DateTime m = change.Metadata.ConflictResolutionTimestamp; long lsn = change.Metadata.Lsn; bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; @@ -197,7 +197,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.AreEqual(expected: "1", actual: createChange.Current.pk.ToString()); Assert.AreEqual(expected: "original test", actual: createChange.Current.description.ToString()); Assert.AreEqual(expected: createChange.Metadata.OperationType, actual: ChangeFeedOperationType.Create); - Assert.AreEqual(expected: createChange.Metadata.PreviousLSN, actual: 0); + Assert.AreEqual(expected: createChange.Metadata.PreviousLsn, actual: 0); Assert.IsNull(createChange.Previous); ChangeFeedItem replaceChange = docs.ElementAt(1); @@ -206,13 +206,13 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.AreEqual(expected: "1", actual: replaceChange.Current.pk.ToString()); Assert.AreEqual(expected: "test after replace", actual: replaceChange.Current.description.ToString()); Assert.AreEqual(expected: replaceChange.Metadata.OperationType, actual: ChangeFeedOperationType.Replace); - Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLSN); + Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLsn); Assert.IsNull(replaceChange.Previous); ChangeFeedItem deleteChange = docs.ElementAt(2); Assert.IsNull(deleteChange.Current.id); Assert.AreEqual(expected: deleteChange.Metadata.OperationType, actual: ChangeFeedOperationType.Delete); - Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousLSN); + Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousLsn); Assert.IsNotNull(deleteChange.Previous); Assert.AreEqual(expected: "1", actual: deleteChange.Previous.id.ToString()); Assert.AreEqual(expected: "1", actual: deleteChange.Previous.pk.ToString()); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs index 3230da542f..2d5a3be2a9 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs @@ -112,7 +112,7 @@ static void ValidateDeserialization(List> activitie Assert.AreEqual(expected: DateTime.Parse("8/1/2024 11:26:31 AM"), actual: deletedChange.Metadata.ConflictResolutionTimestamp); Assert.AreEqual(expected: 17, actual: deletedChange.Metadata.Lsn); Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deletedChange.Metadata.OperationType); - Assert.AreEqual(expected: 16, actual: deletedChange.Metadata.PreviousLSN); + Assert.AreEqual(expected: 16, actual: deletedChange.Metadata.PreviousLsn); Assert.IsTrue(deletedChange.Metadata.IsTimeToLiveExpired); Assert.IsNotNull(deletedChange.Previous); Assert.AreEqual(expected: "Testing TTL on CFP.", actual: deletedChange.Previous.description); @@ -180,7 +180,7 @@ static void ValidateDeserialization(List> activitie Assert.AreEqual(expected: DateTime.Parse("8/1/2024 11:24:13 AM"), actual: createdUpdate.Metadata.ConflictResolutionTimestamp); Assert.AreEqual(expected: 16, actual: createdUpdate.Metadata.Lsn); Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createdUpdate.Metadata.OperationType); - Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousLSN); + Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousLsn); Assert.IsFalse(createdUpdate.Metadata.IsTimeToLiveExpired); Assert.IsNull(createdUpdate.Previous); // No Previous for a Create change. } @@ -285,7 +285,7 @@ static void ValidateDeserialization(List> activitie Assert.AreEqual(expected: DateTime.Parse("7/31/2024 7:59:30 PM"), actual: createdUpdate.Metadata.ConflictResolutionTimestamp); Assert.AreEqual(expected: 374, actual: createdUpdate.Metadata.Lsn); Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createdUpdate.Metadata.OperationType); - Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousLSN); + Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousLsn); Assert.IsFalse(createdUpdate.Metadata.IsTimeToLiveExpired); Assert.IsNull(createdUpdate.Previous); // No Previous for a Create change. @@ -299,7 +299,7 @@ static void ValidateDeserialization(List> activitie Assert.AreEqual(expected: DateTime.Parse("7/31/2024 7:59:31 PM"), actual: replacedChange.Metadata.ConflictResolutionTimestamp); Assert.AreEqual(expected: 375, actual: replacedChange.Metadata.Lsn); Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replacedChange.Metadata.OperationType); - Assert.AreEqual(expected: 374, actual: replacedChange.Metadata.PreviousLSN); + Assert.AreEqual(expected: 374, actual: replacedChange.Metadata.PreviousLsn); Assert.IsFalse(replacedChange.Metadata.IsTimeToLiveExpired); Assert.IsNull(replacedChange.Previous); // No Previous for a Replace change. @@ -313,7 +313,7 @@ static void ValidateDeserialization(List> activitie Assert.AreEqual(expected: DateTime.Parse("7/31/2024 7:59:32 PM"), actual: deletedChange.Metadata.ConflictResolutionTimestamp); Assert.AreEqual(expected: 376, actual: deletedChange.Metadata.Lsn); Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deletedChange.Metadata.OperationType); - Assert.AreEqual(expected: 375, actual: deletedChange.Metadata.PreviousLSN); + Assert.AreEqual(expected: 375, actual: deletedChange.Metadata.PreviousLsn); Assert.IsFalse(deletedChange.Metadata.IsTimeToLiveExpired); Assert.IsNotNull(deletedChange.Previous); Assert.AreEqual(expected: "test after replace", actual: deletedChange.Previous.description); @@ -465,7 +465,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() } ChangeFeedOperationType operationType = change.Metadata.OperationType; - long previousLsn = change.Metadata.PreviousLSN; + long previousLsn = change.Metadata.PreviousLsn; DateTime m = change.Metadata.ConflictResolutionTimestamp; long lsn = change.Metadata.Lsn; bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; @@ -485,7 +485,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.AreEqual(expected: "1", actual: createChange.Current.pk.ToString()); Assert.AreEqual(expected: "original test", actual: createChange.Current.description.ToString()); Assert.AreEqual(expected: createChange.Metadata.OperationType, actual: ChangeFeedOperationType.Create); - Assert.AreEqual(expected: createChange.Metadata.PreviousLSN, actual: 0); + Assert.AreEqual(expected: createChange.Metadata.PreviousLsn, actual: 0); Assert.IsNull(createChange.Previous); ChangeFeedItem replaceChange = docs.ElementAt(1); @@ -494,13 +494,13 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() Assert.AreEqual(expected: "1", actual: replaceChange.Current.pk.ToString()); Assert.AreEqual(expected: "test after replace", actual: replaceChange.Current.description.ToString()); Assert.AreEqual(expected: replaceChange.Metadata.OperationType, actual: ChangeFeedOperationType.Replace); - Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLSN); + Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLsn); Assert.IsNull(replaceChange.Previous); ChangeFeedItem deleteChange = docs.ElementAt(2); Assert.IsNull(deleteChange.Current.id); Assert.AreEqual(expected: deleteChange.Metadata.OperationType, actual: ChangeFeedOperationType.Delete); - Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousLSN); + Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousLsn); Assert.IsNotNull(deleteChange.Previous); Assert.AreEqual(expected: "1", actual: deleteChange.Previous.id.ToString()); Assert.AreEqual(expected: "1", actual: deleteChange.Previous.pk.ToString()); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs index 59f66aac55..a43d86faf5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/FeedToken/ChangeFeedIteratorCoreTests.cs @@ -974,7 +974,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_VerifyingWir Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createOperation.Metadata.OperationType); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); - Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLSN); + Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLsn); Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem replaceOperation = itemChanges.ElementAtOrDefault(1); @@ -988,7 +988,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_VerifyingWir Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replaceOperation.Metadata.OperationType); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLSN); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLsn); Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); break; @@ -1053,7 +1053,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: firstCreateOperation.Metadata.OperationType); Assert.AreNotEqual(notExpected: default, actual: firstCreateOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: firstCreateOperation.Metadata.Lsn); - Assert.AreEqual(expected: default, actual: firstCreateOperation.Metadata.PreviousLSN); + Assert.AreEqual(expected: default, actual: firstCreateOperation.Metadata.PreviousLsn); Assert.IsFalse(firstCreateOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem createOperation = resources[1]; @@ -1066,7 +1066,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createOperation.Metadata.OperationType); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); - Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLSN); + Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLsn); Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem replaceOperation = resources[2]; @@ -1079,7 +1079,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replaceOperation.Metadata.OperationType); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLSN); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLsn); Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem deleteOperation = resources[3]; @@ -1092,7 +1092,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_VerifyingWireFormatTests() Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deleteOperation.Metadata.OperationType); Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousLSN); + Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousLsn); Assert.IsNotNull(deleteOperation.Previous); Assert.AreEqual(expected: id, actual: deleteOperation.Previous.Id); Assert.AreEqual(expected: "205 16th St NW", actual: deleteOperation.Previous.Line1); @@ -1162,7 +1162,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createOperation.Metadata.OperationType); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: createOperation.Metadata.Lsn); - Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLSN); + Assert.AreEqual(expected: default, actual: createOperation.Metadata.PreviousLsn); Assert.IsFalse(createOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem replaceOperation = itemChanges[1]; @@ -1176,7 +1176,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replaceOperation.Metadata.OperationType); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLSN); + Assert.AreNotEqual(notExpected: default, actual: replaceOperation.Metadata.PreviousLsn); Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); ChangeFeedItem deleteOperation = itemChanges[2]; @@ -1185,7 +1185,7 @@ public async Task ChangeFeedIteratorCore_FeedRange_FromPartitionKey_Dynamic_Veri Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deleteOperation.Metadata.OperationType); Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.ConflictResolutionTimestamp); Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.Lsn); - Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousLSN); + Assert.AreNotEqual(notExpected: default, actual: deleteOperation.Metadata.PreviousLsn); Assert.IsFalse(replaceOperation.Metadata.IsTimeToLiveExpired); break; diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index 0a342e71d6..2384d37fe4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -102,12 +102,12 @@ ], "MethodInfo": "Int64 get_Lsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Int64 get_PreviousLSN()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Int64 get_PreviousLsn()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", "Attributes": [ "CompilerGeneratedAttribute" ], - "MethodInfo": "Int64 get_PreviousLSN();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Int64 get_PreviousLsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "Int64 Lsn[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"lsn\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"lsn\")]": { "Type": "Property", @@ -117,13 +117,13 @@ ], "MethodInfo": "Int64 Lsn;CanRead:True;CanWrite:False;Int64 get_Lsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Int64 PreviousLSN[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"previousImageLSN\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"previousImageLSN\")]": { + "Int64 PreviousLsn[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"previousImageLSN\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"previousImageLSN\")]": { "Type": "Property", "Attributes": [ "JsonPropertyAttribute", "JsonPropertyNameAttribute" ], - "MethodInfo": "Int64 PreviousLSN;CanRead:True;CanWrite:False;Int64 get_PreviousLSN();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Int64 PreviousLsn;CanRead:True;CanWrite:False;Int64 get_PreviousLsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", From 375236cf97ef680f903f8ddc95e1a3529464f883 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 6 Aug 2024 14:19:47 -0400 Subject: [PATCH 05/15] STJ TypeConverter support for ChangeFeedMetadata --- .../FullFidelity/ChangeFeedMetadata.cs | 62 +++++--------- .../FullFidelity/ChangeFeedMetadataFields.cs | 15 ++++ .../Converters/ChangeFeedMetadataConverter.cs | 80 +++++++++++++++++++ .../Converters/STJUnixDateTimeConverter.cs | 46 ----------- .../Contracts/DotNetPreviewSDKAPI.json | 40 ++++------ 5 files changed, 130 insertions(+), 113 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadataFields.cs create mode 100644 Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs delete mode 100644 Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/STJUnixDateTimeConverter.cs diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs index 126317713d..ed8a50a428 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs @@ -6,13 +6,15 @@ namespace Microsoft.Azure.Cosmos { using System; using System.Text.Json; - using System.Text.Json.Serialization; + using Microsoft.Azure.Cosmos.Resource.FullFidelity; + using Microsoft.Azure.Cosmos.Resource.FullFidelity.Converters; using Microsoft.Azure.Documents; using Newtonsoft.Json; /// /// The metadata of a change feed resource with is initialized to . /// + [System.Text.Json.Serialization.JsonConverter(typeof(ChangeFeedMetadataConverter))] #if PREVIEW public #else @@ -21,62 +23,34 @@ namespace Microsoft.Azure.Cosmos class ChangeFeedMetadata { /// - /// New instance of meta data for created. + /// The change's conflict resolution timestamp. /// - /// - /// - /// - /// - /// - public ChangeFeedMetadata( - DateTime conflictResolutionTimestamp, - long lsn, - ChangeFeedOperationType operationType, - long previousLsn, - bool isTimeToLiveExpired) - { - this.ConflictResolutionTimestamp = conflictResolutionTimestamp; - this.Lsn = lsn; - this.OperationType = operationType; - this.PreviousLsn = previousLsn; - this.IsTimeToLiveExpired = isTimeToLiveExpired; - } + [JsonProperty(PropertyName = ChangeFeedMetadataFields.ConflictResolutionTimestamp, NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(UnixDateTimeConverter))] + public DateTime ConflictResolutionTimestamp { get; internal set; } /// - /// The conflict resolution timestamp. + /// The current change's logical sequence number. /// - [JsonProperty(PropertyName = "crts", NullValueHandling = NullValueHandling.Ignore)] - [JsonPropertyName("crts")] - [Newtonsoft.Json.JsonConverter(typeof(UnixDateTimeConverter))] - [System.Text.Json.Serialization.JsonConverter(typeof(Resource.FullFidelity.Converters.STJUnixDateTimeConverter))] - public DateTime ConflictResolutionTimestamp { get; } + [JsonProperty(PropertyName = ChangeFeedMetadataFields.Lsn, NullValueHandling = NullValueHandling.Ignore)] + public long Lsn { get; internal set; } /// - /// The current logical sequence number. + /// The change's feed operation type . /// - [JsonProperty(PropertyName = "lsn", NullValueHandling = NullValueHandling.Ignore)] - [JsonPropertyName("lsn")] - public long Lsn { get; } + [JsonProperty(PropertyName = ChangeFeedMetadataFields.OperationType, NullValueHandling = NullValueHandling.Ignore)] + public ChangeFeedOperationType OperationType { get; internal set; } /// - /// The change feed operation type. + /// The previous change's logical sequence number. /// - [JsonProperty(PropertyName = "operationType", NullValueHandling = NullValueHandling.Ignore)] - [JsonPropertyName("operationType")] - public ChangeFeedOperationType OperationType { get; } - - /// - /// The previous logical sequence number. - /// - [JsonProperty(PropertyName = "previousImageLSN", NullValueHandling = NullValueHandling.Ignore)] - [JsonPropertyName("previousImageLSN")] - public long PreviousLsn { get; } + [JsonProperty(PropertyName = ChangeFeedMetadataFields.PreviousImageLSN, NullValueHandling = NullValueHandling.Ignore)] + public long PreviousLsn { get; internal set; } /// /// Used to distinquish explicit deletes (e.g. via DeleteItem) from deletes caused by TTL expiration (a collection may define time-to-live policy for documents). /// - [JsonProperty(PropertyName = "timeToLiveExpired", NullValueHandling = NullValueHandling.Ignore)] - [JsonPropertyName("timeToLiveExpired")] - public bool IsTimeToLiveExpired { get; } + [JsonProperty(PropertyName = ChangeFeedMetadataFields.TimeToLiveExpired, NullValueHandling = NullValueHandling.Ignore)] + public bool IsTimeToLiveExpired { get; internal set; } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadataFields.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadataFields.cs new file mode 100644 index 0000000000..db39a386a9 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadataFields.cs @@ -0,0 +1,15 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Resource.FullFidelity +{ + internal class ChangeFeedMetadataFields + { + public const string ConflictResolutionTimestamp = "crts"; + public const string Lsn = "lsn"; + public const string OperationType = "operationType"; + public const string PreviousImageLSN = "previousImageLSN"; + public const string TimeToLiveExpired = "timeToLiveExpired"; + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs new file mode 100644 index 0000000000..58bbe3e8a3 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs @@ -0,0 +1,80 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Resource.FullFidelity.Converters +{ + using System; + using System.Globalization; + using System.Text.Json; + using System.Text.Json.Serialization; + using Microsoft.Azure.Cosmos.Resource.FullFidelity; + using Microsoft.Azure.Documents; + + /// + /// Converter used to support System.Text.Json de/serialization of type ChangeFeedMetadata/>. + /// + internal class ChangeFeedMetadataConverter : JsonConverter + { + public override ChangeFeedMetadata Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(string.Format(CultureInfo.CurrentCulture, RMResources.JsonUnexpectedToken)); + } + + JsonElement element = JsonDocument.ParseValue(ref reader).RootElement; + + ChangeFeedMetadata metadata = new (); + + foreach (JsonProperty property in element.EnumerateObject()) + { + if (property.NameEquals(ChangeFeedMetadataFields.Lsn)) + { + metadata.Lsn = property.Value.GetInt64(); + } + else if (property.NameEquals(ChangeFeedMetadataFields.ConflictResolutionTimestamp)) + { + metadata.ConflictResolutionTimestamp = DateTimeOffset.FromUnixTimeSeconds(property.Value.GetInt64()).UtcDateTime; + } + else if (property.NameEquals(ChangeFeedMetadataFields.OperationType)) + { + metadata.OperationType = (ChangeFeedOperationType)Enum.Parse(enumType: typeof(ChangeFeedOperationType), value: property.Value.GetString(), ignoreCase: true); + } + else if (property.NameEquals(ChangeFeedMetadataFields.TimeToLiveExpired)) + { + metadata.IsTimeToLiveExpired = property.Value.GetBoolean(); + } + else if (property.NameEquals(ChangeFeedMetadataFields.PreviousImageLSN)) + { + metadata.PreviousLsn = property.Value.GetInt64(); + } + } + + return metadata; + } + + public override void Write(Utf8JsonWriter writer, ChangeFeedMetadata value, JsonSerializerOptions options) + { + if (value == null) + { + return; + } + + writer.WriteStartObject(); + + writer.WriteNumber(ChangeFeedMetadataFields.ConflictResolutionTimestamp, ((DateTimeOffset)value.ConflictResolutionTimestamp).ToUnixTimeSeconds()); + writer.WriteBoolean(ChangeFeedMetadataFields.TimeToLiveExpired, value.IsTimeToLiveExpired); + writer.WriteNumber(ChangeFeedMetadataFields.Lsn, value.Lsn); + writer.WriteString(ChangeFeedMetadataFields.OperationType, value.OperationType.ToString()); + writer.WriteNumber(ChangeFeedMetadataFields.PreviousImageLSN, value.PreviousLsn); + + writer.WriteEndObject(); + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/STJUnixDateTimeConverter.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/STJUnixDateTimeConverter.cs deleted file mode 100644 index db93c62a1c..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/STJUnixDateTimeConverter.cs +++ /dev/null @@ -1,46 +0,0 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Resource.FullFidelity.Converters -{ - using System; - using System.Text.Json; - using System.Text.Json.Serialization; - - /// - /// UnixDateTimeConverter for System.Text.Json - /// - internal class STJUnixDateTimeConverter : JsonConverter - { - /// - /// Read. - /// - /// - /// - /// - /// DateTime. - public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - if (reader.TokenType != JsonTokenType.Number) - { - throw new JsonException("Expected a number representing the Unix timestamp."); - } - - long unixTime = reader.GetInt64(); - return DateTimeOffset.FromUnixTimeSeconds(unixTime).UtcDateTime; - } - - /// - /// Write. - /// - /// - /// - /// - public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) - { - long unixTime = ((DateTimeOffset)value).ToUnixTimeSeconds(); - writer.WriteNumberValue(unixTime); - } - } -} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index 2384d37fe4..577689e583 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -87,13 +87,12 @@ ], "MethodInfo": "Boolean get_IsTimeToLiveExpired();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Boolean IsTimeToLiveExpired[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"timeToLiveExpired\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"timeToLiveExpired\")]": { + "Boolean IsTimeToLiveExpired[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"timeToLiveExpired\")]": { "Type": "Property", "Attributes": [ - "JsonPropertyAttribute", - "JsonPropertyNameAttribute" + "JsonPropertyAttribute" ], - "MethodInfo": "Boolean IsTimeToLiveExpired;CanRead:True;CanWrite:False;Boolean get_IsTimeToLiveExpired();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Boolean IsTimeToLiveExpired;CanRead:True;CanWrite:True;Boolean get_IsTimeToLiveExpired();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "Int64 get_Lsn()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", @@ -109,21 +108,19 @@ ], "MethodInfo": "Int64 get_PreviousLsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Int64 Lsn[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"lsn\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"lsn\")]": { + "Int64 Lsn[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"lsn\")]": { "Type": "Property", "Attributes": [ - "JsonPropertyAttribute", - "JsonPropertyNameAttribute" + "JsonPropertyAttribute" ], - "MethodInfo": "Int64 Lsn;CanRead:True;CanWrite:False;Int64 get_Lsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Int64 Lsn;CanRead:True;CanWrite:True;Int64 get_Lsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Int64 PreviousLsn[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"previousImageLSN\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"previousImageLSN\")]": { + "Int64 PreviousLsn[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"previousImageLSN\")]": { "Type": "Property", "Attributes": [ - "JsonPropertyAttribute", - "JsonPropertyNameAttribute" + "JsonPropertyAttribute" ], - "MethodInfo": "Int64 PreviousLsn;CanRead:True;CanWrite:False;Int64 get_PreviousLsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Int64 PreviousLsn;CanRead:True;CanWrite:True;Int64 get_PreviousLsn();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", @@ -132,23 +129,20 @@ ], "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"operationType\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"operationType\")]": { + "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"operationType\")]": { "Type": "Property", "Attributes": [ - "JsonPropertyAttribute", - "JsonPropertyNameAttribute" + "JsonPropertyAttribute" ], - "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType;CanRead:True;CanWrite:False;Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "System.DateTime ConflictResolutionTimestamp[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"crts\")]-[System.Text.Json.Serialization.JsonPropertyNameAttribute(\"crts\")]-[Newtonsoft.Json.JsonConverterAttribute(typeof(Microsoft.Azure.Documents.UnixDateTimeConverter))]-[System.Text.Json.Serialization.JsonConverterAttribute(typeof(Microsoft.Azure.Cosmos.Resource.FullFidelity.Converters.STJUnixDateTimeConverter))]": { + "System.DateTime ConflictResolutionTimestamp[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"crts\")]-[Newtonsoft.Json.JsonConverterAttribute(typeof(Microsoft.Azure.Documents.UnixDateTimeConverter))]": { "Type": "Property", "Attributes": [ "JsonConverterAttribute", - "JsonConverterAttribute", - "JsonPropertyAttribute", - "JsonPropertyNameAttribute" + "JsonPropertyAttribute" ], - "MethodInfo": "System.DateTime ConflictResolutionTimestamp;CanRead:True;CanWrite:False;System.DateTime get_ConflictResolutionTimestamp();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" + "MethodInfo": "System.DateTime ConflictResolutionTimestamp;CanRead:True;CanWrite:True;System.DateTime get_ConflictResolutionTimestamp();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, "System.DateTime get_ConflictResolutionTimestamp()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", @@ -157,10 +151,10 @@ ], "MethodInfo": "System.DateTime get_ConflictResolutionTimestamp();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Void .ctor(System.DateTime, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean)": { + "Void .ctor()": { "Type": "Constructor", "Attributes": [], - "MethodInfo": "[Void .ctor(System.DateTime, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean), Void .ctor(System.DateTime, Int64, Microsoft.Azure.Cosmos.ChangeFeedOperationType, Int64, Boolean)]" + "MethodInfo": "[Void .ctor(), Void .ctor()]" } }, "NestedTypes": {} From 7acac4d98ee38aad85fe48da3c97fbde8ee43ee6 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 6 Aug 2024 14:50:32 -0400 Subject: [PATCH 06/15] adding bacl StringEnumConverter --- .../src/Resource/FullFidelity/ChangeFeedMetadata.cs | 2 ++ .../Contracts/DotNetPreviewSDKAPI.json | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs index ed8a50a428..1dae4f1e1b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMetadata.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Resource.FullFidelity.Converters; using Microsoft.Azure.Documents; using Newtonsoft.Json; + using Newtonsoft.Json.Converters; /// /// The metadata of a change feed resource with is initialized to . @@ -39,6 +40,7 @@ class ChangeFeedMetadata /// The change's feed operation type . /// [JsonProperty(PropertyName = ChangeFeedMetadataFields.OperationType, NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(StringEnumConverter))] public ChangeFeedOperationType OperationType { get; internal set; } /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json index 577689e583..8c46a28c20 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Contracts/DotNetPreviewSDKAPI.json @@ -129,9 +129,10 @@ ], "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" }, - "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"operationType\")]": { + "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType[Newtonsoft.Json.JsonPropertyAttribute(NullValueHandling = 1, PropertyName = \"operationType\")]-[Newtonsoft.Json.JsonConverterAttribute(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]": { "Type": "Property", "Attributes": [ + "JsonConverterAttribute", "JsonPropertyAttribute" ], "MethodInfo": "Microsoft.Azure.Cosmos.ChangeFeedOperationType OperationType;CanRead:True;CanWrite:True;Microsoft.Azure.Cosmos.ChangeFeedOperationType get_OperationType();IsAbstract:False;IsStatic:False;IsVirtual:False;IsGenericMethod:False;IsConstructor:False;IsFinal:False;" From 6a71a821dc840323bf93d648a458315bcfc742c4 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Tue, 6 Aug 2024 18:33:58 -0400 Subject: [PATCH 07/15] test for Writes ChangeFeedMetadata --- .../BuilderWithCustomSerializerTests.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs index 2d5a3be2a9..cc9e5de6b8 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs @@ -322,6 +322,50 @@ static void ValidateDeserialization(List> activitie } } + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Replace and Deletes have full ChangeFeedMetadata.")] + public void ValidateChangeFeedMetadataSerializationReplaceAnDeleteWriteTest() + { + ChangeFeedMetadata metadata = new() + { + PreviousLsn = 15, + Lsn = 374, + OperationType = ChangeFeedOperationType.Create, + IsTimeToLiveExpired = true, + ConflictResolutionTimestamp = DateTime.Parse("7/31/2024 7:59:30 PM") + }; + + string json = System.Text.Json.JsonSerializer.Serialize( + value: metadata, + options: new JsonSerializerOptions()); + + Assert.AreEqual( + expected: @"{""crts"":1722470370,""timeToLiveExpired"":true,""lsn"":374,""operationType"":""Create"",""previousImageLSN"":15}", + actual: json); + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Creates have partial ChangeFeedMetadata.")] + public void ValidateChangeFeedMetadataSerializationCreateWriteTest() + { + ChangeFeedMetadata metadata = new() + { + Lsn = 374, + OperationType = ChangeFeedOperationType.Create, + ConflictResolutionTimestamp = DateTime.Parse("7/31/2024 7:59:30 PM") + }; + + string json = System.Text.Json.JsonSerializer.Serialize( + value: metadata, + options: new JsonSerializerOptions()); + + Assert.AreEqual( + expected: @"{""crts"":1722470370,""timeToLiveExpired"":false,""lsn"":374,""operationType"":""Create"",""previousImageLSN"":0}", + actual: json); + } + [TestMethod] [Timeout(300000)] [TestCategory("LongRunning")] From b6fde92a6db6ea78ec77efd4d0d0128749b076cc Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 7 Aug 2024 08:30:52 -0400 Subject: [PATCH 08/15] removing DateTimeOffset as results are inconsistent. --- .../Converters/ChangeFeedMetadataConverter.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs index 58bbe3e8a3..c611842850 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs @@ -40,7 +40,7 @@ public override ChangeFeedMetadata Read(ref Utf8JsonReader reader, Type typeToCo } else if (property.NameEquals(ChangeFeedMetadataFields.ConflictResolutionTimestamp)) { - metadata.ConflictResolutionTimestamp = DateTimeOffset.FromUnixTimeSeconds(property.Value.GetInt64()).UtcDateTime; + metadata.ConflictResolutionTimestamp = ChangeFeedMetadataConverter.ToDateTimeFromUnixTimeInSeconds(property.Value.GetInt64()); } else if (property.NameEquals(ChangeFeedMetadataFields.OperationType)) { @@ -68,7 +68,7 @@ public override void Write(Utf8JsonWriter writer, ChangeFeedMetadata value, Json writer.WriteStartObject(); - writer.WriteNumber(ChangeFeedMetadataFields.ConflictResolutionTimestamp, ((DateTimeOffset)value.ConflictResolutionTimestamp).ToUnixTimeSeconds()); + writer.WriteNumber(ChangeFeedMetadataFields.ConflictResolutionTimestamp, ChangeFeedMetadataConverter.ToUnixTimeInSecondsFromDateTime(value.ConflictResolutionTimestamp)); writer.WriteBoolean(ChangeFeedMetadataFields.TimeToLiveExpired, value.IsTimeToLiveExpired); writer.WriteNumber(ChangeFeedMetadataFields.Lsn, value.Lsn); writer.WriteString(ChangeFeedMetadataFields.OperationType, value.OperationType.ToString()); @@ -76,5 +76,17 @@ public override void Write(Utf8JsonWriter writer, ChangeFeedMetadata value, Json writer.WriteEndObject(); } + + private static long ToUnixTimeInSecondsFromDateTime(DateTime date) + { + DateTime unixEpoch = new (1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + return (date.ToUniversalTime().Ticks - unixEpoch.Ticks) / TimeSpan.TicksPerSecond; + } + + private static DateTime ToDateTimeFromUnixTimeInSeconds(long unixTimeInSeconds) + { + DateTime unixEpoch = new (1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + return unixEpoch.AddSeconds(unixTimeInSeconds); + } } } From f2d1e6f8f9be4baabd5514aff186266a7fa4c7c0 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 7 Aug 2024 09:56:52 -0400 Subject: [PATCH 09/15] trying to get GMT, not local --- .../FullFidelity/Converters/ChangeFeedMetadataConverter.cs | 2 +- .../AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs index c611842850..bb2264062b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs @@ -80,7 +80,7 @@ public override void Write(Utf8JsonWriter writer, ChangeFeedMetadata value, Json private static long ToUnixTimeInSecondsFromDateTime(DateTime date) { DateTime unixEpoch = new (1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - return (date.ToUniversalTime().Ticks - unixEpoch.Ticks) / TimeSpan.TicksPerSecond; + return (long)(date - unixEpoch).TotalSeconds; } private static DateTime ToDateTimeFromUnixTimeInSeconds(long unixTimeInSeconds) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs index cc9e5de6b8..92d5ad1eb8 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs @@ -341,7 +341,7 @@ public void ValidateChangeFeedMetadataSerializationReplaceAnDeleteWriteTest() options: new JsonSerializerOptions()); Assert.AreEqual( - expected: @"{""crts"":1722470370,""timeToLiveExpired"":true,""lsn"":374,""operationType"":""Create"",""previousImageLSN"":15}", + expected: @"{""crts"":1722455970,""timeToLiveExpired"":true,""lsn"":374,""operationType"":""Create"",""previousImageLSN"":15}", actual: json); } @@ -362,7 +362,7 @@ public void ValidateChangeFeedMetadataSerializationCreateWriteTest() options: new JsonSerializerOptions()); Assert.AreEqual( - expected: @"{""crts"":1722470370,""timeToLiveExpired"":false,""lsn"":374,""operationType"":""Create"",""previousImageLSN"":0}", + expected: @"{""crts"":1722455970,""timeToLiveExpired"":false,""lsn"":374,""operationType"":""Create"",""previousImageLSN"":0}", actual: json); } From 84fdcc38c351e3b3779300fb38ec3f46b7d71c21 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Wed, 7 Aug 2024 13:00:37 -0400 Subject: [PATCH 10/15] static UnixEpoch --- .../Converters/ChangeFeedMetadataConverter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs index bb2264062b..0b5056051a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/FullFidelity/Converters/ChangeFeedMetadataConverter.cs @@ -16,6 +16,8 @@ namespace Microsoft.Azure.Cosmos.Resource.FullFidelity.Converters /// internal class ChangeFeedMetadataConverter : JsonConverter { + private readonly static DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + public override ChangeFeedMetadata Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) @@ -79,14 +81,12 @@ public override void Write(Utf8JsonWriter writer, ChangeFeedMetadata value, Json private static long ToUnixTimeInSecondsFromDateTime(DateTime date) { - DateTime unixEpoch = new (1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - return (long)(date - unixEpoch).TotalSeconds; + return (long)(date - ChangeFeedMetadataConverter.UnixEpoch).TotalSeconds; } private static DateTime ToDateTimeFromUnixTimeInSeconds(long unixTimeInSeconds) { - DateTime unixEpoch = new (1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - return unixEpoch.AddSeconds(unixTimeInSeconds); + return ChangeFeedMetadataConverter.UnixEpoch.AddSeconds(unixTimeInSeconds); } } } From d30e7afe1039d362043bf280d91efcb994e5599d Mon Sep 17 00:00:00 2001 From: philipthomas Date: Fri, 16 Aug 2024 11:14:18 -0400 Subject: [PATCH 11/15] static qualifier in tests --- .../CFP/AllVersionsAndDeletes/BuilderTests.cs | 14 +++++++------- .../BuilderWithCustomSerializerTests.cs | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs index 6ebfd0f8ed..669c6bd194 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderTests.cs @@ -121,7 +121,7 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA try { - await Task.Delay(ChangeFeedSetupTime); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); await monitoredContainer.CreateItemAsync(new ToDoActivity { id = "1", pk = "1", description = "Testing TTL on CFP.", ttl = ttlInSeconds }, partitionKey: new PartitionKey("1")); // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. @@ -239,7 +239,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() // 1 second delay between operations to get different timestamps. await processor.StartAsync(); - await Task.Delay(ChangeFeedSetupTime); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); await monitoredContainer.CreateItemAsync(new { id = "1", pk = "1", description = "original test" }, partitionKey: new PartitionKey("1")); await Task.Delay(1000); @@ -249,7 +249,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() await monitoredContainer.DeleteItemAsync(id: "1", partitionKey: new PartitionKey("1")); - bool isStartOk = allDocsProcessed.WaitOne(10 * ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); await processor.StopAsync(); @@ -531,8 +531,8 @@ private static async Task BuildChangeFeedProcessorWithLatestVersionAsync( Interlocked.Exchange(ref latestVersionProcessorAtomic, processor); await processor.StartAsync(); - await Task.Delay(ChangeFeedSetupTime); - bool isStartOk = allDocsProcessed.WaitOne(10 * ChangeFeedSetupTime); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); if (exception != default) { @@ -564,8 +564,8 @@ private static async Task BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync Interlocked.Exchange(ref allVersionsAndDeletesProcessorAtomic, processor); await processor.StartAsync(); - await Task.Delay(ChangeFeedSetupTime); - bool isStartOk = allDocsProcessed.WaitOne(10 * ChangeFeedSetupTime); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); if (exception != default) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs index 92d5ad1eb8..c866f6220a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs @@ -453,7 +453,7 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA try { - await Task.Delay(ChangeFeedSetupTime); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); await monitoredContainer.CreateItemAsync(new ToDoActivity { id = "1", pk = "1", description = "Testing TTL on CFP.", ttl = ttlInSeconds }, partitionKey: new PartitionKey("1")); // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. @@ -571,7 +571,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() // 1 second delay between operations to get different timestamps. await processor.StartAsync(); - await Task.Delay(ChangeFeedSetupTime); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); await monitoredContainer.CreateItemAsync(new ToDoActivity { id = "1", pk = "1", description = "original test", ttl = -1 }, partitionKey: new PartitionKey("1")); await Task.Delay(1000); @@ -581,7 +581,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() await monitoredContainer.DeleteItemAsync(id: "1", partitionKey: new PartitionKey("1")); - bool isStartOk = allDocsProcessed.WaitOne(10 * ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); await processor.StopAsync(); @@ -658,8 +658,8 @@ private static async Task BuildChangeFeedProcessorWithLatestVersionAsync( Interlocked.Exchange(ref latestVersionProcessorAtomic, processor); await processor.StartAsync(); - await Task.Delay(ChangeFeedSetupTime); - bool isStartOk = allDocsProcessed.WaitOne(10 * ChangeFeedSetupTime); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); if (exception != default) { @@ -691,8 +691,8 @@ private static async Task BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync Interlocked.Exchange(ref allVersionsAndDeletesProcessorAtomic, processor); await processor.StartAsync(); - await Task.Delay(ChangeFeedSetupTime); - bool isStartOk = allDocsProcessed.WaitOne(10 * ChangeFeedSetupTime); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); if (exception != default) { From 121d45958eff95c90b57b4ff6ec9b865cb794943 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 19 Aug 2024 08:49:25 -0400 Subject: [PATCH 12/15] PropertyNameCaseInsensitive = false tests. copy of True tests. --- ...erPropertyNameCaseInsensitiveFalseTests.cs | 722 ++++++++++++++++++ ...erPropertyNameCaseInsensitiveTrueTests.cs} | 14 +- .../CFP/AllVersionsAndDeletes/ToDoActivity.cs | 18 + 3 files changed, 741 insertions(+), 13 deletions(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs rename Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/{BuilderWithCustomSerializerTests.cs => BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs} (98%) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/ToDoActivity.cs diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs new file mode 100644 index 0000000000..d45640ecb8 --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs @@ -0,0 +1,722 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.CFP.AllVersionsAndDeletes +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos; + using Microsoft.Azure.Cosmos.ChangeFeed.Utils; + using Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed; + using Microsoft.Azure.Cosmos.Services.Management.Tests; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + [TestClass] + [TestCategory("ChangeFeedProcessor")] + public partial class BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests : BaseChangeFeedClientHelper + { + private ContainerInternal MonitoredContainer = null; + + [TestInitialize] + public async Task TestInitialize() + { + CosmosClient cosmosClient = TestCommon.CreateCosmosClient((cosmosClientBuilder) => + cosmosClientBuilder.WithSystemTextJsonSerializerOptions( + new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = false, + Converters = { new JsonStringEnumConverter() } + }), + useCustomSeralizer: false); + + base.database = await cosmosClient.CreateDatabaseIfNotExistsAsync(id: Guid.NewGuid().ToString()); + this.LeaseContainer = await this.database.CreateContainerIfNotExistsAsync(containerProperties: new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + this.MonitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + } + + [TestCleanup] + public async Task Cleanup() + { + await this.TestCleanup(); + } + + [TestMethod] + [Owner("philipthomas")] + [Description("Validating to deserization of ChangeFeedItem with a Delete payload with TimeToLiveExpired set to true.")] + public void ValidateNSJAndSTJSerializationOfChangeFeedItemDeleteTimeToLiveExpiredIsTrueTest() + { + string json = @"[ + { + ""current"": {}, + ""metadata"": { + ""lsn"": 17, + ""crts"": 1722511591, + ""operationType"": ""delete"", + ""timeToLiveExpired"": true, + ""previousImageLSN"": 16 + }, + ""previous"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""Testing TTL on CFP."", + ""ttl"": 5, + ""_rid"": ""SnxPAOM2VfMBAAAAAAAAAA=="", + ""_self"": ""dbs/SnxPAA==/colls/SnxPAOM2VfM=/docs/SnxPAOM2VfMBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e405-5632b83c01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722511453 + } + } + ]"; + + ValidateSystemTextJsonDeserialization(json); + ValidateNewtonsoftJsonDeserialization(json); + + static void ValidateSystemTextJsonDeserialization(string json) + { + ValidateDeserialization( + System.Text.Json.JsonSerializer.Deserialize>>( + json: json, + options: new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = false, + Converters = { new JsonStringEnumConverter(), } + })); + } + + static void ValidateNewtonsoftJsonDeserialization(string json) + { + ValidateDeserialization(JsonConvert.DeserializeObject>>(json)); + } + + static void ValidateDeserialization(List> activities) + { + Assert.IsNotNull(activities); + + ChangeFeedItem deletedChange = activities.ElementAt(0); + Assert.IsNotNull(deletedChange); + Assert.IsNotNull(deletedChange.Current); // Current is not null, but not data. + Assert.AreEqual(expected: default, actual: deletedChange.Current.description); // No current description for Delete + Assert.AreEqual(expected: default, actual: deletedChange.Current.id); // No current id for Delete + Assert.AreEqual(expected: default, actual: deletedChange.Current.ttl); // No current ttl for Delete + Assert.IsNotNull(deletedChange.Metadata); + Assert.AreEqual(expected: DateTime.Parse("8/1/2024 11:26:31 AM"), actual: deletedChange.Metadata.ConflictResolutionTimestamp); + Assert.AreEqual(expected: 17, actual: deletedChange.Metadata.Lsn); + Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deletedChange.Metadata.OperationType); + Assert.AreEqual(expected: 16, actual: deletedChange.Metadata.PreviousLsn); + Assert.IsTrue(deletedChange.Metadata.IsTimeToLiveExpired); + Assert.IsNotNull(deletedChange.Previous); + Assert.AreEqual(expected: "Testing TTL on CFP.", actual: deletedChange.Previous.description); + Assert.AreEqual(expected: "1", actual: deletedChange.Previous.id); + Assert.AreEqual(expected: 5, actual: deletedChange.Previous.ttl); + } + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Validating to deserization using NSJ and STJ of ChangeFeedItem with a Create payload with TTL set to a non-default value.")] + public void ValidateNSJAndSTJSerializationOfChangeFeedItemCreateTTLTest() + { + string json = @"[ + { + ""current"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""Testing TTL on CFP."", + ""ttl"": 5, + ""_rid"": ""SnxPAOM2VfMBAAAAAAAAAA=="", + ""_self"": ""dbs/SnxPAA==/colls/SnxPAOM2VfM=/docs/SnxPAOM2VfMBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e405-5632b83c01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722511453 + }, + ""metadata"": { + ""lsn"": 16, + ""crts"": 1722511453, + ""operationType"": ""create"" + } + } + ]"; + + ValidateSystemTextJsonDeserialization(json); + ValidateNewtonsoftJsonDeserialization(json); + + static void ValidateSystemTextJsonDeserialization(string json) + { + ValidateDeserialization(System.Text.Json.JsonSerializer.Deserialize>>( + json: json, + options: new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true, + Converters = { new JsonStringEnumConverter(), } + })); + } + + static void ValidateNewtonsoftJsonDeserialization(string json) + { + ValidateDeserialization(JsonConvert.DeserializeObject>>(json)); + } + + static void ValidateDeserialization(List> activities) + { + Assert.IsNotNull(activities); + + ChangeFeedItem createdUpdate = activities.ElementAt(0); + Assert.IsNotNull(createdUpdate); + Assert.IsNotNull(createdUpdate.Current); + Assert.AreEqual(expected: "Testing TTL on CFP.", actual: createdUpdate.Current.description); + Assert.AreEqual(expected: "1", actual: createdUpdate.Current.id); + Assert.AreEqual(expected: 5, actual: createdUpdate.Current.ttl); + Assert.IsNotNull(createdUpdate.Metadata); + Assert.AreEqual(expected: DateTime.Parse("8/1/2024 11:24:13 AM"), actual: createdUpdate.Metadata.ConflictResolutionTimestamp); + Assert.AreEqual(expected: 16, actual: createdUpdate.Metadata.Lsn); + Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createdUpdate.Metadata.OperationType); + Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousLsn); + Assert.IsFalse(createdUpdate.Metadata.IsTimeToLiveExpired); + Assert.IsNull(createdUpdate.Previous); // No Previous for a Create change. + } + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Validating to deserization using NSJ and STJ of ChangeFeedItem with a Create, Replace, and Delete payload.")] + public void ValidateNSJAndSTJSerializationOfChangeFeedItemTest() + { + string json = @"[ + { + ""current"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""original test"", + ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", + ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e384-28095c1a01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722455970 + }, + ""metadata"": { + ""crts"": 1722455970, + ""lsn"": 374, + ""operationType"": ""create"", + ""previousImageLSN"": 0, + ""timeToLiveExpired"": false + } + }, + { + ""current"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""test after replace"", + ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", + ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e384-28a5abdd01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722455971 + }, + ""metadata"": { + ""crts"": 1722455971, + ""lsn"": 375, + ""operationType"": ""replace"", + ""previousImageLSN"": 374, + ""timeToLiveExpired"": false + } + }, + { + ""current"": {}, + ""metadata"": { + ""crts"": 1722455972, + ""lsn"": 376, + ""operationType"": ""delete"", + ""previousImageLSN"": 375, + ""timeToLiveExpired"": false + }, + ""previous"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""test after replace"", + ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", + ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e384-28a5abdd01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722455971 + } + } + ]"; + + ValidateSystemTextJsonDeserialization(json); + ValidateNewtonsoftJsonDeserialization(json); + + static void ValidateNewtonsoftJsonDeserialization(string json) + { + ValidateDeserialization(JsonConvert.DeserializeObject>>(json)); + } + + static void ValidateSystemTextJsonDeserialization(string json) + { + ValidateDeserialization(System.Text.Json.JsonSerializer.Deserialize>>( + json: json, + options: new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true, + Converters = { new JsonStringEnumConverter(), } + })); + } + + static void ValidateDeserialization(List> activities) + { + Assert.IsNotNull(activities); + + ChangeFeedItem createdUpdate = activities.ElementAt(0); + Assert.IsNotNull(createdUpdate); + Assert.IsNotNull(createdUpdate.Current); + Assert.AreEqual(expected: "original test", actual: createdUpdate.Current.description); + Assert.AreEqual(expected: "1", actual: createdUpdate.Current.id); + Assert.AreEqual(expected: 0, actual: createdUpdate.Current.ttl); + Assert.IsNotNull(createdUpdate.Metadata); + Assert.AreEqual(expected: DateTime.Parse("7/31/2024 7:59:30 PM"), actual: createdUpdate.Metadata.ConflictResolutionTimestamp); + Assert.AreEqual(expected: 374, actual: createdUpdate.Metadata.Lsn); + Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createdUpdate.Metadata.OperationType); + Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousLsn); + Assert.IsFalse(createdUpdate.Metadata.IsTimeToLiveExpired); + Assert.IsNull(createdUpdate.Previous); // No Previous for a Create change. + + ChangeFeedItem replacedChange = activities.ElementAt(1); + Assert.IsNotNull(replacedChange); + Assert.IsNotNull(replacedChange.Current); + Assert.AreEqual(expected: "test after replace", actual: replacedChange.Current.description); + Assert.AreEqual(expected: "1", actual: replacedChange.Current.id); + Assert.AreEqual(expected: 0, actual: replacedChange.Current.ttl); + Assert.IsNotNull(replacedChange.Metadata); + Assert.AreEqual(expected: DateTime.Parse("7/31/2024 7:59:31 PM"), actual: replacedChange.Metadata.ConflictResolutionTimestamp); + Assert.AreEqual(expected: 375, actual: replacedChange.Metadata.Lsn); + Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replacedChange.Metadata.OperationType); + Assert.AreEqual(expected: 374, actual: replacedChange.Metadata.PreviousLsn); + Assert.IsFalse(replacedChange.Metadata.IsTimeToLiveExpired); + Assert.IsNull(replacedChange.Previous); // No Previous for a Replace change. + + ChangeFeedItem deletedChange = activities.ElementAt(2); + Assert.IsNotNull(deletedChange); + Assert.IsNotNull(deletedChange.Current); // Current is not null, but not data. + Assert.AreEqual(expected: default, actual: deletedChange.Current.description); // No current description for Delete + Assert.AreEqual(expected: default, actual: deletedChange.Current.id); // No current id for Delete + Assert.AreEqual(expected: default, actual: deletedChange.Current.ttl); // No current ttl for Delete + Assert.IsNotNull(deletedChange.Metadata); + Assert.AreEqual(expected: DateTime.Parse("7/31/2024 7:59:32 PM"), actual: deletedChange.Metadata.ConflictResolutionTimestamp); + Assert.AreEqual(expected: 376, actual: deletedChange.Metadata.Lsn); + Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deletedChange.Metadata.OperationType); + Assert.AreEqual(expected: 375, actual: deletedChange.Metadata.PreviousLsn); + Assert.IsFalse(deletedChange.Metadata.IsTimeToLiveExpired); + Assert.IsNotNull(deletedChange.Previous); + Assert.AreEqual(expected: "test after replace", actual: deletedChange.Previous.description); + Assert.AreEqual(expected: "1", actual: deletedChange.Previous.id); + Assert.AreEqual(expected: 0, actual: deletedChange.Previous.ttl); + } + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Replace and Deletes have full ChangeFeedMetadata.")] + public void ValidateChangeFeedMetadataSerializationReplaceAnDeleteWriteTest() + { + ChangeFeedMetadata metadata = new() + { + PreviousLsn = 15, + Lsn = 374, + OperationType = ChangeFeedOperationType.Create, + IsTimeToLiveExpired = true, + ConflictResolutionTimestamp = DateTime.Parse("7/31/2024 7:59:30 PM") + }; + + string json = System.Text.Json.JsonSerializer.Serialize( + value: metadata, + options: new JsonSerializerOptions()); + + Assert.AreEqual( + expected: @"{""crts"":1722455970,""timeToLiveExpired"":true,""lsn"":374,""operationType"":""Create"",""previousImageLSN"":15}", + actual: json); + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Creates have partial ChangeFeedMetadata.")] + public void ValidateChangeFeedMetadataSerializationCreateWriteTest() + { + ChangeFeedMetadata metadata = new() + { + Lsn = 374, + OperationType = ChangeFeedOperationType.Create, + ConflictResolutionTimestamp = DateTime.Parse("7/31/2024 7:59:30 PM") + }; + + string json = System.Text.Json.JsonSerializer.Serialize( + value: metadata, + options: new JsonSerializerOptions()); + + Assert.AreEqual( + expected: @"{""crts"":1722455970,""timeToLiveExpired"":false,""lsn"":374,""operationType"":""Create"",""previousImageLSN"":0}", + actual: json); + } + + [TestMethod] + [Timeout(300000)] + [TestCategory("LongRunning")] + [Owner("philipthomas-MSFT")] + [Description("Scenario: When a document is created with ttl set, there should be 1 create and 1 delete that will appear for that " + + "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] + public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsAsync() + { + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + Exception exception = default; + int ttlInSeconds = 5; + Stopwatch stopwatch = new(); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + + ChangeFeedProcessor processor = monitoredContainer + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => + { + // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. + + Logger.LogLine($"@ {DateTime.Now}, {nameof(stopwatch)} -> CFP AVAD took '{stopwatch.ElapsedMilliseconds}' to read document CRUD in feed."); + + foreach (ChangeFeedItem change in docs) + { + if (change.Metadata.OperationType == ChangeFeedOperationType.Create) + { + // current + Assert.AreEqual(expected: "1", actual: change.Current.id.ToString()); + Assert.AreEqual(expected: "1", actual: change.Current.pk.ToString()); + Assert.AreEqual(expected: "Testing TTL on CFP.", actual: change.Current.description.ToString()); + Assert.AreEqual(expected: ttlInSeconds, actual: change.Current.ttl); + + // metadata + Assert.IsTrue(DateTime.TryParse(s: change.Metadata.ConflictResolutionTimestamp.ToString(), out _), message: "Invalid csrt must be a datetime value."); + Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value."); + Assert.IsFalse(change.Metadata.IsTimeToLiveExpired); + + // previous + Assert.IsNull(change.Previous); + } + else if (change.Metadata.OperationType == ChangeFeedOperationType.Delete) + { + // current + Assert.IsNull(change.Current.id); + + // metadata + Assert.IsTrue(DateTime.TryParse(s: change.Metadata.ConflictResolutionTimestamp.ToString(), out _), message: "Invalid csrt must be a datetime value."); + Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value."); + Assert.IsTrue(change.Metadata.IsTimeToLiveExpired); + + // previous + Assert.AreEqual(expected: "1", actual: change.Previous.id.ToString()); + Assert.AreEqual(expected: "1", actual: change.Previous.pk.ToString()); + Assert.AreEqual(expected: "Testing TTL on CFP.", actual: change.Previous.description.ToString()); + Assert.AreEqual(expected: ttlInSeconds, actual: change.Previous.ttl); + + // stop after reading delete since it is the last document in feed. + stopwatch.Stop(); + allDocsProcessed.Set(); + } + else + { + Assert.Fail("Invalid operation."); + } + } + + return Task.CompletedTask; + }) + .WithInstanceName(Guid.NewGuid().ToString()) + .WithLeaseContainer(this.LeaseContainer) + .WithErrorNotification((leaseToken, error) => + { + exception = error.InnerException; + + return Task.CompletedTask; + }) + .Build(); + + stopwatch.Start(); + + // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. + + Logger.LogLine($"@ {DateTime.Now}, CFProcessor starting..."); + + await processor.StartAsync(); + + try + { + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + await monitoredContainer.CreateItemAsync(new ToDoActivity { id = "1", pk = "1", description = "Testing TTL on CFP.", ttl = ttlInSeconds }, partitionKey: new PartitionKey("1")); + + // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. + + Logger.LogLine($"@ {DateTime.Now}, Document created."); + + bool receivedDelete = allDocsProcessed.WaitOne(250000); + Assert.IsTrue(receivedDelete, "Timed out waiting for docs to process"); + + if (exception != default) + { + Assert.Fail(exception.ToString()); + } + } + finally + { + await processor.StopAsync(); + } + } + + [TestMethod] + [Owner("philipthomas-MSFT")] + [Description("Scenario: When a document is created, then updated, and finally deleted, there should be 3 changes that will appear for that " + + "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] + public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() + { + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + ManualResetEvent allDocsProcessed = new ManualResetEvent(false); + Exception exception = default; + + ChangeFeedProcessor processor = monitoredContainer + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => + { + Logger.LogLine($"@ {DateTime.Now}, {nameof(docs)} -> {System.Text.Json.JsonSerializer.Serialize(docs)}"); + + string id = default; + string pk = default; + string description = default; + + foreach (ChangeFeedItem change in docs) + { + if (change.Metadata.OperationType != ChangeFeedOperationType.Delete) + { + id = change.Current.id.ToString(); + pk = change.Current.pk.ToString(); + description = change.Current.description.ToString(); + } + else + { + id = change.Previous.id.ToString(); + pk = change.Previous.pk.ToString(); + description = change.Previous.description.ToString(); + } + + ChangeFeedOperationType operationType = change.Metadata.OperationType; + long previousLsn = change.Metadata.PreviousLsn; + DateTime m = change.Metadata.ConflictResolutionTimestamp; + long lsn = change.Metadata.Lsn; + bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; + } + + Assert.IsNotNull(context.LeaseToken); + Assert.IsNotNull(context.Diagnostics); + Assert.IsNotNull(context.Headers); + Assert.IsNotNull(context.Headers.Session); + Assert.IsTrue(context.Headers.RequestCharge > 0); + Assert.IsTrue(context.Diagnostics.ToString().Contains("Change Feed Processor Read Next Async")); + Assert.AreEqual(expected: 3, actual: docs.Count); + + ChangeFeedItem createChange = docs.ElementAt(0); + Assert.IsNotNull(createChange.Current); + Assert.AreEqual(expected: "1", actual: createChange.Current.id.ToString()); + Assert.AreEqual(expected: "1", actual: createChange.Current.pk.ToString()); + Assert.AreEqual(expected: "original test", actual: createChange.Current.description.ToString()); + Assert.AreEqual(expected: createChange.Metadata.OperationType, actual: ChangeFeedOperationType.Create); + Assert.AreEqual(expected: createChange.Metadata.PreviousLsn, actual: 0); + Assert.IsNull(createChange.Previous); + + ChangeFeedItem replaceChange = docs.ElementAt(1); + Assert.IsNotNull(replaceChange.Current); + Assert.AreEqual(expected: "1", actual: replaceChange.Current.id.ToString()); + Assert.AreEqual(expected: "1", actual: replaceChange.Current.pk.ToString()); + Assert.AreEqual(expected: "test after replace", actual: replaceChange.Current.description.ToString()); + Assert.AreEqual(expected: replaceChange.Metadata.OperationType, actual: ChangeFeedOperationType.Replace); + Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLsn); + Assert.IsNull(replaceChange.Previous); + + ChangeFeedItem deleteChange = docs.ElementAt(2); + Assert.IsNull(deleteChange.Current.id); + Assert.AreEqual(expected: deleteChange.Metadata.OperationType, actual: ChangeFeedOperationType.Delete); + Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousLsn); + Assert.IsNotNull(deleteChange.Previous); + Assert.AreEqual(expected: "1", actual: deleteChange.Previous.id.ToString()); + Assert.AreEqual(expected: "1", actual: deleteChange.Previous.pk.ToString()); + Assert.AreEqual(expected: "test after replace", actual: deleteChange.Previous.description.ToString()); + + Assert.IsTrue(condition: createChange.Metadata.ConflictResolutionTimestamp < replaceChange.Metadata.ConflictResolutionTimestamp, message: "The create operation must happen before the replace operation."); + Assert.IsTrue(condition: replaceChange.Metadata.ConflictResolutionTimestamp < deleteChange.Metadata.ConflictResolutionTimestamp, message: "The replace operation must happen before the delete operation."); + Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); + Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); + + return Task.CompletedTask; + }) + .WithInstanceName(Guid.NewGuid().ToString()) + .WithLeaseContainer(this.LeaseContainer) + .WithErrorNotification((leaseToken, error) => + { + exception = error.InnerException; + + return Task.CompletedTask; + }) + .Build(); + + // Start the processor, insert 1 document to generate a checkpoint, modify it, and then delete it. + // 1 second delay between operations to get different timestamps. + + await processor.StartAsync(); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + + await monitoredContainer.CreateItemAsync(new ToDoActivity { id = "1", pk = "1", description = "original test", ttl = -1 }, partitionKey: new PartitionKey("1")); + await Task.Delay(1000); + + await monitoredContainer.UpsertItemAsync(new ToDoActivity { id = "1", pk = "1", description = "test after replace", ttl = -1 }, partitionKey: new PartitionKey("1")); + await Task.Delay(1000); + + await monitoredContainer.DeleteItemAsync(id: "1", partitionKey: new PartitionKey("1")); + + bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); + + await processor.StopAsync(); + + if (exception != default) + { + Assert.Fail(exception.ToString()); + } + } + + private static async Task RevertLeaseDocumentsToLegacyWithNoMode( + Container leaseContainer, + int leaseDocumentCount) + { + FeedIterator iterator = leaseContainer.GetItemQueryStreamIterator( + queryText: "SELECT * FROM c", + continuationToken: null); + + List leases = new List(); + while (iterator.HasMoreResults) + { + using (ResponseMessage responseMessage = await iterator.ReadNextAsync().ConfigureAwait(false)) + { + responseMessage.EnsureSuccessStatusCode(); + leases.AddRange(CosmosFeedResponseSerializer.FromFeedResponseStream( + serializerCore: CosmosContainerExtensions.DefaultJsonSerializer, + streamWithServiceEnvelope: responseMessage.Content)); + } + } + + int counter = 0; + + foreach (JObject lease in leases) + { + if (!lease.ContainsKey("Mode")) + { + continue; + } + + counter++; + lease.Remove("Mode"); + + _ = await leaseContainer.UpsertItemAsync(item: lease); + } + + Assert.AreEqual(expected: leaseDocumentCount, actual: counter); + } + + private static async Task BuildChangeFeedProcessorWithLatestVersionAsync( + ContainerInternal monitoredContainer, + Container leaseContainer, + ManualResetEvent allDocsProcessed, + bool withStartFromBeginning) + { + Exception exception = default; + ChangeFeedProcessor latestVersionProcessorAtomic = null; + + ChangeFeedProcessorBuilder processorBuilder = monitoredContainer + .GetChangeFeedProcessorBuilder(processorName: $"processorName", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection documents, CancellationToken token) => Task.CompletedTask) + .WithInstanceName(Guid.NewGuid().ToString()) + .WithLeaseContainer(leaseContainer) + .WithErrorNotification((leaseToken, error) => + { + exception = error.InnerException; + + return Task.CompletedTask; + }); + + if (withStartFromBeginning) + { + processorBuilder.WithStartFromBeginning(); + } + + ChangeFeedProcessor processor = processorBuilder.Build(); + Interlocked.Exchange(ref latestVersionProcessorAtomic, processor); + + await processor.StartAsync(); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); + + if (exception != default) + { + Assert.Fail(exception.ToString()); + } + } + + private static async Task BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( + ContainerInternal monitoredContainer, + Container leaseContainer, + ManualResetEvent allDocsProcessed) + { + Exception exception = default; + ChangeFeedProcessor allVersionsAndDeletesProcessorAtomic = null; + + ChangeFeedProcessorBuilder allVersionsAndDeletesProcessorBuilder = monitoredContainer + .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: $"processorName", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> documents, CancellationToken token) => Task.CompletedTask) + .WithInstanceName(Guid.NewGuid().ToString()) + .WithMaxItems(1) + .WithLeaseContainer(leaseContainer) + .WithErrorNotification((leaseToken, error) => + { + exception = error.InnerException; + + return Task.FromResult(exception); + }); + + ChangeFeedProcessor processor = allVersionsAndDeletesProcessorBuilder.Build(); + Interlocked.Exchange(ref allVersionsAndDeletesProcessorAtomic, processor); + + await processor.StartAsync(); + await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); + bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); + + if (exception != default) + { + Assert.Fail(exception.ToString()); + } + } + + private async Task CreateMonitoredContainer(ChangeFeedMode changeFeedMode) + { + string PartitionKey = "/pk"; + ContainerProperties properties = new ContainerProperties(id: Guid.NewGuid().ToString(), + partitionKeyPath: PartitionKey); + + if (changeFeedMode == ChangeFeedMode.AllVersionsAndDeletes) + { + properties.ChangeFeedPolicy.FullFidelityRetention = TimeSpan.FromMinutes(5); + properties.DefaultTimeToLive = -1; + } + + ContainerResponse response = await this.database.CreateContainerAsync(properties, + throughput: 10000, + cancellationToken: this.cancellationToken); + + return (ContainerInternal)response; + } + } +} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs similarity index 98% rename from Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs rename to Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs index c866f6220a..7a899e340b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs @@ -22,7 +22,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.CFP.AllVersionsAndDeletes [TestClass] [TestCategory("ChangeFeedProcessor")] - public class BuilderWithCustomSerializerTests : BaseChangeFeedClientHelper + public class BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests : BaseChangeFeedClientHelper { private ContainerInternal MonitoredContainer = null; @@ -719,16 +719,4 @@ private async Task CreateMonitoredContainer(ChangeFeedMode ch return (ContainerInternal)response; } } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "")] - public class ToDoActivity - { - public string id { get; set; } - - public string pk { get; set; } - - public string description { get; set; } - - public int ttl { get; set; } - } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/ToDoActivity.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/ToDoActivity.cs new file mode 100644 index 0000000000..1058c8f3fe --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/ToDoActivity.cs @@ -0,0 +1,18 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.CFP.AllVersionsAndDeletes +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Used for CFP AllVersionsAndDeletes builder tests without having attribute annotations from STJ or NSJ.")] + public class ToDoActivity + { + public string id { get; set; } + + public string pk { get; set; } + + public string description { get; set; } + + public int ttl { get; set; } + } +} From 0339f168ad00c4342194306a7bf099450d14552c Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 19 Aug 2024 11:34:11 -0400 Subject: [PATCH 13/15] setting PropertyNameCaseInsensitive correctly for tests --- ...thCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs | 4 ++-- ...ithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs index d45640ecb8..04dd4a55a2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs @@ -156,7 +156,7 @@ static void ValidateSystemTextJsonDeserialization(string json) json: json, options: new JsonSerializerOptions() { - PropertyNameCaseInsensitive = true, + PropertyNameCaseInsensitive = false, Converters = { new JsonStringEnumConverter(), } })); } @@ -266,7 +266,7 @@ static void ValidateSystemTextJsonDeserialization(string json) json: json, options: new JsonSerializerOptions() { - PropertyNameCaseInsensitive = true, + PropertyNameCaseInsensitive = false, Converters = { new JsonStringEnumConverter(), } })); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs index 7a899e340b..994c3b9e6b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs @@ -88,7 +88,7 @@ static void ValidateSystemTextJsonDeserialization(string json) json: json, options: new JsonSerializerOptions() { - PropertyNameCaseInsensitive = false, + PropertyNameCaseInsensitive = true, Converters = { new JsonStringEnumConverter(), } })); } From 3b13a156a79862ee7e5c7bcabc7bd395bf4f8581 Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 19 Aug 2024 13:38:45 -0400 Subject: [PATCH 14/15] removed duplication for propertyNameCaseInsensitive tests --- ...zerPropertyNameCaseInsensitiveTrueTests.cs | 722 ------------------ ...cs => BuilderWithCustomSerializerTests.cs} | 331 ++++---- 2 files changed, 182 insertions(+), 871 deletions(-) delete mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs rename Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/{BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs => BuilderWithCustomSerializerTests.cs} (81%) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs deleted file mode 100644 index 994c3b9e6b..0000000000 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests.cs +++ /dev/null @@ -1,722 +0,0 @@ -//------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -//------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.CFP.AllVersionsAndDeletes -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Text.Json; - using System.Text.Json.Serialization; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Azure.Cosmos; - using Microsoft.Azure.Cosmos.ChangeFeed.Utils; - using Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed; - using Microsoft.Azure.Cosmos.Services.Management.Tests; - using Microsoft.VisualStudio.TestTools.UnitTesting; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - - [TestClass] - [TestCategory("ChangeFeedProcessor")] - public class BuilderWithCustomSerializerPropertyNameCaseInsensitiveTrueTests : BaseChangeFeedClientHelper - { - private ContainerInternal MonitoredContainer = null; - - [TestInitialize] - public async Task TestInitialize() - { - CosmosClient cosmosClient = TestCommon.CreateCosmosClient((cosmosClientBuilder) => - cosmosClientBuilder.WithSystemTextJsonSerializerOptions( - new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - Converters = { new JsonStringEnumConverter() } - }), - useCustomSeralizer: false); - - base.database = await cosmosClient.CreateDatabaseIfNotExistsAsync(id: Guid.NewGuid().ToString()); - this.LeaseContainer = await this.database.CreateContainerIfNotExistsAsync(containerProperties: new ContainerProperties(id: "leases", partitionKeyPath: "/id")); - this.MonitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); - } - - [TestCleanup] - public async Task Cleanup() - { - await this.TestCleanup(); - } - - [TestMethod] - [Owner("philipthomas")] - [Description("Validating to deserization of ChangeFeedItem with a Delete payload with TimeToLiveExpired set to true.")] - public void ValidateNSJAndSTJSerializationOfChangeFeedItemDeleteTimeToLiveExpiredIsTrueTest() - { - string json = @"[ - { - ""current"": {}, - ""metadata"": { - ""lsn"": 17, - ""crts"": 1722511591, - ""operationType"": ""delete"", - ""timeToLiveExpired"": true, - ""previousImageLSN"": 16 - }, - ""previous"": { - ""id"": ""1"", - ""pk"": ""1"", - ""description"": ""Testing TTL on CFP."", - ""ttl"": 5, - ""_rid"": ""SnxPAOM2VfMBAAAAAAAAAA=="", - ""_self"": ""dbs/SnxPAA==/colls/SnxPAOM2VfM=/docs/SnxPAOM2VfMBAAAAAAAAAA==/"", - ""_etag"": ""\""00000000-0000-0000-e405-5632b83c01da\"""", - ""_attachments"": ""attachments/"", - ""_ts"": 1722511453 - } - } - ]"; - - ValidateSystemTextJsonDeserialization(json); - ValidateNewtonsoftJsonDeserialization(json); - - static void ValidateSystemTextJsonDeserialization(string json) - { - ValidateDeserialization( - System.Text.Json.JsonSerializer.Deserialize>>( - json: json, - options: new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - Converters = { new JsonStringEnumConverter(), } - })); - } - - static void ValidateNewtonsoftJsonDeserialization(string json) - { - ValidateDeserialization(JsonConvert.DeserializeObject>>(json)); - } - - static void ValidateDeserialization(List> activities) - { - Assert.IsNotNull(activities); - - ChangeFeedItem deletedChange = activities.ElementAt(0); - Assert.IsNotNull(deletedChange); - Assert.IsNotNull(deletedChange.Current); // Current is not null, but not data. - Assert.AreEqual(expected: default, actual: deletedChange.Current.description); // No current description for Delete - Assert.AreEqual(expected: default, actual: deletedChange.Current.id); // No current id for Delete - Assert.AreEqual(expected: default, actual: deletedChange.Current.ttl); // No current ttl for Delete - Assert.IsNotNull(deletedChange.Metadata); - Assert.AreEqual(expected: DateTime.Parse("8/1/2024 11:26:31 AM"), actual: deletedChange.Metadata.ConflictResolutionTimestamp); - Assert.AreEqual(expected: 17, actual: deletedChange.Metadata.Lsn); - Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deletedChange.Metadata.OperationType); - Assert.AreEqual(expected: 16, actual: deletedChange.Metadata.PreviousLsn); - Assert.IsTrue(deletedChange.Metadata.IsTimeToLiveExpired); - Assert.IsNotNull(deletedChange.Previous); - Assert.AreEqual(expected: "Testing TTL on CFP.", actual: deletedChange.Previous.description); - Assert.AreEqual(expected: "1", actual: deletedChange.Previous.id); - Assert.AreEqual(expected: 5, actual: deletedChange.Previous.ttl); - } - } - - [TestMethod] - [Owner("philipthomas-MSFT")] - [Description("Validating to deserization using NSJ and STJ of ChangeFeedItem with a Create payload with TTL set to a non-default value.")] - public void ValidateNSJAndSTJSerializationOfChangeFeedItemCreateTTLTest() - { - string json = @"[ - { - ""current"": { - ""id"": ""1"", - ""pk"": ""1"", - ""description"": ""Testing TTL on CFP."", - ""ttl"": 5, - ""_rid"": ""SnxPAOM2VfMBAAAAAAAAAA=="", - ""_self"": ""dbs/SnxPAA==/colls/SnxPAOM2VfM=/docs/SnxPAOM2VfMBAAAAAAAAAA==/"", - ""_etag"": ""\""00000000-0000-0000-e405-5632b83c01da\"""", - ""_attachments"": ""attachments/"", - ""_ts"": 1722511453 - }, - ""metadata"": { - ""lsn"": 16, - ""crts"": 1722511453, - ""operationType"": ""create"" - } - } - ]"; - - ValidateSystemTextJsonDeserialization(json); - ValidateNewtonsoftJsonDeserialization(json); - - static void ValidateSystemTextJsonDeserialization(string json) - { - ValidateDeserialization(System.Text.Json.JsonSerializer.Deserialize>>( - json: json, - options: new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - Converters = { new JsonStringEnumConverter(), } - })); - } - - static void ValidateNewtonsoftJsonDeserialization(string json) - { - ValidateDeserialization(JsonConvert.DeserializeObject>>(json)); - } - - static void ValidateDeserialization(List> activities) - { - Assert.IsNotNull(activities); - - ChangeFeedItem createdUpdate = activities.ElementAt(0); - Assert.IsNotNull(createdUpdate); - Assert.IsNotNull(createdUpdate.Current); - Assert.AreEqual(expected: "Testing TTL on CFP.", actual: createdUpdate.Current.description); - Assert.AreEqual(expected: "1", actual: createdUpdate.Current.id); - Assert.AreEqual(expected: 5, actual: createdUpdate.Current.ttl); - Assert.IsNotNull(createdUpdate.Metadata); - Assert.AreEqual(expected: DateTime.Parse("8/1/2024 11:24:13 AM"), actual: createdUpdate.Metadata.ConflictResolutionTimestamp); - Assert.AreEqual(expected: 16, actual: createdUpdate.Metadata.Lsn); - Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createdUpdate.Metadata.OperationType); - Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousLsn); - Assert.IsFalse(createdUpdate.Metadata.IsTimeToLiveExpired); - Assert.IsNull(createdUpdate.Previous); // No Previous for a Create change. - } - } - - [TestMethod] - [Owner("philipthomas-MSFT")] - [Description("Validating to deserization using NSJ and STJ of ChangeFeedItem with a Create, Replace, and Delete payload.")] - public void ValidateNSJAndSTJSerializationOfChangeFeedItemTest() - { - string json = @"[ - { - ""current"": { - ""id"": ""1"", - ""pk"": ""1"", - ""description"": ""original test"", - ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", - ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", - ""_etag"": ""\""00000000-0000-0000-e384-28095c1a01da\"""", - ""_attachments"": ""attachments/"", - ""_ts"": 1722455970 - }, - ""metadata"": { - ""crts"": 1722455970, - ""lsn"": 374, - ""operationType"": ""create"", - ""previousImageLSN"": 0, - ""timeToLiveExpired"": false - } - }, - { - ""current"": { - ""id"": ""1"", - ""pk"": ""1"", - ""description"": ""test after replace"", - ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", - ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", - ""_etag"": ""\""00000000-0000-0000-e384-28a5abdd01da\"""", - ""_attachments"": ""attachments/"", - ""_ts"": 1722455971 - }, - ""metadata"": { - ""crts"": 1722455971, - ""lsn"": 375, - ""operationType"": ""replace"", - ""previousImageLSN"": 374, - ""timeToLiveExpired"": false - } - }, - { - ""current"": {}, - ""metadata"": { - ""crts"": 1722455972, - ""lsn"": 376, - ""operationType"": ""delete"", - ""previousImageLSN"": 375, - ""timeToLiveExpired"": false - }, - ""previous"": { - ""id"": ""1"", - ""pk"": ""1"", - ""description"": ""test after replace"", - ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", - ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", - ""_etag"": ""\""00000000-0000-0000-e384-28a5abdd01da\"""", - ""_attachments"": ""attachments/"", - ""_ts"": 1722455971 - } - } - ]"; - - ValidateSystemTextJsonDeserialization(json); - ValidateNewtonsoftJsonDeserialization(json); - - static void ValidateNewtonsoftJsonDeserialization(string json) - { - ValidateDeserialization(JsonConvert.DeserializeObject>>(json)); - } - - static void ValidateSystemTextJsonDeserialization(string json) - { - ValidateDeserialization(System.Text.Json.JsonSerializer.Deserialize>>( - json: json, - options: new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = true, - Converters = { new JsonStringEnumConverter(), } - })); - } - - static void ValidateDeserialization(List> activities) - { - Assert.IsNotNull(activities); - - ChangeFeedItem createdUpdate = activities.ElementAt(0); - Assert.IsNotNull(createdUpdate); - Assert.IsNotNull(createdUpdate.Current); - Assert.AreEqual(expected: "original test", actual: createdUpdate.Current.description); - Assert.AreEqual(expected: "1", actual: createdUpdate.Current.id); - Assert.AreEqual(expected: 0, actual: createdUpdate.Current.ttl); - Assert.IsNotNull(createdUpdate.Metadata); - Assert.AreEqual(expected: DateTime.Parse("7/31/2024 7:59:30 PM"), actual: createdUpdate.Metadata.ConflictResolutionTimestamp); - Assert.AreEqual(expected: 374, actual: createdUpdate.Metadata.Lsn); - Assert.AreEqual(expected: ChangeFeedOperationType.Create, actual: createdUpdate.Metadata.OperationType); - Assert.AreEqual(expected: 0, actual: createdUpdate.Metadata.PreviousLsn); - Assert.IsFalse(createdUpdate.Metadata.IsTimeToLiveExpired); - Assert.IsNull(createdUpdate.Previous); // No Previous for a Create change. - - ChangeFeedItem replacedChange = activities.ElementAt(1); - Assert.IsNotNull(replacedChange); - Assert.IsNotNull(replacedChange.Current); - Assert.AreEqual(expected: "test after replace", actual: replacedChange.Current.description); - Assert.AreEqual(expected: "1", actual: replacedChange.Current.id); - Assert.AreEqual(expected: 0, actual: replacedChange.Current.ttl); - Assert.IsNotNull(replacedChange.Metadata); - Assert.AreEqual(expected: DateTime.Parse("7/31/2024 7:59:31 PM"), actual: replacedChange.Metadata.ConflictResolutionTimestamp); - Assert.AreEqual(expected: 375, actual: replacedChange.Metadata.Lsn); - Assert.AreEqual(expected: ChangeFeedOperationType.Replace, actual: replacedChange.Metadata.OperationType); - Assert.AreEqual(expected: 374, actual: replacedChange.Metadata.PreviousLsn); - Assert.IsFalse(replacedChange.Metadata.IsTimeToLiveExpired); - Assert.IsNull(replacedChange.Previous); // No Previous for a Replace change. - - ChangeFeedItem deletedChange = activities.ElementAt(2); - Assert.IsNotNull(deletedChange); - Assert.IsNotNull(deletedChange.Current); // Current is not null, but not data. - Assert.AreEqual(expected: default, actual: deletedChange.Current.description); // No current description for Delete - Assert.AreEqual(expected: default, actual: deletedChange.Current.id); // No current id for Delete - Assert.AreEqual(expected: default, actual: deletedChange.Current.ttl); // No current ttl for Delete - Assert.IsNotNull(deletedChange.Metadata); - Assert.AreEqual(expected: DateTime.Parse("7/31/2024 7:59:32 PM"), actual: deletedChange.Metadata.ConflictResolutionTimestamp); - Assert.AreEqual(expected: 376, actual: deletedChange.Metadata.Lsn); - Assert.AreEqual(expected: ChangeFeedOperationType.Delete, actual: deletedChange.Metadata.OperationType); - Assert.AreEqual(expected: 375, actual: deletedChange.Metadata.PreviousLsn); - Assert.IsFalse(deletedChange.Metadata.IsTimeToLiveExpired); - Assert.IsNotNull(deletedChange.Previous); - Assert.AreEqual(expected: "test after replace", actual: deletedChange.Previous.description); - Assert.AreEqual(expected: "1", actual: deletedChange.Previous.id); - Assert.AreEqual(expected: 0, actual: deletedChange.Previous.ttl); - } - } - - [TestMethod] - [Owner("philipthomas-MSFT")] - [Description("Replace and Deletes have full ChangeFeedMetadata.")] - public void ValidateChangeFeedMetadataSerializationReplaceAnDeleteWriteTest() - { - ChangeFeedMetadata metadata = new() - { - PreviousLsn = 15, - Lsn = 374, - OperationType = ChangeFeedOperationType.Create, - IsTimeToLiveExpired = true, - ConflictResolutionTimestamp = DateTime.Parse("7/31/2024 7:59:30 PM") - }; - - string json = System.Text.Json.JsonSerializer.Serialize( - value: metadata, - options: new JsonSerializerOptions()); - - Assert.AreEqual( - expected: @"{""crts"":1722455970,""timeToLiveExpired"":true,""lsn"":374,""operationType"":""Create"",""previousImageLSN"":15}", - actual: json); - } - - [TestMethod] - [Owner("philipthomas-MSFT")] - [Description("Creates have partial ChangeFeedMetadata.")] - public void ValidateChangeFeedMetadataSerializationCreateWriteTest() - { - ChangeFeedMetadata metadata = new() - { - Lsn = 374, - OperationType = ChangeFeedOperationType.Create, - ConflictResolutionTimestamp = DateTime.Parse("7/31/2024 7:59:30 PM") - }; - - string json = System.Text.Json.JsonSerializer.Serialize( - value: metadata, - options: new JsonSerializerOptions()); - - Assert.AreEqual( - expected: @"{""crts"":1722455970,""timeToLiveExpired"":false,""lsn"":374,""operationType"":""Create"",""previousImageLSN"":0}", - actual: json); - } - - [TestMethod] - [Timeout(300000)] - [TestCategory("LongRunning")] - [Owner("philipthomas-MSFT")] - [Description("Scenario: When a document is created with ttl set, there should be 1 create and 1 delete that will appear for that " + - "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] - public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsAsync() - { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); - Exception exception = default; - int ttlInSeconds = 5; - Stopwatch stopwatch = new(); - ManualResetEvent allDocsProcessed = new ManualResetEvent(false); - - ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => - { - // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. - - Logger.LogLine($"@ {DateTime.Now}, {nameof(stopwatch)} -> CFP AVAD took '{stopwatch.ElapsedMilliseconds}' to read document CRUD in feed."); - - foreach (ChangeFeedItem change in docs) - { - if (change.Metadata.OperationType == ChangeFeedOperationType.Create) - { - // current - Assert.AreEqual(expected: "1", actual: change.Current.id.ToString()); - Assert.AreEqual(expected: "1", actual: change.Current.pk.ToString()); - Assert.AreEqual(expected: "Testing TTL on CFP.", actual: change.Current.description.ToString()); - Assert.AreEqual(expected: ttlInSeconds, actual: change.Current.ttl); - - // metadata - Assert.IsTrue(DateTime.TryParse(s: change.Metadata.ConflictResolutionTimestamp.ToString(), out _), message: "Invalid csrt must be a datetime value."); - Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value."); - Assert.IsFalse(change.Metadata.IsTimeToLiveExpired); - - // previous - Assert.IsNull(change.Previous); - } - else if (change.Metadata.OperationType == ChangeFeedOperationType.Delete) - { - // current - Assert.IsNull(change.Current.id); - - // metadata - Assert.IsTrue(DateTime.TryParse(s: change.Metadata.ConflictResolutionTimestamp.ToString(), out _), message: "Invalid csrt must be a datetime value."); - Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value."); - Assert.IsTrue(change.Metadata.IsTimeToLiveExpired); - - // previous - Assert.AreEqual(expected: "1", actual: change.Previous.id.ToString()); - Assert.AreEqual(expected: "1", actual: change.Previous.pk.ToString()); - Assert.AreEqual(expected: "Testing TTL on CFP.", actual: change.Previous.description.ToString()); - Assert.AreEqual(expected: ttlInSeconds, actual: change.Previous.ttl); - - // stop after reading delete since it is the last document in feed. - stopwatch.Stop(); - allDocsProcessed.Set(); - } - else - { - Assert.Fail("Invalid operation."); - } - } - - return Task.CompletedTask; - }) - .WithInstanceName(Guid.NewGuid().ToString()) - .WithLeaseContainer(this.LeaseContainer) - .WithErrorNotification((leaseToken, error) => - { - exception = error.InnerException; - - return Task.CompletedTask; - }) - .Build(); - - stopwatch.Start(); - - // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. - - Logger.LogLine($"@ {DateTime.Now}, CFProcessor starting..."); - - await processor.StartAsync(); - - try - { - await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - await monitoredContainer.CreateItemAsync(new ToDoActivity { id = "1", pk = "1", description = "Testing TTL on CFP.", ttl = ttlInSeconds }, partitionKey: new PartitionKey("1")); - - // NOTE(philipthomas-MSFT): Please allow these Logger.LogLine because TTL on items will purge at random times so I am using this to test when ran locally using emulator. - - Logger.LogLine($"@ {DateTime.Now}, Document created."); - - bool receivedDelete = allDocsProcessed.WaitOne(250000); - Assert.IsTrue(receivedDelete, "Timed out waiting for docs to process"); - - if (exception != default) - { - Assert.Fail(exception.ToString()); - } - } - finally - { - await processor.StopAsync(); - } - } - - [TestMethod] - [Owner("philipthomas-MSFT")] - [Description("Scenario: When a document is created, then updated, and finally deleted, there should be 3 changes that will appear for that " + - "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] - public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() - { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); - ManualResetEvent allDocsProcessed = new ManualResetEvent(false); - Exception exception = default; - - ChangeFeedProcessor processor = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> docs, CancellationToken token) => - { - Logger.LogLine($"@ {DateTime.Now}, {nameof(docs)} -> {System.Text.Json.JsonSerializer.Serialize(docs)}"); - - string id = default; - string pk = default; - string description = default; - - foreach (ChangeFeedItem change in docs) - { - if (change.Metadata.OperationType != ChangeFeedOperationType.Delete) - { - id = change.Current.id.ToString(); - pk = change.Current.pk.ToString(); - description = change.Current.description.ToString(); - } - else - { - id = change.Previous.id.ToString(); - pk = change.Previous.pk.ToString(); - description = change.Previous.description.ToString(); - } - - ChangeFeedOperationType operationType = change.Metadata.OperationType; - long previousLsn = change.Metadata.PreviousLsn; - DateTime m = change.Metadata.ConflictResolutionTimestamp; - long lsn = change.Metadata.Lsn; - bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired; - } - - Assert.IsNotNull(context.LeaseToken); - Assert.IsNotNull(context.Diagnostics); - Assert.IsNotNull(context.Headers); - Assert.IsNotNull(context.Headers.Session); - Assert.IsTrue(context.Headers.RequestCharge > 0); - Assert.IsTrue(context.Diagnostics.ToString().Contains("Change Feed Processor Read Next Async")); - Assert.AreEqual(expected: 3, actual: docs.Count); - - ChangeFeedItem createChange = docs.ElementAt(0); - Assert.IsNotNull(createChange.Current); - Assert.AreEqual(expected: "1", actual: createChange.Current.id.ToString()); - Assert.AreEqual(expected: "1", actual: createChange.Current.pk.ToString()); - Assert.AreEqual(expected: "original test", actual: createChange.Current.description.ToString()); - Assert.AreEqual(expected: createChange.Metadata.OperationType, actual: ChangeFeedOperationType.Create); - Assert.AreEqual(expected: createChange.Metadata.PreviousLsn, actual: 0); - Assert.IsNull(createChange.Previous); - - ChangeFeedItem replaceChange = docs.ElementAt(1); - Assert.IsNotNull(replaceChange.Current); - Assert.AreEqual(expected: "1", actual: replaceChange.Current.id.ToString()); - Assert.AreEqual(expected: "1", actual: replaceChange.Current.pk.ToString()); - Assert.AreEqual(expected: "test after replace", actual: replaceChange.Current.description.ToString()); - Assert.AreEqual(expected: replaceChange.Metadata.OperationType, actual: ChangeFeedOperationType.Replace); - Assert.AreEqual(expected: createChange.Metadata.Lsn, actual: replaceChange.Metadata.PreviousLsn); - Assert.IsNull(replaceChange.Previous); - - ChangeFeedItem deleteChange = docs.ElementAt(2); - Assert.IsNull(deleteChange.Current.id); - Assert.AreEqual(expected: deleteChange.Metadata.OperationType, actual: ChangeFeedOperationType.Delete); - Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousLsn); - Assert.IsNotNull(deleteChange.Previous); - Assert.AreEqual(expected: "1", actual: deleteChange.Previous.id.ToString()); - Assert.AreEqual(expected: "1", actual: deleteChange.Previous.pk.ToString()); - Assert.AreEqual(expected: "test after replace", actual: deleteChange.Previous.description.ToString()); - - Assert.IsTrue(condition: createChange.Metadata.ConflictResolutionTimestamp < replaceChange.Metadata.ConflictResolutionTimestamp, message: "The create operation must happen before the replace operation."); - Assert.IsTrue(condition: replaceChange.Metadata.ConflictResolutionTimestamp < deleteChange.Metadata.ConflictResolutionTimestamp, message: "The replace operation must happen before the delete operation."); - Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The create operation must happen before the replace operation."); - Assert.IsTrue(condition: createChange.Metadata.Lsn < replaceChange.Metadata.Lsn, message: "The replace operation must happen before the delete operation."); - - return Task.CompletedTask; - }) - .WithInstanceName(Guid.NewGuid().ToString()) - .WithLeaseContainer(this.LeaseContainer) - .WithErrorNotification((leaseToken, error) => - { - exception = error.InnerException; - - return Task.CompletedTask; - }) - .Build(); - - // Start the processor, insert 1 document to generate a checkpoint, modify it, and then delete it. - // 1 second delay between operations to get different timestamps. - - await processor.StartAsync(); - await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - - await monitoredContainer.CreateItemAsync(new ToDoActivity { id = "1", pk = "1", description = "original test", ttl = -1 }, partitionKey: new PartitionKey("1")); - await Task.Delay(1000); - - await monitoredContainer.UpsertItemAsync(new ToDoActivity { id = "1", pk = "1", description = "test after replace", ttl = -1 }, partitionKey: new PartitionKey("1")); - await Task.Delay(1000); - - await monitoredContainer.DeleteItemAsync(id: "1", partitionKey: new PartitionKey("1")); - - bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); - - await processor.StopAsync(); - - if (exception != default) - { - Assert.Fail(exception.ToString()); - } - } - - private static async Task RevertLeaseDocumentsToLegacyWithNoMode( - Container leaseContainer, - int leaseDocumentCount) - { - FeedIterator iterator = leaseContainer.GetItemQueryStreamIterator( - queryText: "SELECT * FROM c", - continuationToken: null); - - List leases = new List(); - while (iterator.HasMoreResults) - { - using (ResponseMessage responseMessage = await iterator.ReadNextAsync().ConfigureAwait(false)) - { - responseMessage.EnsureSuccessStatusCode(); - leases.AddRange(CosmosFeedResponseSerializer.FromFeedResponseStream( - serializerCore: CosmosContainerExtensions.DefaultJsonSerializer, - streamWithServiceEnvelope: responseMessage.Content)); - } - } - - int counter = 0; - - foreach (JObject lease in leases) - { - if (!lease.ContainsKey("Mode")) - { - continue; - } - - counter++; - lease.Remove("Mode"); - - _ = await leaseContainer.UpsertItemAsync(item: lease); - } - - Assert.AreEqual(expected: leaseDocumentCount, actual: counter); - } - - private static async Task BuildChangeFeedProcessorWithLatestVersionAsync( - ContainerInternal monitoredContainer, - Container leaseContainer, - ManualResetEvent allDocsProcessed, - bool withStartFromBeginning) - { - Exception exception = default; - ChangeFeedProcessor latestVersionProcessorAtomic = null; - - ChangeFeedProcessorBuilder processorBuilder = monitoredContainer - .GetChangeFeedProcessorBuilder(processorName: $"processorName", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection documents, CancellationToken token) => Task.CompletedTask) - .WithInstanceName(Guid.NewGuid().ToString()) - .WithLeaseContainer(leaseContainer) - .WithErrorNotification((leaseToken, error) => - { - exception = error.InnerException; - - return Task.CompletedTask; - }); - - if (withStartFromBeginning) - { - processorBuilder.WithStartFromBeginning(); - } - - ChangeFeedProcessor processor = processorBuilder.Build(); - Interlocked.Exchange(ref latestVersionProcessorAtomic, processor); - - await processor.StartAsync(); - await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); - - if (exception != default) - { - Assert.Fail(exception.ToString()); - } - } - - private static async Task BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync( - ContainerInternal monitoredContainer, - Container leaseContainer, - ManualResetEvent allDocsProcessed) - { - Exception exception = default; - ChangeFeedProcessor allVersionsAndDeletesProcessorAtomic = null; - - ChangeFeedProcessorBuilder allVersionsAndDeletesProcessorBuilder = monitoredContainer - .GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: $"processorName", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection> documents, CancellationToken token) => Task.CompletedTask) - .WithInstanceName(Guid.NewGuid().ToString()) - .WithMaxItems(1) - .WithLeaseContainer(leaseContainer) - .WithErrorNotification((leaseToken, error) => - { - exception = error.InnerException; - - return Task.FromResult(exception); - }); - - ChangeFeedProcessor processor = allVersionsAndDeletesProcessorBuilder.Build(); - Interlocked.Exchange(ref allVersionsAndDeletesProcessorAtomic, processor); - - await processor.StartAsync(); - await Task.Delay(BaseChangeFeedClientHelper.ChangeFeedSetupTime); - bool isStartOk = allDocsProcessed.WaitOne(10 * BaseChangeFeedClientHelper.ChangeFeedSetupTime); - - if (exception != default) - { - Assert.Fail(exception.ToString()); - } - } - - private async Task CreateMonitoredContainer(ChangeFeedMode changeFeedMode) - { - string PartitionKey = "/pk"; - ContainerProperties properties = new ContainerProperties(id: Guid.NewGuid().ToString(), - partitionKeyPath: PartitionKey); - - if (changeFeedMode == ChangeFeedMode.AllVersionsAndDeletes) - { - properties.ChangeFeedPolicy.FullFidelityRetention = TimeSpan.FromMinutes(5); - properties.DefaultTimeToLive = -1; - } - - ContainerResponse response = await this.database.CreateContainerAsync(properties, - throughput: 10000, - cancellationToken: this.cancellationToken); - - return (ContainerInternal)response; - } - } -} diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs similarity index 81% rename from Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs rename to Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs index 04dd4a55a2..62cb939764 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs @@ -22,73 +22,50 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.CFP.AllVersionsAndDeletes [TestClass] [TestCategory("ChangeFeedProcessor")] - public partial class BuilderWithCustomSerializerPropertyNameCaseInsensitiveFalseTests : BaseChangeFeedClientHelper + public class BuilderWithCustomSerializerTests { - private ContainerInternal MonitoredContainer = null; - - [TestInitialize] - public async Task TestInitialize() - { - CosmosClient cosmosClient = TestCommon.CreateCosmosClient((cosmosClientBuilder) => - cosmosClientBuilder.WithSystemTextJsonSerializerOptions( - new JsonSerializerOptions() - { - PropertyNameCaseInsensitive = false, - Converters = { new JsonStringEnumConverter() } - }), - useCustomSeralizer: false); - - base.database = await cosmosClient.CreateDatabaseIfNotExistsAsync(id: Guid.NewGuid().ToString()); - this.LeaseContainer = await this.database.CreateContainerIfNotExistsAsync(containerProperties: new ContainerProperties(id: "leases", partitionKeyPath: "/id")); - this.MonitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); - } - - [TestCleanup] - public async Task Cleanup() - { - await this.TestCleanup(); - } - [TestMethod] [Owner("philipthomas")] [Description("Validating to deserization of ChangeFeedItem with a Delete payload with TimeToLiveExpired set to true.")] - public void ValidateNSJAndSTJSerializationOfChangeFeedItemDeleteTimeToLiveExpiredIsTrueTest() + [DataRow(true)] + [DataRow(false)] + public void ValidateNSJAndSTJSerializationOfChangeFeedItemDeleteTimeToLiveExpiredIsTrueTest(bool propertyNameCaseInsensitive) { string json = @"[ - { - ""current"": {}, - ""metadata"": { - ""lsn"": 17, - ""crts"": 1722511591, - ""operationType"": ""delete"", - ""timeToLiveExpired"": true, - ""previousImageLSN"": 16 - }, - ""previous"": { - ""id"": ""1"", - ""pk"": ""1"", - ""description"": ""Testing TTL on CFP."", - ""ttl"": 5, - ""_rid"": ""SnxPAOM2VfMBAAAAAAAAAA=="", - ""_self"": ""dbs/SnxPAA==/colls/SnxPAOM2VfM=/docs/SnxPAOM2VfMBAAAAAAAAAA==/"", - ""_etag"": ""\""00000000-0000-0000-e405-5632b83c01da\"""", - ""_attachments"": ""attachments/"", - ""_ts"": 1722511453 - } - } + { + ""current"": {}, + ""metadata"": { + ""lsn"": 17, + ""crts"": 1722511591, + ""operationType"": ""delete"", + ""timeToLiveExpired"": true, + ""previousImageLSN"": 16 + }, + ""previous"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""Testing TTL on CFP."", + ""ttl"": 5, + ""_rid"": ""SnxPAOM2VfMBAAAAAAAAAA=="", + ""_self"": ""dbs/SnxPAA==/colls/SnxPAOM2VfM=/docs/SnxPAOM2VfMBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e405-5632b83c01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722511453 + } + } ]"; - ValidateSystemTextJsonDeserialization(json); + ValidateSystemTextJsonDeserialization(json, propertyNameCaseInsensitive); ValidateNewtonsoftJsonDeserialization(json); - static void ValidateSystemTextJsonDeserialization(string json) + static void ValidateSystemTextJsonDeserialization(string json, bool propertyNameCaseInsensitive) { ValidateDeserialization( System.Text.Json.JsonSerializer.Deserialize>>( json: json, options: new JsonSerializerOptions() { - PropertyNameCaseInsensitive = false, + PropertyNameCaseInsensitive = propertyNameCaseInsensitive, Converters = { new JsonStringEnumConverter(), } })); } @@ -124,39 +101,41 @@ static void ValidateDeserialization(List> activitie [TestMethod] [Owner("philipthomas-MSFT")] [Description("Validating to deserization using NSJ and STJ of ChangeFeedItem with a Create payload with TTL set to a non-default value.")] - public void ValidateNSJAndSTJSerializationOfChangeFeedItemCreateTTLTest() + [DataRow(true)] + [DataRow(false)] + public void ValidateNSJAndSTJSerializationOfChangeFeedItemCreateTTLTest(bool propertyNameCaseInsensitive) { string json = @"[ - { - ""current"": { - ""id"": ""1"", - ""pk"": ""1"", - ""description"": ""Testing TTL on CFP."", - ""ttl"": 5, - ""_rid"": ""SnxPAOM2VfMBAAAAAAAAAA=="", - ""_self"": ""dbs/SnxPAA==/colls/SnxPAOM2VfM=/docs/SnxPAOM2VfMBAAAAAAAAAA==/"", - ""_etag"": ""\""00000000-0000-0000-e405-5632b83c01da\"""", - ""_attachments"": ""attachments/"", - ""_ts"": 1722511453 - }, - ""metadata"": { - ""lsn"": 16, - ""crts"": 1722511453, - ""operationType"": ""create"" - } - } + { + ""current"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""Testing TTL on CFP."", + ""ttl"": 5, + ""_rid"": ""SnxPAOM2VfMBAAAAAAAAAA=="", + ""_self"": ""dbs/SnxPAA==/colls/SnxPAOM2VfM=/docs/SnxPAOM2VfMBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e405-5632b83c01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722511453 + }, + ""metadata"": { + ""lsn"": 16, + ""crts"": 1722511453, + ""operationType"": ""create"" + } + } ]"; - ValidateSystemTextJsonDeserialization(json); + ValidateSystemTextJsonDeserialization(json, propertyNameCaseInsensitive); ValidateNewtonsoftJsonDeserialization(json); - static void ValidateSystemTextJsonDeserialization(string json) + static void ValidateSystemTextJsonDeserialization(string json, bool propertyNameCaseInsensitive) { ValidateDeserialization(System.Text.Json.JsonSerializer.Deserialize>>( json: json, options: new JsonSerializerOptions() { - PropertyNameCaseInsensitive = false, + PropertyNameCaseInsensitive = propertyNameCaseInsensitive, Converters = { new JsonStringEnumConverter(), } })); } @@ -189,70 +168,72 @@ static void ValidateDeserialization(List> activitie [TestMethod] [Owner("philipthomas-MSFT")] [Description("Validating to deserization using NSJ and STJ of ChangeFeedItem with a Create, Replace, and Delete payload.")] - public void ValidateNSJAndSTJSerializationOfChangeFeedItemTest() - { + [DataRow(true)] + [DataRow(false)] + public void ValidateNSJAndSTJSerializationOfChangeFeedItemTest(bool propertyNameCaseInsensitive) + { string json = @"[ - { - ""current"": { - ""id"": ""1"", - ""pk"": ""1"", - ""description"": ""original test"", - ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", - ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", - ""_etag"": ""\""00000000-0000-0000-e384-28095c1a01da\"""", - ""_attachments"": ""attachments/"", - ""_ts"": 1722455970 - }, - ""metadata"": { - ""crts"": 1722455970, - ""lsn"": 374, - ""operationType"": ""create"", - ""previousImageLSN"": 0, - ""timeToLiveExpired"": false - } - }, - { - ""current"": { - ""id"": ""1"", - ""pk"": ""1"", - ""description"": ""test after replace"", - ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", - ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", - ""_etag"": ""\""00000000-0000-0000-e384-28a5abdd01da\"""", - ""_attachments"": ""attachments/"", - ""_ts"": 1722455971 - }, - ""metadata"": { - ""crts"": 1722455971, - ""lsn"": 375, - ""operationType"": ""replace"", - ""previousImageLSN"": 374, - ""timeToLiveExpired"": false - } - }, - { - ""current"": {}, - ""metadata"": { - ""crts"": 1722455972, - ""lsn"": 376, - ""operationType"": ""delete"", - ""previousImageLSN"": 375, - ""timeToLiveExpired"": false - }, - ""previous"": { - ""id"": ""1"", - ""pk"": ""1"", - ""description"": ""test after replace"", - ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", - ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", - ""_etag"": ""\""00000000-0000-0000-e384-28a5abdd01da\"""", - ""_attachments"": ""attachments/"", - ""_ts"": 1722455971 - } - } + { + ""current"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""original test"", + ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", + ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e384-28095c1a01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722455970 + }, + ""metadata"": { + ""crts"": 1722455970, + ""lsn"": 374, + ""operationType"": ""create"", + ""previousImageLSN"": 0, + ""timeToLiveExpired"": false + } + }, + { + ""current"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""test after replace"", + ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", + ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e384-28a5abdd01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722455971 + }, + ""metadata"": { + ""crts"": 1722455971, + ""lsn"": 375, + ""operationType"": ""replace"", + ""previousImageLSN"": 374, + ""timeToLiveExpired"": false + } + }, + { + ""current"": {}, + ""metadata"": { + ""crts"": 1722455972, + ""lsn"": 376, + ""operationType"": ""delete"", + ""previousImageLSN"": 375, + ""timeToLiveExpired"": false + }, + ""previous"": { + ""id"": ""1"", + ""pk"": ""1"", + ""description"": ""test after replace"", + ""_rid"": ""HpxDAL+dzLQBAAAAAAAAAA=="", + ""_self"": ""dbs/HpxDAA==/colls/HpxDAL+dzLQ=/docs/HpxDAL+dzLQBAAAAAAAAAA==/"", + ""_etag"": ""\""00000000-0000-0000-e384-28a5abdd01da\"""", + ""_attachments"": ""attachments/"", + ""_ts"": 1722455971 + } + } ]"; - ValidateSystemTextJsonDeserialization(json); + ValidateSystemTextJsonDeserialization(json, propertyNameCaseInsensitive); ValidateNewtonsoftJsonDeserialization(json); static void ValidateNewtonsoftJsonDeserialization(string json) @@ -260,13 +241,13 @@ static void ValidateNewtonsoftJsonDeserialization(string json) ValidateDeserialization(JsonConvert.DeserializeObject>>(json)); } - static void ValidateSystemTextJsonDeserialization(string json) + static void ValidateSystemTextJsonDeserialization(string json, bool propertyNameCaseInsensitive) { ValidateDeserialization(System.Text.Json.JsonSerializer.Deserialize>>( json: json, options: new JsonSerializerOptions() { - PropertyNameCaseInsensitive = false, + PropertyNameCaseInsensitive = propertyNameCaseInsensitive, Converters = { new JsonStringEnumConverter(), } })); } @@ -325,7 +306,9 @@ static void ValidateDeserialization(List> activitie [TestMethod] [Owner("philipthomas-MSFT")] [Description("Replace and Deletes have full ChangeFeedMetadata.")] - public void ValidateChangeFeedMetadataSerializationReplaceAnDeleteWriteTest() + [DataRow(true)] + [DataRow(false)] + public void ValidateChangeFeedMetadataSerializationReplaceAnDeleteWriteTest(bool propertyNameCaseInsensitive) { ChangeFeedMetadata metadata = new() { @@ -338,7 +321,10 @@ public void ValidateChangeFeedMetadataSerializationReplaceAnDeleteWriteTest() string json = System.Text.Json.JsonSerializer.Serialize( value: metadata, - options: new JsonSerializerOptions()); + options: new JsonSerializerOptions + { + PropertyNameCaseInsensitive = propertyNameCaseInsensitive + }); Assert.AreEqual( expected: @"{""crts"":1722455970,""timeToLiveExpired"":true,""lsn"":374,""operationType"":""Create"",""previousImageLSN"":15}", @@ -348,7 +334,9 @@ public void ValidateChangeFeedMetadataSerializationReplaceAnDeleteWriteTest() [TestMethod] [Owner("philipthomas-MSFT")] [Description("Creates have partial ChangeFeedMetadata.")] - public void ValidateChangeFeedMetadataSerializationCreateWriteTest() + [DataRow(true)] + [DataRow(false)] + public void ValidateChangeFeedMetadataSerializationCreateWriteTest(bool propertyNameCaseInsensitive) { ChangeFeedMetadata metadata = new() { @@ -359,7 +347,10 @@ public void ValidateChangeFeedMetadataSerializationCreateWriteTest() string json = System.Text.Json.JsonSerializer.Serialize( value: metadata, - options: new JsonSerializerOptions()); + options: new JsonSerializerOptions + { + PropertyNameCaseInsensitive = propertyNameCaseInsensitive + }); Assert.AreEqual( expected: @"{""crts"":1722455970,""timeToLiveExpired"":false,""lsn"":374,""operationType"":""Create"",""previousImageLSN"":0}", @@ -372,9 +363,22 @@ public void ValidateChangeFeedMetadataSerializationCreateWriteTest() [Owner("philipthomas-MSFT")] [Description("Scenario: When a document is created with ttl set, there should be 1 create and 1 delete that will appear for that " + "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] - public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsAsync() + [DataRow(true)] + [DataRow(false)] + public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsAsync(bool propertyNameCaseInsensitive) { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + CosmosClient cosmosClient = TestCommon.CreateCosmosClient((cosmosClientBuilder) => + cosmosClientBuilder.WithSystemTextJsonSerializerOptions( + new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = propertyNameCaseInsensitive, + Converters = { new JsonStringEnumConverter() } + }), + useCustomSeralizer: false); + + Database database = await cosmosClient.CreateDatabaseIfNotExistsAsync(id: Guid.NewGuid().ToString()); + Container leaseContainer = await database.CreateContainerIfNotExistsAsync(containerProperties: new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes, database); Exception exception = default; int ttlInSeconds = 5; Stopwatch stopwatch = new(); @@ -434,7 +438,7 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA return Task.CompletedTask; }) .WithInstanceName(Guid.NewGuid().ToString()) - .WithLeaseContainer(this.LeaseContainer) + .WithLeaseContainer(leaseContainer) .WithErrorNotification((leaseToken, error) => { exception = error.InnerException; @@ -472,15 +476,35 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA { await processor.StopAsync(); } + + if (database != null) + { + await database.DeleteAsync(); + } + + cosmosClient?.Dispose(); } [TestMethod] [Owner("philipthomas-MSFT")] [Description("Scenario: When a document is created, then updated, and finally deleted, there should be 3 changes that will appear for that " + "document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")] - public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() + [DataRow(true)] + [DataRow(false)] + public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync(bool propertyNameCaseInsensitive) { - ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes); + CosmosClient cosmosClient = TestCommon.CreateCosmosClient((cosmosClientBuilder) => + cosmosClientBuilder.WithSystemTextJsonSerializerOptions( + new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = propertyNameCaseInsensitive, + Converters = { new JsonStringEnumConverter() } + }), + useCustomSeralizer: false); + + Database database = await cosmosClient.CreateDatabaseIfNotExistsAsync(id: Guid.NewGuid().ToString()); + Container leaseContainer = await database.CreateContainerIfNotExistsAsync(containerProperties: new ContainerProperties(id: "leases", partitionKeyPath: "/id")); + ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes, database); ManualResetEvent allDocsProcessed = new ManualResetEvent(false); Exception exception = default; @@ -558,7 +582,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() return Task.CompletedTask; }) .WithInstanceName(Guid.NewGuid().ToString()) - .WithLeaseContainer(this.LeaseContainer) + .WithLeaseContainer(leaseContainer) .WithErrorNotification((leaseToken, error) => { exception = error.InnerException; @@ -589,6 +613,13 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync() { Assert.Fail(exception.ToString()); } + + if (database != null) + { + await database.DeleteAsync(); + } + + cosmosClient?.Dispose(); } private static async Task RevertLeaseDocumentsToLegacyWithNoMode( @@ -700,7 +731,9 @@ private static async Task BuildChangeFeedProcessorWithAllVersionsAndDeletesAsync } } - private async Task CreateMonitoredContainer(ChangeFeedMode changeFeedMode) + private async Task CreateMonitoredContainer( + ChangeFeedMode changeFeedMode, + Database database) { string PartitionKey = "/pk"; ContainerProperties properties = new ContainerProperties(id: Guid.NewGuid().ToString(), @@ -712,9 +745,9 @@ private async Task CreateMonitoredContainer(ChangeFeedMode ch properties.DefaultTimeToLive = -1; } - ContainerResponse response = await this.database.CreateContainerAsync(properties, + ContainerResponse response = await database.CreateContainerAsync(properties, throughput: 10000, - cancellationToken: this.cancellationToken); + cancellationToken: CancellationToken.None); return (ContainerInternal)response; } From 625fe86b48c7ed68c0e3804b5b170dc71b6f427d Mon Sep 17 00:00:00 2001 From: philipthomas Date: Mon, 19 Aug 2024 19:19:41 -0400 Subject: [PATCH 15/15] remove JsonStringEnumConverter(), from tests --- .../BuilderWithCustomSerializerTests.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs index 62cb939764..a6780e4409 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CFP/AllVersionsAndDeletes/BuilderWithCustomSerializerTests.cs @@ -66,7 +66,6 @@ static void ValidateSystemTextJsonDeserialization(string json, bool propertyName options: new JsonSerializerOptions() { PropertyNameCaseInsensitive = propertyNameCaseInsensitive, - Converters = { new JsonStringEnumConverter(), } })); } @@ -136,7 +135,6 @@ static void ValidateSystemTextJsonDeserialization(string json, bool propertyName options: new JsonSerializerOptions() { PropertyNameCaseInsensitive = propertyNameCaseInsensitive, - Converters = { new JsonStringEnumConverter(), } })); } @@ -247,8 +245,7 @@ static void ValidateSystemTextJsonDeserialization(string json, bool propertyName json: json, options: new JsonSerializerOptions() { - PropertyNameCaseInsensitive = propertyNameCaseInsensitive, - Converters = { new JsonStringEnumConverter(), } + PropertyNameCaseInsensitive = propertyNameCaseInsensitive })); } @@ -372,7 +369,6 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA new JsonSerializerOptions() { PropertyNameCaseInsensitive = propertyNameCaseInsensitive, - Converters = { new JsonStringEnumConverter() } }), useCustomSeralizer: false); @@ -497,8 +493,7 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync(bool pr cosmosClientBuilder.WithSystemTextJsonSerializerOptions( new JsonSerializerOptions() { - PropertyNameCaseInsensitive = propertyNameCaseInsensitive, - Converters = { new JsonStringEnumConverter() } + PropertyNameCaseInsensitive = propertyNameCaseInsensitive }), useCustomSeralizer: false);