diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml new file mode 100644 index 0000000..0d2848c --- /dev/null +++ b/.gitea/workflows/action.yml @@ -0,0 +1,25 @@ +name: CI/CD Pipeline + +on: push + +env: + CARGO_TARGET: x86_64-unknown-linux-musl + +jobs: + test: + runs-on: ubuntu-latest + container: git.hofer.link/philipp/ci-images:rust-latest + steps: + - uses: actions/checkout@v3 + - name: Run Test DB Script + run: ./test_db.sh + + - name: Cache Cargo dependencies + uses: Swatinem/rust-cache@v2 + + - name: Build + run: | + cargo build + + - name: Backend tests + run: cargo test --verbose diff --git a/Cargo.lock b/Cargo.lock index 8856bfa..5ba9d9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -47,12 +47,28 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + [[package]] name = "arc-swap" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[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-trait" version = "0.1.88" @@ -73,6 +89,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "auto-future" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" + [[package]] name = "autocfg" version = "1.4.0" @@ -133,6 +155,36 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-test" +version = "17.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb1dfb84bd48bad8e4aa1acb82ed24c2bb5e855b659959b4e03b4dca118fcac" +dependencies = [ + "anyhow", + "assert-json-diff", + "auto-future", + "axum", + "bytes", + "bytesize", + "cookie", + "http", + "http-body-util", + "hyper", + "hyper-util", + "mime", + "pretty_assertions", + "reserve-port", + "rust-multipart-rfc7578_2", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "tokio", + "tower", + "url", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -221,6 +273,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "bytesize" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c8f83209414aacf0eeae3cf730b18d6981697fba62f200fcfb92b9f082acba" + [[package]] name = "cc" version = "1.2.18" @@ -372,6 +430,12 @@ dependencies = [ "serde", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -781,6 +845,7 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", ] [[package]] @@ -790,13 +855,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", + "futures-channel", "futures-util", "http", "http-body", "hyper", + "libc", "pin-project-lite", + "socket2", "tokio", "tower-service", + "tracing", ] [[package]] @@ -1167,7 +1236,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -1321,6 +1390,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -1364,8 +1443,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy", ] [[package]] @@ -1375,7 +1465,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -1387,6 +1487,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.11" @@ -1425,6 +1534,15 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reserve-port" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3747658ee2585ecf5607fa9887c92eff61b362ff5253dbf797dfeb73d33d78" +dependencies = [ + "thiserror", +] + [[package]] name = "ring" version = "0.17.14" @@ -1452,7 +1570,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", @@ -1513,6 +1631,21 @@ dependencies = [ "triomphe", ] +[[package]] +name = "rust-multipart-rfc7578_2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c839d037155ebc06a571e305af66ff9fd9063a6e662447051737e1ac75beea41" +dependencies = [ + "bytes", + "futures-core", + "futures-util", + "http", + "mime", + "rand 0.9.0", + "thiserror", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1710,7 +1843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1885,7 +2018,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand", + "rand 0.8.5", "rsa", "serde", "sha1", @@ -1924,7 +2057,7 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha2", @@ -1971,6 +2104,7 @@ name = "stationslauf" version = "0.1.0" dependencies = [ "axum", + "axum-test", "chrono", "dotenv", "maud", @@ -2262,7 +2396,7 @@ dependencies = [ "futures", "http", "parking_lot", - "rand", + "rand 0.8.5", "serde", "serde_json", "thiserror", @@ -2326,6 +2460,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typenum" version = "1.18.0" @@ -2416,6 +2556,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.0+wasi-snapshot-preview1" @@ -2760,6 +2909,12 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.7.5" diff --git a/Cargo.toml b/Cargo.toml index d0d1c60..49d885a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,6 @@ tokio = { version = "1.44", features = ["macros", "rt-multi-thread"] } tower-sessions = "0.14" tracing = "0.1" rust-i18n = "3" + +[dev-dependencies] +axum-test = "17.3" diff --git a/migration.sql b/migration.sql index 9ea7920..f102961 100644 --- a/migration.sql +++ b/migration.sql @@ -1,4 +1,4 @@ -CREATE TABLE station ( +CREATE TABLE IF NOT EXISTS station ( id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, notes TEXT, @@ -9,12 +9,12 @@ CREATE TABLE station ( lng REAL ); -CREATE TABLE route ( +CREATE TABLE IF NOT EXISTS route ( id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE -- e.g. 'wiwö' ); -CREATE TABLE route_station ( +CREATE TABLE IF NOT EXISTS route_station ( route_id INTEGER NOT NULL, station_id INTEGER NOT NULL, pos INTEGER NOT NULL, @@ -23,7 +23,7 @@ CREATE TABLE route_station ( FOREIGN KEY (station_id) REFERENCES station(id) ); -CREATE TABLE team ( +CREATE TABLE IF NOT EXISTS team ( id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, notes TEXT, @@ -34,7 +34,7 @@ CREATE TABLE team ( FOREIGN KEY (route_id) REFERENCES route(id) ); -CREATE TABLE rating ( +CREATE TABLE IF NOT EXISTS rating ( team_id INTEGER NOT NULL, station_id INTEGER NOT NULL, points INTEGER, diff --git a/src/admin/station/mod.rs b/src/admin/station/mod.rs index 99feebc..0d742cc 100644 --- a/src/admin/station/mod.rs +++ b/src/admin/station/mod.rs @@ -65,7 +65,7 @@ impl Station { Some(station) } - async fn create(db: &SqlitePool, name: &str) -> Result<(), String> { + pub(crate) async fn create(db: &SqlitePool, name: &str) -> Result<(), String> { sqlx::query!("INSERT INTO station(name) VALUES (?)", name) .execute(db) .await diff --git a/src/lib.rs b/src/lib.rs index f188f43..7ad9b7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,19 @@ #[macro_use] extern crate rust_i18n; +#[cfg(test)] +#[macro_export] +macro_rules! testdb { + () => {{ + let pool = SqlitePool::connect(":memory:").await.unwrap(); + sqlx::query_file!("./migration.sql") + .execute(&pool) + .await + .unwrap(); + pool + }}; +} + i18n!("locales", fallback = "de-AT"); use admin::station::Station; @@ -114,14 +127,13 @@ impl FromRef for Arc { } } -/// Starts the main application. -pub async fn start(listener: TcpListener, db: SqlitePool) { +fn router(db: SqlitePool) -> Router { let session_store = MemoryStore::default(); let session_layer = SessionManagerLayer::new(session_store); let state = AppState { db: Arc::new(db) }; - let app = Router::new() + Router::new() .nest("/s/{id}/{code}", station::routes()) // TODO: maybe switch to "/" .nest("/admin", admin::routes()) .route("/pico.css", get(serve_pico_css)) @@ -130,7 +142,12 @@ pub async fn start(listener: TcpListener, db: SqlitePool) { .route("/leaflet.js", get(serve_leaflet_js)) .route("/marker.png", get(serve_marker_png)) .with_state(state) - .layer(session_layer); + .layer(session_layer) +} + +/// Starts the main application. +pub async fn start(listener: TcpListener, db: SqlitePool) { + let app = router(db); axum::serve(listener, app).await.unwrap(); } diff --git a/src/station.rs b/src/station.rs index 4a00a45..1150221 100644 --- a/src/station.rs +++ b/src/station.rs @@ -304,3 +304,42 @@ pub(super) fn routes() -> Router { .route("/team-finished/{team_id}", get(team_finished)) .route("/remove-left/{team_id}", get(remove_left)) } + +#[cfg(test)] +mod test { + use crate::{router, testdb, Station}; + + use sqlx::SqlitePool; + + use axum_test::TestServer; + + #[sqlx::test] + async fn test_wrong_station() { + let pool = testdb!(); + + Station::create(&pool, "Teststation").await.unwrap(); + + let server = TestServer::new(router(pool)).unwrap(); + + let response = server.get("/s/1/wrong-pw").await; + + response.assert_text_contains( + "Falscher Quick-Einlogg-Link. Bitte nochmal scannen oder neu eingeben.", + ); + } + + #[sqlx::test] + async fn test_correct_station() { + let pool = testdb!(); + + Station::create(&pool, "42-Station").await.unwrap(); + let stations = Station::all(&pool).await; + let station = stations.last().unwrap(); + + let server = TestServer::new(router(pool)).unwrap(); + + let response = server.get(&format!("/s/1/{}", station.pw)).await; + + response.assert_text_contains("42-Station"); + } +}