forked from EduCraft/curriculum-project-hub
752a5c661d
Declarative JSON Schema per kind (segment/example/lemma/sop), embedded as real .json artifacts (ADR-0006) and validated by a small hand-rolled interpreter (properties/required/type/enum/additionalProperties + the `x-cph-content` marker) — no jsonschema-crate churn for a one-optional-string scalar surface. `x-cph-content: true` marks content fields (not in element.toml; require sibling <field>.typ); everything else is an element.toml scalar. `validate(desc)` is self-contained: UnknownKind for unknown kinds, MissingContentFile (path named in message) for absent required .typ, SchemaViolation for bad scalars; lemma proof optional ⇒ no diagnostic when absent. 8 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
77 lines
2.4 KiB
Rust
77 lines
2.4 KiB
Rust
//! Integration tests for `cph-schema::validate`, driven by tiny fixtures under
|
|
//! `tests/fixtures/<case>/` and in-code `ElementDescriptor`s.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use cph_diag::DiagCode;
|
|
use cph_model::ElementDescriptor;
|
|
use cph_schema::validate;
|
|
|
|
/// Absolute path to a fixture directory.
|
|
fn fixture(case: &str) -> PathBuf {
|
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("tests")
|
|
.join("fixtures")
|
|
.join(case)
|
|
}
|
|
|
|
fn desc(kind: &str, dir: PathBuf, scalars: toml::Table) -> ElementDescriptor {
|
|
ElementDescriptor {
|
|
kind: kind.to_string(),
|
|
dir,
|
|
scalars,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn valid_example_no_diagnostics() {
|
|
let mut scalars = toml::Table::new();
|
|
scalars.insert("source".into(), toml::Value::String("Textbook §3".into()));
|
|
let d = desc("example", fixture("example_valid"), scalars);
|
|
let diags = validate(&d);
|
|
assert!(diags.is_empty(), "expected no diagnostics, got {diags:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn example_missing_solution_typ_is_missing_content_file() {
|
|
let d = desc(
|
|
"example",
|
|
fixture("example_missing_solution"),
|
|
toml::Table::new(),
|
|
);
|
|
let diags = validate(&d);
|
|
assert_eq!(diags.len(), 1, "got {diags:?}");
|
|
assert_eq!(diags[0].code, DiagCode::MissingContentFile);
|
|
assert!(diags[0].message.contains("solution.typ"));
|
|
assert!(diags[0].hint.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn lemma_without_proof_typ_is_ok() {
|
|
// stmt.typ exists, proof.typ does not — proof is optional, so no diagnostic.
|
|
let d = desc("lemma", fixture("lemma_no_proof"), toml::Table::new());
|
|
let diags = validate(&d);
|
|
assert!(diags.is_empty(), "expected no diagnostics, got {diags:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_kind_is_reported() {
|
|
let d = desc("frob", fixture("example_valid"), toml::Table::new());
|
|
let diags = validate(&d);
|
|
assert_eq!(diags.len(), 1, "got {diags:?}");
|
|
assert_eq!(diags[0].code, DiagCode::UnknownKind);
|
|
assert!(diags[0].message.contains("frob"));
|
|
}
|
|
|
|
#[test]
|
|
fn example_source_non_string_is_schema_violation() {
|
|
let mut scalars = toml::Table::new();
|
|
scalars.insert("source".into(), toml::Value::Integer(42));
|
|
let d = desc("example", fixture("example_valid"), scalars);
|
|
let diags = validate(&d);
|
|
assert_eq!(diags.len(), 1, "got {diags:?}");
|
|
assert_eq!(diags[0].code, DiagCode::SchemaViolation);
|
|
assert!(diags[0].message.contains("source"));
|
|
assert!(diags[0].message.contains("string"));
|
|
}
|