Skip to content

Commit d34a3f3

Browse files
Routing: Adds assignment guard test for database RID in CollectionCache
Agent-Logs-Url: https://github.com/Azure/azure-cosmos-dotnet-v3/sessions/ae45e8e6-777a-4093-87b6-3a86958850c8 Co-authored-by: kirankumarkolli <6880899+kirankumarkolli@users.noreply.github.com>
1 parent 880c9cd commit d34a3f3

1 file changed

Lines changed: 40 additions & 6 deletions

File tree

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CollectionCacheTests.cs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,48 @@ public async Task ResolveCollectionAsync_WithCollectionRidInResolvedCollectionRi
6565
Assert.AreEqual(1, cache.RidLookupCount);
6666
}
6767

68+
/// <summary>
69+
/// Assignment guard: when the name-based lookup returns a container whose ResourceId is
70+
/// a database-level RID (i.e. the server sent a corrupt response), CollectionCache must
71+
/// throw <see cref="InvalidOperationException"/> before persisting the bad value onto
72+
/// <c>request.RequestContext.ResolvedCollectionRid</c>.
73+
///
74+
/// On the <c>msdata/direct</c> branch an additional first line of defence exists:
75+
/// the <see cref="DocumentServiceRequestContext.ResolvedCollectionRid"/> setter itself
76+
/// emits a <c>TraceWarning</c> (with the call stack) whenever a non-collection RID is
77+
/// assigned, so the exact call site can be identified in production logs even before the
78+
/// error surfaces in <see cref="CollectionCache"/>.
79+
/// </summary>
80+
[TestMethod]
81+
public async Task ResolveCollectionAsync_WhenNameResolutionReturnsDatabaseRid_ThrowsInvalidOperation()
82+
{
83+
const string databaseRid = "jy2ekg==";
84+
85+
// The mock returns a container whose ResourceId is a database RID — simulating
86+
// a corrupt server response or a stale in-process cache entry.
87+
TestCollectionCache cache = new TestCollectionCache(returnRid: databaseRid);
88+
using DocumentServiceRequest request = DocumentServiceRequest.CreateFromName(
89+
OperationType.Read,
90+
"dbs/db1/colls/c1/docs/d1",
91+
ResourceType.Document,
92+
AuthorizationTokenType.PrimaryMasterKey,
93+
null);
94+
95+
await Assert.ThrowsExceptionAsync<InvalidOperationException>(
96+
() => cache.ResolveCollectionAsync(
97+
request,
98+
CancellationToken.None,
99+
NoOpTrace.Singleton));
100+
}
101+
68102
private sealed class TestCollectionCache : CollectionCache
69103
{
70-
private readonly string containerRid;
104+
private readonly string returnRid;
71105

72-
public TestCollectionCache(string containerRid)
106+
public TestCollectionCache(string returnRid)
73107
: base(enableAsyncCacheExceptionNoSharing: false)
74108
{
75-
this.containerRid = containerRid;
109+
this.returnRid = returnRid;
76110
}
77111

78112
public int NameLookupCount { get; private set; }
@@ -87,9 +121,9 @@ protected override Task<ContainerProperties> GetByRidAsync(
87121
CancellationToken cancellationToken)
88122
{
89123
this.RidLookupCount++;
90-
if (StringComparer.Ordinal.Equals(collectionRid, this.containerRid))
124+
if (StringComparer.Ordinal.Equals(collectionRid, this.returnRid))
91125
{
92-
return Task.FromResult(ContainerProperties.CreateWithResourceId(this.containerRid));
126+
return Task.FromResult(ContainerProperties.CreateWithResourceId(this.returnRid));
93127
}
94128
else
95129
{
@@ -105,7 +139,7 @@ protected override Task<ContainerProperties> GetByNameAsync(
105139
CancellationToken cancellationToken)
106140
{
107141
this.NameLookupCount++;
108-
return Task.FromResult(ContainerProperties.CreateWithResourceId(this.containerRid));
142+
return Task.FromResult(ContainerProperties.CreateWithResourceId(this.returnRid));
109143
}
110144
}
111145
}

0 commit comments

Comments
 (0)