surrealpatch/tests/http_integration.rs

1301 lines
39 KiB
Rust

// cargo test --package surreal --bin surreal --no-default-features --features storage-mem,http --test http_integration -- --nocapture
mod common;
use std::time::Duration;
use http::{header, Method};
use reqwest::Client;
use serde_json::json;
use serial_test::serial;
use test_log::test;
use crate::common::{PASS, USER};
#[test(tokio::test)]
#[serial]
async fn basic_auth() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let url = &format!("http://{addr}/sql");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Request without credentials, gives an anonymous session
{
let res = client.post(url).body("CREATE foo").send().await?;
assert_eq!(res.status(), 200);
let body = res.text().await?;
assert!(body.contains("Not enough permissions"), "body: {}", body);
}
// Request with invalid credentials, returns 401
{
let res =
client.post(url).basic_auth("user", Some("pass")).body("CREATE foo").send().await?;
assert_eq!(res.status(), 401);
}
// Request with valid root credentials, gives a ROOT session
{
let res = client.post(url).basic_auth(USER, Some(PASS)).body("CREATE foo").send().await?;
assert_eq!(res.status(), 200);
let body = res.text().await?;
assert!(body.contains(r#"[{"result":[{"id":"foo:"#), "body: {}", body);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn bearer_auth() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let url = &format!("http://{addr}/sql");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Create user
{
let res = client
.post(url)
.basic_auth(USER, Some(PASS))
.body(r#"DEFINE USER user ON DB PASSWORD 'pass' ROLES OWNER"#)
.send()
.await?;
let body = res.text().await?;
assert!(body.contains(r#""status":"OK"#), "body: {}", body);
}
// Signin with user and get the token
let token: String;
{
let req_body = serde_json::to_string(
json!({
"ns": "N",
"db": "D",
"user": "user",
"pass": "pass",
})
.as_object()
.unwrap(),
)
.unwrap();
let res = client.post(format!("http://{addr}/signin")).body(req_body).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
token = body["token"].as_str().unwrap().to_owned();
}
// Request with valid token, gives a USER session
{
let res = client.post(url).bearer_auth(&token).body("CREATE foo").send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body = res.text().await?;
assert!(body.contains(r#"[{"result":[{"id":"foo:"#), "body: {}", body);
// Check the selected namespace and database
let res = client
.post(url)
.header("NS", "N2")
.header("DB", "D2")
.bearer_auth(&token)
.body("SELECT * FROM session::ns(); SELECT * FROM session::db()")
.send()
.await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body = res.text().await?;
assert!(body.contains(r#""result":["N"]"#), "body: {}", body);
assert!(body.contains(r#""result":["D"]"#), "body: {}", body);
}
// Request with invalid token, returns 401
{
let res = client.post(url).bearer_auth("token").body("CREATE foo").send().await?;
assert_eq!(res.status(), 401, "body: {}", res.text().await?);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn client_ip_extractor() -> Result<(), Box<dyn std::error::Error>> {
// TODO: test the client IP extractor
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn export_endpoint() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let url = &format!("http://{addr}/export");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Create some data
{
let res = client
.post(format!("http://{addr}/sql"))
.basic_auth(USER, Some(PASS))
.body("CREATE foo")
.send()
.await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
}
// When no auth is provided, the endpoint returns a 403
{
let res = client.get(url).send().await?;
assert_eq!(res.status(), 403, "body: {}", res.text().await?);
}
// When auth is provided, it returns the contents of the DB
{
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body = res.text().await?;
assert!(body.contains("DEFINE TABLE foo"), "body: {}", body);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn health_endpoint() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let url = &format!("http://{addr}/health");
let res = Client::default().get(url).send().await?;
assert_eq!(res.status(), 200, "response: {:#?}", res);
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn import_endpoint() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let url = &format!("http://{addr}/import");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// When no auth is provided, the endpoint returns a 403
{
let res = client.post(url).body("").send().await?;
assert_eq!(res.status(), 403, "body: {}", res.text().await?);
}
// When auth is provided, it persists the import data
{
let data = r#"
-- --------------------------------
-- OPTION
-- ------------------------------
OPTION IMPORT;
-- ------------------------------
-- TABLE: foo
-- ------------------------------
DEFINE TABLE foo SCHEMALESS PERMISSIONS NONE;
-- ------------------------------
-- TRANSACTION
-- ------------------------------
BEGIN TRANSACTION;
-- ------------------------------
-- TABLE DATA: foo
-- ------------------------------
UPDATE foo:bvklxkhtxumyrfzqoc5i CONTENT { id: foo:bvklxkhtxumyrfzqoc5i };
-- ------------------------------
-- TRANSACTION
-- ------------------------------
COMMIT TRANSACTION;
"#;
let res = client.post(url).basic_auth(USER, Some(PASS)).body(data).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Check that the data was persisted
let res = client
.post(format!("http://{addr}/sql"))
.basic_auth(USER, Some(PASS))
.body("SELECT * FROM foo")
.send()
.await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body = res.text().await?;
assert!(body.contains("foo:bvklxkhtxumyrfzqoc5i"), "body: {}", body);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn rpc_endpoint() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let url = &format!("http://{addr}/rpc");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Test WebSocket upgrade
{
let res = client
.get(url)
.header(header::CONNECTION, "Upgrade")
.header(header::UPGRADE, "websocket")
.header(header::SEC_WEBSOCKET_VERSION, "13")
.header(header::SEC_WEBSOCKET_KEY, "dGhlIHNhbXBsZSBub25jZQ==")
.send()
.await?
.upgrade()
.await;
assert!(res.is_ok(), "upgrade err: {}", res.unwrap_err());
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn signin_endpoint() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let url = &format!("http://{addr}/signin");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Create a user
{
let res = client
.post(format!("http://{addr}/sql"))
.basic_auth(USER, Some(PASS))
.body(r#"DEFINE USER user ON DB PASSWORD 'pass'"#)
.send()
.await?;
assert!(res.status().is_success(), "body: {}", res.text().await?);
}
// Signin with valid credentials and get the token
{
let req_body = serde_json::to_string(
json!({
"ns": "N",
"db": "D",
"user": "user",
"pass": "pass",
})
.as_object()
.unwrap(),
)
.unwrap();
let res = client.post(url).body(req_body).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert!(!body["token"].as_str().unwrap().to_string().is_empty(), "body: {}", body);
}
// Signin with invalid credentials returns 403
{
let req_body = serde_json::to_string(
json!({
"ns": "N",
"db": "D",
"user": "user",
"pass": "invalid_pass",
})
.as_object()
.unwrap(),
)
.unwrap();
let res = client.post(url).body(req_body).send().await?;
assert_eq!(res.status(), 401, "body: {}", res.text().await?);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn signup_endpoint() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let url = &format!("http://{addr}/signup");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Create a scope
{
let res = client
.post(format!("http://{addr}/sql"))
.basic_auth(USER, Some(PASS))
.body(
r#"
DEFINE SCOPE scope SESSION 24h
SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass) )
SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
;
"#,
)
.send()
.await?;
assert!(res.status().is_success(), "body: {}", res.text().await?);
}
// Signup into the scope
{
let req_body = serde_json::to_string(
json!({
"ns": "N",
"db": "D",
"sc": "scope",
"email": "email@email.com",
"pass": "pass",
})
.as_object()
.unwrap(),
)
.unwrap();
let res = client.post(url).body(req_body).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert!(
body["token"].as_str().unwrap().starts_with("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9"),
"body: {}",
body
);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn sql_endpoint() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let url = &format!("http://{addr}/sql");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Options method works
{
let res = client.request(Method::OPTIONS, url).send().await?;
assert_eq!(res.status(), 200);
}
// Creating a record without credentials is not allowed
{
let res = client.post(url).body("CREATE foo").send().await?;
assert_eq!(res.status(), 200);
let body = res.text().await?;
assert!(body.contains("Not enough permissions"), "body: {}", body);
}
// Creating a record with Accept JSON encoding is allowed
{
let res = client.post(url).basic_auth(USER, Some(PASS)).body("CREATE foo").send().await?;
assert_eq!(res.status(), 200);
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["status"], "OK", "body: {}", body);
}
// Creating a record with Accept CBOR encoding is allowed
{
let res = client
.post(url)
.basic_auth(USER, Some(PASS))
.header(header::ACCEPT, "application/cbor")
.body("CREATE foo")
.send()
.await?;
assert_eq!(res.status(), 200);
let _: serde_cbor::Value = serde_cbor::from_slice(&res.bytes().await?).unwrap();
}
// Creating a record with Accept PACK encoding is allowed
{
let res = client
.post(url)
.basic_auth(USER, Some(PASS))
.header(header::ACCEPT, "application/pack")
.body("CREATE foo")
.send()
.await?;
assert_eq!(res.status(), 200);
let _: serde_cbor::Value = serde_pack::from_slice(&res.bytes().await?).unwrap();
}
// Creating a record with Accept Surrealdb encoding is allowed
{
let res = client
.post(url)
.basic_auth(USER, Some(PASS))
.header(header::ACCEPT, "application/surrealdb")
.body("CREATE foo")
.send()
.await?;
assert_eq!(res.status(), 200);
// TODO: parse the result
}
// Creating a record with an unsupported Accept header, returns a 415
{
let res = client
.post(url)
.basic_auth(USER, Some(PASS))
.header(header::ACCEPT, "text/plain")
.body("CREATE foo")
.send()
.await?;
assert_eq!(res.status(), 415);
}
// Test WebSocket upgrade
{
let res = client
.get(url)
.header(header::CONNECTION, "Upgrade")
.header(header::UPGRADE, "websocket")
.header(header::SEC_WEBSOCKET_VERSION, "13")
.header(header::SEC_WEBSOCKET_KEY, "dGhlIHNhbXBsZSBub25jZQ==")
.send()
.await?
.upgrade()
.await;
assert!(res.is_ok(), "upgrade err: {}", res.unwrap_err());
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn sync_endpoint() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let url = &format!("http://{addr}/sync");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// GET
{
let res = client.get(url).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body = res.text().await?;
assert_eq!(body, r#"Save"#, "body: {}", body);
}
// POST
{
let res = client.post(url).body("").send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body = res.text().await?;
assert_eq!(body, r#"Load"#, "body: {}", body);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn version_endpoint() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let url = &format!("http://{addr}/version");
let res = Client::default().get(url).send().await?;
assert_eq!(res.status(), 200, "response: {:#?}", res);
let body = res.text().await?;
assert!(body.starts_with("surrealdb-"), "body: {}", body);
Ok(())
}
///
/// Key endpoint tests
///
async fn seed_table(
client: &Client,
addr: &str,
table: &str,
num_records: usize,
) -> Result<(), Box<dyn std::error::Error>> {
let res = client
.post(format!("http://{addr}/sql"))
.basic_auth(USER, Some(PASS))
.body(format!("CREATE |{table}:1..{num_records}| SET default = 'content'"))
.send()
.await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(
body[0]["result"].as_array().unwrap().len(),
num_records,
"error seeding the table: {}",
body
);
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn key_endpoint_select_all() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let table_name = "table";
let num_records = 50;
let url = &format!("http://{addr}/key/{table_name}");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Seed the table
seed_table(&client, &addr, table_name, num_records).await?;
// GET all records
{
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), num_records, "body: {}", body);
}
// GET records with a limit
{
let res =
client.get(format!("{}?limit=10", url)).basic_auth(USER, Some(PASS)).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 10, "body: {}", body);
}
// GET records with a start
{
let res =
client.get(format!("{}?start=10", url)).basic_auth(USER, Some(PASS)).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), num_records - 10, "body: {}", body);
assert_eq!(body[0]["result"].as_array().unwrap()[0]["id"], "table:11", "body: {}", body);
}
// GET records with a start and limit
{
let res = client
.get(format!("{}?start=10&limit=10", url))
.basic_auth(USER, Some(PASS))
.send()
.await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 10, "body: {}", body);
assert_eq!(body[0]["result"].as_array().unwrap()[0]["id"], "table:11", "body: {}", body);
}
// GET without authentication returns no records
{
let res = client.get(url).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 0, "body: {}", body);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn key_endpoint_create_all() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Create record with random ID
{
let table_name = "table";
let url = &format!("http://{addr}/key/{table_name}");
// Verify there are no records
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 0, "body: {}", body);
// Try to create the record
let res = client
.post(url)
.basic_auth(USER, Some(PASS))
.body(r#"{"name": "record_name"}"#)
.send()
.await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the record was created
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 1, "body: {}", body);
assert_eq!(
body[0]["result"].as_array().unwrap()[0]["name"],
"record_name",
"body: {}",
body
);
}
// POST without authentication creates no records
{
let table_name = "table_noauth";
let url = &format!("http://{addr}/key/{table_name}");
// Try to create the record
let res = client.post(url).body(r#"{"name": "record_name"}"#).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the table is empty
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 0, "body: {}", body);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn key_endpoint_update_all() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let table_name = "table";
let num_records = 10;
let url = &format!("http://{addr}/key/{table_name}");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
seed_table(&client, &addr, table_name, num_records).await?;
// Update all records
{
// Try to update the records
let res = client
.put(url)
.basic_auth(USER, Some(PASS))
.body(r#"{"name": "record_name"}"#)
.send()
.await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the records were updated
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), num_records, "body: {}", body);
// Verify the records have the new data
for record in body[0]["result"].as_array().unwrap() {
assert_eq!(record["name"], "record_name", "body: {}", body);
}
// Verify the records don't have the original data
for record in body[0]["result"].as_array().unwrap() {
assert!(record["default"].is_null(), "body: {}", body);
}
}
// Update all records without authentication
{
// Try to update the records
let res = client.put(url).body(r#"{"noauth": "yes"}"#).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the records were not updated
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), num_records, "body: {}", body);
// Verify the records don't have the new data
for record in body[0]["result"].as_array().unwrap() {
assert!(record["noauth"].is_null(), "body: {}", body);
}
// Verify the records have the original data
for record in body[0]["result"].as_array().unwrap() {
assert_eq!(record["name"], "record_name", "body: {}", body);
}
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn key_endpoint_modify_all() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let table_name = "table";
let num_records = 10;
let url = &format!("http://{addr}/key/{table_name}");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
seed_table(&client, &addr, table_name, num_records).await?;
// Modify all records
{
// Try to modify the records
let res = client
.patch(url)
.basic_auth(USER, Some(PASS))
.body(r#"{"name": "record_name"}"#)
.send()
.await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the records were modified
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), num_records, "body: {}", body);
// Verify the records have the new data
for record in body[0]["result"].as_array().unwrap() {
assert_eq!(record["name"], "record_name", "body: {}", body);
}
// Verify the records also have the original data
for record in body[0]["result"].as_array().unwrap() {
assert_eq!(record["default"], "content", "body: {}", body);
}
}
// Modify all records without authentication
{
// Try to modify the records
let res = client.patch(url).body(r#"{"noauth": "yes"}"#).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the records were not modified
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), num_records, "body: {}", body);
// Verify the records don't have the new data
for record in body[0]["result"].as_array().unwrap() {
assert!(record["noauth"].is_null(), "body: {}", body);
}
// Verify the records have the original data
for record in body[0]["result"].as_array().unwrap() {
assert_eq!(record["name"], "record_name", "body: {}", body);
}
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn key_endpoint_delete_all() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let table_name = "table";
let num_records = 10;
let url = &format!("http://{addr}/key/{table_name}");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Delete all records
{
seed_table(&client, &addr, table_name, num_records).await?;
// Verify there are records
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), num_records, "body: {}", body);
// Try to delete the records
let res = client.delete(url).basic_auth(USER, Some(PASS)).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the records were deleted
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 0, "body: {}", body);
}
// Delete all records without authentication
{
seed_table(&client, &addr, table_name, num_records).await?;
// Try to delete the records
let res = client.delete(url).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the records were not deleted
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), num_records, "body: {}", body);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn key_endpoint_select_one() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let table_name = "table";
let url = &format!("http://{addr}/key/{table_name}/1");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Seed the table
seed_table(&client, &addr, table_name, 1).await?;
// GET one record
{
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 1, "body: {}", body);
}
// GET without authentication returns no record
{
let res = client.get(url).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 0, "body: {}", body);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn key_endpoint_create_one() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let table_name = "table";
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Create record with known ID
{
let url = &format!("http://{addr}/key/{table_name}/new_id");
// Try to create the record
let res = client
.post(url)
.basic_auth(USER, Some(PASS))
.body(r#"{"name": "record_name"}"#)
.send()
.await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the record was created with the given ID
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 1, "body: {}", body);
assert_eq!(
body[0]["result"].as_array().unwrap()[0]["id"],
"table:new_id",
"body: {}",
body
);
}
// Create record with known ID and query params
{
let url = &format!(
"http://{addr}/key/{table_name}/new_id_query?{params}",
params = "age=45&elems=[1,2,3]&other={test: true}"
);
// Try to create the record
let res = client
.post(url)
.basic_auth(USER, Some(PASS))
.body(r#"{ age: $age, elems: $elems, other: $other }"#)
.send()
.await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the record was created with the given ID
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 1, "body: {}", body);
assert_eq!(
body[0]["result"].as_array().unwrap()[0]["id"],
"table:new_id_query",
"body: {}",
body
);
assert_eq!(body[0]["result"].as_array().unwrap()[0]["age"], 45, "body: {}", body);
assert_eq!(
body[0]["result"].as_array().unwrap()[0]["elems"].as_array().unwrap().len(),
3,
"body: {}",
body
);
assert_eq!(
body[0]["result"].as_array().unwrap()[0]["other"].as_object().unwrap()["test"],
true,
"body: {}",
body
);
}
// POST without authentication creates no records
{
let url = &format!("http://{addr}/key/{table_name}/noauth_id");
// Try to create the record
let res = client.post(url).body(r#"{"name": "record_name"}"#).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the table is empty
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 0, "body: {}", body);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn key_endpoint_update_one() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let table_name = "table";
let url = &format!("http://{addr}/key/{table_name}/1");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
seed_table(&client, &addr, table_name, 1).await?;
// Update one record
{
// Try to update the record
let res = client
.put(url)
.basic_auth(USER, Some(PASS))
.body(r#"{"name": "record_name"}"#)
.send()
.await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the record was updated
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap()[0]["id"], "table:1", "body: {}", body);
// Verify the record has the new data
assert_eq!(
body[0]["result"].as_array().unwrap()[0]["name"],
"record_name",
"body: {}",
body
);
// Verify the record doesn't have the original data
assert!(body[0]["result"].as_array().unwrap()[0]["default"].is_null(), "body: {}", body);
}
// Update one record without authentication
{
// Try to update the record
let res = client.put(url).body(r#"{"noauth": "yes"}"#).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the record was not updated
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap()[0]["id"], "table:1", "body: {}", body);
// Verify the record doesn't have the new data
assert!(body[0]["result"].as_array().unwrap()[0]["noauth"].is_null(), "body: {}", body);
// Verify the record has the original data
assert_eq!(
body[0]["result"].as_array().unwrap()[0]["name"],
"record_name",
"body: {}",
body
);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn key_endpoint_modify_one() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let table_name = "table";
let url = &format!("http://{addr}/key/{table_name}/1");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
seed_table(&client, &addr, table_name, 1).await?;
// Modify one record
{
// Try to modify one record
let res = client
.patch(url)
.basic_auth(USER, Some(PASS))
.body(r#"{"name": "record_name"}"#)
.send()
.await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the records were modified
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap()[0]["id"], "table:1", "body: {}", body);
// Verify the record has the new data
assert_eq!(
body[0]["result"].as_array().unwrap()[0]["name"],
"record_name",
"body: {}",
body
);
// Verify the record has the original data too
assert_eq!(
body[0]["result"].as_array().unwrap()[0]["default"],
"content",
"body: {}",
body
);
}
// Modify one record without authentication
{
// Try to modify the record
let res = client.patch(url).body(r#"{"noauth": "yes"}"#).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the record was not modified
let res = client.get(url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap()[0]["id"], "table:1", "body: {}", body);
// Verify the record doesn't have the new data
assert!(body[0]["result"].as_array().unwrap()[0]["noauth"].is_null(), "body: {}", body);
// Verify the record has the original data too
assert_eq!(
body[0]["result"].as_array().unwrap()[0]["default"],
"content",
"body: {}",
body
);
}
Ok(())
}
#[test(tokio::test)]
#[serial]
async fn key_endpoint_delete_one() -> Result<(), Box<dyn std::error::Error>> {
let (addr, _server) = common::start_server_with_defaults().await.unwrap();
let table_name = "table";
let base_url = &format!("http://{addr}/key/{table_name}");
// Prepare HTTP client
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("NS", "N".parse()?);
headers.insert("DB", "D".parse()?);
headers.insert(header::ACCEPT, "application/json".parse()?);
let client = reqwest::Client::builder()
.connect_timeout(Duration::from_millis(10))
.default_headers(headers)
.build()?;
// Delete all records
{
seed_table(&client, &addr, table_name, 2).await?;
// Verify there are records
let res = client.get(base_url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 2, "body: {}", body);
// Try to delete the record
let res =
client.delete(format!("{}/1", base_url)).basic_auth(USER, Some(PASS)).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify only one record was deleted
let res = client.get(base_url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 1, "body: {}", body);
assert_eq!(body[0]["result"].as_array().unwrap()[0]["id"], "table:2", "body: {}", body);
}
// Delete one record without authentication
{
// Try to delete the record
let res = client.delete(format!("{}/2", base_url)).send().await?;
assert_eq!(res.status(), 200, "body: {}", res.text().await?);
// Verify the record was not deleted
let res = client.get(base_url).basic_auth(USER, Some(PASS)).send().await?;
let body: serde_json::Value = serde_json::from_str(&res.text().await?).unwrap();
assert_eq!(body[0]["result"].as_array().unwrap().len(), 1, "body: {}", body);
}
Ok(())
}