test(inbound-api): X-API-Key review nits — whitespace-auth fallthrough test + dedupe + comment wording

- Add WhitespaceAuthorization_ValidXApiKey_Returns200: pins the IsNullOrWhiteSpace
  fall-through — a present-but-blank Authorization header is treated as absent so a
  valid X-API-Key still authenticates (200).
- Remove MissingBearer_Returns401 (added in 510559e): identical path to
  NeitherHeader_Returns401 (no Authorization + no X-API-Key → 401); keep the
  descriptively-named NeitherHeader variant.
- Change "legacy 'X-API-Key'" -> "alternate 'X-API-Key'" in EndpointExtensions.cs and
  the BuildPostWithApiKeyHeader/HappyPath doc comments to avoid implying Bearer is
  the older transport (Bearer was itself introduced by the prior auth re-arch).
This commit is contained in:
Joseph Doherty
2026-06-16 14:06:03 -04:00
parent 510559e1be
commit 1392fd144a
2 changed files with 18 additions and 12 deletions
@@ -116,7 +116,7 @@ public sealed class EndpointExtensionsTests : IDisposable
[Fact]
public async Task HappyPath_ValidXApiKeyInScope_Returns200WithScriptResultJson()
{
// X-API-Key restore: the SAME seeded key, presented in the legacy
// X-API-Key restore: the SAME seeded key, presented in the alternate
// "X-API-Key: sbk_<keyId>_<secret>" header (raw token, no "Bearer " prefix),
// must authenticate through the SAME shared verifier and reach the script —
// proving the broadened credential extraction feeds X-API-Key to VerifyAsync.
@@ -213,22 +213,28 @@ public sealed class EndpointExtensionsTests : IDisposable
}
[Fact]
public async Task MissingBearer_Returns401()
public async Task WhitespaceAuthorization_ValidXApiKey_Returns200()
{
var method = SeedMethod(1, "noKey", "return 1;");
// IsNullOrWhiteSpace fall-through: a PRESENT but whitespace-only Authorization
// header is treated as absent, so a valid X-API-Key in the same request must
// still authenticate (200). This pins the IsNullOrWhiteSpace check: without
// it, a non-null-but-blank Authorization value would be passed straight to
// the verifier (which would fail), and the X-API-Key would never be consulted.
var method = SeedMethod(1, "echo", "return Parameters[\"value\"];",
"""[{"name":"value","type":"Integer","required":true}]""");
using var host = await BuildHostAsync(method);
await SeedKeyAsync(host, "key1", "noKey-caller", new[] { "noKey" });
var token = await SeedKeyAsync(host, keyId: "key1", displayName: "echo-caller",
scopes: new[] { "echo" });
var client = host.GetTestClient();
// No Authorization header — auth should reject with 401.
var request = new HttpRequestMessage(HttpMethod.Post, "/api/noKey")
{
Content = new StringContent("{}", Encoding.UTF8, "application/json"),
};
var request = BuildPostWithApiKeyHeader("echo", """{"value":7}""", token);
request.Headers.TryAddWithoutValidation("Authorization", " ");
var response = await client.SendAsync(request);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Contains("7", body);
}
[Fact]
@@ -553,7 +559,7 @@ public sealed class EndpointExtensionsTests : IDisposable
}
/// <summary>
/// X-API-Key restore: builds a POST carrying the credential in the legacy
/// X-API-Key restore: builds a POST carrying the credential in the alternate
/// <c>X-API-Key: sbk_&lt;keyId&gt;_&lt;secret&gt;</c> header (the RAW token, no
/// "Bearer " prefix) instead of Authorization. The endpoint must extract this
/// header value and feed it to the SAME shared verifier.