1406 lines
42 KiB
Rust
1406 lines
42 KiB
Rust
use hyper::StatusCode;
|
|
use observability_deps::tracing::{debug, info};
|
|
use pretty_assertions::assert_eq;
|
|
use serde_json::{Value, json};
|
|
use test_helpers::assert_contains;
|
|
|
|
use crate::server::{ConfigProvider, TestServer};
|
|
|
|
#[tokio::test]
|
|
async fn api_v3_configure_distinct_cache_create() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/distinct_cache",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
// Write some LP to the database to initialize the catalog
|
|
let db_name = "foo";
|
|
let tbl_name = "bar";
|
|
server
|
|
.write_lp_to_db(
|
|
db_name,
|
|
format!("{tbl_name},t1=a,t2=aa,t3=aaa f1=\"potato\",f2=true,f3=4i,f4=4u,f5=5 1000"),
|
|
influxdb3_client::Precision::Second,
|
|
)
|
|
.await
|
|
.expect("write to db");
|
|
|
|
#[derive(Default)]
|
|
struct TestCase {
|
|
description: &'static str,
|
|
db: Option<&'static str>,
|
|
table: Option<&'static str>,
|
|
cache_name: Option<&'static str>,
|
|
max_cardinality: Option<usize>,
|
|
max_age: Option<u64>,
|
|
columns: &'static [&'static str],
|
|
expected: StatusCode,
|
|
}
|
|
|
|
let test_cases = [
|
|
TestCase {
|
|
description: "no parameters specified",
|
|
expected: StatusCode::BAD_REQUEST,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "missing database name",
|
|
table: Some("bar"),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "invalid database name",
|
|
db: Some("carrot"),
|
|
table: Some("bar"),
|
|
expected: StatusCode::NOT_FOUND,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "invalid table name",
|
|
db: Some("foo"),
|
|
table: Some("celery"),
|
|
expected: StatusCode::NOT_FOUND,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "no columns specified",
|
|
db: Some("foo"),
|
|
table: Some("bar"),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "invalid column specified, that does not exist",
|
|
db: Some("foo"),
|
|
table: Some("bar"),
|
|
columns: &["leek"],
|
|
expected: StatusCode::BAD_REQUEST,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "invalid column specified, that is not tag or string",
|
|
db: Some("foo"),
|
|
table: Some("bar"),
|
|
columns: &["f2"],
|
|
expected: StatusCode::BAD_REQUEST,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "valid request, creates cache",
|
|
db: Some("foo"),
|
|
table: Some("bar"),
|
|
columns: &["t1", "t2"],
|
|
expected: StatusCode::CREATED,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "identical to previous request, still success, but results in no 204 content",
|
|
db: Some("foo"),
|
|
table: Some("bar"),
|
|
columns: &["t1", "t2"],
|
|
expected: StatusCode::CONFLICT,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "same as previous, but with incompatible configuratin, max cardinality",
|
|
db: Some("foo"),
|
|
table: Some("bar"),
|
|
columns: &["t1", "t2"],
|
|
max_cardinality: Some(1),
|
|
expected: StatusCode::CONFLICT,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "same as previous, but with incompatible configuratin, max age",
|
|
db: Some("foo"),
|
|
table: Some("bar"),
|
|
columns: &["t1", "t2"],
|
|
max_age: Some(1),
|
|
expected: StatusCode::CONFLICT,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "valid request, creates cache",
|
|
db: Some("foo"),
|
|
table: Some("bar"),
|
|
columns: &["t1"],
|
|
cache_name: Some("my_cache"),
|
|
expected: StatusCode::CREATED,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "same as previous, but with incompatible configuratino, column set",
|
|
db: Some("foo"),
|
|
table: Some("bar"),
|
|
columns: &["t1", "t2"],
|
|
cache_name: Some("my_cache"),
|
|
expected: StatusCode::CONFLICT,
|
|
..Default::default()
|
|
},
|
|
];
|
|
|
|
for tc in test_cases {
|
|
let mut body = serde_json::json!({
|
|
"db": tc.db,
|
|
"table": tc.table,
|
|
"name": tc.cache_name,
|
|
"columns": tc.columns,
|
|
});
|
|
if let Some(mc) = tc.max_cardinality {
|
|
body["max_cardinality"] = mc.into();
|
|
}
|
|
if let Some(ma) = tc.max_age {
|
|
body["max_age"] = ma.into();
|
|
}
|
|
let resp = client
|
|
.post(&url)
|
|
.json(&body)
|
|
.send()
|
|
.await
|
|
.expect("send request to create distinct cache");
|
|
let status = resp.status();
|
|
assert_eq!(
|
|
tc.expected,
|
|
status,
|
|
"test case failed: {description}",
|
|
description = tc.description
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn api_v3_configure_distinct_cache_delete() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/distinct_cache",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
let db_name = "foo";
|
|
let tbl_name = "bar";
|
|
let cache_name = "test_cache";
|
|
server
|
|
.write_lp_to_db(
|
|
db_name,
|
|
format!("{tbl_name},t1=a,t2=aa f1=true 1000"),
|
|
influxdb3_client::Precision::Second,
|
|
)
|
|
.await
|
|
.expect("write lp to db");
|
|
|
|
struct TestCase {
|
|
description: &'static str,
|
|
request: Request,
|
|
expected: StatusCode,
|
|
}
|
|
|
|
enum Request {
|
|
Create(serde_json::Value),
|
|
Delete(DeleteRequest),
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct DeleteRequest {
|
|
db: Option<&'static str>,
|
|
table: Option<&'static str>,
|
|
name: Option<&'static str>,
|
|
}
|
|
|
|
use Request::*;
|
|
let mut test_cases = [
|
|
TestCase {
|
|
description: "create a distinct cache",
|
|
request: Create(serde_json::json!({
|
|
"db": db_name,
|
|
"table": tbl_name,
|
|
"name": cache_name,
|
|
"columns": ["t1", "t2"],
|
|
})),
|
|
expected: StatusCode::CREATED,
|
|
},
|
|
TestCase {
|
|
description: "delete with no parameters fails",
|
|
request: Delete(DeleteRequest {
|
|
..Default::default()
|
|
}),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
},
|
|
TestCase {
|
|
description: "missing table, name params",
|
|
request: Delete(DeleteRequest {
|
|
db: Some(db_name),
|
|
..Default::default()
|
|
}),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
},
|
|
TestCase {
|
|
description: "missing name param",
|
|
request: Delete(DeleteRequest {
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
..Default::default()
|
|
}),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
},
|
|
TestCase {
|
|
description: "missing table param",
|
|
request: Delete(DeleteRequest {
|
|
db: Some(db_name),
|
|
name: Some(cache_name),
|
|
..Default::default()
|
|
}),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
},
|
|
TestCase {
|
|
description: "missing db param",
|
|
request: Delete(DeleteRequest {
|
|
table: Some(tbl_name),
|
|
name: Some(cache_name),
|
|
..Default::default()
|
|
}),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
},
|
|
TestCase {
|
|
description: "missing db, table param",
|
|
request: Delete(DeleteRequest {
|
|
name: Some(cache_name),
|
|
..Default::default()
|
|
}),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
},
|
|
TestCase {
|
|
description: "missing db, name param",
|
|
request: Delete(DeleteRequest {
|
|
table: Some(tbl_name),
|
|
..Default::default()
|
|
}),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
},
|
|
TestCase {
|
|
description: "valid request, will delete",
|
|
request: Delete(DeleteRequest {
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
name: Some(cache_name),
|
|
}),
|
|
expected: StatusCode::OK,
|
|
},
|
|
TestCase {
|
|
description: "same as previous, but gets 404 as the cache was already deleted",
|
|
request: Delete(DeleteRequest {
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
name: Some(cache_name),
|
|
}),
|
|
expected: StatusCode::NOT_FOUND,
|
|
},
|
|
];
|
|
|
|
// do a pass through the test cases deleting via JSON in the body:
|
|
for tc in &test_cases {
|
|
match &tc.request {
|
|
Create(body) => assert_eq!(
|
|
tc.expected,
|
|
client
|
|
.post(&url)
|
|
.json(&body)
|
|
.send()
|
|
.await
|
|
.expect("create request sends")
|
|
.status(),
|
|
"creation test case failed: {description}",
|
|
description = tc.description
|
|
),
|
|
Delete(DeleteRequest { db, table, name }) => {
|
|
let body = serde_json::json!({
|
|
"db": db,
|
|
"table": table,
|
|
"name": name,
|
|
});
|
|
assert_eq!(
|
|
tc.expected,
|
|
client
|
|
.delete(&url)
|
|
.json(&body)
|
|
.send()
|
|
.await
|
|
.expect("delete request send")
|
|
.status(),
|
|
"deletion test case using JSON body failed: {description}",
|
|
description = tc.description
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// do another pass through the test cases, this time deleting via parameters in the URI:
|
|
|
|
// on this pass, need to switch the status code that does not provide any params to a 415
|
|
// UNSUPPORTED MEDIA TYPE error, as the handler sees no URI parameters, and attempts to parse
|
|
// the body as JSON, thus resulting in the 415.
|
|
test_cases[1].expected = StatusCode::UNSUPPORTED_MEDIA_TYPE;
|
|
for tc in &test_cases {
|
|
match &tc.request {
|
|
Create(body) => assert_eq!(
|
|
tc.expected,
|
|
client
|
|
.post(&url)
|
|
.json(&body)
|
|
.send()
|
|
.await
|
|
.expect("create request sends")
|
|
.status(),
|
|
"creation test case failed: {description}",
|
|
description = tc.description
|
|
),
|
|
Delete(DeleteRequest { db, table, name }) => {
|
|
let mut params = vec![];
|
|
if let Some(db) = db {
|
|
params.push(("db", db));
|
|
}
|
|
if let Some(table) = table {
|
|
params.push(("table", table));
|
|
}
|
|
if let Some(name) = name {
|
|
params.push(("name", name));
|
|
}
|
|
assert_eq!(
|
|
tc.expected,
|
|
client
|
|
.delete(&url)
|
|
.query(¶ms)
|
|
.send()
|
|
.await
|
|
.expect("delete request send")
|
|
.status(),
|
|
"deletion test case using URI parameters failed: {description}",
|
|
description = tc.description
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn api_v3_configure_last_cache_create() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/last_cache",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
// Write some LP to the database to initialize the catalog:
|
|
let db_name = "db";
|
|
let tbl_name = "tbl";
|
|
server
|
|
.write_lp_to_db(
|
|
db_name,
|
|
format!("{tbl_name},t1=a,t2=b,t3=c f1=true,f2=\"hello\",f3=4i,f4=4u,f5=5 1000"),
|
|
influxdb3_client::Precision::Second,
|
|
)
|
|
.await
|
|
.expect("write to db");
|
|
|
|
#[derive(Default)]
|
|
struct TestCase {
|
|
// These attributes all map to parameters of the request body:
|
|
description: &'static str,
|
|
db: Option<&'static str>,
|
|
table: Option<&'static str>,
|
|
cache_name: Option<&'static str>,
|
|
count: Option<usize>,
|
|
ttl: Option<usize>,
|
|
key_cols: Option<&'static [&'static str]>,
|
|
val_cols: Option<&'static [&'static str]>,
|
|
// This is the status code expected in the response:
|
|
expected: StatusCode,
|
|
}
|
|
|
|
let test_cases = [
|
|
TestCase {
|
|
description: "no parameters specified",
|
|
expected: StatusCode::BAD_REQUEST,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "missing database name",
|
|
table: Some(tbl_name),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "missing table name",
|
|
db: Some(db_name),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "Good, will use defaults for everything omitted, and get back a 201",
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
expected: StatusCode::CREATED,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "Same as before, will fail with 409",
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
expected: StatusCode::CONFLICT,
|
|
..Default::default()
|
|
},
|
|
// NOTE: this will only differ from the previous cache in name, should this actually
|
|
// be an error?
|
|
TestCase {
|
|
description: "Use a specific cache name, will succeed and create new cache",
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
cache_name: Some("my_cache"),
|
|
expected: StatusCode::CREATED,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "Same as previous, but will get 409 because it conflicts",
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
cache_name: Some("my_cache"),
|
|
expected: StatusCode::CONFLICT,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "Same as previous, but this time try to use different parameters, this \
|
|
will result in a conflict",
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
cache_name: Some("my_cache"),
|
|
// The default TTL that would have been used is 4 * 60 * 60 seconds (4 hours)
|
|
ttl: Some(666),
|
|
expected: StatusCode::CONFLICT,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "Will create new cache, because key columns are unique, and so will be the name",
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
key_cols: Some(&["t1", "t2"]),
|
|
expected: StatusCode::CREATED,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "Same as previous, but will get 409 because of conflict",
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
key_cols: Some(&["t1", "t2"]),
|
|
expected: StatusCode::CONFLICT,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "Use an invalid key column (by name) is a not found",
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
key_cols: Some(&["not_a_key_column"]),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "Use an invalid key column (by type) is a bad request",
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
// f5 is a float, which is not supported as a key column:
|
|
key_cols: Some(&["f5"]),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "Use an invalid value column is a bad request",
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
val_cols: Some(&["not_a_value_column"]),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
..Default::default()
|
|
},
|
|
TestCase {
|
|
description: "Use an invalid cache size is a bad request",
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
cache_name: Some("too_small"),
|
|
count: Some(0),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
..Default::default()
|
|
},
|
|
];
|
|
|
|
for (i, t) in test_cases.into_iter().enumerate() {
|
|
let mut body = serde_json::json!({
|
|
"db": t.db,
|
|
"table": t.table,
|
|
"name": t.cache_name,
|
|
"key_columns": t.key_cols,
|
|
"value_columns": t.val_cols,
|
|
});
|
|
if let Some(c) = t.count {
|
|
body["count"] = c.into();
|
|
}
|
|
if let Some(t) = t.ttl {
|
|
body["ttl"] = t.into();
|
|
}
|
|
let resp = client
|
|
.post(&url)
|
|
.json(&body)
|
|
.send()
|
|
.await
|
|
.expect("send /api/v3/configure/last_cache request");
|
|
let status = resp.status();
|
|
assert_eq!(
|
|
t.expected,
|
|
status,
|
|
"test case ({i}) failed, {description}",
|
|
description = t.description
|
|
);
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn api_v3_configure_last_cache_delete() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/last_cache",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
// Write some LP to the database to initialize the catalog:
|
|
let db_name = "db";
|
|
let tbl_name = "tbl";
|
|
let cache_name = "test_cache";
|
|
server
|
|
.write_lp_to_db(
|
|
db_name,
|
|
format!("{tbl_name},t1=a,t2=b,t3=c f1=true,f2=\"hello\",f3=4i,f4=4u,f5=5 1000"),
|
|
influxdb3_client::Precision::Second,
|
|
)
|
|
.await
|
|
.expect("write to db");
|
|
|
|
struct TestCase {
|
|
request: Request,
|
|
// This is the status code expected in the response:
|
|
expected: StatusCode,
|
|
}
|
|
|
|
enum Request {
|
|
Create(serde_json::Value),
|
|
Delete(DeleteRequest),
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct DeleteRequest {
|
|
db: Option<&'static str>,
|
|
table: Option<&'static str>,
|
|
name: Option<&'static str>,
|
|
}
|
|
|
|
use Request::*;
|
|
let mut test_cases = [
|
|
// Create a cache:
|
|
TestCase {
|
|
request: Create(serde_json::json!({
|
|
"db": db_name,
|
|
"table": tbl_name,
|
|
"name": cache_name,
|
|
})),
|
|
expected: StatusCode::CREATED,
|
|
},
|
|
// Missing all params:
|
|
TestCase {
|
|
request: Delete(DeleteRequest {
|
|
..Default::default()
|
|
}),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
},
|
|
// Partial params:
|
|
TestCase {
|
|
request: Delete(DeleteRequest {
|
|
db: Some(db_name),
|
|
..Default::default()
|
|
}),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
},
|
|
// Partial params:
|
|
TestCase {
|
|
request: Delete(DeleteRequest {
|
|
table: Some(tbl_name),
|
|
..Default::default()
|
|
}),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
},
|
|
// Partial params:
|
|
TestCase {
|
|
request: Delete(DeleteRequest {
|
|
name: Some(cache_name),
|
|
..Default::default()
|
|
}),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
},
|
|
// Partial params:
|
|
TestCase {
|
|
request: Delete(DeleteRequest {
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
..Default::default()
|
|
}),
|
|
expected: StatusCode::BAD_REQUEST,
|
|
},
|
|
// All params, good:
|
|
TestCase {
|
|
request: Delete(DeleteRequest {
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
name: Some(cache_name),
|
|
}),
|
|
expected: StatusCode::OK,
|
|
},
|
|
// Same as previous, with correct parameters provided, but gest 404, as its already deleted:
|
|
TestCase {
|
|
request: Delete(DeleteRequest {
|
|
db: Some(db_name),
|
|
table: Some(tbl_name),
|
|
name: Some(cache_name),
|
|
}),
|
|
expected: StatusCode::NOT_FOUND,
|
|
},
|
|
];
|
|
|
|
// Do one pass using the JSON body to delete:
|
|
for (i, t) in test_cases.iter().enumerate() {
|
|
match &t.request {
|
|
Create(body) => assert!(
|
|
client
|
|
.post(&url)
|
|
.json(&body)
|
|
.send()
|
|
.await
|
|
.expect("create request succeeds")
|
|
.status()
|
|
.is_success(),
|
|
"Creation test case ({i}) failed"
|
|
),
|
|
Delete(req) => {
|
|
let body = serde_json::json!({
|
|
"db": req.db,
|
|
"table": req.table,
|
|
"name": req.name,
|
|
});
|
|
let resp = client
|
|
.delete(&url)
|
|
.json(&body)
|
|
.send()
|
|
.await
|
|
.expect("send /api/v3/configure/last_cache request");
|
|
let status = resp.status();
|
|
assert_eq!(
|
|
t.expected, status,
|
|
"Deletion test case ({i}) using JSON body failed"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Do another pass using the URI query string to delete:
|
|
// Note: this particular test exhibits different status code, because the empty query string
|
|
// as a result of there being no parameters provided makes the request handler attempt to
|
|
// parse the body as JSON - which gives a 415 error, because there is no body or content type
|
|
test_cases[1].expected = StatusCode::UNSUPPORTED_MEDIA_TYPE;
|
|
for (i, t) in test_cases.iter().enumerate() {
|
|
match &t.request {
|
|
Create(body) => assert!(
|
|
client
|
|
.post(&url)
|
|
.json(&body)
|
|
.send()
|
|
.await
|
|
.expect("create request succeeds")
|
|
.status()
|
|
.is_success(),
|
|
"Creation test case ({i}) failed"
|
|
),
|
|
Delete(req) => {
|
|
let mut params = vec![];
|
|
if let Some(db) = req.db {
|
|
params.push(("db", db));
|
|
}
|
|
if let Some(table) = req.table {
|
|
params.push(("table", table));
|
|
}
|
|
if let Some(name) = req.name {
|
|
params.push(("name", name));
|
|
}
|
|
let resp = client
|
|
.delete(&url)
|
|
.query(¶ms)
|
|
.send()
|
|
.await
|
|
.expect("send /api/v3/configure/last_cache request");
|
|
let status = resp.status();
|
|
assert_eq!(
|
|
t.expected, status,
|
|
"Deletion test case ({i}) using URI query string failed"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test_log::test(tokio::test)]
|
|
async fn api_v3_configure_db_delete() {
|
|
let db_name = "foo";
|
|
let tbl_name = "tbl";
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/database?db={db_name}",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
server
|
|
.write_lp_to_db(
|
|
db_name,
|
|
format!("{tbl_name},t1=a,t2=b,t3=c f1=true,f2=\"hello\",f3=4i,f4=4u,f5=5 1000"),
|
|
influxdb3_client::Precision::Second,
|
|
)
|
|
.await
|
|
.expect("write to db");
|
|
|
|
// check foo db is present
|
|
let result = server
|
|
.api_v3_query_influxql(&[("q", "SHOW DATABASES"), ("format", "json")])
|
|
.await
|
|
.json::<Value>()
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(
|
|
json!([{ "deleted": false, "iox::database": "_internal" }, { "deleted": false, "iox::database": "foo" } ]),
|
|
result
|
|
);
|
|
|
|
let resp = client
|
|
.delete(&url)
|
|
.send()
|
|
.await
|
|
.expect("delete database call succeed");
|
|
assert_eq!(200, resp.status());
|
|
|
|
// check foo db is now foo-YYYYMMDD..
|
|
let result = server
|
|
.api_v3_query_influxql(&[("q", "SHOW DATABASES"), ("format", "json")])
|
|
.await
|
|
.json::<Value>()
|
|
.await
|
|
.unwrap();
|
|
let array_result = result.as_array().unwrap();
|
|
assert_eq!(2, array_result.len());
|
|
let first_db = array_result.first().unwrap();
|
|
assert_contains!(
|
|
first_db
|
|
.as_object()
|
|
.unwrap()
|
|
.get("iox::database")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap(),
|
|
"_internal"
|
|
);
|
|
let second_db = array_result.get(1).unwrap();
|
|
assert_contains!(
|
|
second_db
|
|
.as_object()
|
|
.unwrap()
|
|
.get("iox::database")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap(),
|
|
"foo-"
|
|
);
|
|
|
|
server
|
|
.write_lp_to_db(
|
|
db_name,
|
|
format!("{tbl_name},t1=a,t2=b,t3=c f1=true,f2=\"hello\",f3=4i,f4=4u,f5=5 1000"),
|
|
influxdb3_client::Precision::Second,
|
|
)
|
|
.await
|
|
.expect("write to db");
|
|
|
|
let result = server
|
|
.api_v3_query_influxql(&[("q", "SHOW DATABASES"), ("format", "json")])
|
|
.await
|
|
.json::<Value>()
|
|
.await
|
|
.unwrap();
|
|
let array_result = result.as_array().unwrap();
|
|
// check there are 2 dbs now, foo and foo-*
|
|
assert_eq!(3, array_result.len());
|
|
let first_db = array_result.first().unwrap();
|
|
let second_db = array_result.get(1).unwrap();
|
|
let third_db = array_result.get(2).unwrap();
|
|
assert_contains!(
|
|
first_db
|
|
.as_object()
|
|
.unwrap()
|
|
.get("iox::database")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap(),
|
|
"_internal"
|
|
);
|
|
assert_eq!(
|
|
"foo",
|
|
second_db
|
|
.as_object()
|
|
.unwrap()
|
|
.get("iox::database")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap(),
|
|
);
|
|
assert_contains!(
|
|
third_db
|
|
.as_object()
|
|
.unwrap()
|
|
.get("iox::database")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap(),
|
|
"foo-"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn api_v3_configure_db_delete_no_db() {
|
|
let db_name = "db";
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/database?db={db_name}",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
let resp = client
|
|
.delete(&url)
|
|
.send()
|
|
.await
|
|
.expect("delete database call succeed");
|
|
assert_eq!(StatusCode::NOT_FOUND, resp.status());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn api_v3_configure_db_delete_missing_query_param() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/database",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
let resp = client
|
|
.delete(&url)
|
|
.send()
|
|
.await
|
|
.expect("delete database call succeed");
|
|
assert_eq!(StatusCode::BAD_REQUEST, resp.status());
|
|
}
|
|
|
|
#[test_log::test(tokio::test)]
|
|
async fn api_v3_configure_db_create() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/database",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
let resp = client
|
|
.post(&url)
|
|
.json(&json!({ "db": "foo" }))
|
|
.send()
|
|
.await
|
|
.expect("delete database call succeed");
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
}
|
|
|
|
#[test_log::test(tokio::test)]
|
|
async fn api_v3_configure_db_create_db_with_same_name() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/database",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
let resp = client
|
|
.post(&url)
|
|
.json(&json!({ "db": "foo" }))
|
|
.send()
|
|
.await
|
|
.expect("create database call did not succeed");
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
|
|
let resp = client
|
|
.post(&url)
|
|
.json(&json!({ "db": "foo" }))
|
|
.send()
|
|
.await
|
|
.expect("create database call should succeed");
|
|
assert_eq!(StatusCode::CONFLICT, resp.status());
|
|
}
|
|
|
|
#[test_log::test(tokio::test)]
|
|
async fn api_v3_configure_db_create_db_hit_limit() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/database",
|
|
base = server.client_addr()
|
|
);
|
|
for i in 0..5 {
|
|
let resp = client
|
|
.post(&url)
|
|
.json(&json!({ "db": format!("foo{i}") }))
|
|
.send()
|
|
.await
|
|
.expect("create database call did not succeed");
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
}
|
|
|
|
let resp = client
|
|
.post(&url)
|
|
.json(&json!({ "db": "foo5" }))
|
|
.send()
|
|
.await
|
|
.expect("create database succeeded");
|
|
assert_eq!(StatusCode::UNPROCESSABLE_ENTITY, resp.status());
|
|
}
|
|
|
|
#[test_log::test(tokio::test)]
|
|
async fn api_v3_configure_db_create_db_reuse_old_name() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/database",
|
|
base = server.client_addr()
|
|
);
|
|
let resp = client
|
|
.post(&url)
|
|
.json(&json!({ "db": "foo" }))
|
|
.send()
|
|
.await
|
|
.expect("create database call did not succeed");
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
let resp = client
|
|
.delete(format!("{url}?db=foo"))
|
|
.send()
|
|
.await
|
|
.expect("delete database call did not succeed");
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
let resp = client
|
|
.post(&url)
|
|
.json(&json!({ "db": "foo" }))
|
|
.send()
|
|
.await
|
|
.expect("create database call did not succeed");
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
}
|
|
|
|
#[test_log::test(tokio::test)]
|
|
async fn api_v3_configure_table_create_then_write() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let db_url = format!(
|
|
"{base}/api/v3/configure/database",
|
|
base = server.client_addr()
|
|
);
|
|
let table_url = format!("{base}/api/v3/configure/table", base = server.client_addr());
|
|
|
|
let resp = client
|
|
.post(&db_url)
|
|
.json(&json!({ "db": "foo" }))
|
|
.send()
|
|
.await
|
|
.expect("create database call did not succeed");
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
|
|
let resp = client
|
|
.post(&table_url)
|
|
.json(&json!({
|
|
"db": "foo" ,
|
|
"table": "bar",
|
|
"tags": ["tag1", "tag2"],
|
|
"fields": [
|
|
{
|
|
"name": "field1",
|
|
"type": "uint64"
|
|
},
|
|
{
|
|
"name": "field2",
|
|
"type": "int64"
|
|
},
|
|
{
|
|
"name": "field3",
|
|
"type": "float64"
|
|
},
|
|
{
|
|
"name": "field4",
|
|
"type": "utf8"
|
|
},
|
|
{
|
|
"name": "field5",
|
|
"type": "bool"
|
|
}
|
|
]
|
|
|
|
}))
|
|
.send()
|
|
.await
|
|
.expect("create table call failed");
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
let result = server
|
|
.query_sql("foo")
|
|
.with_sql("SELECT * FROM bar")
|
|
.run()
|
|
.unwrap();
|
|
assert_eq!(result, json!([]));
|
|
server
|
|
.write_lp_to_db(
|
|
"foo",
|
|
"bar,tag1=1,tag2=2 field1=1u,field2=2i,field3=3,field4=\"4\",field5=true 2998574938",
|
|
influxdb3_client::Precision::Second,
|
|
)
|
|
.await
|
|
.expect("write to db");
|
|
let result = server
|
|
.query_sql("foo")
|
|
.with_sql("SELECT * FROM bar")
|
|
.run()
|
|
.unwrap();
|
|
assert_eq!(
|
|
result,
|
|
json!([{
|
|
"tag1": "1",
|
|
"tag2": "2",
|
|
"field1": 1,
|
|
"field2": 2,
|
|
"field3": 3.0,
|
|
"field4": "4",
|
|
"field5": true,
|
|
"time": "2065-01-07T17:28:58"
|
|
}])
|
|
);
|
|
}
|
|
|
|
#[test_log::test(tokio::test)]
|
|
async fn api_v3_configure_table_create_no_fields() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let db_url = format!(
|
|
"{base}/api/v3/configure/database",
|
|
base = server.client_addr()
|
|
);
|
|
let table_url = format!("{base}/api/v3/configure/table", base = server.client_addr());
|
|
|
|
let resp = client
|
|
.post(&db_url)
|
|
.json(&json!({ "db": "foo" }))
|
|
.send()
|
|
.await
|
|
.expect("create database call did not succeed");
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
|
|
let resp = client
|
|
.post(&table_url)
|
|
.json(&json!({
|
|
"db": "foo" ,
|
|
"table": "bar",
|
|
"tags": ["one", "two"],
|
|
"fields": []
|
|
}))
|
|
.send()
|
|
.await
|
|
.expect("create table call failed");
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
let result = server
|
|
.query_sql("foo")
|
|
.with_sql("SELECT * FROM bar")
|
|
.run()
|
|
.unwrap();
|
|
assert_eq!(result, json!([]));
|
|
server
|
|
.write_lp_to_db(
|
|
"foo",
|
|
"bar,one=1,two=2 new_field=0 2998574938",
|
|
influxdb3_client::Precision::Second,
|
|
)
|
|
.await
|
|
.expect("write to db");
|
|
let result = server
|
|
.query_sql("foo")
|
|
.with_sql("SELECT * FROM bar")
|
|
.run()
|
|
.unwrap();
|
|
assert_eq!(
|
|
result,
|
|
json!([{
|
|
"one": "1",
|
|
"two": "2",
|
|
"new_field": 0.0,
|
|
"time": "2065-01-07T17:28:58"
|
|
}])
|
|
);
|
|
}
|
|
|
|
#[test_log::test(tokio::test)]
|
|
async fn api_v3_configure_table_create_invalid_field_types() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let table_url = format!("{base}/api/v3/configure/table", base = server.client_addr());
|
|
|
|
let resp = client
|
|
.post(&table_url)
|
|
.json(&json!({
|
|
"db": "foo",
|
|
"table": "bar",
|
|
"tags": ["tag1"],
|
|
"fields": [
|
|
{
|
|
"name": "invalid",
|
|
"type": "foobar"
|
|
}
|
|
]
|
|
}))
|
|
.send()
|
|
.await
|
|
.expect("create table call failed");
|
|
|
|
assert!(resp.status().is_client_error());
|
|
}
|
|
|
|
#[test_log::test(tokio::test)]
|
|
async fn api_v3_configure_table_delete() {
|
|
let db_name = "foo";
|
|
let tbl_name = "tbl";
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/table?db={db_name}&table={tbl_name}",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
server
|
|
.write_lp_to_db(
|
|
db_name,
|
|
format!("{tbl_name},t1=a,t2=b,t3=c f1=true,f2=\"hello\",f3=4i,f4=4u,f5=5 1000"),
|
|
influxdb3_client::Precision::Second,
|
|
)
|
|
.await
|
|
.expect("write to db");
|
|
|
|
let resp = client
|
|
.delete(&url)
|
|
.send()
|
|
.await
|
|
.expect("delete table call succeed");
|
|
assert_eq!(200, resp.status());
|
|
|
|
// check foo db has table with name in tbl-YYYYMMDD.. format
|
|
let result = server
|
|
.api_v3_query_influxql(&[("q", "SHOW MEASUREMENTS on foo"), ("format", "json")])
|
|
.await
|
|
.json::<Value>()
|
|
.await
|
|
.unwrap();
|
|
debug!(result = ?result, ">> RESULT");
|
|
let array_result = result.as_array().unwrap();
|
|
assert_eq!(1, array_result.len());
|
|
let first_db = array_result.first().unwrap();
|
|
assert_contains!(
|
|
first_db
|
|
.as_object()
|
|
.unwrap()
|
|
.get("name")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap(),
|
|
"tbl-"
|
|
);
|
|
|
|
server
|
|
.write_lp_to_db(
|
|
db_name,
|
|
format!("{tbl_name},t1=a,t2=b,t3=c f1=true,f2=\"hello\",f3=4i,f4=4u,f5=5 1000"),
|
|
influxdb3_client::Precision::Second,
|
|
)
|
|
.await
|
|
.expect("write to db");
|
|
|
|
let result = server
|
|
.api_v3_query_influxql(&[("q", "SHOW MEASUREMENTS on foo"), ("format", "json")])
|
|
.await
|
|
.json::<Value>()
|
|
.await
|
|
.unwrap();
|
|
debug!(result = ?result, ">> RESULT");
|
|
let array_result = result.as_array().unwrap();
|
|
// check there are 2 tables now, tbl and tbl-*
|
|
assert_eq!(2, array_result.len());
|
|
let first_db = array_result.first().unwrap();
|
|
let second_db = array_result.get(1).unwrap();
|
|
assert_eq!(
|
|
"tbl",
|
|
first_db
|
|
.as_object()
|
|
.unwrap()
|
|
.get("name")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap(),
|
|
);
|
|
assert_contains!(
|
|
second_db
|
|
.as_object()
|
|
.unwrap()
|
|
.get("name")
|
|
.unwrap()
|
|
.as_str()
|
|
.unwrap(),
|
|
"tbl-"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn api_v3_configure_table_delete_no_db() {
|
|
let db_name = "db";
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!(
|
|
"{base}/api/v3/configure/table?db={db_name}&table=foo",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
let resp = client
|
|
.delete(&url)
|
|
.send()
|
|
.await
|
|
.expect("delete database call succeed");
|
|
assert_eq!(StatusCode::NOT_FOUND, resp.status());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn api_v3_configure_table_delete_missing_query_param() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let url = format!("{base}/api/v3/configure/table", base = server.client_addr());
|
|
|
|
let resp = client
|
|
.delete(&url)
|
|
.send()
|
|
.await
|
|
.expect("delete table call succeed");
|
|
assert_eq!(StatusCode::BAD_REQUEST, resp.status());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn try_deleting_table_after_db_is_deleted() {
|
|
let db_name = "db";
|
|
let tbl_name = "tbl";
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let delete_db_url = format!(
|
|
"{base}/api/v3/configure/database?db={db_name}",
|
|
base = server.client_addr()
|
|
);
|
|
let delete_table_url = format!(
|
|
"{base}/api/v3/configure/table?db={db_name}&table={tbl_name}",
|
|
base = server.client_addr()
|
|
);
|
|
server
|
|
.write_lp_to_db(
|
|
db_name,
|
|
format!("{tbl_name},t1=a,t2=b,t3=c f1=true,f2=\"hello\",f3=4i,f4=4u,f5=5 1000"),
|
|
influxdb3_client::Precision::Second,
|
|
)
|
|
.await
|
|
.expect("write to db");
|
|
|
|
// db call should succeed
|
|
let resp = client
|
|
.delete(&delete_db_url)
|
|
.send()
|
|
.await
|
|
.expect("delete database call succeed");
|
|
|
|
assert_eq!(StatusCode::OK, resp.status());
|
|
|
|
// but table delete call should fail with NOT_FOUND
|
|
let resp = client
|
|
.delete(&delete_table_url)
|
|
.send()
|
|
.await
|
|
.expect("delete table call succeed");
|
|
assert_eq!(StatusCode::NOT_FOUND, resp.status());
|
|
}
|
|
|
|
#[test_log::test(tokio::test)]
|
|
async fn api_v3_configure_token_delete() {
|
|
let token_name = "_admin";
|
|
let server = TestServer::configure().with_auth().spawn().await;
|
|
let client = server.http_client();
|
|
let create_url = format!(
|
|
"{base}/api/v3/configure/token/admin",
|
|
base = server.client_addr()
|
|
);
|
|
let delete_url = format!("{base}/api/v3/configure/token", base = server.client_addr());
|
|
|
|
let admin_token = server.token().expect("admin token to be present");
|
|
let delete_result = client
|
|
.delete(&delete_url)
|
|
.bearer_auth(admin_token)
|
|
.query(&[("token_name", token_name)])
|
|
.send()
|
|
.await
|
|
.unwrap();
|
|
info!(?delete_result, "test: result running the token delete");
|
|
assert_eq!(delete_result.status(), StatusCode::METHOD_NOT_ALLOWED);
|
|
|
|
// create admin token again - this will fail as operator token already exists
|
|
let result = client.post(&create_url).send().await.unwrap();
|
|
info!(?result, "test: result running the create token");
|
|
assert_eq!(result.status(), StatusCode::CONFLICT);
|
|
}
|
|
|
|
#[test_log::test(tokio::test)]
|
|
async fn test_token_paths_are_not_allowed_when_starting_without_auth() {
|
|
let server = TestServer::spawn().await;
|
|
let client = server.http_client();
|
|
let create_url = format!(
|
|
"{base}/api/v3/configure/token/admin",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
let regenerate_url = format!(
|
|
"{base}/api/v3/configure/token/admin",
|
|
base = server.client_addr()
|
|
);
|
|
|
|
let delete_url = format!("{base}/api/v3/configure/token", base = server.client_addr());
|
|
|
|
for url in &[create_url, regenerate_url] {
|
|
let result = client.post(url).send().await.unwrap();
|
|
assert_eq!(result.status(), StatusCode::METHOD_NOT_ALLOWED);
|
|
}
|
|
let result = client.delete(delete_url).send().await.unwrap();
|
|
assert_eq!(result.status(), StatusCode::METHOD_NOT_ALLOWED);
|
|
}
|