milestone 21: abp list exceptions and a regex rule kind
This commit is contained in:
@@ -59,6 +59,7 @@ pub const StatusView = struct {
|
||||
last_error: []const u8,
|
||||
domains: u32,
|
||||
wildcards: u32,
|
||||
exceptions: u32,
|
||||
skipped_regex: u32,
|
||||
|
||||
pub fn from(status: *const manager_mod.SourceStatus) StatusView {
|
||||
@@ -72,6 +73,7 @@ pub const StatusView = struct {
|
||||
.last_error = status.errorText(),
|
||||
.domains = status.counts.domains,
|
||||
.wildcards = status.counts.wildcards,
|
||||
.exceptions = status.counts.exceptions,
|
||||
.skipped_regex = status.counts.skipped_regex,
|
||||
};
|
||||
}
|
||||
@@ -317,6 +319,7 @@ test "editing a blocklist keeps the counters the refresh wrote" {
|
||||
.last_updated = 1700,
|
||||
.domain_count = 42,
|
||||
.wildcard_count = 3,
|
||||
.exception_count = 2,
|
||||
.skipped_regex_count = 1,
|
||||
.checksum = "abc",
|
||||
});
|
||||
@@ -380,7 +383,7 @@ test "a status becomes the flat shape the API answers with" {
|
||||
const message = "connection refused";
|
||||
@memcpy(status.last_error[0..message.len], message);
|
||||
status.last_error_len = message.len;
|
||||
status.counts = .{ .domains = 10, .wildcards = 2, .skipped_regex = 1 };
|
||||
status.counts = .{ .domains = 10, .wildcards = 2, .exceptions = 4, .skipped_regex = 1 };
|
||||
|
||||
const view: StatusView = .from(&status);
|
||||
try testing.expectEqual(@as(i64, 7), view.id);
|
||||
@@ -389,4 +392,5 @@ test "a status becomes the flat shape the API answers with" {
|
||||
try testing.expectEqualStrings(url, view.url);
|
||||
try testing.expectEqualStrings(message, view.last_error);
|
||||
try testing.expectEqual(@as(u32, 10), view.domains);
|
||||
try testing.expectEqual(@as(u32, 4), view.exceptions);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ pub const Body = struct {
|
||||
reason: []const u8,
|
||||
/// The rule or list entry that decided it; "" when nothing matched.
|
||||
matched: []const u8,
|
||||
/// The list that decided it, which for `blocklist_exception` is the list
|
||||
/// whose `@@` rule lifted the block rather than one that made it.
|
||||
source_url: ?[]const u8,
|
||||
safe_search_rewrite: ?[]const u8,
|
||||
};
|
||||
@@ -50,7 +52,8 @@ pub const Result = struct {
|
||||
blocked: bool,
|
||||
reason: matcher.Reason,
|
||||
matched: []const u8,
|
||||
/// `blocklist_sources` row id of the list that matched.
|
||||
/// `blocklist_sources` row id of the list that matched, whether it blocked
|
||||
/// the name or lifted it through an `@@` exception.
|
||||
source_id: ?i64,
|
||||
safe_search_rewrite: ?[]const u8,
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ const Created = union(enum) { id: i64, fail: Failure };
|
||||
/// not understood.
|
||||
fn toInput(body: Body) union(enum) { input: rules_repo.RuleInput, fail: Failure } {
|
||||
const kind = model.RuleKind.fromDb(body.kind) orelse
|
||||
return .{ .fail = .{ .invalid = "kind must be 'exact' or 'wildcard'" } };
|
||||
return .{ .fail = .{ .invalid = "kind must be 'exact', 'wildcard' or 'regex'" } };
|
||||
const action = model.RuleAction.fromDb(body.action) orelse
|
||||
return .{ .fail = .{ .invalid = "action must be 'allow' or 'block'" } };
|
||||
return .{ .input = .{
|
||||
@@ -308,7 +308,7 @@ test "an unknown kind or action is a 400 before anything is written" {
|
||||
try testing.expect(toInput(.{
|
||||
.group_id = 1,
|
||||
.pattern = "ads.example",
|
||||
.kind = "regex",
|
||||
.kind = "glob",
|
||||
.action = "block",
|
||||
}).fail == .invalid);
|
||||
|
||||
@@ -327,4 +327,46 @@ test "an unknown kind or action is a 400 before anything is written" {
|
||||
});
|
||||
try testing.expectEqual(model.RuleKind.wildcard, good.input.kind);
|
||||
try testing.expectEqual(model.RuleAction.allow, good.input.action);
|
||||
|
||||
const third = toInput(.{
|
||||
.group_id = 1,
|
||||
.pattern = "^ad[0-9]+-",
|
||||
.kind = "regex",
|
||||
.action = "block",
|
||||
});
|
||||
try testing.expectEqual(model.RuleKind.regex, third.input.kind);
|
||||
}
|
||||
|
||||
test "a regex rule is stored, and a pattern the engine refuses is a 400 that names it" {
|
||||
var bench: mutations.Bench = undefined;
|
||||
try bench.init(testing.allocator);
|
||||
defer bench.deinit(testing.allocator);
|
||||
|
||||
const created = try applyCreate(&bench.state, bench.io(), bench.arena(), .{
|
||||
.group_id = 1,
|
||||
.pattern = "^ad[0-9]+-",
|
||||
.kind = .regex,
|
||||
.action = .block,
|
||||
});
|
||||
const row = (try rules_repo.getRule(&bench.database, bench.arena(), created.id)).?;
|
||||
try testing.expectEqual(model.RuleKind.regex, row.kind);
|
||||
// Stored verbatim: a regex is not a name, so nothing lowercases or
|
||||
// dot-strips it on the way to the table.
|
||||
try testing.expectEqualStrings("^ad[0-9]+-", row.pattern);
|
||||
|
||||
const unclosed = try applyCreate(&bench.state, bench.io(), bench.arena(), .{
|
||||
.group_id = 1,
|
||||
.pattern = "(",
|
||||
.kind = .regex,
|
||||
.action = .block,
|
||||
});
|
||||
try testing.expectEqualStrings(
|
||||
"rules[0].pattern: '(' is not a valid regex pattern",
|
||||
unclosed.fail.invalid,
|
||||
);
|
||||
|
||||
// The refused pattern reached no table, and the good one is still the only
|
||||
// row: a 400 costs no write and no reload.
|
||||
try testing.expectEqual(@as(i64, 1), try bench.queryInt("SELECT count(*) FROM rules"));
|
||||
try testing.expectEqual(@as(usize, 1), bench.reloads);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user