fix(esg): structured JSON AuthConfiguration (matches entity doc + UI placeholders) with legacy colon fallback — keys containing ':' no longer misparse
Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -828,6 +828,105 @@ public class ExternalSystemClientTests
|
||||
Assert.Equal("alice:s3cret", decoded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiKeyAuth_JsonConfig_SetsConfiguredHeaderAndKeepsColons()
|
||||
{
|
||||
var system = new ExternalSystemDefinition("TestAPI", "https://api.example.com", "apikey")
|
||||
{
|
||||
Id = 1,
|
||||
AuthConfiguration = "{\"header\":\"X-Auth\",\"key\":\"ab:cd:ef\"}",
|
||||
};
|
||||
var method = new ExternalSystemMethod("getData", "GET", "/data") { Id = 1, ExternalSystemDefinitionId = 1 };
|
||||
StubResolution(system, method);
|
||||
|
||||
var handler = new RequestCapturingHandler(HttpStatusCode.OK, "{}");
|
||||
_httpClientFactory.CreateClient(Arg.Any<string>()).Returns(new HttpClient(handler));
|
||||
|
||||
var client = new ExternalSystemClient(
|
||||
_httpClientFactory, _repository, NullLogger<ExternalSystemClient>.Instance);
|
||||
|
||||
await client.CallAsync("TestAPI", "getData");
|
||||
|
||||
Assert.True(handler.LastHeaders!.TryGetValues("X-Auth", out var values));
|
||||
Assert.Equal("ab:cd:ef", values!.Single());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiKeyAuth_JsonConfig_DefaultHeader()
|
||||
{
|
||||
// The UI-placeholder shape ({"key":"xyz"}) — silently misparsed by the
|
||||
// legacy colon-split before Task 15 (it produced header name '{"key').
|
||||
var system = new ExternalSystemDefinition("TestAPI", "https://api.example.com", "apikey")
|
||||
{
|
||||
Id = 1,
|
||||
AuthConfiguration = "{\"key\":\"xyz\"}",
|
||||
};
|
||||
var method = new ExternalSystemMethod("getData", "GET", "/data") { Id = 1, ExternalSystemDefinitionId = 1 };
|
||||
StubResolution(system, method);
|
||||
|
||||
var handler = new RequestCapturingHandler(HttpStatusCode.OK, "{}");
|
||||
_httpClientFactory.CreateClient(Arg.Any<string>()).Returns(new HttpClient(handler));
|
||||
|
||||
var client = new ExternalSystemClient(
|
||||
_httpClientFactory, _repository, NullLogger<ExternalSystemClient>.Instance);
|
||||
|
||||
await client.CallAsync("TestAPI", "getData");
|
||||
|
||||
Assert.True(handler.LastHeaders!.TryGetValues("X-API-Key", out var values));
|
||||
Assert.Equal("xyz", values!.Single());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BasicAuth_JsonConfig_EncodesUsernamePassword()
|
||||
{
|
||||
var system = new ExternalSystemDefinition("TestAPI", "https://api.example.com", "basic")
|
||||
{
|
||||
Id = 1,
|
||||
AuthConfiguration = "{\"username\":\"u\",\"password\":\"p:w\"}",
|
||||
};
|
||||
var method = new ExternalSystemMethod("getData", "GET", "/data") { Id = 1, ExternalSystemDefinitionId = 1 };
|
||||
StubResolution(system, method);
|
||||
|
||||
var handler = new RequestCapturingHandler(HttpStatusCode.OK, "{}");
|
||||
_httpClientFactory.CreateClient(Arg.Any<string>()).Returns(new HttpClient(handler));
|
||||
|
||||
var client = new ExternalSystemClient(
|
||||
_httpClientFactory, _repository, NullLogger<ExternalSystemClient>.Instance);
|
||||
|
||||
await client.CallAsync("TestAPI", "getData");
|
||||
|
||||
var auth = handler.LastHeaders!.Authorization;
|
||||
Assert.NotNull(auth);
|
||||
Assert.Equal("Basic", auth!.Scheme);
|
||||
var decoded = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(auth.Parameter!));
|
||||
Assert.Equal("u:p:w", decoded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiKeyAuth_LegacyColonConfig_StillWorks()
|
||||
{
|
||||
// Backward compatibility: the legacy "HeaderName:KeyValue" colon form must
|
||||
// keep working for existing rows.
|
||||
var system = new ExternalSystemDefinition("TestAPI", "https://api.example.com", "apikey")
|
||||
{
|
||||
Id = 1,
|
||||
AuthConfiguration = "X-Custom:secret",
|
||||
};
|
||||
var method = new ExternalSystemMethod("getData", "GET", "/data") { Id = 1, ExternalSystemDefinitionId = 1 };
|
||||
StubResolution(system, method);
|
||||
|
||||
var handler = new RequestCapturingHandler(HttpStatusCode.OK, "{}");
|
||||
_httpClientFactory.CreateClient(Arg.Any<string>()).Returns(new HttpClient(handler));
|
||||
|
||||
var client = new ExternalSystemClient(
|
||||
_httpClientFactory, _repository, NullLogger<ExternalSystemClient>.Instance);
|
||||
|
||||
await client.CallAsync("TestAPI", "getData");
|
||||
|
||||
Assert.True(handler.LastHeaders!.TryGetValues("X-Custom", out var values));
|
||||
Assert.Equal("secret", values!.Single());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Call_ConnectionError_IsClassifiedAsTransient()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user