test: add health check test

This commit is contained in:
Kristofers Solo 2025-06-22 13:51:51 +03:00
parent 85765bb3b0
commit 813346a340
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
13 changed files with 715 additions and 34 deletions

459
Cargo.lock generated
View File

@ -106,6 +106,16 @@ dependencies = [
"thiserror 2.0.12",
]
[[package]]
name = "assert-json-diff"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "async-compression"
version = "0.4.25"
@ -153,6 +163,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "attribute-derive"
version = "0.10.3"
@ -491,6 +507,16 @@ dependencies = [
"unicode-segmentation",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@ -575,6 +601,24 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
[[package]]
name = "deadpool"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490"
dependencies = [
"async-trait",
"deadpool-runtime",
"num_cpus",
"tokio",
]
[[package]]
name = "deadpool-runtime"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
[[package]]
name = "der"
version = "0.7.10"
@ -606,6 +650,12 @@ dependencies = [
"syn",
]
[[package]]
name = "deunicode"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04"
[[package]]
name = "digest"
version = "0.10.7"
@ -669,6 +719,16 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "env_logger"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [
"log",
"regex",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@ -681,6 +741,16 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1731451909bde27714eacba19c2566362a7f35224f52b153d3f42cf60f72472"
[[package]]
name = "errno"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "etcetera"
version = "0.8.0"
@ -713,6 +783,22 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "fake"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f5f203b70a419cb8880d1cfe6bebe488add0a0307d404e9f24021e5fd864b80"
dependencies = [
"deunicode",
"rand 0.9.1",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "flate2"
version = "1.1.2"
@ -746,6 +832,21 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -949,6 +1050,25 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17e2ac29387b1aa07a1e448f7bb4f35b500787971e965b02842b900afa5c8f6f"
[[package]]
name = "h2"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
@ -1116,6 +1236,7 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
@ -1124,6 +1245,39 @@ dependencies = [
"pin-project-lite",
"smallvec",
"tokio",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
@ -1132,14 +1286,24 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
dependencies = [
"base64",
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http",
"http-body",
"hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"system-configuration",
"tokio",
"tower-service",
"tracing",
"windows-registry",
]
[[package]]
@ -1298,6 +1462,12 @@ dependencies = [
"rustversion",
]
[[package]]
name = "ipnet"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "iri-string"
version = "0.7.8"
@ -1602,6 +1772,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "litemap"
version = "0.8.0"
@ -1731,6 +1907,23 @@ dependencies = [
"version_check",
]
[[package]]
name = "native-tls"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "next_tuple"
version = "0.1.0"
@ -1844,6 +2037,50 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "openssl"
version = "0.10.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "or_poisoned"
version = "0.1.0"
@ -2069,6 +2306,28 @@ dependencies = [
"yansi",
]
[[package]]
name = "quickcheck"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
dependencies = [
"env_logger",
"log",
"rand 0.8.5",
]
[[package]]
name = "quickcheck_macros"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71ee38b42f8459a88d3362be6f9b841ad2d5421844f61eb1c59c11bff3ac14a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "quote"
version = "1.0.40"
@ -2270,6 +2529,46 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "reqwest"
version = "0.12.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "ring"
version = "0.17.14"
@ -2340,6 +2639,19 @@ dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "rustls"
version = "0.23.28"
@ -2395,6 +2707,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -2411,6 +2732,29 @@ dependencies = [
"zeroize",
]
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "1.0.26"
@ -2529,12 +2873,18 @@ dependencies = [
"app",
"axum",
"config",
"fake",
"leptos",
"leptos_axum",
"log",
"once_cell",
"quickcheck",
"quickcheck_macros",
"reqwest",
"secrecy",
"serde",
"serde-aux",
"serde_json",
"simple_logger",
"sqlx",
"thiserror 2.0.12",
@ -2545,6 +2895,8 @@ dependencies = [
"tracing-bunyan-formatter",
"tracing-log 0.2.0",
"tracing-subscriber",
"uuid",
"wiremock",
]
[[package]]
@ -2974,6 +3326,9 @@ name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
[[package]]
name = "synstructure"
@ -2986,6 +3341,27 @@ dependencies = [
"syn",
]
[[package]]
name = "system-configuration"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "tachys"
version = "0.2.3"
@ -3021,6 +3397,19 @@ dependencies = [
"web-sys",
]
[[package]]
name = "tempfile"
version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
dependencies = [
"fastrand",
"getrandom 0.3.3",
"once_cell",
"rustix",
"windows-sys 0.59.0",
]
[[package]]
name = "thiserror"
version = "1.0.69"
@ -3165,6 +3554,26 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.17"
@ -3388,6 +3797,12 @@ dependencies = [
"tracing-log 0.2.0",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tungstenite"
version = "0.26.2"
@ -3550,6 +3965,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@ -3765,6 +4189,17 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-registry"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3bab093bdd303a1240bb99b8aba8ea8a69ee19d34c9e2ef9594e708a4878820"
dependencies = [
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.3.4"
@ -3940,6 +4375,30 @@ dependencies = [
"memchr",
]
[[package]]
name = "wiremock"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2b8b99d4cdbf36b239a9532e31fe4fb8acc38d1897c1761e161550a7dc78e6a"
dependencies = [
"assert-json-diff",
"async-trait",
"base64",
"deadpool",
"futures",
"http",
"http-body-util",
"hyper",
"hyper-util",
"log",
"once_cell",
"regex",
"serde",
"serde_json",
"tokio",
"url",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"

View File

@ -24,10 +24,16 @@ serde = { version = "1", features = ["derive"] }
simple_logger = "5.0.0"
thiserror = "2"
tokio = { version = "1.45", features = ["rt", "macros", "rt-multi-thread"] }
tower = { version = "0.5.2", features = ["full"] }
tower-http = { version = "0.6.4", features = ["full"] }
tower = { version = "0.5", features = ["full"] }
tower-http = { version = "0.6", features = ["full"] }
uuid = { version = "1.17", features = ["v4"] }
wasm-bindgen = "=0.2.100"
[workspace.lints.clippy]
pedantic = "warn"
nursery = "warn"
unwrap_used = "warn"
# See https://github.com/leptos-rs/cargo-leptos for documentation of all the parameters.
# A leptos project defines which workspace members

46
scripts/init_db Executable file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env bash
set -eo pipefail
if ! [ -x "$(command -v psql)" ]; then
echo >&2 "Error: psql is not installed."
exit 1
fi
if ! [ -x "$(command -v sqlx)" ]; then
echo >&2 "Error: sqlx is not installed."
echo >&2 "Use:"
echo >&2 " cargo install sqlx-cli --no-default-features --features rustls,postgres"
echo >&2 "to install it."
exit 1
fi
DB_USER="${POSTGRES_USER:=postgres}"
DB_PASSWORD="${POSTGRES_PASSWORD:=password}"
DB_NAME="${POSTGRES_DB:=kristofersxyz}"
DB_PORT="${POSTGRES_PORT:=5432}"
DB_HOST="${POSTGRES_HOST:=localhost}"
if [[ -z "${SKIP_DOCKER}" ]]; then
docker run\
-e POSTGRES_USER=${DB_USER}\
-e POSTGRES_PASSWORD=${DB_PASSWORD}\
-e POSTGRES_DB=${DB_NAME}\
-p "${DB_PORT}":5432\
-d postgres\
postgres -N 1000
# Increase max number of connections for testing purposes
fi
# Keep pinging Postgres until it's ready to accept commands
export PGPASSWORD="${DB_PASSWORD}"
until psql -h "${DB_HOST}" -U "${DB_USER}" -p "${DB_PORT}" -d "postgres" -c '\q'; do
>&2 echo "Postgres is still unavailable - sleeping"
sleep 1
done
>&2 echo "Postgres is still up and running on port ${DB_PORT} - runing migrations now!"
DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}"
export DATABASE_URL
sqlx database create
sqlx migrate run

View File

@ -34,3 +34,13 @@ tracing = { version = "0.1", features = ["log"] }
tracing-bunyan-formatter = { version = "0.3", default-features = false }
tracing-log = "0.2.0"
tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] }
uuid.workspace = true
[dev-dependencies]
fake = "4.3"
once_cell = "1.21"
quickcheck = "1.0"
quickcheck_macros = "1.0"
reqwest = "0.12.20"
serde_json = "1"
wiremock = "0.6"

View File

@ -5,7 +5,7 @@ use sqlx::{
ConnectOptions,
postgres::{PgConnectOptions, PgSslMode},
};
use std::{fmt::Display, str::FromStr};
use std::{env::current_dir, fmt::Display, path::PathBuf, str::FromStr};
use thiserror::Error;
/// Top-level application settings.
@ -62,9 +62,11 @@ pub enum ConfigurationError {
///
/// # Errors
/// Returns a `ConfigurationError` if the configuration cannot be loaded.
pub fn get_config() -> Result<Settings, ConfigurationError> {
let base_path = std::env::current_dir().expect("Failed to determine current directory");
let config_directory = base_path.join("config");
pub fn get_config(path: Option<PathBuf>) -> Result<Settings, ConfigurationError> {
let config_directory = path.unwrap_or_else(|| {
let base_path = current_dir().expect("Failed to determine current directory");
base_path.join("config")
});
let env = std::env::var("APP_ENVIRONMENT")
.unwrap_or_else(|_| "local".into())
.parse::<Environment>()?;

View File

@ -1,15 +1,14 @@
use server::{
configuration::get_config,
startup::Application,
startup::{Application, ApplicationError},
telemetry::{get_subscriber, init_subscriber},
};
use std::io::Error;
#[tokio::main]
async fn main() -> Result<(), Error> {
async fn main() -> Result<(), ApplicationError> {
let subscriber = get_subscriber("kristofersxyz", "info", std::io::stdout);
init_subscriber(subscriber);
let config = get_config().expect("Failed to read configuation.");
let config = get_config(None).expect("Failed to read configuation.");
let application = Application::build(&config).await?;
application.start().await?;
Ok(())

View File

@ -1,6 +1,8 @@
mod v1;
use crate::startup::AppState;
use app::{App, shell};
use axum::{Router, extract::State, http::StatusCode, response::IntoResponse, routing::get};
use axum::Router;
use leptos_axum::{LeptosRoutes, generate_route_list};
pub fn route(state: AppState) -> Router {
@ -15,13 +17,8 @@ pub fn route(state: AppState) -> Router {
.fallback(leptos_axum::file_and_error_handler(shell))
.with_state(leptos_options);
let api_router = Router::new()
.route("/health_check", get(health_check))
.with_state(state);
let v1_router = v1::route(state);
let api_router = Router::new().nest("/api", v1_router);
leptos_router.merge(api_router)
}
async fn health_check(State(_state): State<AppState>) -> impl IntoResponse {
StatusCode::OK
}

View File

@ -0,0 +1,6 @@
use crate::startup::AppState;
use axum::{extract::State, http::StatusCode, response::IntoResponse};
pub async fn health_check(State(_state): State<AppState>) -> impl IntoResponse {
StatusCode::OK
}

View File

@ -0,0 +1,13 @@
mod health_check;
use crate::startup::AppState;
use axum::{Router, routing::get};
pub fn route(state: AppState) -> Router {
Router::new()
.nest(
"/v1",
Router::new().route("/health_check", get(health_check::health_check)),
)
.with_state(state)
}

View File

@ -4,20 +4,27 @@ use crate::{
};
use leptos::config::{LeptosOptions, get_configuration};
use sqlx::{PgPool, postgres::PgPoolOptions};
use std::{
io::{Error, ErrorKind},
net::SocketAddr,
ops::Deref,
sync::Arc,
};
use std::{io::Error, net::SocketAddr, ops::Deref, sync::Arc};
use thiserror::Error;
use tokio::{net::TcpListener, task::JoinHandle};
use tracing::{error, info};
#[derive(Debug, Error)]
pub enum ApplicationError {
#[error("Failed to get Leptos configuration: {0}")]
LeptosConfig(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("Server error: {0}")]
Server(String),
}
/// Shared application state.
pub struct App {
pub pool: PgPool,
pub leptos_options: LeptosOptions,
pub addr: SocketAddr,
}
/// Type alias for the shared application state wrapped in `Arc`.
@ -25,7 +32,8 @@ pub type AppState = Arc<App>;
/// Represents the application, including its server and configuration.
pub struct Application {
server: Option<JoinHandle<Result<(), Error>>>,
server: Option<JoinHandle<Result<(), ApplicationError>>>,
addr: SocketAddr,
}
impl Application {
@ -33,7 +41,7 @@ impl Application {
///
/// This method initializes the database connection pool, binds the server
/// to the specified address, and prepares the application for startup.
pub async fn build(config: &Settings) -> Result<Self, Error> {
pub async fn build(config: &Settings) -> Result<Self, ApplicationError> {
let conf = get_configuration(None).unwrap();
let addr = conf.leptos_options.site_addr;
let leptos_options = conf.leptos_options;
@ -46,7 +54,6 @@ impl Application {
let app_state = App {
pool,
leptos_options: leptos_options.clone(),
addr,
}
.into();
@ -56,26 +63,58 @@ impl Application {
let server = tokio::spawn(async move {
axum::serve(listener, route(app_state)).await.map_err(|e| {
error!("Server error: {}", e);
e
Error::other(e.to_string()).into()
})
});
Ok(Self {
server: Some(server),
addr,
})
}
pub async fn start(self) -> Result<(), Error> {
self.server
.ok_or_else(|| Error::new(ErrorKind::Other, "Server was not initialized."))?
.await?
pub async fn start(self) -> Result<(), ApplicationError> {
let server = self
.server
.ok_or_else(|| ApplicationError::Server("Server was not initialized.".to_string()))?;
server
.await
.map_err(|e| ApplicationError::Server(e.to_string()))?
}
/// Returns the socket address the server is listening on.
///
/// This method provides access to the full socket address (IP and port) that the server is bound to.
///
/// # Returns
///
/// Returns a `SocketAddr` containing the server's bound address.
#[must_use]
#[inline]
pub fn addr(&self) -> SocketAddr {
self.addr
}
/// Returns the port number the server is listening on.
///
/// This is a convenience method that extracts just the port number from the server's socket address.
///
/// # Returns
///
/// Returns a `u16` containing the server's port number.
#[must_use]
#[inline]
pub fn port(&self) -> u16 {
self.addr.port()
}
}
/// Initialize the database connection pool.
///
/// This method creates a lazy connection pool using the provided database settings.
fn get_connection_pool(config: &DatabaseSettings) -> PgPool {
#[must_use]
pub fn get_connection_pool(config: &DatabaseSettings) -> PgPool {
PgPoolOptions::new().connect_lazy_with(config.with_db())
}

View File

@ -0,0 +1,17 @@
use crate::helpers::spawn_app;
use reqwest::Client;
#[tokio::test]
async fn health_check() {
let app = spawn_app().await;
let url = format!("{}/health_check", &app.addr);
let client = Client::new();
let response = client
.get(&url)
.send()
.await
.expect("Failed to execute request");
assert!(response.status().is_success());
assert_eq!(Some(0), response.content_length());
}

View File

@ -0,0 +1,85 @@
use once_cell::sync::Lazy;
use server::{
configuration::{DatabaseSettings, get_config},
startup::{Application, get_connection_pool},
telemetry::{get_subscriber, init_subscriber},
};
use sqlx::{Connection, Executor, PgConnection, PgPool};
use std::{env::current_dir, net::SocketAddr};
use uuid::Uuid;
static TRACING: Lazy<()> = Lazy::new(|| {
let default_filter_level = "trace";
let subscriber_name = "test";
if std::env::var("TEST_LOG").is_ok() {
let subscriber = get_subscriber(subscriber_name, default_filter_level, std::io::stdout);
init_subscriber(subscriber);
} else {
let subscriber = get_subscriber(default_filter_level, subscriber_name, std::io::sink);
init_subscriber(subscriber);
}
});
pub struct TestApp {
pub _pool: PgPool,
pub addr: SocketAddr,
}
pub async fn spawn_app() -> TestApp {
Lazy::force(&TRACING);
let config = {
let path = current_dir()
.expect("Failed to determine current directory")
.parent()
.map(|p| p.join("config"));
let mut c = get_config(path).expect("Failed to read configuration.");
c.database.database_name = Uuid::new_v4().to_string();
c.application.port = 0;
c
};
configure_database(&config.database).await;
let application = Application::build(&config)
.await
.expect("Failed to build application.");
let addr = application.addr();
let _ = tokio::spawn(application.start()).await;
TestApp {
_pool: get_connection_pool(&config.database),
addr,
}
}
async fn configure_database(config: &DatabaseSettings) -> PgPool {
let mut connection = PgConnection::connect_with(&config.without_db())
.await
.expect("Failed to connect to Postgres.");
connection
.execute(
format!(
r#"
CREATE DATABASE "{}"
"#,
config.database_name
)
.as_str(),
)
.await
.expect("Failed to create database.");
let pool = PgPool::connect_with(config.with_db())
.await
.expect("Failed to connect to Postgres.");
sqlx::migrate!("../migrations")
.run(&pool)
.await
.expect("Failed to migrate database");
pool
}

2
server/tests/api/main.rs Normal file
View File

@ -0,0 +1,2 @@
mod health_check;
mod helpers;