Skip to content

Commit

Permalink
Add HTTP integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bradenrayhorn committed Jun 5, 2024
1 parent abe26cb commit cc95f7e
Show file tree
Hide file tree
Showing 13 changed files with 431 additions and 15 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ jobs:
- name: Checkout code
uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4

- name: Setup go
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5
with:
go-version: '1.22'
cache: false

- name: Install fake-oidc
run: go install github.com/bradenrayhorn/fake-oidc@v0

- name: test
run: cargo test

66 changes: 61 additions & 5 deletions server/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ anyhow = "1.0"
axum = { version = "0.7.5" }
serde = { version = "1.0.199", features = ["derive"] }
openidconnect = { version = "4.0.0-alpha.1" }
reqwest = { version = "0.12.4" }
reqwest = { version = "0.12.4", features = ["cookies", "json"] }
axum-extra = { version = "0.9.3", features = ["cookie-private"] }
serde_json = "1.0.117"
cookie = "0.18.1"
Expand Down
8 changes: 6 additions & 2 deletions server/src/bin/server.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use mise::{config, datastore, http::Server, session_store::SessionStore, sqlite};
use mise::{config, datastore, http::Server, oidc, session_store::SessionStore, sqlite};

#[tokio::main]
async fn main() {
Expand Down Expand Up @@ -29,7 +29,11 @@ async fn main() {
let pool = datastore::Pool::new(senders);
let cache = SessionStore::new(session_store_sender);

let s = Server::new(config, pool, cache);
let oidc_provider = oidc::Provider::new((&config).try_into().unwrap())
.await
.unwrap();

let s = Server::new(config, pool, cache, oidc_provider);

if let Err(err) = s.start().await {
println!("Failed to start http server: {:?}", err)
Expand Down
10 changes: 7 additions & 3 deletions server/src/domain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ pub mod recipe {
impl InstructionBlock {
#[must_use]
pub fn title(&self) -> Option<&str> {
self.title.as_ref().map(|x| &**x)
self.title.as_deref()
}

#[must_use]
Expand Down Expand Up @@ -366,7 +366,9 @@ pub mod recipe {
gather_text(node, &mut heading);
Ok(heading)
} else {
Err(ValidationError::Format("Expected heading, found ?.".into()))
Err(ValidationError::Format(
"Expected heading, found not a heading.".into(),
))
}
}

Expand All @@ -376,7 +378,9 @@ pub mod recipe {
if let comrak::nodes::NodeValue::List(_) = node.data.borrow().value {
Ok(gather_list(node))
} else {
Err(ValidationError::Format(format!("Expected list, found ?.")))
Err(ValidationError::Format(
"Expected list, found not a list.".into(),
))
}
}

Expand Down
13 changes: 9 additions & 4 deletions server/src/http/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub struct Server {
config: Config,
datasource: Pool,
session_store: SessionStore,
oidc_provider: Arc<oidc::Provider>,
}

#[derive(Clone)]
Expand All @@ -42,24 +43,28 @@ impl FromRef<AppState> for Key {

impl Server {
#[must_use]
pub fn new(config: Config, datasource: Pool, session_store: SessionStore) -> Self {
pub fn new(
config: Config,
datasource: Pool,
session_store: SessionStore,
oidc_provider: oidc::Provider,
) -> Self {
Server {
config,
datasource,
session_store,
oidc_provider: Arc::new(oidc_provider),
}
}

pub async fn start(&self) -> anyhow::Result<()> {
println!("Starting http server on port {:?}", self.config.http_port);

let oidc_provider = oidc::Provider::new((&self.config).try_into()?).await?;

let state = AppState {
key: Key::generate(),
session_store: self.session_store.clone(),
datasource: self.datasource.clone(),
oidc_provider: Arc::new(oidc_provider),
oidc_provider: self.oidc_provider.clone(),
};

let router: Router = Router::new()
Expand Down
1 change: 1 addition & 0 deletions server/src/oidc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ impl TryFrom<&config::Config> for Config {
}
}

#[derive(Clone)]
pub struct Provider {
http_client: reqwest::Client,
openid_client: CoreClient<
Expand Down
16 changes: 16 additions & 0 deletions server/tests/http/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use super::setup;
use anyhow::Result;
use reqwest::StatusCode;

#[tokio::test]
async fn can_get_me() -> Result<()> {
let mut harness = setup::harness().await?;

harness.authenticate("thomas").await?;

let response = harness.get("/api/v1/auth/me").send().await?;

assert_eq!(StatusCode::OK, response.status());

Ok(())
}
71 changes: 71 additions & 0 deletions server/tests/http/recipe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use super::{requests, responses, setup};
use anyhow::Result;
use reqwest::StatusCode;

#[tokio::test]
async fn cannot_get_unknown_recipe() -> Result<()> {
let harness = setup::with_auth().await?;

let random_id = uuid::Uuid::new_v4();

let response = harness
.get(&format!("/api/v1/recipes/{random_id}"))
.send()
.await?;

assert_eq!(StatusCode::NOT_FOUND, response.status());

Ok(())
}

#[tokio::test]
async fn can_create_and_get_recipe() -> Result<()> {
let harness = setup::with_auth().await?;

let response = harness
.post("/api/v1/recipes")
.json(&requests::CreateRecipe {
title: "Chicken Parm".into(),
ingredients: "\
- One chicken\n\
- Parmesan cheese\
"
.into(),
instructions: "\
- Broil the chicken\n\
- Add the parmesan\
"
.into(),
notes: Some("Best served hot!".into()),
})
.send()
.await?;

assert_eq!(StatusCode::OK, response.status());
let id = response.json::<responses::Id>().await?.data;

// try to get recipe
let response = harness.get(&format!("/api/v1/recipes/{id}")).send().await?;
assert_eq!(StatusCode::OK, response.status());

let result = response.json::<responses::GetRecipe>().await?.data;

assert_eq!("Chicken Parm", result.title);
assert_eq!(
vec![responses::Ingredients {
title: None,
ingredients: vec!["One chicken".into(), "Parmesan cheese".into()]
}],
result.ingredient_blocks
);
assert_eq!(
vec![responses::Instructions {
title: None,
instructions: vec!["Broil the chicken".into(), "Add the parmesan".into()]
}],
result.instruction_blocks
);
assert_eq!(Some("Best served hot!".into()), result.notes);

Ok(())
}
9 changes: 9 additions & 0 deletions server/tests/http/requests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use serde::Serialize;

#[derive(Serialize)]
pub struct CreateRecipe {
pub title: String,
pub ingredients: String,
pub instructions: String,
pub notes: Option<String>,
}
32 changes: 32 additions & 0 deletions server/tests/http/responses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use serde::Deserialize;

#[derive(Deserialize)]
pub struct Data<T> {
pub data: T,
}

pub type Id = Data<uuid::Uuid>;

pub type GetRecipe = Data<Recipe>;

#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct Recipe {
pub id: String,
pub hash: String,
pub title: String,
pub ingredient_blocks: Vec<Ingredients>,
pub instruction_blocks: Vec<Instructions>,
pub notes: Option<String>,
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct Instructions {
pub title: Option<String>,
pub instructions: Vec<String>,
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct Ingredients {
pub title: Option<String>,
pub ingredients: Vec<String>,
}
Loading

0 comments on commit cc95f7e

Please sign in to comment.