use std::{
path::PathBuf,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use actix_web::{
App,
body::MessageBody,
dev::{ServiceFactory, ServiceRequest, ServiceResponse},
http::StatusCode,
test,
web::{self, Bytes},
};
use diesel::{SqliteConnection, r2d2::ConnectionManager};
use diesel_migrations::MigrationHarness;
use graphannis::{CorpusStorage, corpusstorage::ImportFormat};
use insta::assert_snapshot;
use jsonwebtoken::EncodingKey;
use log::{Level, Log, RecordBuilder};
use tempfile::NamedTempFile;
use crate::{
api::administration::BackgroundJobs,
auth::Claims,
create_logger,
settings::{JWTVerification, Settings},
};
pub const JWT_SECRET: &str = "not-a-secret";
pub fn create_empty_dbpool() -> r2d2::Pool<ConnectionManager<SqliteConnection>> {
let manager = ConnectionManager::<SqliteConnection>::new(":memory:");
let db_pool = r2d2::Pool::builder().build(manager).unwrap();
let mut conn = db_pool.get().unwrap();
conn.run_pending_migrations(crate::MIGRATIONS).unwrap();
db_pool
}
pub fn create_test_app(
cs: web::Data<CorpusStorage>,
mut settings: Settings,
) -> App<
impl ServiceFactory<
ServiceRequest,
Response = ServiceResponse<impl MessageBody>,
Config = (),
InitError = (),
Error = actix_web::Error,
>,
> {
settings.auth.token_verification = JWTVerification::HS256 {
secret: JWT_SECRET.to_string(),
};
let db_pool = create_empty_dbpool();
let settings = web::Data::new(settings);
let db_pool = web::Data::new(db_pool);
let background_jobs = web::Data::new(BackgroundJobs::default());
let app = crate::create_app(cs, settings, db_pool, background_jobs);
app
}
pub fn create_auth_header() -> (&'static str, String) {
let in_sixty_minutes = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.checked_add(Duration::from_secs(3600))
.unwrap();
let admin_claims = Claims {
sub: "admin".to_string(),
exp: Some(in_sixty_minutes.as_millis() as i64),
roles: vec!["admin".to_string()],
groups: vec![],
};
let bearer_token = jsonwebtoken::encode(
&jsonwebtoken::Header::default(),
&admin_claims,
&EncodingKey::from_secret(JWT_SECRET.as_ref()),
)
.unwrap();
("Authorization", format!("Bearer {bearer_token}"))
}
pub(crate) fn import_test_corpora(cs: &CorpusStorage) {
let cargo_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
cs.import_from_fs(
&cargo_dir.join("../graphannis/tests/SaltSampleCorpus.graphml"),
ImportFormat::GraphML,
Some("A".into()),
false,
true,
|_| {},
)
.unwrap();
cs.import_from_fs(
&cargo_dir.join("../graphannis/tests/SaltSampleCorpus.graphml"),
ImportFormat::GraphML,
Some("B".into()),
false,
true,
|_| {},
)
.unwrap();
cs.import_from_fs(
&cargo_dir.join("../graphannis/tests/SaltSampleCorpus.graphml"),
ImportFormat::GraphML,
Some("C".into()),
false,
true,
|_| {},
)
.unwrap();
}
fn standard_filter() -> insta::Settings {
let mut settings = insta::Settings::clone_current();
settings.add_filter("\x1b", "");
settings.add_filter("\\[[0-9]+m", "");
settings.add_filter("[0-9]+:[0-9]+:[0-9]+ ", "12:00:00");
settings.add_filter("[0-9.]+[MG]B / [0-9.]+[MG]B", "100MB / 300MB");
settings.add_filter("in [0-9]+ ms", "in 10 ms");
settings.add_filter("\\[DEBUG\\] \\([0-9]+\\)", "[DEBUG] (1)");
settings
}
#[test]
async fn test_logfile() -> Result<(), Box<dyn std::error::Error>> {
let logfile = NamedTempFile::new()?;
let mut settings = Settings::default();
settings.logging.file = Some(logfile.path().to_string_lossy().to_string());
let (logger, _) = create_logger(&settings)?;
let record = RecordBuilder::new()
.level(Level::Info)
.args(format_args!("Hello World"))
.build();
logger.log(&record);
let record = RecordBuilder::new()
.level(Level::Debug)
.args(format_args!("Debug Message"))
.build();
logger.log(&record);
let logfile_content = std::fs::read_to_string(logfile.path())?;
let snapshot_settings = standard_filter();
snapshot_settings.bind(|| assert_snapshot!(logfile_content));
Ok(())
}
#[test]
async fn test_logfile_debug() -> Result<(), Box<dyn std::error::Error>> {
let logfile = NamedTempFile::new()?;
let mut settings = Settings::default();
settings.logging.file = Some(logfile.path().to_string_lossy().to_string());
settings.logging.debug = true;
let (logger, _) = create_logger(&settings)?;
let record = RecordBuilder::new()
.level(Level::Info)
.args(format_args!("Hello World"))
.build();
logger.log(&record);
let record = RecordBuilder::new()
.level(Level::Debug)
.args(format_args!("Debug Message"))
.build();
logger.log(&record);
let logfile_content = std::fs::read_to_string(logfile.path())?;
let snapshot_settings = standard_filter();
snapshot_settings.bind(|| assert_snapshot!(logfile_content));
Ok(())
}
#[actix_web::test]
async fn serve_static_files() {
let db_dir = tempfile::TempDir::new().unwrap();
let cs = graphannis::CorpusStorage::with_auto_cache_size(db_dir.path(), false).unwrap(); import_test_corpora(&cs);
let app = test::init_service(create_test_app(web::Data::new(cs), Settings::default())).await;
let req = test::TestRequest::get()
.uri("/v1/api-docs.html")
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let response_body: Bytes = test::read_body(resp).await;
assert_eq!(false, response_body.is_empty());
}