From e1414a8c5ea84623831a479808bc7aff60ff2378 Mon Sep 17 00:00:00 2001 From: George Mulhearn <57472912+gmulhearn@users.noreply.github.com> Date: Thu, 20 Jul 2023 18:31:23 +1000 Subject: [PATCH] Partially #870 - Simple Message Relay for testing/demo purposes (#891) Partially #870 - Simple Message Relay for testing/demo purposes (#891) Signed-off-by: gmulhearn --- Cargo.lock | 404 ++++++++++++++++++++++++- Cargo.toml | 3 +- tools/simple_message_relay/Cargo.toml | 9 + tools/simple_message_relay/README.md | 64 ++++ tools/simple_message_relay/src/main.rs | 179 +++++++++++ 5 files changed, 654 insertions(+), 5 deletions(-) create mode 100644 tools/simple_message_relay/Cargo.toml create mode 100644 tools/simple_message_relay/README.md create mode 100644 tools/simple_message_relay/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 941d93f3ea..42d3339fdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,187 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "actix-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +dependencies = [ + "bitflags 1.3.2", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash 0.8.3", + "base64 0.21.2", + "bitflags 1.3.2", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa 1.0.6", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.8.5", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "actix-router" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" +dependencies = [ + "bytestring", + "http", + "regex", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e8613a75dd50cc45f473cee3c34d59ed677c0f7b44480ce3b8247d7dc519327" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "num_cpus", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash 0.7.6", + "bytes", + "bytestring", + "cfg-if 1.0.0", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "itoa 1.0.6", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time 0.3.21", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "addr2line" version = "0.19.0" @@ -122,6 +303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if 1.0.0", + "getrandom 0.2.9", "once_cell", "version_check", ] @@ -135,6 +317,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "amcl" version = "0.2.0" @@ -516,7 +713,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "miniz_oxide", + "miniz_oxide 0.6.2", "object", "rustc-demangle", ] @@ -657,6 +854,27 @@ dependencies = [ "log", ] +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bs58" version = "0.3.1" @@ -702,6 +920,15 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "bytestring" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +dependencies = [ + "bytes", +] + [[package]] name = "c2-chacha" version = "0.2.4" @@ -748,6 +975,9 @@ name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -905,6 +1135,17 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time 0.3.21", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -951,6 +1192,15 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -1747,6 +1997,16 @@ dependencies = [ "log", ] +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide 0.7.1", +] + [[package]] name = "float-cmp" version = "0.9.0" @@ -1855,7 +2115,7 @@ checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" dependencies = [ "futures-core", "lock_api", - "parking_lot", + "parking_lot 0.11.2", ] [[package]] @@ -2560,6 +2820,15 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.63" @@ -2599,6 +2868,12 @@ dependencies = [ "log", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.4.0" @@ -2750,6 +3025,24 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" + [[package]] name = "lock_api" version = "0.4.9" @@ -2888,6 +3181,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.6" @@ -3219,7 +3521,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.8", ] [[package]] @@ -3236,6 +3548,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets 0.48.0", +] + [[package]] name = "paste" version = "1.0.12" @@ -4017,6 +4342,17 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.6", +] + [[package]] name = "sha2" version = "0.9.9" @@ -4098,6 +4434,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "1.3.2" @@ -4120,6 +4465,13 @@ dependencies = [ "thiserror", ] +[[package]] +name = "simple_message_relay" +version = "0.1.0" +dependencies = [ + "actix-web", +] + [[package]] name = "siphasher" version = "0.3.10" @@ -4229,7 +4581,7 @@ dependencies = [ "memchr", "num-bigint 0.3.3", "once_cell", - "parking_lot", + "parking_lot 0.11.2", "percent-encoding", "rand 0.8.5", "rsa", @@ -4464,8 +4816,10 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ + "itoa 1.0.6", "serde", "time-core", + "time-macros", ] [[package]] @@ -4474,6 +4828,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +[[package]] +name = "time-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -4500,7 +4863,9 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot 0.12.1", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", @@ -4604,6 +4969,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", + "log", "pin-project-lite", "tracing-core", ] @@ -5386,3 +5752,33 @@ dependencies = [ "metadeps", "zeromq-src", ] + +[[package]] +name = "zstd" +version = "0.12.3+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "6.0.5+zstd.1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 07214ccd78..cee3266015 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,8 @@ members = [ "did_resolver_sov", "did_resolver_web", "indy_ledger_response_parser", - "wallet_migrator" + "wallet_migrator", + "tools/simple_message_relay" ] [workspace.package] diff --git a/tools/simple_message_relay/Cargo.toml b/tools/simple_message_relay/Cargo.toml new file mode 100644 index 0000000000..471c00522e --- /dev/null +++ b/tools/simple_message_relay/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "simple_message_relay" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +actix-web = "4" \ No newline at end of file diff --git a/tools/simple_message_relay/README.md b/tools/simple_message_relay/README.md new file mode 100644 index 0000000000..c3ad7d7e05 --- /dev/null +++ b/tools/simple_message_relay/README.md @@ -0,0 +1,64 @@ +# Simple Message Relay +The simple message relay is a basic implementation of a mediator/relay service which can be used for testing agent-to-agent comms. + +*This relay should never be used in production/public environments, as it intentionally lacks user authorization and data persistance for testing simplicitly purposes.* + +Like a mediator, an agent can provide their HTTP/s "user endpoint" to peers during DIDComm connection protocols. An agent can then poll to collect incoming messages that have been sent to their endpoint. + +The simplic design makes this service ideal for testing situations where a message-receiving endpoint is required by an agent, without the overhead of bootstrapping and connecting with an official Aries RFC based mediator. + +# Service Setup +Within this directory, the service can be ran with `cargo`: +``` +cargo run +``` + +Or from the aries-vcx repo base directory: +``` +cargo run --bin simple_message_relay +``` + +This will start the relay serving on localhost port `8420`. + +## Public Endpoints +The service can be exposed publicly by running the service on a machine with a public IP address exposed, ensuring that the service's port is exposed to incoming traffic. + +Alternatively, the port can easily be exposed using [ngrok](https://ngrok.com/). +``` +ngrok http 8420 +``` + +# Service Usage +## Inform Peers +To use the service as an aries agent, the agent should pick a unique ID (representing their user account) and encode it within their endpoint for the service: + +``` +{base_url}/send_user_message/{user_id} + +// e.g. + +http://localhost:8420/send_user_message/1234-1234-1234-1234 +``` + +this "user endpoint" can then be used as the agent's `service_endpoint` in connection protocols, such as providing it into aries_vcx `Connection::send_request(.., service_endpoint: Url, ...)` API. This will inform the peer that they should send messages to this address. + +Peer agents can use this endpoint as if it was any other DIDComm http endpoint. + +## Collect Incoming Messages +After a peer sends the agent a message to the endpoint, they can be collected by calling `GET` on the following endpoint for the user: + +``` +{base_url}/pop_user_message/{user_id} + +// e.g. + +http://localhost:8420/pop_user_message/1234-1234-1234-1234 +``` + +The `user_id` should match the user_id selected in the [above section](#inform-peers). + +Calling this endpoint will return a body with the bytes of the oldest message in the relay message queue. Internally, the service will pop this message from the queue, such that the next call to the endpoint will return the next oldest message. + +If no message could be found, an empty body and a `NO CONTENT` status is returned. + +The bytes returned should be exactly as they were sent by the peer. As such, they should be able to be decrypted/unpacked and parsed as a DIDComm message, where they can then be passed into aries_vcx protocol handlers. \ No newline at end of file diff --git a/tools/simple_message_relay/src/main.rs b/tools/simple_message_relay/src/main.rs new file mode 100644 index 0000000000..65373942f1 --- /dev/null +++ b/tools/simple_message_relay/src/main.rs @@ -0,0 +1,179 @@ +use std::{ + collections::{HashMap, VecDeque}, + sync::Mutex, +}; + +use actix_web::{ + get, post, + web::{self, Bytes}, + App, HttpResponse, HttpServer, Responder, +}; + +#[derive(Default)] +struct UserMessages { + messages_by_user_id: Mutex>>>, +} + +#[get("/")] +async fn hello() -> impl Responder { + HttpResponse::Ok().body("Hello world!") +} + +#[get("/pop_user_message/{user_id}")] +async fn pop_user_message(path: web::Path, data: web::Data) -> impl Responder { + let user_id = path.into_inner(); + let mut messages_by_user_id = data.messages_by_user_id.lock().unwrap(); + let messages_for_user = messages_by_user_id.get_mut(&user_id); + + let message_body = messages_for_user.and_then(|msgs| msgs.pop_front()); + + if let Some(body) = message_body { + return HttpResponse::Ok().body(body); + } else { + return HttpResponse::NoContent().into(); + } +} + +#[post("/send_user_message/{user_id}")] +async fn send_user_message(path: web::Path, body: Bytes, data: web::Data) -> impl Responder { + let user_id = path.into_inner(); + + let body = body.to_vec(); + + let mut messages_by_user_id = data.messages_by_user_id.lock().unwrap(); + + let messages_for_user = messages_by_user_id.get_mut(&user_id); + + if let Some(messages) = messages_for_user { + messages.push_back(body); + } else { + messages_by_user_id.insert(user_id, vec![body].into()); + } + + HttpResponse::Ok() +} + +#[get("/status")] +async fn status() -> impl Responder { + HttpResponse::Ok() +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let user_messages = web::Data::new(UserMessages::default()); + + HttpServer::new(move || { + App::new() + .app_data(user_messages.clone()) + .service(pop_user_message) + .service(send_user_message) + .service(status) + }) + .bind(("0.0.0.0", 8420))? + .run() + .await +} + +#[cfg(test)] +mod tests { + use actix_web::{ + body, + http::StatusCode, + test::{self, TestRequest}, + web, App, + }; + + use crate::{pop_user_message, send_user_message, UserMessages}; + + fn pop_message_request(user_id: &str) -> TestRequest { + test::TestRequest::get().uri(&format!("/pop_user_message/{user_id}")) + } + + fn send_message_request(user_id: &str, msg: &'static str) -> TestRequest { + test::TestRequest::post() + .uri(&format!("/send_user_message/{user_id}")) + .set_payload(msg) + } + + #[actix_web::test] + async fn test_standard_user_flow() { + // assemble service + let user_messages = web::Data::new(UserMessages::default()); + + let app = test::init_service( + App::new() + .app_data(user_messages) + .service(send_user_message) + .service(pop_user_message), + ) + .await; + + let user_id = "user1"; + + // pop for unknown user == NO CONTENT + let pop_response = test::call_service(&app, pop_message_request(user_id).to_request()).await; + assert_eq!(pop_response.status(), StatusCode::NO_CONTENT); + let body = pop_response.into_body(); + assert!(body::to_bytes(body).await.unwrap().is_empty()); + + // post a message in + let message = "hello world"; + let req = send_message_request(user_id, message).to_request(); + + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_success()); + + // pop for known user == OK and body + let pop_response = test::call_service(&app, pop_message_request(user_id).to_request()).await; + assert_eq!(pop_response.status(), StatusCode::OK); + let body = pop_response.into_body(); + assert_eq!(body::to_bytes(body).await.unwrap(), message.as_bytes()); + + // pop for no messages == NO CONTENT + let pop_response = test::call_service(&app, pop_message_request(user_id).to_request()).await; + assert_eq!(pop_response.status(), StatusCode::NO_CONTENT); + let body = pop_response.into_body(); + assert!(body::to_bytes(body).await.unwrap().is_empty()); + } + + #[actix_web::test] + async fn test_multi_message_multi_user_flow() { + // assemble service + let user_messages = web::Data::new(UserMessages::default()); + + let app = test::init_service( + App::new() + .app_data(user_messages) + .service(send_user_message) + .service(pop_user_message), + ) + .await; + + let user_id1 = "user1"; + let user_id2 = "user2"; + + let message1 = "message1"; + let message2 = "message2"; + let message3 = "message3"; + let message4 = "message4"; + + // populate + test::call_service(&app, send_message_request(user_id1, message1).to_request()).await; + test::call_service(&app, send_message_request(user_id1, message2).to_request()).await; + test::call_service(&app, send_message_request(user_id2, message3).to_request()).await; + test::call_service(&app, send_message_request(user_id2, message4).to_request()).await; + + // pop and check + let res = test::call_service(&app, pop_message_request(user_id1).to_request()).await; + assert_eq!(body::to_bytes(res.into_body()).await.unwrap(), message1.as_bytes()); + + let res = test::call_service(&app, pop_message_request(user_id2).to_request()).await; + assert_eq!(body::to_bytes(res.into_body()).await.unwrap(), message3.as_bytes()); + + let res = test::call_service(&app, pop_message_request(user_id1).to_request()).await; + assert_eq!(body::to_bytes(res.into_body()).await.unwrap(), message2.as_bytes()); + + let res = test::call_service(&app, pop_message_request(user_id2).to_request()).await; + assert_eq!(body::to_bytes(res.into_body()).await.unwrap(), message4.as_bytes()); + } +}