@@ -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