///|
fn clean_about_blank() -> String {
"{\"title\":\"Not Found\",\"status\":404}"
}
///|
fn registered_date_problem() -> String {
"{\"type\":\"https://iana.org/assignments/http-problem-types#date\",\"title\":\"Date Not Acceptable\",\"status\":400}"
}
///|
fn batch_has_finding(
report : ProblemBatchReport,
kind : BatchFindingKind,
) -> Bool {
for finding in report.findings() {
if finding.kind() == kind {
return true
}
}
false
}
///|
fn batch_type_count(report : ProblemBatchReport, type_uri : String) -> Int {
for frequency in report.type_frequencies() {
if frequency.type_uri() == type_uri {
return frequency.count()
}
}
0
}
///|
fn batch_status_count(report : ProblemBatchReport, status : Int?) -> Int {
for frequency in report.status_frequencies() {
if frequency.status() == status {
return frequency.count()
}
}
0
}
///|
fn batch_extension_count(report : ProblemBatchReport, name : String) -> Int {
for frequency in report.extension_frequencies() {
if frequency.name() == name {
return frequency.count()
}
}
0
}
///|
test "empty batch is accepted" {
let report = analyze_problem_batch([])
assert_true(report.is_accepted())
assert_int_eq(report.input_count(), 0)
assert_int_eq(report.parsed_count(), 0)
assert_int_eq(report.quality_score(), 100)
}
///|
test "clean about blank problem is accepted" {
let report = analyze_problem_batch([clean_about_blank()])
assert_true(report.decision() == Accept)
assert_int_eq(report.parsed_count(), 1)
assert_int_eq(report.about_blank_count(), 1)
assert_int_eq(report.registered_type_count(), 1)
}
///|
test "registered explicit problem type is accepted" {
let report = analyze_problem_batch([registered_date_problem()])
assert_true(report.decision() == Accept)
assert_int_eq(report.registered_type_count(), 1)
assert_int_eq(report.unregistered_type_count(), 0)
}
///|
test "malformed document is a parse failure" {
let report = analyze_problem_batch(["{"])
assert_int_eq(report.parse_failure_count(), 1)
assert_int_eq(report.parsed_count(), 0)
assert_true(batch_has_finding(report, ParseFailure))
assert_true(report.decision() == Reject)
}
///|
test "wrong type member becomes a diagnostic" {
let report = analyze_problem_batch(["{\"title\":12,\"status\":400}"])
assert_int_eq(report.diagnostic_count(), 1)
assert_true(batch_has_finding(report, ParseDiagnostic))
assert_true(report.decision() == Review)
}
///|
test "invalid status becomes a semantic violation" {
let report = analyze_problem_batch(["{\"title\":\"Bad\",\"status\":700}"])
assert_int_eq(report.semantic_issue_count(), 1)
assert_true(batch_has_finding(report, SemanticViolation))
assert_true(report.decision() == Reject)
}
///|
test "missing title and status are counted" {
let report = analyze_problem_batch(["{}"])
assert_int_eq(report.missing_title_count(), 1)
assert_int_eq(report.missing_status_count(), 1)
assert_int_eq(report.audit_issue_count(), 2)
assert_true(report.decision() == Review)
}
///|
test "strict policy requires title" {
let report = analyze_problem_batch(
["{\"status\":404}"],
policy=ProblemBatchPolicy::strict(),
)
assert_true(batch_has_finding(report, MissingRequiredTitle))
assert_true(report.decision() == Reject)
}
///|
test "strict policy requires status" {
let report = analyze_problem_batch(
["{\"title\":\"Error\"}"],
policy=ProblemBatchPolicy::strict(),
)
assert_true(batch_has_finding(report, MissingRequiredStatus))
assert_true(report.decision() == Reject)
}
///|
test "relative type is recorded" {
let report = analyze_problem_batch([
"{\"type\":\"local/problem\",\"title\":\"Local\",\"status\":400}",
])
assert_int_eq(report.relative_type_count(), 1)
assert_true(batch_has_finding(report, RelativeProblemType))
assert_true(report.decision() == Review)
}
///|
test "strict policy rejects relative type" {
let report = analyze_problem_batch(
["{\"type\":\"local/problem\",\"title\":\"Local\",\"status\":400}"],
policy=ProblemBatchPolicy::strict().with_max_audit_issues(10),
)
assert_true(report.decision() == Reject)
}
///|
test "custom policy can allow relative type" {
let policy = ProblemBatchPolicy::strict()
.with_allow_relative_types(true)
.with_max_audit_issues(10)
let report = analyze_problem_batch(
["{\"type\":\"local/problem\",\"title\":\"Local\",\"status\":400}"],
policy~,
)
assert_true(report.decision() == Review)
}
///|
test "unknown absolute type is unregistered" {
let report = analyze_problem_batch([
"{\"type\":\"https://example.com/problems/quota\",\"title\":\"Quota\",\"status\":429}",
])
assert_int_eq(report.unregistered_type_count(), 1)
assert_true(batch_has_finding(report, UnregisteredProblemType))
}
///|
test "strict policy rejects unregistered type" {
let report = analyze_problem_batch(
[
"{\"type\":\"https://example.com/problems/quota\",\"title\":\"Quota\",\"status\":429}",
],
policy=ProblemBatchPolicy::strict(),
)
assert_true(report.decision() == Reject)
}
///|
test "custom unregistered type budget permits review" {
let policy = ProblemBatchPolicy::strict().with_max_unregistered_types(1)
let report = analyze_problem_batch(
[
"{\"type\":\"https://example.com/problems/quota\",\"title\":\"Quota\",\"status\":429}",
],
policy~,
)
assert_true(report.decision() == Review)
}
///|
test "document limit is enforced" {
let policy = ProblemBatchPolicy::default().with_max_documents(1)
let report = analyze_problem_batch(
[clean_about_blank(), clean_about_blank()],
policy~,
)
assert_true(batch_has_finding(report, DocumentLimitExceeded))
assert_true(report.decision() == Reject)
}
///|
test "negative policy budgets clamp to zero" {
let policy = ProblemBatchPolicy::default()
.with_max_documents(-1)
.with_max_parse_failures(-2)
.with_max_semantic_issues(-3)
.with_max_audit_issues(-4)
.with_max_unregistered_types(-5)
assert_int_eq(policy.max_documents(), 0)
assert_int_eq(policy.max_parse_failures(), 0)
assert_int_eq(policy.max_semantic_issues(), 0)
assert_int_eq(policy.max_audit_issues(), 0)
assert_int_eq(policy.max_unregistered_types(), 0)
}
///|
test "policy boolean accessors reflect updates" {
let policy = ProblemBatchPolicy::default()
.with_require_title(true)
.with_require_status(true)
.with_allow_relative_types(false)
assert_true(policy.requires_title())
assert_true(policy.requires_status())
assert_false(policy.allows_relative_types())
}
///|
test "permissive policy keeps parse failures reviewable" {
let report = analyze_problem_batch(
["{"],
policy=ProblemBatchPolicy::permissive(),
)
assert_true(report.decision() == Review)
assert_int_eq(report.parse_failure_count(), 1)
}
///|
test "type frequencies aggregate effective types" {
let report = analyze_problem_batch([
clean_about_blank(),
clean_about_blank(),
registered_date_problem(),
])
assert_int_eq(batch_type_count(report, "about:blank"), 2)
assert_int_eq(
batch_type_count(
report, "https://iana.org/assignments/http-problem-types#date",
),
1,
)
}
///|
test "status frequencies aggregate declared status" {
let report = analyze_problem_batch([
clean_about_blank(),
"{\"title\":\"Bad Request\",\"status\":400}",
"{\"title\":\"No status\"}",
])
assert_int_eq(batch_status_count(report, Some(404)), 1)
assert_int_eq(batch_status_count(report, Some(400)), 1)
assert_int_eq(batch_status_count(report, None), 1)
}
///|
test "extension frequencies aggregate names" {
let report = analyze_problem_batch([
"{\"title\":\"Bad Request\",\"status\":400,\"request_id\":\"a\"}", "{\"title\":\"Bad Request\",\"status\":400,\"request_id\":\"b\",\"errors\":[]}",
])
assert_int_eq(batch_extension_count(report, "request_id"), 2)
assert_int_eq(batch_extension_count(report, "errors"), 1)
}
///|
test "frequency arrays returned by report are copies" {
let report = analyze_problem_batch([clean_about_blank()])
let frequencies = report.type_frequencies()
frequencies.clear()
assert_int_eq(report.type_frequencies().length(), 1)
}
///|
test "finding array returned by report is a copy" {
let report = analyze_problem_batch(["{}"])
let findings = report.findings()
findings.clear()
assert_true(report.findings().length() > 0)
}
///|
test "member totals and maximum are calculated" {
let report = analyze_problem_batch([
clean_about_blank(),
"{\"title\":\"Bad Request\",\"status\":400,\"request_id\":\"x\"}",
])
assert_int_eq(report.total_member_count(), 5)
assert_int_eq(report.max_member_count(), 3)
assert_true(report.average_member_count() == 2.5)
}
///|
test "empty batch average member count is zero" {
assert_true(analyze_problem_batch([]).average_member_count() == 0.0)
}
///|
test "informational status class is counted" {
let report = analyze_problem_batch(["{\"title\":\"Continue\",\"status\":100}"])
assert_int_eq(report.informational_status_count(), 1)
}
///|
test "successful status class is counted" {
let report = analyze_problem_batch(["{\"title\":\"Created\",\"status\":201}"])
assert_int_eq(report.successful_status_count(), 1)
}
///|
test "redirection status class is counted" {
let report = analyze_problem_batch(["{\"title\":\"Redirect\",\"status\":302}"])
assert_int_eq(report.redirection_status_count(), 1)
}
///|
test "client error status class is counted" {
let report = analyze_problem_batch([clean_about_blank()])
assert_int_eq(report.client_error_status_count(), 1)
}
///|
test "server error status class is counted" {
let report = analyze_problem_batch([
"{\"title\":\"Internal Server Error\",\"status\":500}",
])
assert_int_eq(report.server_error_status_count(), 1)
}
///|
test "missing and invalid statuses use other class" {
let report = analyze_problem_batch([
"{\"title\":\"Missing\"}", "{\"title\":\"Invalid\",\"status\":700}",
])
assert_int_eq(report.other_status_count(), 2)
}
///|
test "quality score remains perfect for clean input" {
assert_int_eq(
analyze_problem_batch([clean_about_blank()]).quality_score(),
100,
)
}
///|
test "quality score decreases for findings" {
let report = analyze_problem_batch(["{", "{}"])
assert_true(report.quality_score() < 100)
assert_true(report.quality_score() >= 0)
}
///|
test "quality score clamps at zero" {
let inputs : Array[String] = []
for i = 0; i < 10; i = i + 1 {
inputs.push("{")
}
assert_int_eq(
analyze_problem_batch(inputs, policy=ProblemBatchPolicy::permissive()).quality_score(),
0,
)
}
///|
test "batch finding accessors expose stable evidence" {
let finding = analyze_problem_batch(["{"]).findings()[0]
assert_int_eq(finding.document_index(), 0)
assert_true(finding.kind() == ParseFailure)
assert_true(finding.context().contains("InvalidJson"))
}
///|
test "batch finding kind names are stable" {
assert_str_eq(ParseFailure.name(), "ParseFailure")
assert_str_eq(ParseDiagnostic.name(), "ParseDiagnostic")
assert_str_eq(SemanticViolation.name(), "SemanticViolation")
assert_str_eq(AuditWarning.name(), "AuditWarning")
}
///|
test "batch decision names are stable" {
assert_str_eq(Accept.name(), "accept")
assert_str_eq(Review.name(), "review")
assert_str_eq(Reject.name(), "reject")
}
///|
test "summary json contains gate metrics" {
let summary = analyze_problem_batch([clean_about_blank()]).summary_json()
assert_true(summary.contains("\"decision\":\"accept\""))
assert_true(summary.contains("\"quality_score\":100"))
assert_true(summary.contains("\"input_count\":1"))
assert_true(summary.contains("\"parsed_count\":1"))
}
///|
test "summary json reports malformed documents" {
let summary = analyze_problem_batch(["{"]).summary_json()
assert_true(summary.contains("\"decision\":\"reject\""))
assert_true(summary.contains("\"parse_failures\":1"))
assert_true(summary.contains("\"parsed_count\":0"))
}
///|
test "custom parser limits apply to every document" {
let large = "{\"detail\":\"" + "x".repeat(20_000) + "\"}"
let report = analyze_problem_batch(
[clean_about_blank(), large],
limits=Limits::strict(),
)
assert_int_eq(report.input_count(), 2)
assert_int_eq(report.parsed_count(), 1)
assert_int_eq(report.parse_failure_count(), 1)
}
///|
test "mixed batch preserves document indices" {
let report = analyze_problem_batch([
clean_about_blank(),
"{",
"{\"title\":\"Bad\",\"status\":700}",
])
let mut saw_parse_at_one = false
let mut saw_semantic_at_two = false
for finding in report.findings() {
if finding.kind() == ParseFailure && finding.document_index() == 1 {
saw_parse_at_one = true
}
if finding.kind() == SemanticViolation && finding.document_index() == 2 {
saw_semantic_at_two = true
}
}
assert_true(saw_parse_at_one)
assert_true(saw_semantic_at_two)
}