Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat#46 users integration test #51

Merged
merged 19 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions .env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export DB_HOSTNAME="localhost"
export DB_PORT="3306"
export DB_DATABASE="world"
export RUST_LOG="debug"
export DATABASE_URL="mariadb://$DB_USERNAME:$DB_PASSWORD@$DB_HOSTNAME:$DB_PORT/$DB_DATABASE"

export MAIL_ADDRESS="[email protected]"
export MAIL_PASSWORD="**** **** **** ****"
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ env:
jobs:
test:
runs-on: ubuntu-latest
services:
mariadb:
image: mariadb:latest
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: world
TZ: Asia/Tokyo
ports:
- 3306:3306
options: --health-cmd="mariadb-admin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -32,7 +42,7 @@ jobs:
- name: Build test
run: cargo build --release --verbose
- name: Run test
run: cargo test --verbose
run: source .env.dev && cargo test --verbose
- name: Lint with clippy
run: cargo clippy --all-targets --all-features
- name: Check formatting
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ edition = "2021"

[dependencies]
axum = "0.7"
http-body-util = "0.1.0"
axum-extra = { version = "0.9", features = [ "typed-header" ] }
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"
tower = "0.4"
tower = { version = "0.4", features = ["full"] }
tower-http = { version = "0.5.0", features = ["add-extension", "trace", "fs"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
Expand Down
10 changes: 7 additions & 3 deletions src/handler/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,18 @@ pub async fn put_me(
self_introduction: body.self_introduction.unwrap_or(user.self_introduction),
};

let icon_url = new_body.icon_url.clone();

state
.update_user(user.display_id, new_body)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

Ok(Json(serde_json::json!({"iconUrl": icon_url})))
let new_user = state
.get_user_by_display_id(display_id)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;

Ok(Json(new_user))
}

pub async fn get_user(
Expand Down
12 changes: 12 additions & 0 deletions src/repository.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ impl Repository {

Ok(())
}

pub async fn create_by_pool(pool: sqlx::MySqlPool) -> anyhow::Result<Self> {
let session_store =
MySqlSessionStore::from_client(pool.clone()).with_table_name("user_sessions");
session_store.migrate().await?;

Ok(Self {
pool,
session_store,
bcrypt_cost: bcrypt::DEFAULT_COST,
})
}
}

fn get_option_from_env() -> anyhow::Result<MySqlConnectOptions> {
Expand Down
2 changes: 2 additions & 0 deletions src/repository/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use super::Repository;
#[derive(Debug, Clone, PartialEq, sqlx::Type, Serialize)]
#[repr(i32)]
pub enum UserRole {
#[serde(rename = "commonUser")]
common_user = 0,
#[serde(rename = "traPUser")]
traP_user = 1,
admin = 2,
}
Expand Down
1 change: 1 addition & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod users;
50 changes: 50 additions & 0 deletions tests/users/common/check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use super::remove_field;
use serde_json::{json, Value};

pub fn users_check_by_id(id: i64, resp_json: &mut Value) -> anyhow::Result<()> {
let users_json = match id {
1 => json!({
"id": "11111111-1111-1111-1111-111111111111",
"displayId": 1,
"name": "test_user_1",
"traqId": null,
"githubId": "test_github_id_1",
"iconUrl": null,
"githubLink": "https://github.com/test_user_1",
"xLink": null,
"selfIntroduction": "test_self_introduction_1",
"role": "commonUser",
}),
2 => json!({
"id": "22222222-2222-2222-2222-222222222222",
"displayId": 2,
"name": "test_user_2",
"traqId": "test_traq_id_2",
"githubId": null,
"iconUrl": null,
"githubLink": null,
"xLink": "https://x.com/test_user_2",
"selfIntroduction": "",
"role": "traPUser",
}),
3 => json!({
"id": "33333333-3333-3333-3333-333333333333",
"displayId": 3,
"name": "test_user_3",
"traqId": null,
"githubId": null,
"iconUrl": "https://icon.com/test_user_3",
"githubLink": null,
"xLink": null,
"selfIntroduction": "",
"role": "admin",
}),
_ => return Err(anyhow::anyhow!("Invalid id")),
};
let ignore_field = vec!["createdAt", "updatedAt"];

remove_field(resp_json, &ignore_field);
assert_eq!(resp_json, &users_json);

Ok(())
}
31 changes: 31 additions & 0 deletions tests/users/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use axum::{
body::Body,
http::{request, Request},
};

pub mod check;

pub trait RequestBuilderExt {
fn json(self, json: serde_json::Value) -> Request<Body>;
}

impl RequestBuilderExt for request::Builder {
fn json(self, json: serde_json::Value) -> Request<Body> {
self.header("Content-Type", "application/json")
.body(Body::from(json.to_string()))
.unwrap()
}
}

pub fn remove_field(json: &mut serde_json::Value, ignore_fields: &Vec<&str>) {
match json {
serde_json::Value::Object(obj) => {
for field in ignore_fields {
obj.remove(*field);
}
}
_ => {
panic!("Invalid JSON format");
}
}
}
41 changes: 41 additions & 0 deletions tests/users/fixtures/common.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
INSERT INTO users
(id, name, role, github_id, github_link, self_introduction, email)
VALUES
(
UNHEX(REPLACE('11111111-1111-1111-1111-111111111111','-','')),
'test_user_1',
0,
"test_github_id_1",
"https://github.com/test_user_1",
"test_self_introduction_1",
"[email protected]"
);

INSERT INTO users_passwords
(user_id, password)
VALUES
(
UNHEX(REPLACE('11111111-1111-1111-1111-111111111111','-','')),
"test_password_1"
);

INSERT INTO
users (id, name, role, traq_id, x_link)
VALUES
(
UNHEX(REPLACE('22222222-2222-2222-2222-222222222222','-','')),
'test_user_2',
1,
"test_traq_id_2",
"https://x.com/test_user_2"
);

INSERT INTO users
(id, name, role, icon_url)
VALUES
(
UNHEX(REPLACE('33333333-3333-3333-3333-333333333333','-','')),
'test_user_3',
2,
"https://icon.com/test_user_3"
);
116 changes: 116 additions & 0 deletions tests/users/get_users.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use std::borrow::BorrowMut;

use super::common::check::users_check_by_id;
use axum::{
body::Body,
http::{self, Request},
};
use http_body_util::BodyExt;
use serde_json::Value;
use tower::ServiceExt;
use trao_judge_backend::{make_router, Repository};

#[sqlx::test(fixtures("common"))]
async fn get_user_by_id(pool: sqlx::MySqlPool) -> anyhow::Result<()> {
let state = Repository::create_by_pool(pool).await?;
let mut app = make_router(state);

let tests = vec![1, 2, 3];

for id in tests {
let response = app
.borrow_mut()
.oneshot(
Request::builder()
.method(http::Method::GET)
.uri(format!("/users/{}", id))
.body(Body::empty())?,
)
.await?;

assert_eq!(response.status(), 200);

let mut resp_json: Value =
serde_json::from_slice(&response.into_body().collect().await?.to_bytes())?;

users_check_by_id(id, &mut resp_json)?;
}

Ok(())
}

#[sqlx::test(fixtures("common"))]
async fn get_user_by_id_not_found(pool: sqlx::MySqlPool) -> anyhow::Result<()> {
let state = Repository::create_by_pool(pool).await?;
let mut app = make_router(state);

let not_found_case = vec![0, 4, 10, 1000000];
for id in not_found_case {
let response = app
.borrow_mut()
.oneshot(
Request::builder()
.method(http::Method::GET)
.uri(format!("/users/{}", id))
.body(Body::empty())?,
)
.await?;

assert_eq!(response.status(), 404);
}

Ok(())
}

#[sqlx::test(fixtures("common"))]
async fn get_user_me(pool: sqlx::MySqlPool) -> anyhow::Result<()> {
let state = Repository::create_by_pool(pool).await?;
let mut app = make_router(state.clone());

let tests = vec![1, 2, 3];

for id in tests {
let session_id = state
.create_session(state.get_user_by_display_id(id).await?.unwrap())
.await?;

let response = app
.borrow_mut()
.oneshot(
Request::builder()
.method(http::Method::GET)
.uri("/users/me")
.header("Cookie", format!("session_id={}", session_id))
.body(Body::empty())?,
)
.await?;
assert_eq!(response.status(), 200);

let mut resp_json: Value =
serde_json::from_slice(&response.into_body().collect().await?.to_bytes())?;

users_check_by_id(id, &mut resp_json)?;
}

Ok(())
}

#[sqlx::test(fixtures("common"))]
async fn get_user_me_unauthorized(pool: sqlx::MySqlPool) -> anyhow::Result<()> {
let state = Repository::create_by_pool(pool).await?;
let mut app = make_router(state.clone());

// Test unauthorized case
let response = app
.borrow_mut()
.oneshot(
Request::builder()
.method(http::Method::GET)
.uri("/users/me")
.body(Body::empty())?,
)
.await?;
assert_eq!(response.status(), 401);

Ok(())
}
4 changes: 4 additions & 0 deletions tests/users/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod common;

mod get_users;
mod put_users;
Loading
Loading