diff --git a/Cargo.lock b/Cargo.lock index e54cc5c..cf79d9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -613,6 +613,18 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "const-oid" version = "0.9.5" @@ -872,6 +884,12 @@ dependencies = [ "serde", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -1629,6 +1647,7 @@ dependencies = [ "futures", "governor", "hornbeam", + "insta", "josekit", "metrics", "metrics-exporter-prometheus", @@ -1723,6 +1742,19 @@ dependencies = [ "libc", ] +[[package]] +name = "insta" +version = "1.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" +dependencies = [ + "console", + "lazy_static", + "linked-hash-map", + "serde", + "similar", +] + [[package]] name = "instant" version = "0.1.12" @@ -1901,6 +1933,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.1.4" @@ -3017,6 +3055,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "similar" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" + [[package]] name = "sketches-ddsketch" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 2bdade6..179952c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,5 +42,6 @@ tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } [dev-dependencies] axum-test-helper = "0.3.0" +insta = { version = "1.39.0", features = ["serde", "yaml"] } pgtemp = "0.3.0" rstest = "0.21.0" diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 23f6caf..671305a 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -10,4 +10,4 @@ # Development - +- [Testing](dev/testing.md) diff --git a/docs/dev/testing.md b/docs/dev/testing.md new file mode 100644 index 0000000..e6aa61a --- /dev/null +++ b/docs/dev/testing.md @@ -0,0 +1,45 @@ +# Testing + +## Testing approach + +### Unit tests + +Unit tests should live inside a module in the code unit they are testing, +gated behind `#[cfg(test)]`. \ +This is fairly common Rust practice. + +There is no hard and fast rule for the granularity of the unit tests, +but they should test the smallest amount of logic that is simple to test, +but no smaller. \ +In practice this means that unit tests should be at the function-level +where this makes sense, or at the struct-level if this makes more sense. + +For now, avoid the use of test mocks, but use them if it makes +strong sense to do so. + + +### Integration tests + +Integration tests should live in the `tests/` directory. + +In general, each test will get its own throwaway Postgres database. + + +#### Snapshot tests + +Some of the integration tests will compare snapshots (of e.g. HTML) against +a gold standard. + +When a new snapshot is created, the output should be manually verified, +including in a browser if necessary. + +It goes without saying that all snapshot changes should be expected; +if they are not then treated as failures. + + +### End-to-end tests + +idCoop doesn't currently have end-to-end tests but this is on the wishlist +for the future. + +Will eventually look into Playwright etc. diff --git a/src/lib.rs b/src/lib.rs index c888d47..6720ec7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,3 +9,6 @@ pub mod config; pub mod passwords; pub mod store; pub mod web; + +#[cfg(test)] +mod tests; diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..3e8039a --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,91 @@ +use std::sync::Arc; + +use axum::Router; +use confique::{Config, Partial}; +use josekit::jwk::alg::rsa::RsaKeyPair; +use pgtemp::PgTempDB; +use serde_json::json; + +use crate::{ + config::{Configuration, SecretConfig}, + store::IdCoopStore, + web::make_router, +}; + +struct TestSystem { + database: PgTempDB, + web: Router, +} + +const RSA_KEY_PAIR_PEM: &[u8] = include_bytes!("tests/keypair.pem"); +const RSA_PUBLIC_KEY_PEM: &[u8] = include_bytes!("tests/publickey.crt"); + +// #[rstest::fixture] +async fn basic_system() -> TestSystem { + let temp_db = pgtemp::PgTempDBBuilder::new() + .with_dbname("test_idcoop") + .start_async() + .await; + + let store = IdCoopStore::connect(&temp_db.connection_uri()) + .await + .expect("failed to connect to pgtemp db"); + + let config_partial: ::Partial = + serde_json::from_value(json!({ + "listen": { + // Not useful, not actually used in the tests + "bind": "127.0.0.1:1", + "public_base_uri": "http://idcoop.example.com", + }, + "postgres": { + "connect": "postgres://not-used-in-tests" + }, + "oidc": { + "issuer": "http://issuer.example.com", + "rsa_keypair": "not-used-in-tests", + "clients": { + "aclient": { + "redirect_uris": [ + "http://aclient.example.com/redirect" + ], + "name": "AClient", + "allow_user_classes": ["active"], + "secret": "secretA", + } + } + }, + "password_hashing": { + // Use weak password hash settings; we're not testing Argon2 here, + // we just want it to be fast. + "memory": 512, + "iterations": 1, + }, + "ratelimits": { + "login": "3 per hour, 2 burst", + }, + })) + .expect("bad test config"); + let config = + Configuration::from_partial(config_partial.with_fallback(Partial::default_values())) + .expect("failed to load builtin config"); + + let secrets = SecretConfig { + rsa_key_pair: RsaKeyPair::from_pem(RSA_KEY_PAIR_PEM) + .expect("failed to decode builtin RSA keypair"), + }; + + let router = make_router(Arc::new(store), Arc::new(config), Arc::new(secrets)) + .await + .expect("failed to make router"); + + TestSystem { + database: temp_db, + web: router, + } +} + +#[tokio::test] +async fn test_demo() { + let basic = basic_system().await; +} diff --git a/src/tests/keypair.pem b/src/tests/keypair.pem new file mode 100644 index 0000000..c6719a7 --- /dev/null +++ b/src/tests/keypair.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDu6acOa+3ae2S +0llp5oMsXjBMd5QJeQJCcY5Q9NAITF2U9VBwAiMf2wmaTZ1aWWFGSb/zWef7Hx1e +qNhsK9MYL+QdJih2I+KpMtDWm7hhy9FtCHVc9i1z9PruXb0om2jDWLuBkPdCqJZT +C58ObZKmgL4OH5F1Qv5JR/ZX21OjLolXPJo1sonLv9mlgufvhUmnC17onSSqFLBA +nhUedjbfdnLShkp0xa8G0nW7Ls7Idaxyo8M5S2M+azJyLI87eqjjfz0yIW0Am890 +mRa81hO2D+YNcVA2wIE9MEI/ie480YxLQ0VHCjX4DcVir4ceExysYkdL8VK+U14g +NO67k4NVAgMBAAECggEABnseJyoZ0V7miOgCIemKClwMCVwkQLQLCRwtdCzG/p9Y +sef1g9/uPc3I4Z0USruO5v7mJi6h6cS7+jhpAhvpX3GmgfiTemXxyVxvYcvCLSrM +gmm3SR61npNMA7yC2OdcbqtvefjM1x4x7AoEeDvUkULOCDWvYUyYkuCZHYubl1mS +Rtcp9rxzky2tjdp8CHySBa9Kz9LEjWdFGky7g3vSyqZtw6tkK5CTwMPb9aHwiEq1 +yDWCbqAPPnb300dXSqx7z3AcsxBi/lpCs79fQS1vSJ1/L9POpYxX0SMtffkR5+Bl +Mkg9dZUVenfbP4n40FdMypTTX2KJiMkc8+f0+Tz9QQKBgQDrdT7xs0lqMeZWp2rD +y2KLzRl0iO/+yKse+BgkeBggZ/Vh2TeF9ylTRYdmimxkJ8eZRipTr0F64BS/LPEk +RgWviuf9dUl0gyuhYTOJgUw1wcgtB6e+04UKEUsQW6JoNZekkM+xTa7FskLmlJ4J +zzowjF1lgJeEyX1tvWXKIVe+/QKBgQDUzy5nZoUqBrVTYxL2uadIUh8oiBKPU93U +Gz3DUq90yfDa7lFhwMRQRXfNqGUy6tshsaF4fT1b62hZDSz1OH3h/y1LKQOdF5kc +JJyk/4b7NJna16kwBLzWje5SjQKr51aQWU8JftZ5/8uck2j7vMi+mgwzpG45J7kv +Q1I5decBOQKBgFq1sKotB/uBfdukY91KXYy+VzAuEUd2x3YG3kYufhz97+riZCGY +NrN99cvrSBbNvHewMF5NBkzwRw3foob28vnN6dIbfVEFt6lUaSZwSYvsO9IdQOKj +Wn2ma+TBaK/89Y7QuzLzWoGPS3bJipj83M4XRWP1RmpBtbCxZqWYctWBAoGAMZPi +16wGsffGHpsiO+CcnDilkafByypar6N5DBwjTC4PsrF6vC9QjPLiKkNk8CvOyVa8 +q3lh5hw9vyFWq/pxOUldn/j6Iorw3KGa7MWrCLMEdPtxKwKvi7ydHRZE3Q+UFyT3 +SNsH1HxHTz74Yk1k5yK0XQOduisK9XvVmBVjr+ECgYEAyoSbo/1cyLKWgrIr0K/f +stiKL9SmBmYbaGaxtQToB5Hnqso7Hz5YEDlrcr8s1ukEFghgeNYuDYw3ZKKGGfZm +yVQKAt8ouoO8rfkLrtt0H+/0uJgouhewDEqf/O+MfzwDnFcT89J5ZTEf+9n6pjry +fuiQnuwEsPYGCCFuWWlrdHQ= +-----END PRIVATE KEY----- diff --git a/src/tests/publickey.crt b/src/tests/publickey.crt new file mode 100644 index 0000000..bd89332 --- /dev/null +++ b/src/tests/publickey.crt @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw7umnDmvt2ntktJZaeaD +LF4wTHeUCXkCQnGOUPTQCExdlPVQcAIjH9sJmk2dWllhRkm/81nn+x8dXqjYbCvT +GC/kHSYodiPiqTLQ1pu4YcvRbQh1XPYtc/T67l29KJtow1i7gZD3QqiWUwufDm2S +poC+Dh+RdUL+SUf2V9tToy6JVzyaNbKJy7/ZpYLn74VJpwte6J0kqhSwQJ4VHnY2 +33Zy0oZKdMWvBtJ1uy7OyHWscqPDOUtjPmsyciyPO3qo4389MiFtAJvPdJkWvNYT +tg/mDXFQNsCBPTBCP4nuPNGMS0NFRwo1+A3FYq+HHhMcrGJHS/FSvlNeIDTuu5OD +VQIDAQAB +-----END PUBLIC KEY-----