fix(inbound-api): resolve InboundAPI-009,010,011,013 — cache failed compiles, reject unknown body fields, close enumeration oracle, drop misnamed factory; InboundAPI-007,012 flagged

This commit is contained in:
Joseph Doherty
2026-05-16 22:24:03 -04:00
parent 8664cdf940
commit 858fe24add
7 changed files with 255 additions and 19 deletions

View File

@@ -56,15 +56,45 @@ public class ApiKeyValidatorTests
}
[Fact]
public async Task ValidKey_MethodNotFound_Returns400()
public async Task ValidKey_MethodNotFound_IsIndistinguishableFromNotApproved()
{
// InboundAPI-011: a "method not found" response must not be observably
// different from a "key not approved" response, or a caller holding any
// valid key could enumerate which method names exist on the central API.
var key = new ApiKey("test", "valid-key") { Id = 1, IsEnabled = true };
var method = new ApiMethod("realMethod", "return 1;") { Id = 10 };
_repository.GetAllApiKeysAsync().Returns(new List<ApiKey> { key });
_repository.GetMethodByNameAsync("nonExistent").Returns((ApiMethod?)null);
_repository.GetMethodByNameAsync("realMethod").Returns(method);
_repository.GetApprovedKeysForMethodAsync(10).Returns(new List<ApiKey>());
var notFound = await _validator.ValidateAsync("valid-key", "nonExistent");
var notApproved = await _validator.ValidateAsync("valid-key", "realMethod");
Assert.False(notFound.IsValid);
Assert.False(notApproved.IsValid);
// Status code and error message must be identical so existence is not observable.
Assert.Equal(notApproved.StatusCode, notFound.StatusCode);
Assert.Equal(notApproved.ErrorMessage, notFound.ErrorMessage);
Assert.Equal(403, notFound.StatusCode);
}
[Fact]
public async Task ValidKey_MethodNotFound_ErrorMessageDoesNotEchoMethodName()
{
// InboundAPI-011: the error body must not echo the caller-supplied method
// name back verbatim (reflected-input) and must not confirm non-existence.
var key = new ApiKey("test", "valid-key") { Id = 1, IsEnabled = true };
_repository.GetAllApiKeysAsync().Returns(new List<ApiKey> { key });
_repository.GetMethodByNameAsync("probe-XYZ").Returns((ApiMethod?)null);
var result = await _validator.ValidateAsync("valid-key", "probe-XYZ");
var result = await _validator.ValidateAsync("valid-key", "nonExistent");
Assert.False(result.IsValid);
Assert.Equal(400, result.StatusCode);
Assert.DoesNotContain("probe-XYZ", result.ErrorMessage ?? string.Empty);
Assert.DoesNotContain("not found", result.ErrorMessage ?? string.Empty,
StringComparison.OrdinalIgnoreCase);
}
[Fact]