add Graphql ci (#4681)
This commit is contained in:
parent
7ab4d94e93
commit
ebe8d49ed9
4 changed files with 238 additions and 4 deletions
27
.github/workflows/ci.yml
vendored
27
.github/workflows/ci.yml
vendored
|
@ -310,6 +310,33 @@ jobs:
|
|||
path: target/llvm-cov/html/
|
||||
retention-days: 5
|
||||
|
||||
graphql-integration:
|
||||
name: GraphQL integration
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install stable toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
|
||||
- name: Install cargo-make
|
||||
run: cargo install --debug --locked cargo-make
|
||||
|
||||
- name: run graphql integration test
|
||||
run: cargo make ci-graphql-integration
|
||||
|
||||
test-sdk-build:
|
||||
name: Test SDK build
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
@ -45,6 +45,11 @@ command = "cargo"
|
|||
env = { RUST_BACKTRACE = 1, RUST_LOG = { value = "cli_integration::common=debug", condition = { env_not_set = ["RUST_LOG"] } } }
|
||||
args = ["test", "--locked", "--features", "storage-mem,ml", "--workspace", "--test", "ml_integration", "--", "ml_integration", "--nocapture"]
|
||||
|
||||
[tasks.ci-graphql-integration]
|
||||
category = "GRAPHQL - INTEGRATION TESTS"
|
||||
command = "cargo"
|
||||
env = { RUST_BACKTRACE = 1, RUST_LOG = { value = "cli_integration::common=debug", condition = { env_not_set = ["RUST_LOG"] } }, SURREAL_EXPERIMENTAL_GRAPHQL = "true", RUSTFLAGS = "--cfg surrealdb_unstable" }
|
||||
args = ["test", "--locked", "--features", "storage-mem", "--workspace", "--test", "graphql_integration", "--", "graphql_integration", "--nocapture"]
|
||||
|
||||
[tasks.ci-test-workspace]
|
||||
category = "CI - INTEGRATION TESTS"
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use rand::{thread_rng, Rng};
|
||||
use std::collections::btree_set::Iter;
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -95,7 +97,11 @@ impl Drop for Child {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn run_internal<P: AsRef<Path>>(args: &str, current_dir: Option<P>) -> Child {
|
||||
pub fn run_internal<P: AsRef<Path>>(
|
||||
args: &str,
|
||||
current_dir: Option<P>,
|
||||
vars: Option<HashMap<String, String>>,
|
||||
) -> Child {
|
||||
let mut path = std::env::current_exe().unwrap();
|
||||
assert!(path.pop());
|
||||
if path.ends_with("deps") {
|
||||
|
@ -118,6 +124,9 @@ pub fn run_internal<P: AsRef<Path>>(args: &str, current_dir: Option<P>) -> Child
|
|||
let stderr = Stdio::from(File::create(&stderr_path).unwrap());
|
||||
|
||||
cmd.env_clear();
|
||||
if let Some(v) = vars {
|
||||
cmd.envs(v);
|
||||
}
|
||||
cmd.stdin(Stdio::piped());
|
||||
cmd.stdout(stdout);
|
||||
cmd.stderr(stderr);
|
||||
|
@ -132,12 +141,12 @@ pub fn run_internal<P: AsRef<Path>>(args: &str, current_dir: Option<P>) -> Child
|
|||
|
||||
/// Run the CLI with the given args
|
||||
pub fn run(args: &str) -> Child {
|
||||
run_internal::<String>(args, None)
|
||||
run_internal::<String>(args, None, None)
|
||||
}
|
||||
|
||||
/// Run the CLI with the given args inside a temporary directory
|
||||
pub fn run_in_dir<P: AsRef<Path>>(args: &str, current_dir: P) -> Child {
|
||||
run_internal(args, Some(current_dir))
|
||||
run_internal(args, Some(current_dir), None)
|
||||
}
|
||||
|
||||
pub fn tmp_file(name: &str) -> String {
|
||||
|
@ -153,6 +162,7 @@ pub struct StartServerArguments {
|
|||
pub tick_interval: time::Duration,
|
||||
pub temporary_directory: Option<String>,
|
||||
pub args: String,
|
||||
pub vars: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl Default for StartServerArguments {
|
||||
|
@ -165,6 +175,7 @@ impl Default for StartServerArguments {
|
|||
tick_interval: time::Duration::new(1, 0),
|
||||
temporary_directory: None,
|
||||
args: "".to_string(),
|
||||
vars: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,6 +218,18 @@ pub async fn start_server_with_temporary_directory(
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn start_server_gql_without_auth() -> Result<(String, Child), Box<dyn Error>> {
|
||||
start_server(StartServerArguments {
|
||||
auth: false,
|
||||
vars: Some(HashMap::from([(
|
||||
"SURREAL_EXPERIMENTAL_GRAPHQL".to_string(),
|
||||
"true".to_string(),
|
||||
)])),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn start_server(
|
||||
StartServerArguments {
|
||||
path,
|
||||
|
@ -216,6 +239,7 @@ pub async fn start_server(
|
|||
tick_interval,
|
||||
temporary_directory,
|
||||
args,
|
||||
vars,
|
||||
}: StartServerArguments,
|
||||
) -> Result<(String, Child), Box<dyn Error>> {
|
||||
let mut rng = thread_rng();
|
||||
|
@ -257,7 +281,7 @@ pub async fn start_server(
|
|||
info!("starting server with args: {start_args}");
|
||||
|
||||
// Configure where the logs go when running the test
|
||||
let server = run_internal::<String>(&start_args, None);
|
||||
let server = run_internal::<String>(&start_args, None, vars.clone());
|
||||
|
||||
if !wait_is_ready {
|
||||
return Ok((addr, server));
|
||||
|
|
178
tests/graphql_integration.rs
Normal file
178
tests/graphql_integration.rs
Normal file
|
@ -0,0 +1,178 @@
|
|||
mod common;
|
||||
|
||||
#[cfg(surrealdb_unstable)]
|
||||
mod graphql_integration {
|
||||
use std::time::Duration;
|
||||
|
||||
use assert_fs::assert;
|
||||
use http::header::HeaderValue;
|
||||
use http::{header, Method};
|
||||
use reqwest::Client;
|
||||
use serde_json::json;
|
||||
use surrealdb::headers::{AUTH_DB, AUTH_NS};
|
||||
use surrealdb::sql;
|
||||
use test_log::test;
|
||||
use ulid::Ulid;
|
||||
|
||||
use super::common::{self, StartServerArguments, PASS, USER};
|
||||
|
||||
#[test(tokio::test)]
|
||||
async fn basic() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (addr, _server) = common::start_server_gql_without_auth().await.unwrap();
|
||||
let gql_url = &format!("http://{addr}/graphql");
|
||||
let sql_url = &format!("http://{addr}/sql");
|
||||
|
||||
let mut headers = reqwest::header::HeaderMap::new();
|
||||
let ns = Ulid::new().to_string();
|
||||
let db = Ulid::new().to_string();
|
||||
headers.insert("surreal-ns", ns.parse()?);
|
||||
headers.insert("surreal-db", db.parse()?);
|
||||
headers.insert(header::ACCEPT, "application/json".parse()?);
|
||||
let client = reqwest::Client::builder()
|
||||
.connect_timeout(Duration::from_millis(10))
|
||||
.default_headers(headers)
|
||||
.build()?;
|
||||
|
||||
// check errors with no tables
|
||||
{
|
||||
let res = client.post(gql_url).body("").send().await?;
|
||||
assert_eq!(res.status(), 400);
|
||||
let body = res.text().await?;
|
||||
assert!(body.contains("no tables found in database"), "body: {body}")
|
||||
}
|
||||
|
||||
// add schema and data
|
||||
{
|
||||
let res = client
|
||||
.post(sql_url)
|
||||
.body(
|
||||
r#"
|
||||
DEFINE TABLE foo SCHEMAFUL;
|
||||
DEFINE FIELD val ON foo TYPE int;
|
||||
CREATE foo:1 set val = 42;
|
||||
CREATE foo:2 set val = 43;
|
||||
"#,
|
||||
)
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(res.status(), 200);
|
||||
}
|
||||
|
||||
// fetch data via graphql
|
||||
{
|
||||
let res = client
|
||||
.post(gql_url)
|
||||
.body(json!({"query": r#"query{foo{id, val}}"#}).to_string())
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(res.status(), 200);
|
||||
let body = res.text().await?;
|
||||
let expected = json!({
|
||||
"data": {
|
||||
"foo": [
|
||||
{
|
||||
"id": "foo:1",
|
||||
"val": 42
|
||||
},
|
||||
{
|
||||
"id": "foo:2",
|
||||
"val": 43
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
assert_eq!(expected.to_string(), body)
|
||||
}
|
||||
|
||||
// test limit
|
||||
{
|
||||
let res = client
|
||||
.post(gql_url)
|
||||
.body(json!({"query": r#"query{foo(limit: 1){id, val}}"#}).to_string())
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(res.status(), 200);
|
||||
let body = res.text().await?;
|
||||
let expected = json!({
|
||||
"data": {
|
||||
"foo": [
|
||||
{
|
||||
"id": "foo:1",
|
||||
"val": 42
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
assert_eq!(expected.to_string(), body)
|
||||
}
|
||||
|
||||
// test start
|
||||
{
|
||||
let res = client
|
||||
.post(gql_url)
|
||||
.body(json!({"query": r#"query{foo(start: 1){id, val}}"#}).to_string())
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(res.status(), 200);
|
||||
let body = res.text().await?;
|
||||
let expected = json!({
|
||||
"data": {
|
||||
"foo": [
|
||||
{
|
||||
"id": "foo:2",
|
||||
"val": 43
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
assert_eq!(expected.to_string(), body)
|
||||
}
|
||||
|
||||
// test order
|
||||
{
|
||||
let res = client
|
||||
.post(gql_url)
|
||||
.body(json!({"query": r#"query{foo(order: {desc: val}){id}}"#}).to_string())
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(res.status(), 200);
|
||||
let body = res.text().await?;
|
||||
let expected = json!({
|
||||
"data": {
|
||||
"foo": [
|
||||
{
|
||||
"id": "foo:2",
|
||||
},
|
||||
{
|
||||
"id": "foo:1",
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
assert_eq!(expected.to_string(), body)
|
||||
}
|
||||
|
||||
// test filter
|
||||
{
|
||||
let res = client
|
||||
.post(gql_url)
|
||||
.body(json!({"query": r#"query{foo(filter: {val: {eq: 42}}){id}}"#}).to_string())
|
||||
.send()
|
||||
.await?;
|
||||
assert_eq!(res.status(), 200);
|
||||
let body = res.text().await?;
|
||||
let expected = json!({
|
||||
"data": {
|
||||
"foo": [
|
||||
{
|
||||
"id": "foo:1",
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
assert_eq!(expected.to_string(), body)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue