Compare commits

...

10 Commits

Author SHA1 Message Date
d7c3443022 dynamic_db (#17)
All checks were successful
Release Tagging / release (push) Successful in 39s
Rust Build / Check (push) Successful in 41s
Rust Build / Test Suite (push) Successful in 48s
Rust Build / Rustfmt (push) Successful in 28s
Rust Build / Clippy (push) Successful in 48s
Rust Build / build (push) Successful in 1m13s
Rust Build / Check (pull_request) Successful in 57s
Rust Build / Test Suite (pull_request) Successful in 48s
Rust Build / Rustfmt (pull_request) Successful in 26s
Rust Build / Clippy (pull_request) Successful in 46s
Rust Build / build (pull_request) Successful in 1m12s
Reviewed-on: #17
Co-authored-by: phoenix <kundeng94@gmail.com>
Co-committed-by: phoenix <kundeng94@gmail.com>
2025-04-05 01:30:35 +00:00
6bdc893147 Version bump (#13)
Some checks failed
Release Tagging / release (push) Failing after 34s
Rust Build / Check (pull_request) Successful in 50s
Rust Build / Test Suite (pull_request) Successful in 56s
Rust Build / Rustfmt (pull_request) Successful in 28s
Rust Build / Clippy (pull_request) Successful in 48s
Rust Build / build (pull_request) Successful in 1m14s
Rust Build / Check (push) Successful in 45s
Rust Build / Test Suite (push) Successful in 1m10s
Rust Build / Rustfmt (push) Successful in 30s
Rust Build / Clippy (push) Successful in 50s
Rust Build / build (push) Successful in 1m11s
Reviewed-on: #13
2025-04-03 16:50:09 +00:00
4b8430e114 Updated ssh key (#14)
Some checks failed
Release Tagging / release (push) Has been cancelled
Rust Build / Check (push) Has been cancelled
Rust Build / Test Suite (push) Has been cancelled
Rust Build / Rustfmt (push) Has been cancelled
Rust Build / Clippy (push) Has been cancelled
Rust Build / build (push) Has been cancelled
Rust Build / Check (pull_request) Successful in 45s
Rust Build / Test Suite (pull_request) Successful in 53s
Rust Build / Rustfmt (pull_request) Successful in 25s
Rust Build / Clippy (pull_request) Successful in 46s
Rust Build / build (pull_request) Successful in 1m9s
Reviewed-on: #14
2025-04-03 16:49:45 +00:00
KD
238fb15e6d Updated ssh key
All checks were successful
Rust Build / Check (pull_request) Successful in 45s
Rust Build / Test Suite (pull_request) Successful in 1m0s
Rust Build / Rustfmt (pull_request) Successful in 27s
Rust Build / Clippy (pull_request) Successful in 45s
Rust Build / build (pull_request) Successful in 1m18s
2025-04-03 12:40:29 -04:00
5b0592f51d Moved router code to its own function (#12)
All checks were successful
Release Tagging / release (push) Successful in 34s
Rust Build / Check (push) Successful in 46s
Rust Build / Rustfmt (push) Successful in 25s
Rust Build / Check (pull_request) Successful in 49s
Rust Build / Test Suite (pull_request) Successful in 58s
Rust Build / Rustfmt (pull_request) Successful in 26s
Rust Build / Test Suite (push) Successful in 51s
Rust Build / Clippy (push) Successful in 46s
Rust Build / build (push) Successful in 1m9s
Rust Build / Clippy (pull_request) Successful in 43s
Rust Build / build (pull_request) Successful in 1m14s
Reviewed-on: #12
2025-04-03 16:31:54 +00:00
KD
9be38542c1 Version bump
All checks were successful
Rust Build / Check (pull_request) Successful in 44s
Rust Build / Test Suite (pull_request) Successful in 50s
Rust Build / Rustfmt (pull_request) Successful in 25s
Rust Build / Clippy (pull_request) Successful in 48s
Rust Build / build (pull_request) Successful in 1m17s
2025-04-03 12:27:48 -04:00
KD
7e189e84d8 Adding code to use test database when in debug mode (#10)
Some checks failed
Release Tagging / release (push) Successful in 38s
Rust Build / Test Suite (push) Has been cancelled
Rust Build / Rustfmt (push) Has been cancelled
Rust Build / Clippy (push) Has been cancelled
Rust Build / Check (push) Has been cancelled
Rust Build / build (push) Has been cancelled
Reviewed-on: #10
Co-authored-by: KD <kundeng94@gmail.com>
Co-committed-by: KD <kundeng94@gmail.com>
2025-04-03 16:26:36 +00:00
KD
79f6ebdc09 Moved router code to its own function
Some checks failed
Rust Build / Check (pull_request) Successful in 53s
Rust Build / Test Suite (pull_request) Failing after 58s
Rust Build / Rustfmt (pull_request) Successful in 28s
Rust Build / Clippy (pull_request) Successful in 57s
Rust Build / build (pull_request) Successful in 1m21s
2025-04-03 12:25:01 -04:00
68a9998572 Removing test file (#11)
Some checks failed
Release Tagging / release (push) Successful in 35s
Rust Build / Check (push) Successful in 42s
Rust Build / Test Suite (push) Failing after 57s
Rust Build / Rustfmt (push) Successful in 24s
Rust Build / Clippy (push) Successful in 50s
Rust Build / build (push) Successful in 1m9s
Reviewed-on: #11
2025-04-03 14:17:10 +00:00
KD
b6787de66b Removing test file
Some checks failed
Rust Build / Check (pull_request) Successful in 48s
Rust Build / Test Suite (pull_request) Failing after 54s
Rust Build / Rustfmt (pull_request) Successful in 33s
Rust Build / Clippy (pull_request) Successful in 53s
Rust Build / build (pull_request) Successful in 1m28s
2025-04-03 10:07:58 -04:00
12 changed files with 417 additions and 230 deletions

View File

@@ -1,2 +1 @@
DATABASE_URL=postgres://username:password@localhost/database_name DATABASE_URL=postgres://username:password@localhost/database_name
TEST_DATABASE_URL=postgres://username:password@localhost/database_name_test

View File

@@ -21,17 +21,34 @@ jobs:
toolchain: 1.85.0 toolchain: 1.85.0
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/gitlab_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key
chmod 600 ~/.ssh/gitlab_deploy_key chmod 600 ~/.ssh/icarus_models_deploy_key
ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/gitlab_deploy_key ssh-add -v ~/.ssh/icarus_models_deploy_key
cargo check cargo check
test: test:
name: Test Suite name: Test Suite
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
# --- Add database service definition ---
services:
postgres:
image: postgres:17.4 # Or pin to a more specific version like 14.9
env:
# Use secrets for DB init, with fallbacks for flexibility
POSTGRES_USER: ${{ secrets.DB_TEST_USER || 'testuser' }}
POSTGRES_PASSWORD: ${{ secrets.DB_TEST_PASSWORD || 'testpassword' }}
POSTGRES_DB: ${{ secrets.DB_TEST_NAME || 'testdb' }}
# Options to wait until the database is ready
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
@@ -51,14 +68,23 @@ jobs:
echo "Docker environment check complete." echo "Docker environment check complete."
# NOTE: Do NOT use continue-on-error here. # NOTE: Do NOT use continue-on-error here.
# If Docker isn't working as expected, the job SHOULD fail here. # If Docker isn't working as expected, the job SHOULD fail here.
- run: | # --- Optional but Recommended: Database Migrations Step ---
- name: Run tests
env:
# Define DATABASE_URL for tests to use
DATABASE_URL: postgresql://${{ secrets.DB_TEST_USER || 'testuser' }}:${{ secrets.DB_TEST_PASSWORD || 'testpassword' }}@postgres:5432/${{ secrets.DB_TEST_NAME || 'testdb' }}
RUST_LOG: info # Optional: configure test log level
# Make SSH agent available if tests fetch private dependencies
SSH_AUTH_SOCK: ${{ env.SSH_AUTH_SOCK }}
run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/gitlab_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key
chmod 600 ~/.ssh/gitlab_deploy_key chmod 600 ~/.ssh/icarus_models_deploy_key
ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/gitlab_deploy_key ssh-add -v ~/.ssh/icarus_models_deploy_key
cargo test cargo test
fmt: fmt:
@@ -72,12 +98,12 @@ jobs:
- run: rustup component add rustfmt - run: rustup component add rustfmt
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/gitlab_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key
chmod 600 ~/.ssh/gitlab_deploy_key chmod 600 ~/.ssh/icarus_models_deploy_key
ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/gitlab_deploy_key ssh-add -v ~/.ssh/icarus_models_deploy_key
cargo fmt --all -- --check cargo fmt --all -- --check
clippy: clippy:
@@ -91,12 +117,12 @@ jobs:
- run: rustup component add clippy - run: rustup component add clippy
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/gitlab_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key
chmod 600 ~/.ssh/gitlab_deploy_key chmod 600 ~/.ssh/icarus_models_deploy_key
ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/gitlab_deploy_key ssh-add -v ~/.ssh/icarus_models_deploy_key
cargo clippy -- -D warnings cargo clippy -- -D warnings
build: build:
@@ -109,11 +135,10 @@ jobs:
toolchain: 1.85.0 toolchain: 1.85.0
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/gitlab_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key
chmod 600 ~/.ssh/gitlab_deploy_key chmod 600 ~/.ssh/icarus_models_deploy_key
ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ vars.MYHOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/gitlab_deploy_key ssh-add -v ~/.ssh/icarus_models_deploy_key
cargo build --release cargo build --release

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "icarus_auth" name = "icarus_auth"
version = "0.1.0" version = "0.1.1"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
@@ -11,11 +11,15 @@ tokio = { version = "1.44.1", features = ["rt-multi-thread"] }
tracing-subscriber = { version = "0.3.19" } tracing-subscriber = { version = "0.3.19" }
tower = { version = "0.5.2" } tower = { version = "0.5.2" }
hyper = { version = "1.6.0" } hyper = { version = "1.6.0" }
sqlx = { version = "0.8.3", features = ["postgres", "runtime-tokio-native-tls"] } sqlx = { version = "0.8.3", features = ["postgres", "runtime-tokio-native-tls", "uuid"] }
dotenv = { version = "0.15" } dotenvy = { version = "0.15.7" }
icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.2.0" } uuid = { version = "1.16.0", features = ["v4", "serde"] }
argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x version
rand = { version = "0.9" }
icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.3.0" }
[dev-dependencies] [dev-dependencies]
http-body-util = "0.1.3" http-body-util = { version = "0.1.3" }
url = { version = "2.5" }
reqwest = { version = "0.12.5", features = ["json"] } # For making HTTP requests in tests reqwest = { version = "0.12.5", features = ["json"] } # For making HTTP requests in tests
once_cell = "1.19" # Useful for lazy initialization in tests/app setup once_cell = { version = "1.19" } # Useful for lazy initialization in tests/app setup

View File

@@ -1 +1,9 @@
-- Add migration script here -- Add migration script here
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE TABLE IF NOT EXISTS "user" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username TEXT NOT NULL,
password TEXT NOT NULL,
date_created TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

View File

@@ -1,3 +1,25 @@
# Make sure role has CREATEDB
ALTER ROLE username_that_needs_permission CREATEDB;
# Install migrations
cargo install sqlx-cli cargo install sqlx-cli
# Make sure to populate DATABASE_URL with correct value.
# By default, the DATABASE_URL found in .env file will be used
export DATABASE_URL="postgres://icarus_op_test:password@localhost/icarus_auth_test"
# init
sqlx migrate add init_migration sqlx migrate add init_migration
sqlx migrate run sqlx migrate run
# Create
sqlx database create
# Drop
sqlx database drop
# setup
sqlx database setup
# Reset
sqlx database reset

View File

@@ -1,12 +1,47 @@
use axum::{Json, http::StatusCode}; use axum::{Json, http::StatusCode};
use crate::models; use crate::models;
use crate::repo;
mod response {
use serde::{Deserialize, Serialize};
use crate::models;
#[derive(Deserialize, Serialize)]
pub struct Response {
pub message: String,
pub data: models::common::User,
}
}
pub async fn register_user( pub async fn register_user(
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
Json(payload): Json<models::common::CreateUser>, Json(payload): Json<models::common::CreateUser>,
) -> (StatusCode, Json<models::common::User>) { ) -> (StatusCode, Json<response::Response>) {
let user = models::common::User { let mut user = models::common::User {
id: uuid::Uuid::nil(),
username: payload.username.clone(), username: payload.username.clone(),
password: payload.password.clone(),
}; };
(StatusCode::CREATED, Json(user))
match repo::user::insert(&pool, &user).await {
Ok(id) => {
user.id = id;
(
StatusCode::CREATED,
Json(response::Response {
message: String::from("User inserted"),
data: user,
}),
)
}
Err(err) => (
StatusCode::BAD_REQUEST,
Json(response::Response {
message: err.to_string(),
data: user,
}),
),
}
} }

73
src/hashing/mod.rs Normal file
View File

@@ -0,0 +1,73 @@
use argon2::{
Argon2, // The Argon2 algorithm struct
PasswordVerifier,
password_hash::{
PasswordHasher,
SaltString,
rand_core::OsRng, // Secure random number generator
},
};
pub fn hash_password(password: &String) -> Result<String, argon2::password_hash::Error> {
let password_bytes = password.as_bytes();
// Generate a random salt
// SaltString::generate uses OsRng internally for cryptographic security
let salt = SaltString::generate(&mut OsRng);
// Create an Argon2 instance with default parameters (recommended)
// You could customize parameters here if needed, but defaults are strong
let argon2 = Argon2::default();
// Hash the password with the salt
// The output is a PasswordHash string format that includes algorithm, version,
// parameters, salt, and the hash itself.
let password_hash = argon2.hash_password(password_bytes, &salt)?.to_string();
Ok(password_hash)
}
pub fn verify_password(
password_attempt: &String,
stored_hash: String,
) -> Result<bool, argon2::password_hash::Error> {
let password_bytes = password_attempt.as_bytes();
// Parse the stored hash string
// This extracts the salt, parameters, and hash digest
let parsed_hash = argon2::PasswordHash::new(stored_hash.as_str())?;
// Create an Argon2 instance (it will use the parameters from the parsed hash)
let argon2 = Argon2::default();
// Verify the password against the parsed hash
// This automatically uses the correct salt and parameters embedded in `parsed_hash`
match argon2.verify_password(password_bytes, &parsed_hash) {
Ok(()) => Ok(true), // Passwords match
Err(argon2::password_hash::Error::Password) => Ok(false), // Passwords don't match
Err(e) => Err(e), // Some other error occurred (e.g., invalid hash format)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_password() {
let some_password = String::from("somethingrandom");
match hash_password(&some_password) {
Ok(p) => match verify_password(&some_password, p.clone()) {
Ok(res) => {
assert_eq!(res, true);
}
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
},
Err(eerr) => {
assert!(false, "Error: {:?}", eerr.to_string());
}
}
}
}

View File

@@ -1,8 +1,10 @@
pub mod callers; pub mod callers;
pub mod config; pub mod config;
pub mod hashing;
pub mod models; pub mod models;
pub mod repo;
mod keys { pub mod keys {
pub const DBURL: &str = "DATABASE_URL"; pub const DBURL: &str = "DATABASE_URL";
pub mod error { pub mod error {
@@ -22,12 +24,19 @@ pub mod db_pool {
use crate::{connection_settings, keys}; use crate::{connection_settings, keys};
pub async fn create_pool() -> Result<sqlx::PgPool, sqlx::Error> { pub async fn create_pool() -> Result<sqlx::PgPool, sqlx::Error> {
dotenv::dotenv().ok(); let database_url = get_db_url().await;
let database_url = env::var(keys::DBURL).expect(keys::error::ERROR); println!("Database url: {:?}", database_url);
PgPoolOptions::new() PgPoolOptions::new()
.max_connections(connection_settings::MAXCONN) .max_connections(connection_settings::MAXCONN)
.connect(&database_url) .connect(&database_url)
.await .await
} }
async fn get_db_url() -> String {
#[cfg(debug_assertions)] // Example: Only load .env in debug builds
dotenvy::dotenv().ok();
env::var(keys::DBURL).expect(keys::error::ERROR)
}
} }

View File

@@ -1,19 +1,12 @@
use axum::{
Router,
routing::{get, post},
};
// use std::net::SocketAddr;
use icarus_auth::callers; use icarus_auth::callers;
use icarus_auth::config; use icarus_auth::config;
// use sqlx::Postgres;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
// initialize tracing // initialize tracing
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let app = app().await; let app = init::app().await;
// run our app with hyper, listening globally on port 3000 // run our app with hyper, listening globally on port 3000
let url = config::get_full(); let url = config::get_full();
@@ -21,40 +14,143 @@ async fn main() {
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
} }
async fn app() -> Router { mod db {
let pool = icarus_auth::db_pool::create_pool() pub async fn migrations(pool: &sqlx::PgPool) {
.await // Run migrations using the sqlx::migrate! macro
.expect("Failed to create pool"); // Assumes your migrations are in a ./migrations folder relative to Cargo.toml
sqlx::migrate!("./migrations")
.run(pool)
.await
.expect("Failed to run migrations on testcontainer DB");
}
}
// build our application with a route mod init {
Router::new() use axum::{
.route(callers::endpoints::DBTEST, get(callers::common::db_ping)) Router,
.route(callers::endpoints::ROOT, get(callers::common::root)) routing::{get, post},
.route( };
callers::endpoints::REGISTER,
post(callers::register::register_user), use crate::callers;
) use crate::db;
.layer(axum::Extension(pool))
pub async fn routes() -> Router {
// build our application with a route
Router::new()
.route(callers::endpoints::DBTEST, get(callers::common::db_ping))
.route(callers::endpoints::ROOT, get(callers::common::root))
.route(
callers::endpoints::REGISTER,
post(callers::register::register_user),
)
}
pub async fn app() -> Router {
let pool = icarus_auth::db_pool::create_pool()
.await
.expect("Failed to create pool");
db::migrations(&pool).await;
routes().await.layer(axum::Extension(pool))
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use axum::{ use axum::{
body::Body, body::Body,
// extract::connect_info::MockConnectInfo,
http::{Request, StatusCode}, http::{Request, StatusCode},
}; };
use http_body_util::BodyExt; use http_body_util::BodyExt;
// use http_body_util::BodyExt; // for `collect` use serde::{Deserialize, Serialize};
// use serde_json::{Value, json}; use serde_json::json;
// use tokio::net::TcpListener;
// use tower::{Service, ServiceExt}; // for `call`, `oneshot`, and `ready`
use tower::ServiceExt; // for `call`, `oneshot`, and `ready` use tower::ServiceExt; // for `call`, `oneshot`, and `ready`
#[derive(Deserialize, Serialize)]
struct Response {
pub message: String,
pub data: icarus_auth::models::common::User,
}
mod db_mgr {
use std::str::FromStr;
use icarus_auth::keys;
pub const LIMIT: usize = 6;
pub async fn get_pool() -> Result<sqlx::PgPool, sqlx::Error> {
let tm_db_url = std::env::var(keys::DBURL).expect("DATABASE_URL must be present");
let tm_options = sqlx::postgres::PgConnectOptions::from_str(&tm_db_url).unwrap();
sqlx::PgPool::connect_with(tm_options).await
}
pub async fn generate_db_name() -> String {
let db_name =
get_database_name().unwrap() + &"_" + &uuid::Uuid::new_v4().to_string()[..LIMIT];
db_name
}
pub async fn connect_to_db(db_name: &str) -> Result<sqlx::PgPool, sqlx::Error> {
let db_url = std::env::var(keys::DBURL).expect("DATABASE_URL must be set for tests");
let options = sqlx::postgres::PgConnectOptions::from_str(&db_url)?.database(db_name);
sqlx::PgPool::connect_with(options).await
}
pub async fn create_database(
template_pool: &sqlx::PgPool,
db_name: &str,
) -> Result<(), sqlx::Error> {
let create_query = format!("CREATE DATABASE {}", db_name);
match sqlx::query(&create_query).execute(template_pool).await {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
}
// Function to drop a database
pub async fn drop_database(
template_pool: &sqlx::PgPool,
db_name: &str,
) -> Result<(), sqlx::Error> {
let drop_query = format!("DROP DATABASE IF EXISTS {} WITH (FORCE)", db_name);
sqlx::query(&drop_query).execute(template_pool).await?;
Ok(())
}
pub fn get_database_name() -> Result<String, Box<dyn std::error::Error>> {
dotenvy::dotenv().ok(); // Load .env file if it exists
match std::env::var(keys::DBURL) {
Ok(database_url) => {
let parsed_url = url::Url::parse(&database_url)?;
if parsed_url.scheme() == "postgres" || parsed_url.scheme() == "postgresql" {
match parsed_url
.path_segments()
.and_then(|segments| segments.last().map(|s| s.to_string()))
{
Some(sss) => Ok(sss),
None => Err("Error parsing".into()),
}
} else {
// Handle other database types if needed
Err("Error parsing".into())
}
}
Err(_) => {
// DATABASE_URL environment variable not found
Err("Error parsing".into())
}
}
}
}
#[tokio::test] #[tokio::test]
async fn hello_world() { async fn test_hello_world() {
let app = app().await; let app = init::app().await;
// `Router` implements `tower::Service<Request<Body>>` so we can // `Router` implements `tower::Service<Request<Body>>` so we can
// call it like any tower service, no need to run an HTTP server. // call it like any tower service, no need to run an HTTP server.
@@ -70,24 +166,70 @@ mod tests {
assert_eq!(response.status(), StatusCode::OK); assert_eq!(response.status(), StatusCode::OK);
/*
match response.into_body().collect().await {
Ok(o) => {
let parsed: String = match String::from_utf8(o.to_bytes()) {
Ok(s) => s,
Err(err) => {
String::new()
}
};
}
Err(err) => {
assert!(false,
"Error: {:?}", err.to_string());
}
}
*/
let body = response.into_body().collect().await.unwrap().to_bytes(); let body = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&body[..], b"Hello, World!"); assert_eq!(&body[..], b"Hello, World!");
} }
#[tokio::test]
async fn test_register_user() {
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
match db_mgr::create_database(&tm_pool, &db_name).await {
Ok(_) => {
println!("Success");
}
Err(e) => {
assert!(false, "Error: {:?}", e.to_string());
}
}
let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
db::migrations(&pool).await;
let app = init::routes().await.layer(axum::Extension(pool));
let usr = icarus_auth::models::common::CreateUser {
username: String::from("somethingsss"),
password: String::from("Raindown!"),
};
let payload = json!({
"username": &usr.username,
"password": &usr.password,
});
let response = app
.oneshot(
Request::builder()
.method(axum::http::Method::POST)
.uri(callers::endpoints::REGISTER)
.header(axum::http::header::CONTENT_TYPE, "application/json")
.body(Body::from(payload.to_string()))
.unwrap(),
)
.await;
match response {
Ok(resp) => {
assert_eq!(resp.status(), StatusCode::CREATED, "Message: {:?}", resp);
let body = axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap();
let parsed_body: Response = serde_json::from_slice(&body).unwrap();
assert_eq!(
usr.username, parsed_body.data.username,
"Usernames do not match"
);
}
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
};
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
} }

View File

@@ -1,11 +1,14 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Deserialize)] #[derive(Deserialize, Serialize)]
pub struct CreateUser { pub struct CreateUser {
pub username: String, pub username: String,
pub password: String,
} }
#[derive(Serialize)] #[derive(Deserialize, Serialize)]
pub struct User { pub struct User {
pub id: uuid::Uuid,
pub username: String, pub username: String,
pub password: String,
} }

20
src/repo/mod.rs Normal file
View File

@@ -0,0 +1,20 @@
pub mod user {
use crate::models;
pub async fn insert(
pool: &sqlx::PgPool,
user: &models::common::User,
) -> Result<uuid::Uuid, sqlx::Error> {
let insert_sql = "INSERT INTO \"user\" (username, password) VALUES ($1, $2) RETURNING id";
match sqlx::query_scalar(insert_sql)
.bind(&user.username) // Bind the input message securely
.bind(&user.password)
.fetch_one(pool) // Execute and expect exactly ONE row with ONE column back
.await
{
Ok(o) => Ok(o),
Err(err) => Err(err), // _ => uuid::Uuid::nil(),
}
}
}

View File

@@ -1,153 +0,0 @@
extern crate icarus_auth;
use crate::icarus_auth::callers;
// use axum::Extension;
use axum::body::Body;
// use axum::response::Response;
use axum::{
Router,
http::{Request, StatusCode},
routing::get,
};
// use hyper::client::conn;
// use sqlx::PgPool;
// use sqlx::postgres::{self, PgPoolOptions};
// use testcontainers_modules::testcontainers::runners::AsyncRunner;
// use hyper::client;
// use sqlx::postgres;
// use http::{Request, StatusCode};
// use serde_json::json;
// use tower::ServiceExt; // for `.oneshot()`
use tower::util::ServiceExt;
// use testcontainers_modules::testcontainers::core::client::
const TEST_DATABASE_URL_ENV: &str = "TEST_DATABASE_URL";
const DEFAULT_TEST_DATABASE_URL: &str =
"postgres://icarus_op_test:password@localhost:5432/icarus_auth_test";
static SETUP: std::sync::Once = std::sync::Once::new();
// Ensure tracing is initialized only once for all tests in this file
/*
static TRACING_INIT: Lazy<()> = Lazy::new(|| {
if std::env::var("RUST_LOG").is_err() {
// Set default log level if not provided
unsafe {
std::env::set_var("RUST_LOG", "info,tower_http=debug,your_project_name=debug");
}
}
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.with_test_writer() // Write logs to the test output capture
.init();
});
*/
/*
async fn setup_database() -> sqlx::PgPool {
let database_url = std::env::var(TEST_DATABASE_URL_ENV)
.unwrap_or_else(|_| DEFAULT_TEST_DATABASE_URL.to_string());
let pool = sqlx::PgPool::connect(&database_url)
.await
.expect("Failed to connect to test database");
let migrator = sqlx::migrate::Migrator::new(std::path::Path::new("./migrations"))
.await
.expect("Failed to create migrator");
migrator.run(&pool).await.expect("Failed to run migrations");
// Seed here if needed
pool
}
*/
/*
#[tokio::test]
async fn test_db_health() {
SETUP.call_once(|| {
tokio::runtime::Runtime::new().unwrap().block_on(async {
setup_database().await;
});
});
}
*/
/*
async fn setup_test(pool: sqlx::PgPool) -> Router {
Router::new()
.route(callers::endpoints::DBTEST, get(callers::common::db_ping))
.layer(Extension(pool))
}
*/
/*
#[tokio::test]
async fn test_hello_world() {
let app = Router::new().route(callers::endpoints::ROOT, get(callers::common::root)); // Replace with your handler
let response = app
.oneshot(
Request::builder()
.uri(callers::endpoints::ROOT)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = String::from_utf8(
axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap()
.to_vec(),
)
.unwrap();
assert_eq!(body, "Hello, World!");
}
*/
/*
#[tokio::test]
async fn _test_db_health_check() {
let container = testcontainers_modules::postgres::Postgres::default()
.start()
.await
.unwrap();
let _host_ip = container.get_host().await.unwrap();
let port = 5432;
let host_port = container.get_host_port_ipv4(port).await.unwrap();
let conn_string = &format!(
"postgres://postgres:postgres@localhost:{}/postgres",
host_port
);
println!("Test Database: {}", conn_string);
let app = Router::new().route(callers::endpoints::DBTEST, get(callers::common::db_ping)); // Replace with your handler
let response = app
.oneshot(
Request::builder()
.uri(callers::endpoints::DBTEST)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
match PgPoolOptions::new().connect(conn_string).await {
Ok(_) => {
assert!(true, "Success");
}
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
};
}
*/