Compare commits
7 Commits
b5dbf99e50
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbc6da3e54
|
||
|
|
aa6a90d3f0
|
||
|
|
aafca8b714
|
||
|
|
055088d11e
|
||
|
|
190dad39b6
|
||
|
|
eeaebdd2ba
|
||
|
|
23a73b222a
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
e2e/target
|
||||
tmp
|
||||
5
.gitmodules
vendored
5
.gitmodules
vendored
@@ -3,4 +3,7 @@
|
||||
url = git@git.orionkindel.com:dnim/db
|
||||
[submodule "api"]
|
||||
path = api
|
||||
url = git@git.orionkindel.com:dnim/api
|
||||
url = ../api
|
||||
branch = main
|
||||
[submodule "git@git.orionkindel.com:dnim/api"]
|
||||
url = ../api
|
||||
|
||||
2
api
2
api
Submodule api updated: 568b248f3e...7e89edc30f
2
db
2
db
Submodule db updated: be07aca4c5...aad2e076fe
1374
e2e/Cargo.lock
generated
Normal file
1374
e2e/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
e2e/Cargo.toml
Normal file
16
e2e/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "e2e"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
postgres = {path = "../api/postgres/postgres"}
|
||||
toad = "1.0.0-beta.8"
|
||||
toad-msg = "1.0.0-beta.5"
|
||||
nb = "1.1"
|
||||
simple_logger = "4.2"
|
||||
serde_json = "1"
|
||||
no-std-net = "0.6.0"
|
||||
log = "0.4"
|
||||
24
e2e/rustfmt.toml
Normal file
24
e2e/rustfmt.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
# General
|
||||
max_width = 100
|
||||
newline_style = "Unix"
|
||||
tab_spaces = 2
|
||||
indent_style = "Visual"
|
||||
format_code_in_doc_comments = true
|
||||
format_macro_bodies = true
|
||||
|
||||
# Match statements
|
||||
match_arm_leading_pipes = "Always"
|
||||
match_block_trailing_comma = true
|
||||
|
||||
# Structs
|
||||
use_field_init_shorthand = true
|
||||
struct_field_align_threshold = 0
|
||||
|
||||
# Enums
|
||||
enum_discrim_align_threshold = 0
|
||||
|
||||
# Imports
|
||||
group_imports = "StdExternalCrate"
|
||||
imports_granularity = "Module"
|
||||
imports_indent = "Visual"
|
||||
imports_layout = "HorizontalVertical"
|
||||
185
e2e/src/lib.rs
Normal file
185
e2e/src/lib.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
#![cfg(test)]
|
||||
|
||||
mod __toad_aliases {
|
||||
use toad::std::{dtls, Platform};
|
||||
use toad::step::runtime::std::Runtime;
|
||||
pub type Toad = Platform<dtls::N, Runtime<dtls::N>>;
|
||||
}
|
||||
|
||||
mod user_signup;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::{SocketAddr, UdpSocket};
|
||||
use std::ops::DerefMut;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::{Once, Mutex, Barrier, Arc};
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
|
||||
pub use __toad_aliases::Toad;
|
||||
use toad::net::Addrd;
|
||||
use toad::platform::Platform;
|
||||
use toad::resp::code;
|
||||
use toad_msg::alloc::Message;
|
||||
use toad_msg::{Code, Type, MessageOptions};
|
||||
|
||||
pub fn run<S, F, R>(script: S, init: R, mut fold_lines: F) -> R
|
||||
where S: AsRef<str>,
|
||||
F: FnMut(R, &str) -> R
|
||||
{
|
||||
let mut child = Command::new("bash").args(["-c", script.as_ref()])
|
||||
.current_dir("../")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
|
||||
let mut r: R = init;
|
||||
let mut out = child.stdout.take().unwrap();
|
||||
let mut out_str = String::new();
|
||||
let mut seen = 0;
|
||||
|
||||
macro_rules! do_fold {
|
||||
() => {{
|
||||
out.read_to_string(&mut out_str).unwrap();
|
||||
|
||||
r = out_str.split('\n').skip(seen).fold(r, |mut r, line| {
|
||||
if !line.trim().is_empty() {
|
||||
r = fold_lines(r, line);
|
||||
}
|
||||
seen += 1;
|
||||
r
|
||||
});
|
||||
}};
|
||||
}
|
||||
|
||||
loop {
|
||||
std::thread::sleep(Duration::from_micros(500));
|
||||
|
||||
do_fold!();
|
||||
|
||||
let exit = child.try_wait().unwrap();
|
||||
if exit.map(|e| e.success()) == Some(false) {
|
||||
let mut err = child.stderr.take().unwrap();
|
||||
let mut e = String::new();
|
||||
err.read_to_string(&mut e).unwrap();
|
||||
panic!("{}", e);
|
||||
} else if exit.is_some() {
|
||||
do_fold!();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
r
|
||||
}
|
||||
|
||||
pub struct Fixture {
|
||||
pub toad: &'static Toad,
|
||||
pub api_addr: no_std_net::SocketAddr,
|
||||
pub api_thread: JoinHandle<()>,
|
||||
pub pg: postgres::Client,
|
||||
}
|
||||
|
||||
impl Drop for Fixture {
|
||||
fn drop(&mut self) {
|
||||
let mut api_thread: JoinHandle<()> = std::thread::spawn(|| ());
|
||||
std::mem::swap(&mut self.api_thread, &mut api_thread);
|
||||
|
||||
let msg = Message::builder(Type::Con, Code::POST).path("debug/shutdown")
|
||||
.build();
|
||||
|
||||
let shutdown = Addrd(msg, self.api_addr);
|
||||
let rep = nb::block!({
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
self.toad.send_req(&shutdown)
|
||||
}).unwrap();
|
||||
if rep.data().code != code::CREATED {
|
||||
eprintln!("{} != 2.01", String::from_iter(rep.data().code.to_human()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() -> Fixture {
|
||||
struct ApiThreadPorts(Vec<usize>);
|
||||
|
||||
static mut SHARED: Option<(Toad, Mutex<ApiThreadPorts>)> = None;
|
||||
|
||||
static INIT: Once = Once::new();
|
||||
INIT.call_once(|| {
|
||||
simple_logger::init_with_level(log::Level::Info).unwrap();
|
||||
|
||||
log::info!("[init] build db");
|
||||
run("cd api; cargo build", (), |_, _| ());
|
||||
|
||||
run("cd db; ./scripts/build.sh 2>&1", (), |_, _| {
|
||||
print!(".");
|
||||
std::io::stdout().flush().unwrap();
|
||||
});
|
||||
println!();
|
||||
|
||||
let toad = Toad::try_new("127.0.0.1:3999".parse::<SocketAddr>().unwrap(),
|
||||
Default::default()).unwrap();
|
||||
|
||||
let available_ports: Vec<usize> =
|
||||
(4000..5000).filter(|p| UdpSocket::bind(format!("127.0.0.1:{p}")).is_ok())
|
||||
.collect();
|
||||
log::info!("[init] {} ports available for api threads",
|
||||
available_ports.len());
|
||||
|
||||
unsafe {
|
||||
SHARED = Some((toad,
|
||||
Mutex::new(ApiThreadPorts(available_ports))));
|
||||
}
|
||||
});
|
||||
|
||||
while unsafe { SHARED.as_ref() }.is_none() {
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
}
|
||||
|
||||
log::info!("[init] start api");
|
||||
let mut ports = unsafe { SHARED.as_ref() }.unwrap().1.lock().unwrap();
|
||||
let port = ports.deref_mut().0.remove(0);
|
||||
drop(ports);
|
||||
|
||||
let env = HashMap::from([("POSTGRES_HOST", "127.0.0.1".to_string()),
|
||||
("POSTGRES_PORT", "5433".to_string()),
|
||||
("POSTGRES_USER", "postgres".to_string()),
|
||||
("POSTGRES_PASS", "password".to_string()),
|
||||
("ENVIRON", "debug".to_string()),
|
||||
("API_ADDR", format!("127.0.0.1:{port}"))]);
|
||||
|
||||
let env_sh = env.iter()
|
||||
.fold(String::new(), |b, (k, v)| format!("{b} {k}=\"{v}\""));
|
||||
|
||||
let api_thread = std::thread::spawn(move || {
|
||||
run(format!("echo '' > ./tmp/log.{port}; (cd api; {env_sh} target/debug/api 2>&1) 1>>./tmp/log.{port}"), (), |_, _| ());
|
||||
});
|
||||
|
||||
Fixture {
|
||||
toad: &unsafe {SHARED.as_ref()}.unwrap().0,
|
||||
api_addr: env.get("API_ADDR").unwrap().parse().unwrap(),
|
||||
api_thread,
|
||||
pg: postgres::Client::connect("host=127.0.0.1 port=5433 dbname=dnim user=postgres password=password", postgres::NoTls).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_works() {
|
||||
let s = run("echo foo; echo bar", String::new(), |b, a| {
|
||||
format!("{b}\n{a}")
|
||||
});
|
||||
assert_eq!(s, "\nfoo\nbar");
|
||||
|
||||
let s = run("nohup sleep 10 >/dev/null & echo $!",
|
||||
String::new(),
|
||||
|b, a| format!("{b}\n{a}"));
|
||||
usize::from_str_radix(&s.trim().split('\n').nth(0).unwrap(), 10).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_works() {
|
||||
init();
|
||||
}
|
||||
114
e2e/src/user_signup.rs
Normal file
114
e2e/src/user_signup.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use toad::net::Addrd;
|
||||
use toad::platform::Platform;
|
||||
use toad_msg::{Code, ContentFormat, Message, MessageOptions, Type};
|
||||
|
||||
use crate::{init, Fixture};
|
||||
|
||||
#[test]
|
||||
pub fn user_signup() {
|
||||
let fx = init();
|
||||
|
||||
let msg =
|
||||
Message::builder(Type::Con, Code::POST).path("users")
|
||||
.accept(ContentFormat::Json)
|
||||
.content_format(ContentFormat::Json)
|
||||
.payload(serde_json::to_vec(&serde_json::json!({
|
||||
"tag": "foo",
|
||||
"email": "foo@bar.baz",
|
||||
"password": "bingus"
|
||||
})).unwrap())
|
||||
.build();
|
||||
|
||||
let msg = Addrd(msg, fx.api_addr);
|
||||
|
||||
let rep = nb::block!({
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
fx.toad.send_req(&msg)
|
||||
}).unwrap();
|
||||
|
||||
assert_eq!(rep.data().code, toad::resp::code::CREATED);
|
||||
assert_eq!(rep.data().content_format(), Some(ContentFormat::Json));
|
||||
let rep_json =
|
||||
serde_json::from_slice::<serde_json::Value>(rep.data().payload().as_bytes()).unwrap();
|
||||
|
||||
let groups = rep_json.as_object()
|
||||
.unwrap()
|
||||
.get("links")
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("groups")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
|
||||
let login = rep_json.as_object()
|
||||
.unwrap()
|
||||
.get("links")
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("login")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
|
||||
let login =
|
||||
Message::builder(Type::Con, Code::POST).path(login)
|
||||
.accept(ContentFormat::Json)
|
||||
.content_format(ContentFormat::Json)
|
||||
.payload(serde_json::to_vec(&serde_json::json!({
|
||||
"tag": "foo",
|
||||
"password": "bingus"
|
||||
})).unwrap())
|
||||
.build();
|
||||
let login = Addrd(login, fx.api_addr);
|
||||
let rep = nb::block!({
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
fx.toad.send_req(&login)
|
||||
}).unwrap();
|
||||
assert_eq!(rep.data().code, toad::resp::code::CREATED);
|
||||
assert_eq!(rep.data().content_format(), Some(ContentFormat::Json));
|
||||
let rep_json =
|
||||
serde_json::from_slice::<serde_json::Value>(rep.data().payload().as_bytes()).unwrap();
|
||||
|
||||
let session = rep_json.as_object()
|
||||
.unwrap()
|
||||
.get("session")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap();
|
||||
|
||||
let get_groups =
|
||||
Message::builder(Type::Con, Code::GET).path(groups)
|
||||
.accept(ContentFormat::Json)
|
||||
.content_format(ContentFormat::Json)
|
||||
.payload(serde_json::to_vec(&serde_json::json!({
|
||||
"session": session
|
||||
})).unwrap())
|
||||
.build();
|
||||
|
||||
let get_groups = Addrd(get_groups, fx.api_addr);
|
||||
|
||||
let rep = nb::block!({
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
fx.toad.send_req(&get_groups)
|
||||
}).unwrap();
|
||||
|
||||
assert_eq!(rep.data().code, toad::resp::code::CONTENT);
|
||||
assert_eq!(rep.data().content_format(), Some(ContentFormat::Json));
|
||||
let rep_json =
|
||||
serde_json::from_slice::<serde_json::Value>(rep.data().payload().as_bytes()).unwrap();
|
||||
|
||||
let groups =
|
||||
rep_json.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|g| g.as_object().unwrap().get("name").unwrap().as_str().unwrap()).collect::<Vec<_>>();
|
||||
|
||||
if !groups.contains(&"unverified_email") {
|
||||
panic!("expected {groups:?} to contain 'unverified_email'");
|
||||
}
|
||||
|
||||
drop(fx);
|
||||
}
|
||||
Reference in New Issue
Block a user