Skip to content

Commit 54f3b2c

Browse files
Add tests to validate 404/1003 substatus code distinction
Co-authored-by: kirankumarkolli <6880899+kirankumarkolli@users.noreply.github.com>
1 parent 69bb1f6 commit 54f3b2c

2 files changed

Lines changed: 211 additions & 0 deletions

File tree

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosNotFoundTests.cs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,115 @@ public async Task ValidateQueryNotFoundResponse()
9797
await db.DeleteAsync();
9898
}
9999

100+
/// <summary>
101+
/// Validates that 404 with substatus 0 is returned when an item doesn't exist,
102+
/// and 404 with substatus 1003 is returned when the container doesn't exist.
103+
/// This allows disambiguation between item not found vs owner resource (container/database) not found.
104+
/// </summary>
105+
[TestMethod]
106+
public async Task ValidateSubStatusCodeForItemNotFoundVsContainerNotFound()
107+
{
108+
// Create a test database and container
109+
Database db = await CosmosNotFoundTests.client.CreateDatabaseAsync("NotFoundTest" + Guid.NewGuid().ToString());
110+
Container container = await db.CreateContainerAsync("NotFoundTest" + Guid.NewGuid().ToString(), "/pk", 500);
111+
112+
// Test 1: Item doesn't exist in existing container - should return 404 with substatus 0
113+
ResponseMessage response = await container.ReadItemStreamAsync(
114+
partitionKey: new Cosmos.PartitionKey(DoesNotExist),
115+
id: DoesNotExist);
116+
117+
Assert.IsNotNull(response);
118+
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
119+
Assert.AreEqual(0, (int)response.Headers.SubStatusCode,
120+
"SubStatusCode should be 0 when item doesn't exist in an existing container");
121+
122+
// Test 2: Container doesn't exist - should return 404 with substatus 1003
123+
Container nonExistentContainer = db.GetContainer(DoesNotExist);
124+
response = await nonExistentContainer.ReadItemStreamAsync(
125+
partitionKey: new Cosmos.PartitionKey(DoesNotExist),
126+
id: DoesNotExist);
127+
128+
Assert.IsNotNull(response);
129+
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
130+
Assert.AreEqual(1003, (int)response.Headers.SubStatusCode,
131+
"SubStatusCode should be 1003 when container doesn't exist (owner resource not found)");
132+
133+
// Test 3: Database doesn't exist - should also return 404 with substatus 1003
134+
Database nonExistentDb = CosmosNotFoundTests.client.GetDatabase(DoesNotExist);
135+
Container containerInNonExistentDb = nonExistentDb.GetContainer(DoesNotExist);
136+
response = await containerInNonExistentDb.ReadItemStreamAsync(
137+
partitionKey: new Cosmos.PartitionKey(DoesNotExist),
138+
id: DoesNotExist);
139+
140+
Assert.IsNotNull(response);
141+
Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode);
142+
Assert.AreEqual(1003, (int)response.Headers.SubStatusCode,
143+
"SubStatusCode should be 1003 when database doesn't exist (owner resource not found)");
144+
145+
// Cleanup
146+
await db.DeleteAsync();
147+
}
148+
149+
/// <summary>
150+
/// Validates that CosmosException exposes the substatus code when thrown,
151+
/// allowing developers to distinguish between different types of 404 errors.
152+
/// </summary>
153+
[TestMethod]
154+
public async Task ValidateCosmosExceptionSubStatusCodeForNotFound()
155+
{
156+
// Create a test database and container
157+
Database db = await CosmosNotFoundTests.client.CreateDatabaseAsync("NotFoundTest" + Guid.NewGuid().ToString());
158+
Container container = await db.CreateContainerAsync("NotFoundTest" + Guid.NewGuid().ToString(), "/pk", 500);
159+
160+
// Test 1: Item doesn't exist in existing container - CosmosException should have substatus 0
161+
try
162+
{
163+
ItemResponse<dynamic> response = await container.ReadItemAsync<dynamic>(
164+
partitionKey: new Cosmos.PartitionKey(DoesNotExist),
165+
id: DoesNotExist);
166+
Assert.Fail("Expected CosmosException to be thrown");
167+
}
168+
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
169+
{
170+
Assert.AreEqual(0, ex.SubStatusCode,
171+
"SubStatusCode should be 0 when item doesn't exist in an existing container");
172+
}
173+
174+
// Test 2: Container doesn't exist - CosmosException should have substatus 1003
175+
Container nonExistentContainer = db.GetContainer(DoesNotExist);
176+
try
177+
{
178+
ItemResponse<dynamic> response = await nonExistentContainer.ReadItemAsync<dynamic>(
179+
partitionKey: new Cosmos.PartitionKey(DoesNotExist),
180+
id: DoesNotExist);
181+
Assert.Fail("Expected CosmosException to be thrown");
182+
}
183+
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
184+
{
185+
Assert.AreEqual(1003, ex.SubStatusCode,
186+
"SubStatusCode should be 1003 when container doesn't exist (owner resource not found)");
187+
}
188+
189+
// Test 3: Database doesn't exist - CosmosException should have substatus 1003
190+
Database nonExistentDb = CosmosNotFoundTests.client.GetDatabase(DoesNotExist);
191+
Container containerInNonExistentDb = nonExistentDb.GetContainer(DoesNotExist);
192+
try
193+
{
194+
ItemResponse<dynamic> response = await containerInNonExistentDb.ReadItemAsync<dynamic>(
195+
partitionKey: new Cosmos.PartitionKey(DoesNotExist),
196+
id: DoesNotExist);
197+
Assert.Fail("Expected CosmosException to be thrown");
198+
}
199+
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
200+
{
201+
Assert.AreEqual(1003, ex.SubStatusCode,
202+
"SubStatusCode should be 1003 when database doesn't exist (owner resource not found)");
203+
}
204+
205+
// Cleanup
206+
await db.DeleteAsync();
207+
}
208+
100209
private async Task ContainerOperations(Database database, bool dbNotExist)
101210
{
102211
// Create should fail if the database does not exist

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

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,108 @@ public void GenerateEventWithDiagnosticWithFilterOnStatusCodeTest()
457457
}
458458
}
459459

460+
/// <summary>
461+
/// Validates that substatus code 1003 (OwnerResourceNotFound) can be used to distinguish
462+
/// between a regular 404 (item not found) and a 404 where the parent resource doesn't exist.
463+
/// </summary>
464+
[TestMethod]
465+
public void ValidateSubStatusCode1003ForOwnerResourceNotFound()
466+
{
467+
// Test creating a NotFoundException with substatus code 1003
468+
string testMessage = "Owner resource not found";
469+
string activityId = Guid.NewGuid().ToString();
470+
int ownerNotFoundSubStatus = 1003; // SubStatusCodes.OwnerResourceNotFound
471+
double requestCharge = 1.0;
472+
473+
CosmosException exception = CosmosExceptionFactory.CreateNotFoundException(
474+
testMessage,
475+
new Headers()
476+
{
477+
SubStatusCodeLiteral = ownerNotFoundSubStatus.ToString(),
478+
ActivityId = activityId,
479+
RequestCharge = requestCharge
480+
});
481+
482+
Assert.AreEqual(HttpStatusCode.NotFound, exception.StatusCode);
483+
Assert.AreEqual(ownerNotFoundSubStatus, exception.SubStatusCode);
484+
Assert.AreEqual(ownerNotFoundSubStatus.ToString(), exception.Headers.SubStatusCodeLiteral);
485+
Assert.AreEqual(testMessage, exception.ResponseBody);
486+
Assert.AreEqual(activityId, exception.ActivityId);
487+
Assert.AreEqual(requestCharge, exception.RequestCharge);
488+
489+
// Verify the exception message contains all the relevant information
490+
Assert.IsTrue(exception.Message.Contains("404"));
491+
Assert.IsTrue(exception.Message.Contains("1003"));
492+
Assert.IsTrue(exception.ToString().Contains(testMessage));
493+
}
494+
495+
/// <summary>
496+
/// Validates that substatus code 0 distinguishes a regular item not found from owner not found.
497+
/// </summary>
498+
[TestMethod]
499+
public void ValidateSubStatusCode0ForRegularItemNotFound()
500+
{
501+
// Test creating a NotFoundException with substatus code 0 (regular item not found)
502+
string testMessage = "Item not found";
503+
string activityId = Guid.NewGuid().ToString();
504+
int itemNotFoundSubStatus = 0;
505+
double requestCharge = 1.0;
506+
507+
CosmosException exception = CosmosExceptionFactory.CreateNotFoundException(
508+
testMessage,
509+
new Headers()
510+
{
511+
SubStatusCodeLiteral = itemNotFoundSubStatus.ToString(),
512+
ActivityId = activityId,
513+
RequestCharge = requestCharge
514+
});
515+
516+
Assert.AreEqual(HttpStatusCode.NotFound, exception.StatusCode);
517+
Assert.AreEqual(itemNotFoundSubStatus, exception.SubStatusCode);
518+
Assert.AreEqual(itemNotFoundSubStatus.ToString(), exception.Headers.SubStatusCodeLiteral);
519+
Assert.AreEqual(testMessage, exception.ResponseBody);
520+
Assert.AreEqual(activityId, exception.ActivityId);
521+
Assert.AreEqual(requestCharge, exception.RequestCharge);
522+
523+
// Verify the exception message contains all the relevant information
524+
Assert.IsTrue(exception.Message.Contains("404"));
525+
Assert.IsTrue(exception.Message.Contains("0"));
526+
Assert.IsTrue(exception.ToString().Contains(testMessage));
527+
}
528+
529+
/// <summary>
530+
/// Validates that ResponseMessage correctly exposes substatus codes for 404 errors.
531+
/// </summary>
532+
[TestMethod]
533+
public void ValidateResponseMessageSubStatusCodeForNotFound()
534+
{
535+
// Create ResponseMessage with 404 and substatus 0 (item not found)
536+
Headers headersItemNotFound = new Headers()
537+
{
538+
SubStatusCodeLiteral = "0",
539+
ActivityId = Guid.NewGuid().ToString()
540+
};
541+
542+
ResponseMessage responseItemNotFound = new ResponseMessage(HttpStatusCode.NotFound)
543+
{
544+
Headers = { }
545+
};
546+
responseItemNotFound.Headers.SubStatusCodeLiteral = "0";
547+
548+
Assert.AreEqual(HttpStatusCode.NotFound, responseItemNotFound.StatusCode);
549+
Assert.AreEqual(0, (int)responseItemNotFound.Headers.SubStatusCode);
550+
551+
// Create ResponseMessage with 404 and substatus 1003 (owner resource not found)
552+
ResponseMessage responseOwnerNotFound = new ResponseMessage(HttpStatusCode.NotFound)
553+
{
554+
Headers = { }
555+
};
556+
responseOwnerNotFound.Headers.SubStatusCodeLiteral = "1003";
557+
558+
Assert.AreEqual(HttpStatusCode.NotFound, responseOwnerNotFound.StatusCode);
559+
Assert.AreEqual(1003, (int)responseOwnerNotFound.Headers.SubStatusCode);
560+
}
561+
460562
private void ValidateExceptionInfo(
461563
CosmosException exception,
462564
HttpStatusCode httpStatusCode,

0 commit comments

Comments
 (0)