diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 260eaf5..cc74c8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: toolchain: stable override: true - name: Check library - run: cargo check + run: cargo check --all-targets test: name: Run tests diff --git a/Cargo.toml b/Cargo.toml index 92b50db..d600ecf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,10 @@ serde_json = { default-features = false, version = "^1.0" } url = "^2.2" ureq = { version = "2", features = ["json", "tls"], default-features = false } base64 = { version = "0.21", default-features = false, features = ["alloc"] } + +[dev-dependencies] +env_logger = "0.11.3" +rustainers = "0.12.0" +rustls = { version = "0.22.4" } +tokio = { version = "1.37.0", features = ["rt", "macros"] } +ureq = "=2.9.7" diff --git a/generator/src/main/resources/crust/Cargo.mustache b/generator/src/main/resources/crust/Cargo.mustache index 652fa50..402254e 100644 --- a/generator/src/main/resources/crust/Cargo.mustache +++ b/generator/src/main/resources/crust/Cargo.mustache @@ -41,3 +41,10 @@ serde_json = { default-features = false, version = "^1.0" } url = "^2.2" ureq = { version = "2", features = ["json", "tls"], default-features = false } base64 = { version = "0.21", default-features = false, features = ["alloc"] } + +[dev-dependencies] +env_logger = "0.11.3" +rustainers = "0.12.0" +rustls = { version = "0.22.4" } +tokio = { version = "1.37.0", features = ["rt", "macros"] } +ureq = "=2.9.7" diff --git a/tests/basic.rs b/tests/basic.rs new file mode 100644 index 0000000..0442c94 --- /dev/null +++ b/tests/basic.rs @@ -0,0 +1,12 @@ +mod utils; + +use nethsm_sdk_rs::apis::default_api; + +#[tokio::test] +async fn test_health_state() { + utils::with_container(|config| { + let result = default_api::health_state_get(&config); + assert!(result.is_ok(), "{result:?}"); + }) + .await +} diff --git a/tests/utils/mod.rs b/tests/utils/mod.rs new file mode 100644 index 0000000..2f76cec --- /dev/null +++ b/tests/utils/mod.rs @@ -0,0 +1,124 @@ +// Based on: +// https://gitlab.archlinux.org/archlinux/signstar/-/blob/579131fe6b9db9b8fe1b9ffd3ad6d5e98afae816/nethsm/tests/common/container.rs +// Author: David Runge +// License: Apache-2.0 OR MIT + +use std::sync::Arc; + +use nethsm_sdk_rs::apis::configuration::Configuration; +use rustainers::{ + runner::{RunOption, Runner}, + ExposedPort, ImageName, RunnableContainer, RunnableContainerBuilder, ToRunnableContainer, + WaitStrategy, +}; +use rustls::{ + client::{ + danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + ClientConfig, + }, + crypto::{self, WebPkiSupportedAlgorithms}, + pki_types::{CertificateDer, ServerName, UnixTime}, + DigitallySignedStruct, SignatureScheme, +}; + +pub async fn with_container T, T>(f: F) -> T { + let _ = env_logger::builder().is_test(true).try_init(); + + let runner = Runner::auto().unwrap(); + let options = RunOption::builder().with_remove(true).build(); + let container = runner + .start_with_options(Image::new(), options) + .await + .unwrap(); + + let tls_config = Arc::new( + ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(Arc::new(Verifier::default())) + .with_no_client_auth(), + ); + let config = Configuration { + base_path: container.api().await, + client: ureq::builder().tls_config(tls_config).build(), + ..Default::default() + }; + + let result = f(config); + drop(container); + result +} + +struct Image { + name: ImageName, + port: ExposedPort, +} + +impl Image { + fn new() -> Self { + Self { + name: ImageName::new_with_tag("nitrokey/nethsm", "testing"), + port: ExposedPort::new(8443), + } + } + + async fn api(&self) -> String { + let port = self.port.host_port().await.unwrap(); + format!("https://localhost:{port}/api/v1") + } +} + +impl ToRunnableContainer for Image { + fn to_runnable(&self, builder: RunnableContainerBuilder) -> RunnableContainer { + builder + .with_image(self.name.clone()) + .with_wait_strategy(WaitStrategy::stderr_contains( + "listening on 8443/TCP for HTTPS", + )) + .with_port_mappings([self.port.clone()]) + .build() + } +} + +#[derive(Debug)] +struct Verifier(WebPkiSupportedAlgorithms); + +impl Default for Verifier { + fn default() -> Self { + Self(crypto::ring::default_provider().signature_verification_algorithms) + } +} + +impl ServerCertVerifier for Verifier { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp_response: &[u8], + _now: UnixTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + crypto::verify_tls12_signature(message, cert, dss, &self.0) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> Result { + crypto::verify_tls13_signature(message, cert, dss, &self.0) + } + + fn supported_verify_schemes(&self) -> Vec { + self.0.supported_schemes() + } +}