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/
|
path: target/llvm-cov/html/
|
||||||
retention-days: 5
|
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:
|
test-sdk-build:
|
||||||
name: Test SDK build
|
name: Test SDK build
|
||||||
runs-on: ubuntu-latest
|
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"] } } }
|
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"]
|
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]
|
[tasks.ci-test-workspace]
|
||||||
category = "CI - INTEGRATION TESTS"
|
category = "CI - INTEGRATION TESTS"
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
use std::collections::btree_set::Iter;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::{Path, PathBuf};
|
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();
|
let mut path = std::env::current_exe().unwrap();
|
||||||
assert!(path.pop());
|
assert!(path.pop());
|
||||||
if path.ends_with("deps") {
|
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());
|
let stderr = Stdio::from(File::create(&stderr_path).unwrap());
|
||||||
|
|
||||||
cmd.env_clear();
|
cmd.env_clear();
|
||||||
|
if let Some(v) = vars {
|
||||||
|
cmd.envs(v);
|
||||||
|
}
|
||||||
cmd.stdin(Stdio::piped());
|
cmd.stdin(Stdio::piped());
|
||||||
cmd.stdout(stdout);
|
cmd.stdout(stdout);
|
||||||
cmd.stderr(stderr);
|
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
|
/// Run the CLI with the given args
|
||||||
pub fn run(args: &str) -> Child {
|
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
|
/// 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 {
|
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 {
|
pub fn tmp_file(name: &str) -> String {
|
||||||
|
@ -153,6 +162,7 @@ pub struct StartServerArguments {
|
||||||
pub tick_interval: time::Duration,
|
pub tick_interval: time::Duration,
|
||||||
pub temporary_directory: Option<String>,
|
pub temporary_directory: Option<String>,
|
||||||
pub args: String,
|
pub args: String,
|
||||||
|
pub vars: Option<HashMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StartServerArguments {
|
impl Default for StartServerArguments {
|
||||||
|
@ -165,6 +175,7 @@ impl Default for StartServerArguments {
|
||||||
tick_interval: time::Duration::new(1, 0),
|
tick_interval: time::Duration::new(1, 0),
|
||||||
temporary_directory: None,
|
temporary_directory: None,
|
||||||
args: "".to_string(),
|
args: "".to_string(),
|
||||||
|
vars: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,6 +218,18 @@ pub async fn start_server_with_temporary_directory(
|
||||||
.await
|
.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(
|
pub async fn start_server(
|
||||||
StartServerArguments {
|
StartServerArguments {
|
||||||
path,
|
path,
|
||||||
|
@ -216,6 +239,7 @@ pub async fn start_server(
|
||||||
tick_interval,
|
tick_interval,
|
||||||
temporary_directory,
|
temporary_directory,
|
||||||
args,
|
args,
|
||||||
|
vars,
|
||||||
}: StartServerArguments,
|
}: StartServerArguments,
|
||||||
) -> Result<(String, Child), Box<dyn Error>> {
|
) -> Result<(String, Child), Box<dyn Error>> {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
@ -257,7 +281,7 @@ pub async fn start_server(
|
||||||
info!("starting server with args: {start_args}");
|
info!("starting server with args: {start_args}");
|
||||||
|
|
||||||
// Configure where the logs go when running the test
|
// 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 {
|
if !wait_is_ready {
|
||||||
return Ok((addr, server));
|
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