use crate::{Auth, HttpGet, HttpPost, HttpResult};
use reqwest::IntoUrl;
use serde::Serialize;
use serde::de::DeserializeOwned;
use std::fs;
#[cfg(doc)]
use crate::HttpService;
pub struct HttpTestService {
root: String,
ext: String,
}
impl HttpTestService {
pub fn new(root: impl Into<String>) -> Self {
let root = root.into();
let ext = String::from("json"); Self { root, ext }
}
fn load_resource(&self, uri: impl IntoUrl + Send) -> String {
let path = format!("{}{}.{}", self.root, uri.as_str(), self.ext);
fs::read_to_string(path).expect("could not find test data")
}
}
impl HttpGet for HttpTestService {
async fn get<U>(&self, uri: U) -> HttpResult<String>
where
U: IntoUrl + Send,
{
Ok(self.load_resource(uri).trim().to_string())
}
}
impl HttpPost for HttpTestService {
async fn post<U, D, R>(&self, uri: U, _auth: &Auth, _data: &D) -> HttpResult<R>
where
U: IntoUrl + Send,
D: Serialize + Sync,
R: DeserializeOwned,
{
let data = self.load_resource(uri);
Ok(serde_json::from_str(&data)?)
}
}
pub struct TestDataLoader {
root: String,
ext: String,
}
impl TestDataLoader {
pub fn new(root: impl Into<String>) -> Self {
let root = root.into();
let ext = String::from("json"); Self { root, ext }
}
}
impl TestDataLoader {
pub fn load<T>(&self, resource: impl Into<String>) -> T
where
T: DeserializeOwned,
{
let resource = resource.into();
let path = format!("{}/{resource}.{}", self.root, self.ext);
let data = fs::read_to_string(path).expect("could not read test data");
serde_json::from_str(&data).expect("could not deserialize test data")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Auth, HttpError, HttpGet, HttpPost};
use serde::{Deserialize, Serialize};
use std::sync::LazyLock;
static LOADER: LazyLock<TestDataLoader> =
LazyLock::new(|| TestDataLoader::new("tests/data/input"));
static SERVICE: LazyLock<HttpTestService> =
LazyLock::new(|| HttpTestService::new("tests/data/output"));
#[derive(Debug, Deserialize, Serialize)]
struct User {
username: String,
}
#[tokio::test]
async fn get_loads_data() -> Result<(), HttpError> {
let response = SERVICE.get("/users/foo/about").await?;
assert_eq!(response, "{\"username\": \"foo\"}");
Ok(())
}
#[tokio::test]
#[should_panic]
async fn get_panics_if_data_does_not_exist() {
let _ = SERVICE.get("/no-resource").await;
}
#[tokio::test]
async fn post_loads_data() -> Result<(), HttpError> {
let auth = Auth::new("my-api-key");
let data: User = LOADER.load("user");
let response: User = SERVICE.post("/users", &auth, &data).await?;
assert_eq!(response.username, "foo");
Ok(())
}
#[tokio::test]
#[should_panic]
async fn post_panics_if_input_data_does_not_exist() {
let auth = Auth::new("my-api-key");
let data: User = LOADER.load("no-resource");
let _: Result<User, _> = SERVICE.post("/users", &auth, &data).await;
}
#[tokio::test]
#[should_panic]
async fn post_panics_if_output_data_does_not_exist() {
let auth = Auth::new("my-api-key");
let data: User = LOADER.load("user");
let _: Result<User, _> = SERVICE.post("/admin", &auth, &data).await;
}
}