Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
Add tests around cache invalidation (and fix bug they caught!)
Browse files Browse the repository at this point in the history
  • Loading branch information
OmegaJak committed Oct 11, 2023
1 parent d37e323 commit b4a4de4
Show file tree
Hide file tree
Showing 9 changed files with 378 additions and 62 deletions.
2 changes: 1 addition & 1 deletion src/routes/v2/teams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -955,7 +955,7 @@ pub async fn remove_team_member(
}

TeamMember::clear_cache(id, &redis).await?;
User::clear_project_cache(&[current_user.id.into()], &redis).await?;
User::clear_project_cache(&[delete_member.user_id.into()], &redis).await?;

transaction.commit().await?;
Ok(HttpResponse::NoContent().body(""))
Expand Down
3 changes: 3 additions & 0 deletions tests/common/asserts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub fn assert_status(response: actix_web::dev::ServiceResponse, status: actix_http::StatusCode) {
assert_eq!(response.status(), status, "{:#?}", response.response());
}
122 changes: 83 additions & 39 deletions tests/common/dummy_data.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use actix_http::StatusCode;
use actix_web::test::{self, TestRequest};
use labrinth::{models::projects::Project, models::projects::Version};
use serde_json::json;
Expand All @@ -10,9 +11,11 @@ use crate::common::{

use super::{
actix::{MultipartSegment, MultipartSegmentData},
asserts::assert_status,
environment::TestEnvironment,
};

#[allow(dead_code)]
pub const DUMMY_CATEGORIES: &'static [&str] = &[
"combat",
"decoration",
Expand All @@ -23,6 +26,14 @@ pub const DUMMY_CATEGORIES: &'static [&str] = &[
"optimization",
];

#[allow(dead_code)]
pub enum DummyJarFile {
DummyProjectAlpha,
DummyProjectBeta,
BasicMod,
BasicModDifferent,
}

pub struct DummyData {
pub alpha_team_id: String,
pub beta_team_id: String,
Expand Down Expand Up @@ -75,26 +86,34 @@ pub async fn add_dummy_data(test_env: &TestEnvironment) -> DummyData {
}

pub async fn add_project_alpha(test_env: &TestEnvironment) -> (Project, Version) {
add_public_dummy_project("alpha", DummyJarFile::DummyProjectAlpha, test_env).await
}

pub async fn add_project_beta(test_env: &TestEnvironment) -> (Project, Version) {
// Adds dummy data to the database with sqlx (projects, versions, threads)
// Generate test project data.
let json_data = json!(
{
"title": "Test Project Alpha",
"slug": "alpha",
"title": "Test Project Beta",
"slug": "beta",
"description": "A dummy project for testing with.",
"body": "This project is approved, and versions are listed.",
"body": "This project is not-yet-approved, and versions are draft.",
"client_side": "required",
"server_side": "optional",
"initial_versions": [{
"file_parts": ["dummy-project-alpha.jar"],
"file_parts": ["dummy-project-beta.jar"],
"version_number": "1.2.3",
"version_title": "start",
"status": "unlisted",
"requested_status": "unlisted",
"dependencies": [],
"game_versions": ["1.20.1"] ,
"release_channel": "release",
"loaders": ["fabric"],
"featured": true
}],
"status": "private",
"requested_status": "private",
"categories": [],
"license_id": "MIT"
}
Expand All @@ -110,11 +129,11 @@ pub async fn add_project_alpha(test_env: &TestEnvironment) -> (Project, Version)

// Basic file
let file_segment = MultipartSegment {
name: "dummy-project-alpha.jar".to_string(),
filename: Some("dummy-project-alpha.jar".to_string()),
name: "dummy-project-beta.jar".to_string(),
filename: Some("dummy-project-beta.jar".to_string()),
content_type: Some("application/java-archive".to_string()),
data: MultipartSegmentData::Binary(
include_bytes!("../../tests/files/dummy-project-alpha.jar").to_vec(),
include_bytes!("../../tests/files/dummy-project-beta.jar").to_vec(),
),
};

Expand All @@ -125,32 +144,20 @@ pub async fn add_project_alpha(test_env: &TestEnvironment) -> (Project, Version)
.set_multipart(vec![json_segment.clone(), file_segment.clone()])
.to_request();
let resp = test_env.call(req).await;
assert_eq!(resp.status(), 200);

// Approve as a moderator.
let req = TestRequest::patch()
.uri("/v2/project/alpha")
.append_header(("Authorization", MOD_USER_PAT))
.set_json(json!(
{
"status": "approved"
}
))
.to_request();
let resp = test_env.call(req).await;
assert_eq!(resp.status(), 204);
assert_eq!(resp.status(), 200);

// Get project
let req = TestRequest::get()
.uri("/v2/project/alpha")
.uri("/v2/project/beta")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = test_env.call(req).await;
let project: Project = test::read_body_json(resp).await;

// Get project's versions
let req = TestRequest::get()
.uri("/v2/project/alpha/version")
.uri("/v2/project/beta/version")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = test_env.call(req).await;
Expand All @@ -160,31 +167,31 @@ pub async fn add_project_alpha(test_env: &TestEnvironment) -> (Project, Version)
(project, version)
}

pub async fn add_project_beta(test_env: &TestEnvironment) -> (Project, Version) {
pub async fn add_public_dummy_project(
slug: &str,
jar: DummyJarFile,
test_env: &TestEnvironment,
) -> (Project, Version) {
// Adds dummy data to the database with sqlx (projects, versions, threads)
// Generate test project data.
let json_data = json!(
{
"title": "Test Project Beta",
"slug": "beta",
"title": format!("Test Project {slug}"),
"slug": slug,
"description": "A dummy project for testing with.",
"body": "This project is not-yet-approved, and versions are draft.",
"body": "This project is approved, and versions are listed.",
"client_side": "required",
"server_side": "optional",
"initial_versions": [{
"file_parts": ["dummy-project-beta.jar"],
"file_parts": [jar.filename()],
"version_number": "1.2.3",
"version_title": "start",
"status": "unlisted",
"requested_status": "unlisted",
"dependencies": [],
"game_versions": ["1.20.1"] ,
"release_channel": "release",
"loaders": ["fabric"],
"featured": true
}],
"status": "private",
"requested_status": "private",
"categories": [],
"license_id": "MIT"
}
Expand All @@ -200,12 +207,10 @@ pub async fn add_project_beta(test_env: &TestEnvironment) -> (Project, Version)

// Basic file
let file_segment = MultipartSegment {
name: "dummy-project-beta.jar".to_string(),
filename: Some("dummy-project-beta.jar".to_string()),
name: jar.filename(),
filename: Some(jar.filename()),
content_type: Some("application/java-archive".to_string()),
data: MultipartSegmentData::Binary(
include_bytes!("../../tests/files/dummy-project-beta.jar").to_vec(),
),
data: MultipartSegmentData::Binary(jar.bytes()),
};

// Add a project.
Expand All @@ -215,20 +220,32 @@ pub async fn add_project_beta(test_env: &TestEnvironment) -> (Project, Version)
.set_multipart(vec![json_segment.clone(), file_segment.clone()])
.to_request();
let resp = test_env.call(req).await;
assert_status(resp, StatusCode::OK);

assert_eq!(resp.status(), 200);
// Approve as a moderator.
let req = TestRequest::patch()
.uri(&format!("/v2/project/{slug}"))
.append_header(("Authorization", MOD_USER_PAT))
.set_json(json!(
{
"status": "approved"
}
))
.to_request();
let resp = test_env.call(req).await;
assert_status(resp, StatusCode::NO_CONTENT);

// Get project
let req = TestRequest::get()
.uri("/v2/project/beta")
.uri(&format!("/v2/project/{slug}"))
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = test_env.call(req).await;
let project: Project = test::read_body_json(resp).await;

// Get project's versions
let req = TestRequest::get()
.uri("/v2/project/beta/version")
.uri(&format!("/v2/project/{slug}/version"))
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = test_env.call(req).await;
Expand All @@ -237,3 +254,30 @@ pub async fn add_project_beta(test_env: &TestEnvironment) -> (Project, Version)

(project, version)
}

impl DummyJarFile {
pub fn filename(&self) -> String {
match self {
DummyJarFile::DummyProjectAlpha => "dummy-project-alpha.jar",
DummyJarFile::DummyProjectBeta => "dummy-project-beta.jar",
DummyJarFile::BasicMod => "basic-mod.jar",
DummyJarFile::BasicModDifferent => "basic-mod-different.jar",
}
.to_string()
}

pub fn bytes(&self) -> Vec<u8> {
match self {
DummyJarFile::DummyProjectAlpha => {
include_bytes!("../../tests/files/dummy-project-alpha.jar").to_vec()
}
DummyJarFile::DummyProjectBeta => {
include_bytes!("../../tests/files/dummy-project-beta.jar").to_vec()
}
DummyJarFile::BasicMod => include_bytes!("../../tests/files/basic-mod.jar").to_vec(),
DummyJarFile::BasicModDifferent => {
include_bytes!("../../tests/files/basic-mod-different.jar").to_vec()
}
}
}
}
114 changes: 113 additions & 1 deletion tests/common/environment.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
#![allow(dead_code)]

use super::{database::TemporaryDatabase, dummy_data};
use super::{
asserts::assert_status,
database::{TemporaryDatabase, FRIEND_USER_ID, USER_USER_PAT},
dummy_data,
};
use crate::common::setup;
use actix_http::StatusCode;
use actix_web::{dev::ServiceResponse, test, App};
use futures::Future;
use labrinth::models::{notifications::Notification, projects::Project};
use serde_json::json;

pub async fn with_test_environment<Fut>(f: impl FnOnce(TestEnvironment) -> Fut)
where
Expand Down Expand Up @@ -54,6 +61,111 @@ impl TestEnvironment {
pub async fn call(&self, req: actix_http::Request) -> ServiceResponse {
self.test_app.call(req).await.unwrap()
}

pub async fn generate_friend_user_notification(&self) {
let resp = self
.add_user_to_team(
&self.dummy.as_ref().unwrap().alpha_team_id,
FRIEND_USER_ID,
USER_USER_PAT,
)
.await;
assert_status(resp, StatusCode::NO_CONTENT);
}

pub async fn remove_project(&self, project_slug_or_id: &str, pat: &str) -> ServiceResponse {
let req = test::TestRequest::delete()
.uri(&format!("/v2/project/{project_slug_or_id}"))
.append_header(("Authorization", pat))
.to_request();
let resp = self.call(req).await;
assert_eq!(resp.status(), 204);
resp
}

pub async fn get_user_projects_deserialized(
&self,
user_id_or_username: &str,
pat: &str,
) -> Vec<Project> {
let req = test::TestRequest::get()
.uri(&format!("/v2/user/{}/projects", user_id_or_username))
.append_header(("Authorization", pat))
.to_request();
let resp = self.call(req).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}

pub async fn add_user_to_team(
&self,
team_id: &str,
user_id: &str,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::post()
.uri(&format!("/v2/team/{team_id}/members"))
.append_header(("Authorization", pat))
.set_json(json!( {
"user_id": user_id
}))
.to_request();
self.call(req).await
}

pub async fn join_team(&self, team_id: &str, pat: &str) -> ServiceResponse {
let req = test::TestRequest::post()
.uri(&format!("/v2/team/{team_id}/join"))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}

pub async fn remove_from_team(
&self,
team_id: &str,
user_id: &str,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::delete()
.uri(&format!("/v2/team/{team_id}/members/{user_id}"))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}

pub async fn get_user_notifications_deserialized(
&self,
user_id: &str,
pat: &str,
) -> Vec<Notification> {
let req = test::TestRequest::get()
.uri(&format!("/v2/user/{user_id}/notifications"))
.append_header(("Authorization", pat))
.to_request();
let resp = self.call(req).await;
test::read_body_json(resp).await
}

pub async fn mark_notification_read(
&self,
notification_id: &str,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::patch()
.uri(&format!("/v2/notification/{notification_id}"))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}

pub async fn delete_notification(&self, notification_id: &str, pat: &str) -> ServiceResponse {
let req = test::TestRequest::delete()
.uri(&format!("/v2/notification/{notification_id}"))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}
}

trait LocalService {
Expand Down
1 change: 1 addition & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::sync::Arc;
use self::database::TemporaryDatabase;

pub mod actix;
pub mod asserts;
pub mod database;
pub mod dummy_data;
pub mod environment;
Expand Down
Loading

0 comments on commit b4a4de4

Please sign in to comment.