library test suite for drop validation, fixed drop return structures
This commit is contained in:
8
build.rs
8
build.rs
@ -24,12 +24,12 @@ fn main() {
|
|||||||
println!("cargo:rerun-if-changed=tests/fixtures");
|
println!("cargo:rerun-if-changed=tests/fixtures");
|
||||||
println!("cargo:rerun-if-changed=Cargo.toml");
|
println!("cargo:rerun-if-changed=Cargo.toml");
|
||||||
|
|
||||||
// File 1: src/tests.rs for #[pg_test]
|
// File 1: src/tests/fixtures.rs for #[pg_test]
|
||||||
let pg_dest_path = Path::new("src/tests.rs");
|
let pg_dest_path = Path::new("src/tests/fixtures.rs");
|
||||||
let mut pg_file = File::create(&pg_dest_path).unwrap();
|
let mut pg_file = File::create(&pg_dest_path).unwrap();
|
||||||
|
|
||||||
// File 2: tests/tests.rs for standard #[test] integration
|
// File 2: tests/fixtures.rs for standard #[test] integration
|
||||||
let std_dest_path = Path::new("tests/tests.rs");
|
let std_dest_path = Path::new("tests/fixtures.rs");
|
||||||
let mut std_file = File::create(&std_dest_path).unwrap();
|
let mut std_file = File::create(&std_dest_path).unwrap();
|
||||||
|
|
||||||
// Write headers
|
// Write headers
|
||||||
|
|||||||
2
flow
2
flow
@ -100,7 +100,7 @@ install() {
|
|||||||
|
|
||||||
test() {
|
test() {
|
||||||
info "Running jspg tests..."
|
info "Running jspg tests..."
|
||||||
cargo test --test tests "$@" || return $?
|
cargo test --tests "$@" || return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
clean() {
|
clean() {
|
||||||
|
|||||||
@ -13,7 +13,7 @@ pub struct Drop {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub response: Option<Value>,
|
pub response: Option<Value>,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub errors: Vec<Error>,
|
pub errors: Vec<Error>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ impl Drop {
|
|||||||
pub fn success() -> Self {
|
pub fn success() -> Self {
|
||||||
Self {
|
Self {
|
||||||
type_: "drop".to_string(),
|
type_: "drop".to_string(),
|
||||||
response: Some(serde_json::json!({ "result": "success" })), // Or appropriate success response
|
response: Some(serde_json::json!("success")),
|
||||||
errors: vec![],
|
errors: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,8 +53,6 @@ impl Drop {
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub punc: Option<String>,
|
|
||||||
pub code: String,
|
pub code: String,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub details: ErrorDetails,
|
pub details: ErrorDetails,
|
||||||
|
|||||||
68
src/lib.rs
68
src/lib.rs
@ -25,7 +25,7 @@ lazy_static::lazy_static! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[pg_extern(strict)]
|
#[pg_extern(strict)]
|
||||||
fn cache_json_schemas(enums: JsonB, types: JsonB, puncs: JsonB) -> JsonB {
|
pub fn cache_json_schemas(enums: JsonB, types: JsonB, puncs: JsonB) -> JsonB {
|
||||||
// 1. Build a new Registry LOCALLY (on stack)
|
// 1. Build a new Registry LOCALLY (on stack)
|
||||||
let mut registry = registry::Registry::new();
|
let mut registry = registry::Registry::new();
|
||||||
|
|
||||||
@ -107,11 +107,12 @@ fn cache_json_schemas(enums: JsonB, types: JsonB, puncs: JsonB) -> JsonB {
|
|||||||
*lock = Some(new_arc);
|
*lock = Some(new_arc);
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonB(json!({ "response": "success" }))
|
let drop = crate::drop::Drop::success();
|
||||||
|
JsonB(serde_json::to_value(drop).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pg_extern(strict, parallel_safe)]
|
#[pg_extern(strict, parallel_safe)]
|
||||||
fn mask_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
pub fn mask_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
||||||
// 1. Acquire Snapshot
|
// 1. Acquire Snapshot
|
||||||
let validator_arc = {
|
let validator_arc = {
|
||||||
let lock = GLOBAL_VALIDATOR.read().unwrap();
|
let lock = GLOBAL_VALIDATOR.read().unwrap();
|
||||||
@ -135,7 +136,6 @@ fn mask_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
|||||||
.errors
|
.errors
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|e| crate::drop::Error {
|
.map(|e| crate::drop::Error {
|
||||||
punc: None,
|
|
||||||
code: e.code,
|
code: e.code,
|
||||||
message: e.message,
|
message: e.message,
|
||||||
details: crate::drop::ErrorDetails { path: e.path },
|
details: crate::drop::ErrorDetails { path: e.path },
|
||||||
@ -148,7 +148,6 @@ fn mask_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Schema Not Found or other fatal error
|
// Schema Not Found or other fatal error
|
||||||
let error = crate::drop::Error {
|
let error = crate::drop::Error {
|
||||||
punc: None,
|
|
||||||
code: e.code,
|
code: e.code,
|
||||||
message: e.message,
|
message: e.message,
|
||||||
details: crate::drop::ErrorDetails { path: e.path },
|
details: crate::drop::ErrorDetails { path: e.path },
|
||||||
@ -158,19 +157,20 @@ fn mask_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
JsonB(json!({
|
let error = crate::drop::Error {
|
||||||
"punc": null,
|
code: "VALIDATOR_NOT_INITIALIZED".to_string(),
|
||||||
"errors": [{
|
message: "JSON Schemas have not been cached yet. Run cache_json_schemas()".to_string(),
|
||||||
"code": "VALIDATOR_NOT_INITIALIZED",
|
details: crate::drop::ErrorDetails {
|
||||||
"message": "JSON Schemas have not been cached yet. Run cache_json_schemas()",
|
path: "".to_string(),
|
||||||
"details": { "path": "" }
|
},
|
||||||
}]
|
};
|
||||||
}))
|
let drop = crate::drop::Drop::with_errors(vec![error]);
|
||||||
|
JsonB(serde_json::to_value(drop).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pg_extern(strict, parallel_safe)]
|
#[pg_extern(strict, parallel_safe)]
|
||||||
fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
pub fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
||||||
// 1. Acquire Snapshot
|
// 1. Acquire Snapshot
|
||||||
let validator_arc = {
|
let validator_arc = {
|
||||||
let lock = GLOBAL_VALIDATOR.read().unwrap();
|
let lock = GLOBAL_VALIDATOR.read().unwrap();
|
||||||
@ -189,7 +189,6 @@ fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
|||||||
.errors
|
.errors
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|e| crate::drop::Error {
|
.map(|e| crate::drop::Error {
|
||||||
punc: None,
|
|
||||||
code: e.code,
|
code: e.code,
|
||||||
message: e.message,
|
message: e.message,
|
||||||
details: crate::drop::ErrorDetails { path: e.path },
|
details: crate::drop::ErrorDetails { path: e.path },
|
||||||
@ -201,7 +200,6 @@ fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let error = crate::drop::Error {
|
let error = crate::drop::Error {
|
||||||
punc: None,
|
|
||||||
code: e.code,
|
code: e.code,
|
||||||
message: e.message,
|
message: e.message,
|
||||||
details: crate::drop::ErrorDetails { path: e.path },
|
details: crate::drop::ErrorDetails { path: e.path },
|
||||||
@ -211,19 +209,20 @@ fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
JsonB(json!({
|
let error = crate::drop::Error {
|
||||||
"punc": null,
|
code: "VALIDATOR_NOT_INITIALIZED".to_string(),
|
||||||
"errors": [{
|
message: "JSON Schemas have not been cached yet. Run cache_json_schemas()".to_string(),
|
||||||
"code": "VALIDATOR_NOT_INITIALIZED",
|
details: crate::drop::ErrorDetails {
|
||||||
"message": "JSON Schemas have not been cached yet. Run cache_json_schemas()",
|
path: "".to_string(),
|
||||||
"details": { "path": "" }
|
},
|
||||||
}]
|
};
|
||||||
}))
|
let drop = crate::drop::Drop::with_errors(vec![error]);
|
||||||
|
JsonB(serde_json::to_value(drop).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pg_extern(strict, parallel_safe)]
|
#[pg_extern(strict, parallel_safe)]
|
||||||
fn json_schema_cached(schema_id: &str) -> bool {
|
pub fn json_schema_cached(schema_id: &str) -> bool {
|
||||||
if let Some(validator) = GLOBAL_VALIDATOR.read().unwrap().as_ref() {
|
if let Some(validator) = GLOBAL_VALIDATOR.read().unwrap().as_ref() {
|
||||||
match validator.validate(schema_id, &serde_json::Value::Null) {
|
match validator.validate(schema_id, &serde_json::Value::Null) {
|
||||||
Err(e) if e.code == "SCHEMA_NOT_FOUND" => false,
|
Err(e) if e.code == "SCHEMA_NOT_FOUND" => false,
|
||||||
@ -235,18 +234,23 @@ fn json_schema_cached(schema_id: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[pg_extern(strict)]
|
#[pg_extern(strict)]
|
||||||
fn clear_json_schemas() -> JsonB {
|
pub fn clear_json_schemas() -> JsonB {
|
||||||
let mut lock = GLOBAL_VALIDATOR.write().unwrap();
|
let mut lock = GLOBAL_VALIDATOR.write().unwrap();
|
||||||
*lock = None;
|
*lock = None;
|
||||||
JsonB(json!({ "response": "success" }))
|
let drop = crate::drop::Drop::success();
|
||||||
|
JsonB(serde_json::to_value(drop).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pg_extern(strict, parallel_safe)]
|
#[pg_extern(strict, parallel_safe)]
|
||||||
fn show_json_schemas() -> JsonB {
|
pub fn show_json_schemas() -> JsonB {
|
||||||
if let Some(_validator) = GLOBAL_VALIDATOR.read().unwrap().as_ref() {
|
if let Some(validator) = GLOBAL_VALIDATOR.read().unwrap().as_ref() {
|
||||||
JsonB(json!({ "response": "success", "status": "active" }))
|
let mut keys = validator.get_schema_ids();
|
||||||
|
keys.sort();
|
||||||
|
let drop = crate::drop::Drop::success_with_val(json!(keys));
|
||||||
|
JsonB(serde_json::to_value(drop).unwrap())
|
||||||
} else {
|
} else {
|
||||||
JsonB(json!({ "response": "success", "status": "empty" }))
|
let drop = crate::drop::Drop::success_with_val(json!([]));
|
||||||
|
JsonB(serde_json::to_value(drop).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,7 +258,7 @@ fn show_json_schemas() -> JsonB {
|
|||||||
#[pg_schema]
|
#[pg_schema]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pgrx::prelude::*;
|
use pgrx::prelude::*;
|
||||||
include!("tests.rs");
|
include!("tests/fixtures.rs");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@ -1279,6 +1279,10 @@ impl Validator {
|
|||||||
Self { registry }
|
Self { registry }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_schema_ids(&self) -> Vec<String> {
|
||||||
|
self.registry.schemas.keys().cloned().collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_type(t: &str, val: &Value) -> bool {
|
pub fn check_type(t: &str, val: &Value) -> bool {
|
||||||
if let Value::String(s) = val {
|
if let Value::String(s) = val {
|
||||||
if s.is_empty() {
|
if s.is_empty() {
|
||||||
|
|||||||
113
tests/lib.rs
Normal file
113
tests/lib.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use jspg::*;
|
||||||
|
use pgrx::JsonB;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_library_api() {
|
||||||
|
// 1. Initially, schemas are not cached.
|
||||||
|
assert!(!json_schema_cached("test_schema"));
|
||||||
|
|
||||||
|
// Expected uninitialized drop format: errors + null response
|
||||||
|
let uninitialized_drop = validate_json_schema("test_schema", JsonB(json!({})));
|
||||||
|
assert_eq!(
|
||||||
|
uninitialized_drop.0,
|
||||||
|
json!({
|
||||||
|
"type": "drop",
|
||||||
|
"errors": [{
|
||||||
|
"code": "VALIDATOR_NOT_INITIALIZED",
|
||||||
|
"message": "JSON Schemas have not been cached yet. Run cache_json_schemas()",
|
||||||
|
"details": { "path": "" }
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Cache schemas
|
||||||
|
let puncs = json!([]);
|
||||||
|
let types = json!([{
|
||||||
|
"schemas": [{
|
||||||
|
"$id": "test_schema",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string" }
|
||||||
|
},
|
||||||
|
"required": ["name"]
|
||||||
|
}]
|
||||||
|
}]);
|
||||||
|
let enums = json!([]);
|
||||||
|
|
||||||
|
let cache_drop = cache_json_schemas(JsonB(enums), JsonB(types), JsonB(puncs));
|
||||||
|
assert_eq!(
|
||||||
|
cache_drop.0,
|
||||||
|
json!({
|
||||||
|
"type": "drop",
|
||||||
|
"response": "success"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Check schemas are cached
|
||||||
|
assert!(json_schema_cached("test_schema"));
|
||||||
|
|
||||||
|
let show_drop = show_json_schemas();
|
||||||
|
assert_eq!(
|
||||||
|
show_drop.0,
|
||||||
|
json!({
|
||||||
|
"type": "drop",
|
||||||
|
"response": ["test_schema"]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Validate Happy Path
|
||||||
|
let happy_drop = validate_json_schema("test_schema", JsonB(json!({"name": "Neo"})));
|
||||||
|
assert_eq!(
|
||||||
|
happy_drop.0,
|
||||||
|
json!({
|
||||||
|
"type": "drop",
|
||||||
|
"response": "success"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. Validate Unhappy Path
|
||||||
|
let unhappy_drop = validate_json_schema("test_schema", JsonB(json!({"wrong": "data"})));
|
||||||
|
assert_eq!(
|
||||||
|
unhappy_drop.0,
|
||||||
|
json!({
|
||||||
|
"type": "drop",
|
||||||
|
"errors": [
|
||||||
|
{
|
||||||
|
"code": "REQUIRED_FIELD_MISSING",
|
||||||
|
"message": "Missing name",
|
||||||
|
"details": { "path": "/name" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "STRICT_PROPERTY_VIOLATION",
|
||||||
|
"message": "Unexpected property 'wrong'",
|
||||||
|
"details": { "path": "/wrong" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. Mask Happy Path
|
||||||
|
let mask_drop = mask_json_schema(
|
||||||
|
"test_schema",
|
||||||
|
JsonB(json!({"name": "Neo", "extra": "data"})),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
mask_drop.0,
|
||||||
|
json!({
|
||||||
|
"type": "drop",
|
||||||
|
"response": {"name": "Neo"}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 7. Clear Schemas
|
||||||
|
let clear_drop = clear_json_schemas();
|
||||||
|
assert_eq!(
|
||||||
|
clear_drop.0,
|
||||||
|
json!({
|
||||||
|
"type": "drop",
|
||||||
|
"response": "success"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert!(!json_schema_cached("test_schema"));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user