Skip to content

Commit f83d93c

Browse files
committed
Add robust CLI budget and JSON edge tests (wildcards, regex, invalid input, negative sampling, null meta) and verify aggregated violation messages
1 parent 06a5b0a commit f83d93c

File tree

3 files changed

+155
-0
lines changed

3 files changed

+155
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using FluentAssertions;
2+
using Xunit;
3+
4+
namespace KeelMatrix.QueryWatch.Cli.IntegrationTests {
5+
public class PatternBudgetWildcardsAndRegexTests {
6+
[Fact]
7+
public void Wildcard_QuestionMark_Matches_Single_Character_And_Ignores_Case() {
8+
var f = System.IO.Path.Combine(AppContext.BaseDirectory, "Fixtures", "pattern.json");
9+
10+
// Lowercase pattern, '?' for one char in "Users", '*' for the rest of the line
11+
var (ok, stdoutOk, stderrOk) = CliRunner.Run(new[] {
12+
"--input", f,
13+
"--budget", "select * from user?* = 2".Replace(" ", "") // avoid quoting/space headaches
14+
});
15+
ok.Should().Be(0, stdoutOk + Environment.NewLine + stderrOk);
16+
17+
// Overly strict budget should fail (2 matches > 1 allowed)
18+
var (fail, stdoutFail, stderrFail) = CliRunner.Run(new[] {
19+
"--input", f,
20+
"--budget", "SELECT*FROM*User?*=1"
21+
});
22+
fail.Should().Be(4, stdoutFail + Environment.NewLine + stderrFail);
23+
}
24+
25+
[Fact]
26+
public void Regex_Budget_Is_Case_Insensitive_And_Anchored_From_Start() {
27+
var f = System.IO.Path.Combine(AppContext.BaseDirectory, "Fixtures", "pattern.json");
28+
29+
// Should match both SELECTs in pattern.json
30+
var (ok, so1, se1) = CliRunner.Run(new[] {
31+
"--input", f,
32+
"--budget", @"regex:^select\s+\*\s+from\s+users\b.*=2"
33+
});
34+
ok.Should().Be(0, so1 + Environment.NewLine + se1);
35+
36+
// Only allow 1 -> should fail because there are 2 matches
37+
var (bad, so2, se2) = CliRunner.Run(new[] {
38+
"--input", f,
39+
"--budget", @"regex:^SELECT\s+\*\s+FROM\s+Users\b.*=1"
40+
});
41+
bad.Should().Be(4, so2 + Environment.NewLine + se2);
42+
}
43+
44+
[Fact]
45+
public void Invalid_Regex_Budget_Returns_InvalidArguments() {
46+
var f = System.IO.Path.Combine(AppContext.BaseDirectory, "Fixtures", "pattern.json");
47+
48+
var spec = "regex:(unclosed=2";
49+
var (code, stdout, stderr) = CliRunner.Run(new[] { "--input", f, "--budget", spec });
50+
code.Should().Be(1, stdout + Environment.NewLine + stderr); // ExitCodes.InvalidArguments
51+
stderr.Should().Contain($"Invalid --budget value '{spec}'").And.Contain("Invalid regex");
52+
}
53+
54+
[Fact]
55+
public void Zero_Allowed_Count_Fails_When_There_Are_Matches() {
56+
var f = System.IO.Path.Combine(AppContext.BaseDirectory, "Fixtures", "pattern.json");
57+
58+
var (code, stdout, stderr) = CliRunner.Run(new[] {
59+
"--input", f,
60+
"--budget", "SELECT * FROM Users*=0"
61+
});
62+
code.Should().Be(4, stdout + Environment.NewLine + stderr); // BudgetExceeded
63+
}
64+
65+
[Fact]
66+
public void Multiple_Budgets_One_Over_Still_Triggers_Failure() {
67+
var f = System.IO.Path.Combine(AppContext.BaseDirectory, "Fixtures", "pattern.json");
68+
69+
var (code, stdout, stderr) = CliRunner.Run(new[] {
70+
"--input", f,
71+
"--budget", "SELECT * FROM Users*=1", // over (2 > 1)
72+
"--budget", "INSERT INTO Users*=2" // under (1 <= 2)
73+
});
74+
code.Should().Be(4, stdout + Environment.NewLine + stderr);
75+
}
76+
}
77+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System.Text.Json;
2+
using FluentAssertions;
3+
using KeelMatrix.QueryWatch.Reporting;
4+
using Xunit;
5+
6+
namespace KeelMatrix.QueryWatch.Tests {
7+
public class QueryWatchJsonEdgeTests {
8+
[Fact]
9+
public void ToSummary_Clamps_Negative_SampleTop_To_Zero() {
10+
using var session = KeelMatrix.QueryWatch.QueryWatcher.Start();
11+
session.Record("a", TimeSpan.FromMilliseconds(1));
12+
session.Record("b", TimeSpan.FromMilliseconds(2));
13+
var report = session.Stop();
14+
15+
var summary = QueryWatchJson.ToSummary(report, sampleTop: -5);
16+
summary.Should().NotBeNull();
17+
summary.Events.Should().NotBeNull();
18+
summary.Events.Count.Should().Be(0, "negative sampleTop should be treated as zero");
19+
summary.Meta.Should().ContainKey("sampleTop").WhoseValue.Should().Be("0");
20+
}
21+
22+
[Fact]
23+
public void Export_Omits_Event_Meta_Property_When_Null() {
24+
using var session = KeelMatrix.QueryWatch.QueryWatcher.Start();
25+
session.Record("query", TimeSpan.FromMilliseconds(1), meta: null);
26+
var report = session.Stop();
27+
28+
var tempRoot = Path.Combine(Path.GetTempPath(), "qwatch-json-edge-" + Guid.NewGuid().ToString("N"));
29+
var path = Path.Combine(tempRoot, "out", "summary.json");
30+
31+
try {
32+
QueryWatchJson.ExportToFile(report, path, sampleTop: 1);
33+
File.Exists(path).Should().BeTrue();
34+
35+
using var doc = JsonDocument.Parse(File.ReadAllText(path));
36+
var root = doc.RootElement;
37+
var events = root.GetProperty("events");
38+
events.GetArrayLength().Should().BeGreaterThanOrEqualTo(1);
39+
var first = events.EnumerateArray().First();
40+
// Contract: omit event-level "meta" when it's null to keep JSON compact
41+
first.TryGetProperty("meta", out _).Should().BeFalse("null meta should be omitted to reduce JSON size");
42+
}
43+
finally {
44+
if (Directory.Exists(tempRoot)) {
45+
try { Directory.Delete(tempRoot, recursive: true); } catch { /* ignore */ }
46+
}
47+
}
48+
}
49+
}
50+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using FluentAssertions;
2+
using Xunit;
3+
4+
namespace KeelMatrix.QueryWatch.Tests {
5+
public class QueryWatchReportMessageTests {
6+
[Fact]
7+
public void ThrowIfViolations_Aggregates_All_Problems_And_Prefixes_With_Summary_Header() {
8+
var options = new KeelMatrix.QueryWatch.QueryWatchOptions {
9+
MaxQueries = 1,
10+
MaxAverageDuration = TimeSpan.FromMilliseconds(1),
11+
MaxTotalDuration = TimeSpan.FromMilliseconds(1)
12+
};
13+
using var session = KeelMatrix.QueryWatch.QueryWatcher.Start(options);
14+
15+
// Two slow queries → violate all three
16+
session.Record("SELECT 1", TimeSpan.FromMilliseconds(2));
17+
session.Record("SELECT 2", TimeSpan.FromMilliseconds(2));
18+
var report = session.Stop();
19+
20+
Action act = () => report.ThrowIfViolations();
21+
act.Should().Throw<KeelMatrix.QueryWatch.QueryWatchViolationException>()
22+
.Which.Message.Should().Contain("Summary:")
23+
.And.Contain("MaxQueries=")
24+
.And.Contain("MaxAverageDuration=")
25+
.And.Contain("MaxTotalDuration=");
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)