fix: security, data-integrity, and doc-drift findings from repo audit
Consolidates duplicate KIS API client implementations (governance tests were exercising an unused class instead of the one actually running in production), closes a SQL injection path in the DB admin page, fixes a migration that used MySQL-only syntax and had never actually applied (confirmed against production), resyncs docs/db/quantengine.dbml with all migrations, and removes a duplicate OMS·WMS·ERP frontend tree in favor of src/frontend/. Also corrects several unverifiable/inflated claims in the OMS planning docs and realigns CI/CD and architecture documentation with what's actually in the repo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
using System.Reflection;
|
||||
using QuantEngine.Infrastructure.External;
|
||||
using QuantEngine.Infrastructure.Data;
|
||||
using QuantEngine.Infrastructure.Services;
|
||||
|
||||
namespace QuantEngine.Core.Tests;
|
||||
|
||||
@@ -12,9 +11,7 @@ public class SecurityTests
|
||||
[InlineData("/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice", "FHKST03010100")]
|
||||
public void AssertReadOnly_AllowsReadOnlyQuotationPaths(string path, string trId)
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var ex = Record.Exception(() => InvokeAssertReadOnly(client, path, trId));
|
||||
var ex = Record.Exception(() => InvokeAssertReadOnly(path, trId));
|
||||
|
||||
Assert.Null(ex);
|
||||
}
|
||||
@@ -25,45 +22,34 @@ public class SecurityTests
|
||||
[InlineData("/uapi/domestic-stock/v1/trading/order-cash", "FHKST01010100")]
|
||||
public void AssertReadOnly_BlocksTradingPathsOrIds(string path, string trId)
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var ex = Assert.Throws<TargetInvocationException>(() => InvokeAssertReadOnly(client, path, trId));
|
||||
var ex = Assert.Throws<TargetInvocationException>(() => InvokeAssertReadOnly(path, trId));
|
||||
Assert.IsType<InvalidOperationException>(ex.InnerException);
|
||||
Assert.Contains("BLOCKED", ex.InnerException!.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AssertReadOnly_BlocksKnownTradingTrIdPrefixes()
|
||||
[Theory]
|
||||
[InlineData("VTTC8434R00")]
|
||||
[InlineData("TTTC9912U")]
|
||||
[InlineData("VTTC5555X")]
|
||||
public void AssertReadOnly_BlocksEntireTradingTrIdFamily_NotJustHardcodedCodes(string trId)
|
||||
{
|
||||
var client = CreateClient();
|
||||
|
||||
var ex = Assert.Throws<TargetInvocationException>(() => InvokeAssertReadOnly(client, "/uapi/domestic-stock/v1/quotations/inquire-price", "VTTC8434R00"));
|
||||
// These TR_IDs are not among the previously hardcoded exact-match list — they only get
|
||||
// blocked once the guard checks the true TTTC*/VTTC* prefix family instead of a fixed
|
||||
// set of known order codes.
|
||||
var ex = Assert.Throws<TargetInvocationException>(() =>
|
||||
InvokeAssertReadOnly("/uapi/domestic-stock/v1/quotations/inquire-price", trId));
|
||||
Assert.IsType<InvalidOperationException>(ex.InnerException);
|
||||
Assert.Contains("TR_ID", ex.InnerException!.Message);
|
||||
}
|
||||
|
||||
private static KisApiClient CreateClient()
|
||||
private static void InvokeAssertReadOnly(string path, string trId)
|
||||
{
|
||||
Environment.SetEnvironmentVariable("KIS_APP_Key_TEST", "mock-key");
|
||||
Environment.SetEnvironmentVariable("KIS_APP_Secret_TEST", "mock-secret");
|
||||
return new KisApiClient(new HttpClient(new DummyHandler()), new NoopConnectionFactory());
|
||||
}
|
||||
|
||||
private static void InvokeAssertReadOnly(KisApiClient client, string path, string trId)
|
||||
{
|
||||
var method = typeof(KisApiClient).GetMethod("AssertReadOnly", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
// QuantEngine.Infrastructure.Services.KisApiClient is the class actually DI-registered
|
||||
// in Program.cs and running in production — a second, unused KisApiClient used to live
|
||||
// under Infrastructure.External with its own independent AssertReadOnly implementation
|
||||
// that this test suite exercised instead. That class has been removed.
|
||||
var method = typeof(KisApiClient).GetMethod("AssertReadOnly", BindingFlags.Static | BindingFlags.NonPublic)
|
||||
?? throw new InvalidOperationException("AssertReadOnly method not found.");
|
||||
method.Invoke(client, new object[] { path, trId });
|
||||
}
|
||||
|
||||
private sealed class DummyHandler : HttpMessageHandler
|
||||
{
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
=> Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK));
|
||||
}
|
||||
|
||||
private sealed class NoopConnectionFactory : IDbConnectionFactory
|
||||
{
|
||||
public System.Data.IDbConnection CreateConnection() => throw new NotSupportedException("Not needed for read-only guard tests.");
|
||||
method.Invoke(null, new object[] { path, trId });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user