use std::path::{Path, PathBuf};
use surrealdb_types::ToSql;
use crate::dbs::TopLevelStatement;
pub(super) fn each_corpus_statement(
mut f: impl FnMut(&Path, &crate::sql::TopLevelExpr),
) -> Option<usize> {
let corpus = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.join("language-tests")
.join("tests");
if !corpus.is_dir() {
println!("skipped: no language-test corpus at {}", corpus.display());
return None;
}
let mut files = Vec::new();
collect_surql(&corpus, &mut files);
assert!(!files.is_empty(), "the corpus directory exists but holds no `.surql` fixtures");
let mut visited = 0;
let mut unparsed = 0;
for path in files {
let Ok(source) = std::fs::read_to_string(&path) else {
continue;
};
let Ok(ast) = crate::syn::parse(&source) else {
unparsed += 1;
continue;
};
for expr in &ast.expressions {
f(&path, expr);
visited += 1;
}
}
println!("swept {visited} statements; {unparsed} files did not parse");
Some(visited)
}
fn collect_surql(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
collect_surql(&path, out);
} else if path.extension().and_then(|x| x.to_str()) == Some("surql") {
out.push(path);
}
}
}
const CORPUS_FLOOR: usize = 13_800;
fn assert_swept(visited: Option<usize>) {
let Some(visited) = visited else {
return;
};
assert!(
visited >= CORPUS_FLOOR,
"only {visited} statements were compared, below the {CORPUS_FLOOR} the corpus holds; \
the sweep is asserting less than it looks like it does"
);
}
#[test]
fn both_forms_answer_a_statement_the_same_way() {
let mut rendered = 0;
let visited = each_corpus_statement(|path, expr| {
let surface = TopLevelStatement::Ast(expr.clone());
let lowered = TopLevelStatement::Plan(expr.clone().into());
let context = path.display();
assert_eq!(
surface.statement_type(),
lowered.statement_type(),
"statement_type differs between the two forms in {context}"
);
assert_eq!(
surface.dispatch(),
lowered.dispatch(),
"dispatch differs between the two forms in {context}"
);
assert_eq!(
surface.read_only(),
lowered.read_only(),
"read_only differs between the two forms in {context}"
);
if !expr.to_sql().to_ascii_uppercase().contains("PASSWORD") {
assert_eq!(
surface.to_sql(),
lowered.to_sql(),
"statement text differs between the two forms in {context}"
);
rendered += 1;
}
});
assert_swept(visited);
assert!(
visited.is_none_or(|visited| rendered * 100 / visited.max(1) > 95),
"only {rendered} of {visited:?} statements had their text compared"
);
}
#[test]
fn statement_text_never_carries_a_plaintext_password() {
for sql in [
"DEFINE USER api ON DATABASE PASSWORD 'hunter2' ROLES VIEWER",
"DEFINE FUNCTION fn::x() { DEFINE USER api ON NAMESPACE PASSWORD 'hunter2' ROLES OWNER }",
] {
let mut ast = crate::syn::parse(sql).expect("fixture should parse");
let surface = ast.expressions.remove(0);
assert!(
surface.to_sql().contains("hunter2"),
"the fixture must actually carry a plaintext password, or it asserts nothing"
);
let text = TopLevelStatement::Ast(surface).to_sql();
assert!(!text.contains("hunter2"), "statement text disclosed a plaintext password: {text}");
}
}