#![allow(clippy::unwrap_used, clippy::panic)] #![allow(clippy::print_stderr)]
use std::sync::Arc;
use fraiseql_core::{
db::{postgres::PostgresAdapter, traits::DatabaseAdapter},
runtime::Executor,
schema::{CompiledSchema, FieldType},
};
use fraiseql_test_utils::schema_builder::{TestQueryBuilder, TestSchemaBuilder, TestTypeBuilder};
use serde_json::Value;
const VIEW: &str = "v_string_fidelity_note";
const FIXTURE: &str = r#"
DROP TABLE IF EXISTS v_string_fidelity_note;
CREATE TABLE v_string_fidelity_note (pk_note bigint, data jsonb);
INSERT INTO v_string_fidelity_note (pk_note, data) VALUES
(1, jsonb_build_object(
'id', '11110000-0000-0000-0000-000000000001',
'plain', 'hello',
'jsonish', '{"a": 1, "b": [2,3]}',
'listish', '[1,2,3]',
'payload', '{"kept": true}',
'blob', '{"kept": true}'));
"#;
const JSONISH: &str = r#"{"a": 1, "b": [2,3]}"#;
const LISTISH: &str = "[1,2,3]";
fn schema() -> CompiledSchema {
let note = TestTypeBuilder::new("Note", VIEW)
.with_simple_field("id", FieldType::Uuid)
.with_simple_field("plain", FieldType::String)
.with_simple_field("jsonish", FieldType::String)
.with_simple_field("listish", FieldType::String)
.with_simple_field("payload", FieldType::Json)
.with_simple_field("blob", FieldType::Scalar("Blob".to_string()))
.build();
let notes = TestQueryBuilder::new("notes", "Note")
.returns_list(true)
.with_sql_source(VIEW)
.build();
TestSchemaBuilder::new().with_type(note).with_query(notes).build()
}
async fn executor() -> Option<Executor> {
let pg = fraiseql_test_support::postgres().await?;
let adapter = PostgresAdapter::new(pg.url()).await.expect("connect to the bound PostgreSQL");
for stmt in FIXTURE.split(";\n") {
if stmt.trim().is_empty() {
continue;
}
adapter.execute_raw_query(stmt).await.expect("provision the #1192 fixture");
}
Some(Executor::new(schema(), Arc::new(adapter)))
}
async fn first_note(exec: &Executor, doc: &str) -> Value {
let response = exec.execute(doc, None).await.expect("the query must resolve");
response["data"]["notes"][0].clone()
}
#[tokio::test]
async fn a_string_field_holding_json_text_is_returned_as_a_string() {
let Some(exec) = executor().await else {
eprintln!("SKIP: no PostgreSQL (set DATABASE_URL)");
return;
};
let note = first_note(&exec, "{ notes { id plain jsonish listish } }").await;
assert_eq!(
note["jsonish"],
Value::String(JSONISH.to_string()),
"`jsonish` is declared String and holds JSON *text*; a client generated from this \
schema declares it `string` and `JSON.parse`s it. Response was: {note}"
);
assert_eq!(
note["listish"],
Value::String(LISTISH.to_string()),
"the array form of the same fault: {note}"
);
}
#[tokio::test]
async fn a_string_field_holding_ordinary_text_is_unaffected() {
let Some(exec) = executor().await else {
eprintln!("SKIP: no PostgreSQL (set DATABASE_URL)");
return;
};
let note = first_note(&exec, "{ notes { id plain } }").await;
assert_eq!(note["plain"], Value::String("hello".to_string()), "response was: {note}");
}
#[tokio::test]
async fn the_returned_value_is_a_json_string_not_an_object() {
let Some(exec) = executor().await else {
eprintln!("SKIP: no PostgreSQL (set DATABASE_URL)");
return;
};
let note = first_note(&exec, "{ notes { jsonish listish } }").await;
assert!(
note["jsonish"].is_string(),
"expected a JSON string, got {}: {note}",
if note["jsonish"].is_object() {
"an object"
} else {
"neither"
}
);
assert!(note["listish"].is_string(), "expected a JSON string: {note}");
}
#[tokio::test]
async fn json_and_custom_scalar_fields_still_recover_text_extracted_values() {
let Some(exec) = executor().await else {
eprintln!("SKIP: no PostgreSQL (set DATABASE_URL)");
return;
};
let note = first_note(&exec, "{ notes { payload blob } }").await;
assert_eq!(
note["payload"],
serde_json::json!({"kept": true}),
"a `Json` field must still be recovered from its text form: {note}"
);
assert_eq!(
note["blob"],
serde_json::json!({"kept": true}),
"a custom scalar is not adjudicated, so its recovery is unchanged: {note}"
);
}