KpiSqlSecurityValidatorTests.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. using Admin.NET.Plugin.AiDOP.SmartOps;
  2. using Xunit;
  3. namespace Admin.NET.Plugin.AiDOP.Tests.SmartOps;
  4. /// <summary>KPI 配置 SQL 安全校验(多层非正则)单元测试。覆盖合法查询 + 各类写/危险/绕过。</summary>
  5. public class KpiSqlSecurityValidatorTests
  6. {
  7. private const string LegalSelect =
  8. "SELECT AVG(cycle_days) AS metric_value FROM dwd_t8_material_online_cycle WHERE tenant_id=@tenant_id AND cycle_days IS NOT NULL";
  9. [Fact]
  10. public void LegalSelect_Valid()
  11. {
  12. var r = KpiSqlSecurityValidator.Validate(LegalSelect);
  13. Assert.True(r.Ok);
  14. Assert.Contains("dwd_t8_material_online_cycle", r.ReferencedTables);
  15. }
  16. [Fact]
  17. public void LegalWithSelect_Valid()
  18. {
  19. var sql = "WITH t AS (SELECT cycle_days FROM dwd_t8_material_online_cycle WHERE tenant_id=@tenant_id) " +
  20. "SELECT AVG(cycle_days) AS metric_value FROM mdp_std_t8_kc_tz_head";
  21. var r = KpiSqlSecurityValidator.Validate(sql);
  22. Assert.True(r.Ok);
  23. }
  24. [Theory]
  25. [InlineData("INSERT INTO dwd_t8_x(a) VALUES(1)")]
  26. [InlineData("UPDATE dwd_t8_x SET a=1")]
  27. [InlineData("DELETE FROM dwd_t8_x")]
  28. [InlineData("DROP TABLE dwd_t8_x")]
  29. [InlineData("ALTER TABLE dwd_t8_x ADD c INT")]
  30. [InlineData("CREATE TABLE t(a INT)")]
  31. [InlineData("TRUNCATE TABLE dwd_t8_x")]
  32. [InlineData("REPLACE INTO dwd_t8_x VALUES(1)")]
  33. [InlineData("MERGE INTO dwd_t8_x")]
  34. public void WriteStatements_Invalid(string sql)
  35. {
  36. var r = KpiSqlSecurityValidator.Validate(sql);
  37. Assert.False(r.Ok);
  38. }
  39. [Theory]
  40. [InlineData("EXEC sp_x")]
  41. [InlineData("EXECUTE sp_x")]
  42. [InlineData("CALL my_proc()")]
  43. public void ProcCalls_Invalid(string sql)
  44. {
  45. Assert.False(KpiSqlSecurityValidator.Validate(sql).Ok);
  46. }
  47. [Fact]
  48. public void MultiStatement_Invalid()
  49. {
  50. var r = KpiSqlSecurityValidator.Validate("SELECT 1 AS metric_value FROM dwd_t8_x; SELECT 2");
  51. Assert.False(r.Ok);
  52. Assert.Equal("MULTI_STATEMENT", r.ErrorCode);
  53. }
  54. [Fact]
  55. public void CommentHiddenSecondStatement_StrippedThenSingleStatement_Valid()
  56. {
  57. // 注释里的第二语句被去掉后应为单条合法 SELECT
  58. var sql = "SELECT AVG(cycle_days) AS metric_value FROM dwd_t8_material_online_cycle -- ; DROP TABLE x";
  59. Assert.True(KpiSqlSecurityValidator.Validate(sql).Ok);
  60. }
  61. [Fact]
  62. public void CommentHiddenKeyword_NotFalsePositive()
  63. {
  64. var sql = "SELECT AVG(cycle_days) AS metric_value /* delete update drop */ FROM dwd_t8_material_online_cycle";
  65. Assert.True(KpiSqlSecurityValidator.Validate(sql).Ok);
  66. }
  67. [Fact]
  68. public void KeywordInsideStringLiteral_NotFalsePositive()
  69. {
  70. var sql = "SELECT 'DROP TABLE users' AS result_message, 1 AS metric_value FROM dwd_t8_material_online_cycle";
  71. Assert.True(KpiSqlSecurityValidator.Validate(sql).Ok);
  72. }
  73. [Fact]
  74. public void IllegalTable_Invalid()
  75. {
  76. var r = KpiSqlSecurityValidator.Validate("SELECT metric_value FROM sys_user WHERE tenant_id=@tenant_id");
  77. Assert.False(r.Ok);
  78. Assert.Equal("TABLE_NOT_ALLOWED", r.ErrorCode);
  79. }
  80. [Theory]
  81. [InlineData("SHOW TABLES")]
  82. [InlineData("SET @a=1")]
  83. [InlineData("USE mysql")]
  84. public void NonSelectStart_Invalid(string sql)
  85. {
  86. Assert.False(KpiSqlSecurityValidator.Validate(sql).Ok);
  87. }
  88. [Fact]
  89. public void IntoOutfile_Invalid()
  90. {
  91. var r = KpiSqlSecurityValidator.Validate(
  92. "SELECT metric_value INTO OUTFILE '/tmp/x' FROM dwd_t8_material_online_cycle");
  93. Assert.False(r.Ok);
  94. }
  95. [Fact]
  96. public void Empty_Invalid()
  97. {
  98. Assert.False(KpiSqlSecurityValidator.Validate(" ").Ok);
  99. Assert.False(KpiSqlSecurityValidator.Validate("-- only comment").Ok);
  100. }
  101. // ── AIDOP-KPI-SQL-CONFIG-MIGRATION-EXISTING-1:本批 5 条 CONFIG_SQL 的代表性形态须通过校验 ──
  102. [Fact]
  103. public void DailyAvgFulfillment_Valid()
  104. {
  105. // S5_L1_002 / S7_L1_002 形态:AVG(rate)[*100] over dwd,逐单比率均值
  106. var sql = "SELECT ROUND(AVG(fulfillment_rate) * 100, 4) AS metric_value, COUNT(*) AS numerator_value " +
  107. "FROM dwd_t8_material_online_fulfillment " +
  108. "WHERE tenant_id = @tenant_id AND factory_id = @factory_id AND biz_date = @biz_date " +
  109. "AND source_ztid = @ztid AND fulfillment_rate IS NOT NULL";
  110. var r = KpiSqlSecurityValidator.Validate(sql);
  111. Assert.True(r.Ok);
  112. Assert.Contains("dwd_t8_material_online_fulfillment", r.ReferencedTables);
  113. }
  114. [Fact]
  115. public void MonthlyEfficiency_NullifAndDateFormat_Valid()
  116. {
  117. // S5_L1_003 / S7_L1_003 形态:NULLIF 防除零 + DATE_FORMAT 月份匹配,人效 headcount 空/0→NO_DATA
  118. var sql = "SELECT ROUND(shipment_qty / NULLIF(warehouse_headcount, 0), 4) AS metric_value, " +
  119. "shipment_qty AS numerator_value, warehouse_headcount AS denominator_value, " +
  120. "denominator_status AS result_message " +
  121. "FROM dwd_t8_finished_warehouse_efficiency " +
  122. "WHERE tenant_id = @tenant_id AND factory_id = @factory_id " +
  123. "AND biz_month = DATE_FORMAT(@biz_date, '%Y-%m') AND source_ztid = @ztid";
  124. var r = KpiSqlSecurityValidator.Validate(sql);
  125. Assert.True(r.Ok);
  126. Assert.Contains("dwd_t8_finished_warehouse_efficiency", r.ReferencedTables);
  127. }
  128. [Fact]
  129. public void DailyAvgCycle_Valid()
  130. {
  131. // S7_L1_001 形态:AVG(cycle_days) 排除 null
  132. var sql = "SELECT ROUND(AVG(cycle_days), 4) AS metric_value, COUNT(*) AS numerator_value " +
  133. "FROM dwd_t8_order_shipment_cycle " +
  134. "WHERE tenant_id = @tenant_id AND factory_id = @factory_id AND biz_date = @biz_date " +
  135. "AND source_ztid = @ztid AND cycle_days IS NOT NULL";
  136. Assert.True(KpiSqlSecurityValidator.Validate(sql).Ok);
  137. }
  138. }