From d5149633f09264ba7658dc1f9e2b5dc3c5f7393a Mon Sep 17 00:00:00 2001 From: Sehyun Park Date: Thu, 28 Dec 2023 00:07:25 +0900 Subject: [PATCH] add mock for Client --- Cargo.toml | 1 + src/client.rs | 28 ++++++++ src/lib.rs | 177 ++++++++++++++++++++++++++++---------------------- 3 files changed, 128 insertions(+), 78 deletions(-) create mode 100644 src/client.rs diff --git a/Cargo.toml b/Cargo.toml index 5a85f79..1d4e325 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,5 @@ serde_json = { version = "1" } embedded-svc = { version = "0.26" } +[dev-dependencies] mockall = "0.12.1" diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..f98ce41 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,28 @@ +use embedded_svc::{http::Method, io::Error}; +#[cfg(test)] +use mockall::automock; + +#[cfg(test)] +#[derive(Debug)] +pub struct TestError; + +#[cfg(test)] +impl Error for TestError { + fn kind(&self) -> embedded_svc::io::ErrorKind { + embedded_svc::io::ErrorKind::Other + } +} + +#[cfg_attr(test, automock(type Error=TestError;))] +pub trait Client { + type Error: Error; + + fn request<'a>( + &mut self, + method: Method, + uri: &'a str, + headers: &'a [(&'a str, &'a str)], + body: &[u8], + buf: &mut [u8], + ) -> Result<(usize, u16), Self::Error>; +} diff --git a/src/lib.rs b/src/lib.rs index e0f256f..b925db9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,9 @@ +pub mod client; pub mod url; use std::fmt::Display; -use embedded_svc::{ - http::client::{Client, Connection}, - io::ErrorType, -}; +use client::Client; use serde::{de::DeserializeOwned, Deserialize, Serialize}; #[repr(u8)] @@ -49,8 +47,8 @@ pub enum DexcomError { UnknownError, } -pub struct Dexcom<'a, C: Connection> { - client: &'a mut Client, +pub struct Dexcom<'a, C: Client> { + client: &'a mut C, } #[derive(Serialize)] @@ -148,10 +146,10 @@ impl From for ClientError { } } -type Result = std::result::Result::Error>>; +type Result = std::result::Result::Error>>; -impl<'a, C: Connection> Dexcom<'a, C> { - pub fn new(client: &'a mut Client) -> Self { +impl<'a, C: Client> Dexcom<'a, C> { + pub fn new(client: &'a mut C) -> Self { Self { client } } @@ -161,23 +159,19 @@ impl<'a, C: Connection> Dexcom<'a, C> { request: &S, ) -> Result { let body = serde_json::to_vec(&request).map_err(|e| SerdeJsonError(e))?; + let mut buf = [0; 512]; - let mut request = self.client.request( + let (size, status_code) = self.client.request( embedded_svc::http::Method::Post, uri, - &[("Content-Type", "application/json")], + &[ + ("Content-Type", "application/json"), + ("User-Agent", "rsdexcom/0.0.1"), + ], + &body, + &mut buf, )?; - request.write(&body)?; - - let mut response = request.submit()?; - - let status_code = response.status(); - - let mut buf = [0_u8; 512]; - - let size = response.read(&mut buf)?; - let buf = &buf[..size]; #[cfg(feature = "log")] @@ -257,61 +251,88 @@ impl<'a, C: Connection> Dexcom<'a, C> { #[cfg(test)] mod tests { - // impl Connection for FakeClient { - // type Error = core::fmt::Error; - // fn post( - // &mut self, - // url: &str, - // _: &[u8], - // buf: &mut Vec, - // ) -> Result { - // match url { - // url::DEXCOM_GLUCOSE_READINGS_ENDPOINT => { - // buf.write_str(&r#"[{"WT":"Date(1699110415000)","ST":"Date(1699110415000)","DT":"Date(1699110415000+0900)","Value":153,"Trend":"Flat"}]"#)?; - // Ok(200) - // } - // url::DEXCOM_LOGIN_ID_ENDPOINT => { - // buf.write_str(&r#""a21d18db-a276-40bc-8337-77dcd02df53e""#)?; - // Ok(200) - // } - // url::DEXCOM_AUTHENTICATE_ENDPOINT => { - // buf.write_str(&r#""1e913fce-5a34-4d27-a991-b6cb3a3bd3d8""#)?; - // Ok(200) - // } - // _ => unreachable!(), - // } - // } - // } - - // #[test] - // fn test_get_current_glucose_reading() { - // let dexcom = Dexcom::new(); - // let mut client = FakeClient {}; - - // let session_id = dexcom.load_session_id(&mut client, "", "", "").unwrap(); - // assert_eq!( - // session_id, - // UUID::from_str("1e913fce-5a34-4d27-a991-b6cb3a3bd3d8").unwrap() - // ); - - // let glucose = dexcom.get_current_glucose_reading(&mut client, &session_id); - - // assert!(glucose.is_ok()); - // assert_eq!( - // glucose, - // Ok([GlucosReading { - // trend: Trend::Flat, - // value: 153, - // }]) - // ) - // } - - // #[test] - // fn test_dexcom_error_response() { - // let message = r#"{"Code":"SessionIdNotFound"}"#; - // let (response, _) = serde_json_core::from_str::(message).unwrap(); - - // let error: DexcomError = response.into(); - // assert_eq!(error, DexcomError::SessionError); - // } + use std::io::Write; + + use embedded_svc::http::Method; + use mockall::predicate::*; + + use super::{url, Dexcom, DexcomError, DexcomErrorResponse, GlucosReading, Trend}; + + use super::client::*; + + #[test] + fn test_get_current_glucose_reading() { + let mut client = MockClient::new(); + + client + .expect_request() + .with( + eq(Method::Post), + eq(url::DEXCOM_AUTHENTICATE_ENDPOINT), + always(), + always(), + always(), + ) + .returning(|_, _, _, _, mut buf| { + let size = buf + .write(b"\"1e913fce-5a34-4d27-a991-b6cb3a3bd3d8\"") + .unwrap(); + Ok((size, 200u16)) + }); + + client + .expect_request() + .with( + eq(Method::Post), + eq(url::DEXCOM_LOGIN_ID_ENDPOINT), + always(), + always(), + always(), + ) + .returning(|_, _, _, _, mut buf| { + let size = buf + .write(b"\"a21d18db-a276-40bc-8337-77dcd02df53e\"") + .unwrap(); + Ok((size, 200u16)) + }); + + client + .expect_request() + .with( + eq(Method::Post), + eq(url::DEXCOM_GLUCOSE_READINGS_ENDPOINT), + always(), + always(), + always(), + ) + .returning(|_, _, _, _, mut buf| { + let size = buf.write(r#"[{"WT":"Date(1699110415000)","ST":"Date(1699110415000)","DT":"Date(1699110415000+0900)","Value":153,"Trend":"Flat"}]"#.as_bytes()).unwrap(); + Ok((size, 200u16)) + }); + + let mut dexcom = Dexcom::new(&mut client); + + let session_id = dexcom.load_session_id("", "", "").unwrap(); + assert_eq!(session_id, "a21d18db-a276-40bc-8337-77dcd02df53e"); + + let glucose = dexcom.get_current_glucose_reading(&session_id); + + assert!(glucose.is_ok()); + assert_eq!( + glucose.unwrap(), + [GlucosReading { + trend: Trend::Flat, + value: 153, + }] + ) + } + + #[test] + fn test_dexcom_error_response() { + let message = r#"{"Code":"SessionIdNotFound"}"#; + let response = serde_json::from_str::(message).unwrap(); + + let error: DexcomError = response.into(); + assert_eq!(error, DexcomError::SessionError); + } }