Skip to content

Commit

Permalink
Changed ERror Handling, Also added testing system
Browse files Browse the repository at this point in the history
  • Loading branch information
wyatt-herkamp committed Aug 30, 2024
1 parent 6a1f851 commit 863e577
Show file tree
Hide file tree
Showing 44 changed files with 648 additions and 248 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ target/
.vscode/
test/
.env
nr_tests.env
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,24 @@ derive_more.workspace = true
digestible.workspace = true
url.workspace = true
nr-macros.workspace = true

# Testing
anyhow = { workspace = true, optional = true }
tokio = { workspace = true, optional = true }
tracing-subscriber = { version = "0.3", features = [
"env-filter",
"json",
], optional = true }
tracing-appender = { version = "0.2", optional = true }
[features]
default = ["migrations", "testing"]
migrations = []
testing = [
"migrations",
"anyhow",
"tokio",
"tracing-subscriber",
"tracing-appender",
]
[lints]
workspace = true
6 changes: 6 additions & 0 deletions crates/core/src/database/migration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use sqlx::{migrate::Migrator, PgPool};
static MIGRATOR: Migrator = sqlx::migrate!();
pub async fn run_migrations(pool: &PgPool) -> Result<(), sqlx::Error> {
MIGRATOR.run(pool).await?;
Ok(())
}
5 changes: 2 additions & 3 deletions crates/core/src/database/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[cfg(feature = "migrations")]
pub mod migration;
pub mod project;
pub mod repository;
pub mod storage;
Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/database/user/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ impl NewUserRequest {
password,
} = self;
let user = sqlx::query_as(
r#"INSERT INTO users (name, username, email, password, admin, user_manager, storage_manager, repository_manager) VALUES ($1, $2, $3, $4, true, true, true, true) RETURNING *"#,
r#"INSERT INTO users (name, username, email, password, admin, user_manager, system_manager) VALUES ($1, $2, $3, $4, true, true, true) RETURNING *"#,
)
.bind(name)
.bind(username)
Expand Down
2 changes: 2 additions & 0 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ pub mod builder_error;
pub mod database;
pub mod repository;
pub mod storage;
#[cfg(feature = "testing")]
pub mod testing;
pub mod utils;
8 changes: 5 additions & 3 deletions crates/core/src/repository/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,12 @@ pub trait RepositoryConfigType: Send + Sync + Debug {
..Default::default()
}
}
/// Sanitizes the config for public view. By default this function returns None which will mean the config is not shown to the public
/// Sanitizes the config for public view.
///
/// By default this function returns None which will mean the config is not shown to the public
#[inline(always)]
fn sanitize_for_public_view(&self, _: Value) -> Option<Value> {
None
fn sanitize_for_public_view(&self, _: Value) -> Result<Option<Value>, RepositoryConfigError> {
Ok(None)
}
/// Validate the config. If the config is invalid this function should return an error
fn validate_config(&self, config: Value) -> Result<(), RepositoryConfigError>;
Expand Down
41 changes: 41 additions & 0 deletions crates/core/src/testing/env_file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
use std::{collections::HashMap, path::PathBuf};

use tracing::{debug, info, instrument};
#[instrument]
pub fn find_file(dir: PathBuf, file_name: &str) -> Option<PathBuf> {
let env_file = dir.join(file_name);
info!("Checking for file: {:?}", env_file);
if env_file.exists() {
return Some(env_file);
}
let parent = dir.parent()?;
debug!("Checking parent: {:?}", parent);
find_file(parent.to_path_buf(), file_name)
}
#[derive(Debug)]
pub struct EnvFile {
pub file: PathBuf,
pub key_values: HashMap<String, String>,
}
impl EnvFile {
pub fn load(file_name: &str) -> anyhow::Result<Self> {
let current_dir = std::env::current_dir()?;
let file =
find_file(current_dir, file_name).ok_or_else(|| anyhow::anyhow!("File not found"))?;
let file_contents = std::fs::read_to_string(&file)?;
let mut key_values = HashMap::new();
for line in file_contents.lines() {
let mut parts = line.splitn(2, '=');
let key = parts.next().unwrap();
let value = parts.next().unwrap();
key_values.insert(key.to_string(), value.to_string());
}
Ok(Self { file, key_values })
}
pub fn get(&self, key: &str) -> Option<String> {
if let Ok(key) = std::env::var(key) {
return Some(key);
}
self.key_values.get(key).map(|s| s.to_owned())
}
}
143 changes: 143 additions & 0 deletions crates/core/src/testing/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use std::str::FromStr;

use env_file::EnvFile;
use sqlx::PgPool;
use tracing::{debug, error, info};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer};
pub mod env_file;

use crate::{
database::{
user::{NewUserRequest, UserSafeData, UserType},
DateTime,
},
user::{Email, Username},
};
/// The password for the test user
pub static TEST_USER_USERNAME: &str = "test_user";

pub static TEST_USER_PASSWORD: &str = "password";
static TEST_USER_PASSWORD_HASHED: &str =
"$argon2id$v=19$m=16,t=2,p=1$b1o5VWFvVFYxRTFhUUJjeA$bpK+ySI4DIDIOh4emBFTqw";
/// Table Name: `nr_test_environment`
static TEST_INFO_TABLE: &str = include_str!("test_info.sql");
static LOGGING_INIT: std::sync::Once = std::sync::Once::new();

pub struct TestCore {
pub db: PgPool,
}
impl TestCore {
pub async fn new(function_path: String) -> anyhow::Result<(Self, TestInfoEntry)> {
let env_file = env_file::EnvFile::load("nr_tests.env")?;
Self::start_logger(&env_file);
let database = Self::connect(&env_file).await?;
let new = Self { db: database };
new.init_test_environment().await?;

let entry = TestInfoEntry::get_or_create(&function_path, &new.db).await?;
Ok((new, entry))
}
fn start_logger(env_file: &EnvFile) {
let log = env_file.get("LOG");
if let Some(log) = log {
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| log.into());

LOGGING_INIT.call_once(|| {
let stdout_log = tracing_subscriber::fmt::layer().pretty();
match tracing_subscriber::registry()
.with(stdout_log.with_filter(env_filter))
.try_init()
{
Ok(_) => {
debug!("Logging initialized");
}
Err(err) => {
eprintln!("Error initializing logging: {}", err);
}
};
});
}
}
async fn connect(env_file: &EnvFile) -> anyhow::Result<PgPool> {
let env = env_file.get("DATABASE_URL").unwrap();
debug!("Connecting to database {}", env);
let db = PgPool::connect(&env).await?;
Ok(db)
}
async fn init_test_environment(&self) -> anyhow::Result<()> {
crate::database::migration::run_migrations(&self.db).await?;
sqlx::query(TEST_INFO_TABLE).execute(&self.db).await?;
Ok(())
}

pub async fn get_test_user(&self) -> anyhow::Result<Option<UserSafeData>> {
if let Some(user) = UserSafeData::get_by_id(1, &self.db).await? {
return Ok(Some(user));
} else {
let user = NewUserRequest {
name: "Test User".to_string(),
username: Username::from_str(TEST_USER_USERNAME)?,
email: Email::from_str("[email protected]")?,
password: Some(TEST_USER_PASSWORD_HASHED.to_owned()),
};
let user = user.insert_admin(&self.db).await?;
return Ok(Some(user.into()));
}
}
}
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct TestInfoEntry {
pub id: i32,
pub function_path: String,
pub run_successfully: Option<bool>,
pub started_at: Option<DateTime>,
}
impl TestInfoEntry {
pub async fn get_or_create(
function_path: &str,
db: &PgPool,
) -> Result<TestInfoEntry, sqlx::Error> {
let entry = sqlx::query_as::<_, TestInfoEntry>(
r#"INSERT INTO nr_test_environment (function_path) VALUES($1) ON CONFLICT (function_path) DO UPDATE
SET run_successfully = null, started_at = CURRENT_TIMESTAMP
RETURNING *;"#,
)
.bind(function_path.to_owned())
.fetch_one(db)
.await?;
Ok(entry)
}

pub async fn set_success(&self, db: &PgPool) -> Result<(), sqlx::Error> {
sqlx::query(r#"UPDATE nr_test_environment SET run_successfully = true WHERE id = $1;"#)
.bind(self.id)
.execute(db)
.await?;
Ok(())
}
}

async fn does_table_exist(table_name: &str, db: &PgPool) -> Result<bool, sqlx::Error> {
let table_exists: bool = sqlx::query_scalar(
r#"
SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = $1) AS table_existence;"#,
)
.bind(table_name)
.fetch_one(db)
.await?;
info!("Table {} exists: {}", table_name, table_exists);
Ok(table_exists)
}

#[cfg(test)]
mod tests {
#[tokio::test]
pub async fn test_test_core() {
let (core, entry) = super::TestCore::new(format!("{}::test_test_core", module_path!()))
.await
.unwrap();
let user = core.get_test_user().await.unwrap();
assert!(user.is_some());
entry.set_success(&core.db).await.unwrap();
}
}
7 changes: 7 additions & 0 deletions crates/core/src/testing/test_info.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS nr_test_environment (
id SERIAL PRIMARY KEY,
function_path TEXT NOT NULL,
constraint function_path_unique UNIQUE (function_path),
run_successfully BOOLEAN,
started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Loading

0 comments on commit 863e577

Please sign in to comment.