Files
jspg/old_code/suite.rs
2026-02-17 17:41:54 -05:00

236 lines
9.3 KiB
Rust

// use crate::schema::Schema;
use crate::registry::REGISTRY;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use pgrx::JsonB;
use std::{fs, path::Path};
#[derive(Debug, Serialize, Deserialize)]
struct ExpectedError {
code: String,
path: String,
message_contains: Option<String>,
cause: Option<Value>,
context: Option<Value>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Group {
description: String,
schema: Option<Value>,
enums: Option<Value>,
types: Option<Value>,
puncs: Option<Value>,
tests: Vec<TestCase>,
}
#[derive(Debug, Serialize, Deserialize)]
struct TestCase {
description: String,
data: Value,
valid: bool,
action: Option<String>,
schema_id: Option<String>,
expect_errors: Option<Vec<ExpectedError>>,
}
include!("tests.rs");
fn load_remotes(dir: &Path, base_url: &str) {
if !dir.exists() { return; }
for entry in fs::read_dir(dir).expect("Failed to read remotes directory") {
let entry = entry.unwrap();
let path = entry.path();
let file_name = path.file_name().unwrap().to_str().unwrap();
if path.is_file() && file_name.ends_with(".json") {
let content = fs::read_to_string(&path).expect("Failed to read remote file");
if let Ok(schema_value) = serde_json::from_str::<Value>(&content) {
// Just check if it's a valid JSON value for a schema (object or bool)
if schema_value.is_object() || schema_value.is_boolean() {
let schema_id = format!("{}{}", base_url, file_name);
REGISTRY.insert(schema_id, schema_value);
}
}
} else if path.is_dir() {
load_remotes(&path, &format!("{}{}/", base_url, file_name));
}
}
// Mock the meta-schema for testing recursive refs
let meta_id = "https://json-schema.org/draft/2020-12/schema";
if REGISTRY.get(meta_id).is_none() {
// Just mock it as a permissive schema for now so refs resolve
REGISTRY.insert(meta_id.to_string(), json!({ "$id": meta_id }));
}
}
#[allow(dead_code)]
fn run_dir(dir: &Path, base_url: Option<&str>) -> (usize, usize) {
let mut file_count = 0;
let mut test_count = 0;
for entry in fs::read_dir(dir).expect("Failed to read directory") {
let entry = entry.unwrap();
let path = entry.path();
let file_name = path.file_name().unwrap().to_str().unwrap();
if path.is_file() && file_name.ends_with(".json") {
let count = run_file(&path, base_url);
test_count += count;
file_count += 1;
} else if path.is_dir() {
if !file_name.starts_with('.') && file_name != "optional" {
let (f, t) = run_dir(&path, base_url);
file_count += f;
test_count += t;
}
}
}
(file_count, test_count)
}
fn run_file(path: &Path, base_url: Option<&str>) -> usize {
let content = fs::read_to_string(path).expect("Failed to read file");
let groups: Vec<Group> = serde_json::from_str(&content).expect("Failed to parse JSON");
let filename = path.file_name().unwrap().to_str().unwrap();
let mut test_count = 0;
for group in groups {
// Handle JSPG setup if any JSPG fields are present
if group.enums.is_some() || group.types.is_some() || group.puncs.is_some() {
let enums = group.enums.clone().unwrap_or(json!([]));
let types = group.types.clone().unwrap_or(json!([]));
let puncs = group.puncs.clone().unwrap_or(json!([]));
// Use internal helper to register without clearing
let result = crate::cache_json_schemas(JsonB(enums), JsonB(types), JsonB(puncs));
if let Some(errors) = result.0.get("errors") {
// If the group has a test specifically for caching failures, don't panic here
let has_cache_test = group.tests.iter().any(|t| t.action.as_deref() == Some("cache"));
if !has_cache_test {
panic!("FAILED: File: {}, Group: {}\nCache failed: {:?}", filename, group.description, errors);
}
}
}
let mut temp_id = "test_root".to_string();
if let Some(schema_value) = &group.schema {
temp_id = base_url.map(|b| format!("{}schema.json", b)).unwrap_or_else(|| "test_root".to_string());
if schema_value.is_object() || schema_value.is_boolean() {
REGISTRY.insert(temp_id.clone(), schema_value.clone());
}
} else {
// Fallback for JSPG style tests where the schema is in the puncs/types
let get_first_id = |items: &Option<Value>| {
items.as_ref()
.and_then(|v| v.as_array())
.and_then(|arr| arr.first())
.and_then(|item| item.get("schemas"))
.and_then(|v| v.as_array())
.and_then(|arr| arr.first())
.and_then(|sch| sch.get("$id"))
.and_then(|id| id.as_str())
.map(|s| s.to_string())
};
if let Some(id) = get_first_id(&group.puncs).or_else(|| get_first_id(&group.types)) {
temp_id = id;
}
}
for test in &group.tests {
test_count += 1;
let sid = test.schema_id.clone().unwrap_or_else(|| temp_id.clone());
let action = test.action.as_deref().unwrap_or("validate");
pgrx::notice!("Starting Test: {}", test.description);
let result = if action == "cache" {
let enums = group.enums.clone().unwrap_or(json!([]));
let types = group.types.clone().unwrap_or(json!([]));
let puncs = group.puncs.clone().unwrap_or(json!([]));
crate::cache_json_schemas(JsonB(enums), JsonB(types), JsonB(puncs))
} else {
crate::validate_json_schema(&sid, JsonB(test.data.clone()))
};
let is_success = result.0.get("response").is_some();
pgrx::notice!("TEST: file={}, group={}, test={}, valid={}, outcome={}",
filename,
&group.description,
&test.description,
test.valid,
if is_success { "SUCCESS" } else { "ERRORS" }
);
if is_success != test.valid {
if let Some(errs) = result.0.get("errors") {
panic!(
"FAILED: File: {}, Group: {}, Test: {}\nExpected valid: {}, got ERRORS: {:?}",
filename,
group.description,
test.description,
test.valid,
errs
);
} else {
panic!(
"FAILED: File: {}, Group: {}, Test: {}\nExpected invalid, got SUCCESS",
filename,
group.description,
test.description
);
}
}
// Perform detailed assertions if present
if let Some(expectations) = &test.expect_errors {
let actual_errors = result.0.get("errors").and_then(|e| e.as_array()).expect("Expected errors array in failure response");
for expected in expectations {
let found = actual_errors.iter().any(|e| {
let code = e["code"].as_str().unwrap_or("");
let path = e["details"]["path"].as_str().unwrap_or("");
let message = e["message"].as_str().unwrap_or("");
let code_match = code == expected.code;
let path_match = path == expected.path;
let msg_match = if let Some(sub) = &expected.message_contains {
message.contains(sub)
} else {
true
};
let matches_cause = if let Some(expected_cause) = &expected.cause {
e["details"]["cause"] == *expected_cause
} else {
true
};
let matches_context = if let Some(expected_context) = &expected.context {
e["details"]["context"] == *expected_context
} else {
true
};
code_match && path_match && msg_match && matches_cause && matches_context
});
if !found {
panic!(
"FAILED: File: {}, Group: {}, Test: {}\nMissing expected error: code='{}', path='{}'\nActual errors: {:?}",
filename,
group.description,
test.description,
expected.code,
expected.path,
actual_errors
);
}
}
}
} // end of test loop
} // end of group loop
test_count
}