mod cache;
mod get;
mod katex;
mod post;
pub mod server;
mod state;
mod template;
#[cfg(test)]
mod tests {
use std::fs::create_dir_all;
use portpicker::pick_unused_port;
use reqwest::StatusCode;
use tempfile::tempdir;
use tokio::spawn;
use crate::cmd::drill::server::AnswerControls;
use crate::cmd::drill::server::ServerConfig;
use crate::cmd::drill::server::start_server;
use crate::error::Fallible;
use crate::helper::create_tmp_copy_of_test_directory;
use crate::types::timestamp::Timestamp;
use crate::utils::wait_for_server;
const TEST_HOST: &str = "127.0.0.1";
#[tokio::test]
async fn test_start_server_on_non_existent_directory() -> Fallible<()> {
let port = pick_unused_port().unwrap();
let session_started_at = Timestamp::now();
let config = ServerConfig {
directory: Some("./derpherp".to_string()),
host: TEST_HOST.to_string(),
port,
session_started_at,
card_limit: None,
new_card_limit: None,
deck_filter: None,
shuffle: false,
answer_controls: AnswerControls::Full,
bury_siblings: false,
};
let result = start_server(config).await;
assert!(result.is_err());
let err = result.err().unwrap();
assert_eq!(err.to_string(), "error: directory does not exist.");
Ok(())
}
#[tokio::test]
async fn test_start_server_with_no_cards_due() -> Fallible<()> {
let port = pick_unused_port().unwrap();
let dir = tempdir()?.path().to_path_buf().canonicalize()?;
create_dir_all(&dir)?;
let session_started_at = Timestamp::now();
let dir = dir.canonicalize().unwrap().display().to_string();
let config = ServerConfig {
directory: Some(dir),
host: TEST_HOST.to_string(),
port,
session_started_at,
card_limit: None,
new_card_limit: None,
deck_filter: None,
shuffle: false,
answer_controls: AnswerControls::Full,
bury_siblings: false,
};
start_server(config).await?;
Ok(())
}
#[tokio::test]
async fn test_e2e() -> Fallible<()> {
let port = pick_unused_port().unwrap();
let directory = create_tmp_copy_of_test_directory()?;
let session_started_at = Timestamp::now();
let config = ServerConfig {
directory: Some(directory),
host: TEST_HOST.to_string(),
port,
session_started_at,
card_limit: None,
new_card_limit: None,
deck_filter: None,
shuffle: false,
answer_controls: AnswerControls::Full,
bury_siblings: false,
};
spawn(async move { start_server(config).await });
wait_for_server(TEST_HOST, port).await?;
let response = reqwest::get(format!("http://{TEST_HOST}:{port}/style.css")).await?;
assert!(response.status().is_success());
assert_eq!(response.headers().get("content-type").unwrap(), "text/css");
let response = reqwest::get(format!("http://{TEST_HOST}:{port}/script.js")).await?;
assert!(response.status().is_success());
assert_eq!(
response.headers().get("content-type").unwrap(),
"text/javascript"
);
let response = reqwest::get(format!("http://{TEST_HOST}:{port}/herp-derp")).await?;
assert_eq!(response.status(), StatusCode::NOT_FOUND);
let response = reqwest::get(format!("http://{TEST_HOST}:{port}/file/foo.jpg")).await?;
assert!(response.status().is_success());
assert_eq!(
response.headers().get("content-type").unwrap(),
"image/jpeg"
);
let response = reqwest::get(format!("http://{TEST_HOST}:{port}/file/foo.png")).await?;
assert_eq!(response.status(), StatusCode::NOT_FOUND);
let response = reqwest::get(format!("http://{TEST_HOST}:{port}/")).await?;
assert!(response.status().is_success());
assert_eq!(
response.headers().get("content-type").unwrap(),
"text/html; charset=utf-8"
);
let html = response.text().await?;
assert!(html.contains("baz <span class='cloze'>.............</span>"));
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "Reveal")])
.send()
.await?;
assert!(response.status().is_success());
let html = response.text().await?;
assert!(html.contains("baz <span class='cloze-reveal'>quux</span>"));
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "Good")])
.send()
.await?;
assert!(response.status().is_success());
let html = response.text().await?;
assert!(html.contains("FOO"));
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "Reveal")])
.send()
.await?;
assert!(response.status().is_success());
let html = response.text().await?;
assert!(html.contains("BAR"));
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "Good")])
.send()
.await?;
assert!(response.status().is_success());
let html = response.text().await?;
assert!(html.contains("Session Completed"));
Ok(())
}
#[tokio::test]
async fn test_undo() -> Fallible<()> {
let port = pick_unused_port().unwrap();
let directory = create_tmp_copy_of_test_directory()?;
let session_started_at = Timestamp::now();
let config = ServerConfig {
directory: Some(directory),
host: TEST_HOST.to_string(),
port,
session_started_at,
card_limit: None,
new_card_limit: None,
deck_filter: None,
shuffle: false,
answer_controls: AnswerControls::Full,
bury_siblings: false,
};
spawn(async move { start_server(config).await });
wait_for_server(TEST_HOST, port).await?;
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "Reveal")])
.send()
.await?;
assert!(response.status().is_success());
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "Good")])
.send()
.await?;
assert!(response.status().is_success());
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "Undo")])
.send()
.await?;
assert!(response.status().is_success());
let html = response.text().await?;
assert!(html.contains("baz <span class='cloze'>.............</span>"));
Ok(())
}
#[tokio::test]
async fn test_undo_initial() -> Fallible<()> {
let port = pick_unused_port().unwrap();
let directory = create_tmp_copy_of_test_directory()?;
let session_started_at = Timestamp::now();
let config = ServerConfig {
directory: Some(directory),
host: TEST_HOST.to_string(),
port,
session_started_at,
card_limit: None,
new_card_limit: None,
deck_filter: None,
shuffle: false,
answer_controls: AnswerControls::Full,
bury_siblings: false,
};
spawn(async move { start_server(config).await });
wait_for_server(TEST_HOST, port).await?;
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "Undo")])
.send()
.await?;
assert!(response.status().is_success());
Ok(())
}
#[tokio::test]
async fn test_answer_without_reveal() -> Fallible<()> {
let port = pick_unused_port().unwrap();
let directory = create_tmp_copy_of_test_directory()?;
let session_started_at = Timestamp::now();
let config = ServerConfig {
directory: Some(directory),
host: TEST_HOST.to_string(),
port,
session_started_at,
card_limit: None,
new_card_limit: None,
deck_filter: None,
shuffle: false,
answer_controls: AnswerControls::Full,
bury_siblings: false,
};
spawn(async move { start_server(config).await });
wait_for_server(TEST_HOST, port).await?;
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "Hard")])
.send()
.await?;
assert!(response.status().is_success());
Ok(())
}
#[tokio::test]
async fn test_undo_forgetting() -> Fallible<()> {
let port = pick_unused_port().unwrap();
let directory = create_tmp_copy_of_test_directory()?;
let session_started_at = Timestamp::now();
let config = ServerConfig {
directory: Some(directory),
host: TEST_HOST.to_string(),
port,
session_started_at,
card_limit: None,
new_card_limit: None,
deck_filter: None,
shuffle: false,
answer_controls: AnswerControls::Full,
bury_siblings: false,
};
spawn(async move { start_server(config).await });
wait_for_server(TEST_HOST, port).await?;
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "Reveal")])
.send()
.await?;
assert!(response.status().is_success());
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "Forgot")])
.send()
.await?;
assert!(response.status().is_success());
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "Undo")])
.send()
.await?;
assert!(response.status().is_success());
let html = response.text().await?;
assert!(html.contains("baz <span class='cloze'>.............</span>"));
Ok(())
}
#[tokio::test]
async fn test_end() -> Fallible<()> {
let port = pick_unused_port().unwrap();
let directory = create_tmp_copy_of_test_directory()?;
let session_started_at = Timestamp::now();
let config = ServerConfig {
directory: Some(directory),
host: TEST_HOST.to_string(),
port,
session_started_at,
card_limit: None,
new_card_limit: None,
deck_filter: None,
shuffle: false,
answer_controls: AnswerControls::Full,
bury_siblings: false,
};
spawn(async move { start_server(config).await });
wait_for_server(TEST_HOST, port).await?;
let response = reqwest::Client::new()
.post(format!("http://{TEST_HOST}:{port}/"))
.form(&[("action", "End")])
.send()
.await?;
assert!(response.status().is_success());
let html = response.text().await?;
assert!(html.contains("Session Completed"));
Ok(())
}
}