From 8e870aa40d130055df4cda4c56fb37fc46b06851 Mon Sep 17 00:00:00 2001 From: nkz <5499916+nkz-soft@users.noreply.github.com> Date: Mon, 4 Nov 2024 21:37:01 +0300 Subject: [PATCH] fix integration tests again --- .github/dependabot.yml | 8 + Cargo.lock | 300 +++++++++++++++++++++++- Cargo.toml | 5 + README.md | 100 +++++++- src/starter/Cargo.toml | 15 +- src/starter/src/lib.rs | 4 +- src/starter/src/settings.rs | 2 +- src/starter/tests/integration.rs | 151 ++++++++++++ src/starter/tests/utils/mod.rs | 1 + src/starter/tests/utils/server_utils.rs | 63 +++++ tests/integration/Cargo.toml | 3 + tests/integration/tests/integration.rs | 18 ++ 12 files changed, 663 insertions(+), 7 deletions(-) create mode 100644 src/starter/tests/integration.rs create mode 100644 src/starter/tests/utils/mod.rs create mode 100644 src/starter/tests/utils/server_utils.rs diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e8d486a..eaa23c9 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,3 +9,11 @@ updates: directory: "/" # Location of package manifests schedule: interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" diff --git a/Cargo.lock b/Cargo.lock index 8b238cb..a8744fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,6 +296,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" + [[package]] name = "application" version = "0.1.0" @@ -311,6 +317,131 @@ dependencies = [ "uuid", ] +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.3.1", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", + "tokio", +] + +[[package]] +name = "async-io" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener 5.3.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" +dependencies = [ + "async-attributes", + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "async-trait" version = "0.1.80" @@ -322,6 +453,12 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.2.0" @@ -379,6 +516,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel 2.3.1", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bollard" version = "0.17.1" @@ -512,6 +662,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "config" version = "0.14.0" @@ -612,6 +771,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "crunchy" version = "0.2.2" @@ -628,6 +793,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +dependencies = [ + "quote", + "syn 2.0.72", +] + [[package]] name = "darling" version = "0.20.10" @@ -827,6 +1002,33 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener 5.3.1", + "pin-project-lite", +] + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -945,6 +1147,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f1fa2f9765705486b33fd2acf1577f8ec449c2ba1f318ae5447697b7c08d210" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -1015,6 +1230,18 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.3.26" @@ -1083,6 +1310,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" @@ -1355,6 +1588,7 @@ name = "integration_tests" version = "0.0.1" dependencies = [ "application", + "deadpool-postgres", "reqwest", "serde", "serial_test", @@ -1407,6 +1641,15 @@ dependencies = [ "serde", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1469,6 +1712,9 @@ name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +dependencies = [ + "value-bag", +] [[package]] name = "md-5" @@ -1525,7 +1771,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -1580,7 +1826,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] @@ -1653,6 +1899,12 @@ dependencies = [ "hashbrown 0.13.2", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1814,12 +2066,38 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "polling" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "postgres-protocol" version = "0.6.6" @@ -2458,19 +2736,29 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "starter" -version = "0.4.1" +version = "0.4.2" dependencies = [ "actix-web", + "anyhow", "application", + "async-std", "config", + "ctor", "deadpool-postgres", "domain", "env_logger", "infrastructure", "log", + "once_cell", "presentation", "readonly", + "reqwest", "serde", + "serial_test", + "testcontainers", + "testcontainers-modules", + "tokio", + "uuid", ] [[package]] @@ -2981,6 +3269,12 @@ dependencies = [ "serde", ] +[[package]] +name = "value-bag" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index f039463..e01cc77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,8 @@ quote = "1.0.36" syn = { version = "2.0.60", features = ["full"] } serial_test = "3.1.0" +# Error Handling +anyhow = "1.0.89" +thiserror = "1.0.64" +ctor = "0.2.8" +async-std = { version = "1", features = ["attributes", "tokio1"] } diff --git a/README.md b/README.md index 564bae5..520b02e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,100 @@ -# vortex +# rust-microservice-template +![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/nkz-soft/rust-microservice-template?style=flat-square) +![license](https://img.shields.io/github/license/nkz-soft/rust-microservice-template?style=flat-square) +![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/nkz-soft/rust-microservice-template/build-by-tag.yaml) + +Template for microservice based on Domain Driven Design principles with Rust + +The purpose of this project is to provide a means for building microservices with the last version of Rust that follows basic Domain Driven Design principles + +### ⭐ Give a star + +If you're using this repository for your learning, samples or your project, please give a star. Thanks :+1: + +## Table of Contents + +- [Installation](#installation) +- [Architecture](#architecture ) +- [Deployment](#deployment) +- [Plan](#plan) +- [Technologies - Libraries](#technologies-used) + +## Usage +### Prerequisites + +Before you can install and configure the microservice, you will need to ensure that you have the following prerequisites installed: +- Docker and Docker Compose + +### Installation +Once you have all the prerequisites installed, you can follow these steps to install and configure your microservice: + +1. Clone the repository for your microservice using Git: +```bash +git clone https://github.com/nkz-soft/rust-microservice-template.git +``` +2. Change into the directory for your microservice: +```bash +cd rust-microservice-template/deployment/docker +``` + +3. You can use the included Dockerfile and docker-compose.yml files to build and deploy the microservice. + Simply run the following command in the root directory of your project: +```bash +./docker-compose.sh up --build -d +``` +4. Verify that the microservice is running correctly by visiting the endpoint in your web browser or using a tool like curl: +```bash +curl -v http://localhost:8181/to-do-items +``` + +### Configuration +To configure the microservice, you will need to modify the configuration file: config.app.toml. + +## Architecture +The microservice is divided into three layers: the Domain Layer, the Application Layer, and the Infrastructure Layer. + +### Domain Layer +The Domain Layer is the heart of the Domain Driven Design (DDD) approach. It contains the business logic and rules that drive the application. +In Rust, the Domain Layer consists of structs, traits, enums, and functions that model the problem domain in a way that is easy to understand and maintain. + +### Application Layer +The Application Layer is responsible for coordinating the Domain Layer and the Infrastructure Layer. +It translates user requests and external events into actions that the Domain Layer can understand, and communicates the results back to the user or external systems. + +### Infrastructure Layer +The Infrastructure Layer is responsible for providing the necessary infrastructure to run the application. +This can include things like databases, message queues, and external APIs. + +### Presentation Layer +The presentation layer is responsible for handling user interactions and presenting information to users. +This layer typically includes user interfaces such as web applications, desktop applications, mobile apps, or APIs. + + + +### CQRS +CQRS (Command Query Responsibility Segregation) is a pattern that separates the read and write responsibilities of an application into separate models. + +## Deployment + +## Plan + +- [x] New blank microservice solution +- [x] Basic Github actions +- [x] Docker compose +- [x] REST API +- [x] CQRS (Command Query Responsibility Segregation) +- [x] PostgreSQL storage + - [X] Add support for migrations + - [X] CRUD Operation +- [X] Integration tests +- [ ] Advanced error handling +- [ ] Coming soon :) + +## Technologies used + +- [Rust](https://github.com/rust-lang/rust): The Rust Programming Language +- [Actix](https://github.com/actix/actix-web): Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust +- [Refinery](https://github.com/rust-db/refinery): Refinery strives to make running migrations for different databases as easy as possible. +- [rust-postgres](https://github.com/sfackler/rust-postgres): PostgreSQL support for Rust. +- [testcontainers-rs](https://github.com/testcontainers/testcontainers-rs): Testcontainers-rs is the official Rust language fork of http://testcontainers.org. diff --git a/src/starter/Cargo.toml b/src/starter/Cargo.toml index e008319..f0286a7 100644 --- a/src/starter/Cargo.toml +++ b/src/starter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "starter" -version = "0.4.1" +version = "0.4.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,8 +13,21 @@ actix-web.workspace = true config.workspace = true readonly.workspace = true deadpool-postgres.workspace = true +tokio.workspace = true +anyhow.workspace = true +reqwest.workspace = true domain = { path = "../domain" } application = { path = "../application" } presentation = { path = "../presentation" } infrastructure = { path = "../infrastructure" } +once_cell = "1.19.0" +uuid = { version = "1.10.0", features = ["v4"] } + +[dev-dependencies] +testcontainers.workspace = true +testcontainers-modules.workspace = true +ctor.workspace = true +async-std.workspace = true +serial_test.workspace = true + diff --git a/src/starter/src/lib.rs b/src/starter/src/lib.rs index 9a0c92c..36c2f70 100644 --- a/src/starter/src/lib.rs +++ b/src/starter/src/lib.rs @@ -2,6 +2,7 @@ use crate::settings::Settings; use actix_web::dev::Server; use actix_web::{middleware, web, App, HttpServer}; use deadpool_postgres::tokio_postgres::NoTls; +use log::{debug, info}; mod settings; @@ -20,7 +21,8 @@ pub async fn run_with_config(path: &str) -> Result { } pub async fn run_internal(settings: &Settings) -> Result { - log::info!("Starting HTTP server at {}", &settings.web_url); + info!("Starting HTTP server at {}", &settings.web_url); + debug!("with configuration: {:?}", &settings); let pool = settings.pg.create_pool(None, NoTls).unwrap(); diff --git a/src/starter/src/settings.rs b/src/starter/src/settings.rs index 076030f..27e946b 100644 --- a/src/starter/src/settings.rs +++ b/src/starter/src/settings.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; pub const CONFIG_FILE_NAME: &str = "config.app.toml"; #[readonly::make] -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub struct Settings { pub web_url: String, pub pg: deadpool_postgres::Config, diff --git a/src/starter/tests/integration.rs b/src/starter/tests/integration.rs new file mode 100644 index 0000000..d67c956 --- /dev/null +++ b/src/starter/tests/integration.rs @@ -0,0 +1,151 @@ +mod utils; + + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use crate::utils::server_utils; + use serial_test::serial; + use uuid::Uuid; + use crate::prepare_test_environment; + + pub(crate) const CONFIG_FILE_PATH: &str = "./../../"; + #[serial] + #[tokio::test] + async fn start_server_and_test() { + let client = prepare_test_environment!(); + assert!(client.get("http://127.0.0.1:8181").send().await.is_ok()); + } + + #[serial] + #[tokio::test] + async fn test_get_all() { + let client = prepare_test_environment!(); + + // Act + let response = client + .get("http://127.0.0.1:8181/to-do-items") + .send() + .await + .expect("Failed to execute request."); + + // Assert + assert!(response.status().is_success()); + } + + #[serial] + #[tokio::test] + async fn test_get_by_id() { + let client = prepare_test_environment!(); + let mut map_create = HashMap::new(); + map_create.insert("title", "title1"); + map_create.insert("note", "note1"); + + // Act + let id = client + .post("http://127.0.0.1:8181/to-do-items") + .json(&map_create) + .send() + .await + .expect("Failed to execute request.") + .json::() + .await + .expect("Failed to deserialize response."); + + let response = client + .get(format!("http://127.0.0.1:8181/to-do-items/{id}", id = id)) + .send() + .await + .expect("Failed to execute request."); + + // Assert + assert!(response.status().is_success()); + } + + #[serial] + #[tokio::test] + async fn test_create() { + let client = prepare_test_environment!(); + let mut map = HashMap::new(); + map.insert("title", "title1"); + map.insert("note", "note1"); + + // Act + let response = client + .post("http://127.0.0.1:8181/to-do-items") + .json(&map) + .send() + .await + .expect("Failed to execute request."); + + // Assert + assert!(response.status().is_success()); + } + + #[serial] + #[tokio::test] + async fn test_update() { + let client = prepare_test_environment!(); + let mut map_create = HashMap::new(); + map_create.insert("title", "title1"); + map_create.insert("note", "note1"); + + // Act + let id = client + .post("http://127.0.0.1:8181/to-do-items") + .json(&map_create) + .send() + .await + .expect("Failed to execute request.") + .json::() + .await + .expect("Failed to deserialize response."); + + let mut map_update = HashMap::new(); + map_update.insert("id", id.to_string()); + map_update.insert("title", String::from("title1")); + map_update.insert("note", String::from("note1")); + + let response = client + .put("http://127.0.0.1:8181/to-do-items") + .json(&map_update) + .send() + .await + .expect("Failed to execute request."); + + // Assert + assert!(response.status().is_success()); + } + + #[serial] + #[tokio::test] + async fn test_delete() { + let client = prepare_test_environment!(); + let mut map_create = HashMap::new(); + map_create.insert("title", "title1"); + map_create.insert("note", "note1"); + + // Act + let id = client + .post("http://127.0.0.1:8181/to-do-items") + .json(&map_create) + .send() + .await + .expect("Failed to execute request.") + .json::() + .await + .expect("Failed to deserialize response."); + + let response = client + .delete(format!("http://127.0.0.1:8181/to-do-items/{id}", id = id)) + .send() + .await + .expect("Failed to execute request."); + + // Assert + assert!(response.status().is_success()); + } +} + + + diff --git a/src/starter/tests/utils/mod.rs b/src/starter/tests/utils/mod.rs new file mode 100644 index 0000000..4292a5e --- /dev/null +++ b/src/starter/tests/utils/mod.rs @@ -0,0 +1 @@ +pub mod server_utils; diff --git a/src/starter/tests/utils/server_utils.rs b/src/starter/tests/utils/server_utils.rs new file mode 100644 index 0000000..565af71 --- /dev/null +++ b/src/starter/tests/utils/server_utils.rs @@ -0,0 +1,63 @@ +use crate::tests::CONFIG_FILE_PATH; +use ctor::dtor; +use testcontainers::core::IntoContainerPort; +use testcontainers::{ContainerAsync, ImageExt}; +use testcontainers_modules::postgres::Postgres; +use testcontainers_modules::testcontainers::runners::AsyncRunner; +use tokio::sync::OnceCell; +use tokio::task::JoinHandle; + +#[macro_export] +macro_rules! prepare_test_environment { + () => {{ + server_utils::init_once().await; + reqwest::Client::new() + }} +} + +pub struct Server { + #[allow(dead_code)] + server_handle: JoinHandle<()>, + container: ContainerAsync, +} + +impl Server { + pub async fn start() -> Self { + let container = Postgres::default() + .with_db_name("rust_template_db") + .with_mapped_port(5432, 5432.tcp()) + .with_tag("16-alpine") + .start().await.unwrap(); + + let server_handle = tokio::spawn(async move { + let server = starter::run_with_config(&CONFIG_FILE_PATH) + .await + .expect("Failed to bind address"); + let _server_task = tokio::spawn(server); + }); + Server { server_handle, container } + } + + pub fn container(&self) -> &ContainerAsync { + &self.container + } +} + +pub(crate) static TEST_SERVER_ONCE: OnceCell = OnceCell::const_new(); + +pub(crate) async fn init_once() { + TEST_SERVER_ONCE.get_or_init(Server::start).await; +} + +//see https://stackoverflow.com/questions/78969766/how-can-i-call-drop-in-a-tokio-static-oncelock-in-rust +#[dtor] +fn cleanup() { + //This is crazy but it works + let id = TEST_SERVER_ONCE.get().unwrap().container().id(); + + std::process::Command::new("docker") + .arg("kill") + .arg(id) + .output() + .expect("failed to kill container"); +} diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index e85f52d..f7366a4 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -15,3 +15,6 @@ uuid.workspace = true starter = { path = "../../src/starter" } application = { path = "../../src/application" } + +[dev-dependencies] +deadpool-postgres.workspace = true diff --git a/tests/integration/tests/integration.rs b/tests/integration/tests/integration.rs index 1cdc237..2411a68 100644 --- a/tests/integration/tests/integration.rs +++ b/tests/integration/tests/integration.rs @@ -12,6 +12,7 @@ mod tests { use testcontainers_modules::testcontainers::runners::AsyncRunner; use testcontainers_modules::testcontainers::ImageExt; use uuid::Uuid; + use std::sync::Once; const CONFIG_FILE_PATH: &str = "./../../"; @@ -32,6 +33,23 @@ mod tests { }; } + static INIT: Once = Once::new(); + + pub fn initialize() { + INIT.call_once(|| { + let _node = Postgres::default() + .with_db_name("rust_template_db") + .with_mapped_port(5432, 5432.tcp()) + .with_tag("16-alpine") + .start().await.unwrap(); + + let server = starter::run_with_config(&CONFIG_FILE_PATH) + .await + .expect("Failed to bind address"); + let _server_task = tokio::spawn(server); + }); + } + #[serial] #[tokio::test] async fn test_get_all() {