Modrinth/tests/common/dummy_data.rs
Wyatt Verchere 172b93d07f
Tests v2 recreate (#760)
* added common project information; setup for v2 test change

* all tests now use with_test_environment

* progress, failing

* finished re-adding tests

* prepare

* cargo sqlx prepare -- --tests

* fmt; clippy; prepare

* sqlx prepare

* adds version_create fix and corresponding test

* merge fixes; rev

* fmt, clippy, prepare

* test cargo sqlx prepare
2023-11-25 14:42:39 -07:00

531 lines
16 KiB
Rust

#![allow(dead_code)]
use std::io::{Cursor, Write};
use actix_http::StatusCode;
use actix_web::test::{self, TestRequest};
use labrinth::models::{
oauth_clients::OAuthClient, organizations::Organization, pats::Scopes, projects::ProjectId,
};
use serde_json::json;
use sqlx::Executor;
use zip::{write::FileOptions, CompressionMethod, ZipWriter};
use crate::common::{api_common::Api, database::USER_USER_PAT};
use labrinth::util::actix::{AppendsMultipart, MultipartSegment, MultipartSegmentData};
use super::{
api_common::{
models::{CommonProject, CommonVersion},
ApiProject,
},
api_v3::ApiV3,
database::TemporaryDatabase,
};
use super::{asserts::assert_status, database::USER_USER_ID, get_json_val_str};
pub const DUMMY_DATA_UPDATE: i64 = 5;
#[allow(dead_code)]
pub const DUMMY_CATEGORIES: &[&str] = &[
"combat",
"decoration",
"economy",
"food",
"magic",
"mobs",
"optimization",
];
pub const DUMMY_OAUTH_CLIENT_ALPHA_SECRET: &str = "abcdefghijklmnopqrstuvwxyz";
#[allow(dead_code)]
pub enum TestFile {
DummyProjectAlpha,
DummyProjectBeta,
BasicMod,
BasicModDifferent,
// Randomly generates a valid .jar with a random hash.
// Unlike the other dummy jar files, this one is not a static file.
// and BasicModRandom.bytes() will return a different file each time.
BasicModRandom { filename: String, bytes: Vec<u8> },
BasicModpackRandom { filename: String, bytes: Vec<u8> },
}
impl TestFile {
pub fn build_random_jar() -> Self {
let filename = format!("random-mod-{}.jar", rand::random::<u64>());
let fabric_mod_json = serde_json::json!({
"schemaVersion": 1,
"id": filename,
"version": "1.0.1",
"name": filename,
"description": "Does nothing",
"authors": [
"user"
],
"contact": {
"homepage": "https://www.modrinth.com",
"sources": "https://www.modrinth.com",
"issues": "https://www.modrinth.com"
},
"license": "MIT",
"icon": "none.png",
"environment": "client",
"entrypoints": {
"main": [
"io.github.modrinth.Modrinth"
]
},
"depends": {
"minecraft": ">=1.20-"
}
}
)
.to_string();
// Create a simulated zip file
let mut cursor = Cursor::new(Vec::new());
{
let mut zip = ZipWriter::new(&mut cursor);
zip.start_file(
"fabric.mod.json",
FileOptions::default().compression_method(CompressionMethod::Stored),
)
.unwrap();
zip.write_all(fabric_mod_json.as_bytes()).unwrap();
zip.finish().unwrap();
}
let bytes = cursor.into_inner();
TestFile::BasicModRandom { filename, bytes }
}
pub fn build_random_mrpack() -> Self {
let filename = format!("random-modpack-{}.mrpack", rand::random::<u64>());
let modrinth_index_json = serde_json::json!({
"formatVersion": 1,
"game": "minecraft",
"versionId": "1.20.1-9.6",
"name": filename,
"files": [],
"dependencies": {
"fabric-loader": "0.14.22",
"minecraft": "1.20.1"
}
}
)
.to_string();
// Create a simulated zip file
let mut cursor = Cursor::new(Vec::new());
{
let mut zip = ZipWriter::new(&mut cursor);
zip.start_file(
"modrinth.index.json",
FileOptions::default().compression_method(CompressionMethod::Stored),
)
.unwrap();
zip.write_all(modrinth_index_json.as_bytes()).unwrap();
zip.finish().unwrap();
}
let bytes = cursor.into_inner();
TestFile::BasicModpackRandom { filename, bytes }
}
}
#[derive(Clone)]
#[allow(dead_code)]
pub enum DummyImage {
SmallIcon, // 200x200
}
#[derive(Clone)]
pub struct DummyData {
/// Alpha project:
/// This is a dummy project created by USER user.
/// It's approved, listed, and visible to the public.
pub project_alpha: DummyProjectAlpha,
/// Beta project:
/// This is a dummy project created by USER user.
/// It's not approved, unlisted, and not visible to the public.
pub project_beta: DummyProjectBeta,
/// Zeta organization:
/// This is a dummy organization created by USER user.
/// There are no projects in it.
pub organization_zeta: DummyOrganizationZeta,
/// Alpha OAuth Client:
/// This is a dummy OAuth client created by USER user.
///
/// All scopes are included in its max scopes
///
/// It has one valid redirect URI
pub oauth_client_alpha: DummyOAuthClientAlpha,
}
impl DummyData {
pub fn new(
project_alpha: CommonProject,
project_alpha_version: CommonVersion,
project_beta: CommonProject,
project_beta_version: CommonVersion,
organization_zeta: Organization,
oauth_client_alpha: OAuthClient,
) -> Self {
DummyData {
project_alpha: DummyProjectAlpha {
team_id: project_alpha.team.to_string(),
project_id: project_alpha.id.to_string(),
project_slug: project_alpha.slug.unwrap(),
project_id_parsed: project_alpha.id,
version_id: project_alpha_version.id.to_string(),
thread_id: project_alpha.thread_id.to_string(),
file_hash: project_alpha_version.files[0].hashes["sha1"].clone(),
},
project_beta: DummyProjectBeta {
team_id: project_beta.team.to_string(),
project_id: project_beta.id.to_string(),
project_slug: project_beta.slug.unwrap(),
project_id_parsed: project_beta.id,
version_id: project_beta_version.id.to_string(),
thread_id: project_beta.thread_id.to_string(),
file_hash: project_beta_version.files[0].hashes["sha1"].clone(),
},
organization_zeta: DummyOrganizationZeta {
organization_id: organization_zeta.id.to_string(),
team_id: organization_zeta.team_id.to_string(),
organization_title: organization_zeta.title,
},
oauth_client_alpha: DummyOAuthClientAlpha {
client_id: get_json_val_str(oauth_client_alpha.id),
client_secret: DUMMY_OAUTH_CLIENT_ALPHA_SECRET.to_string(),
valid_redirect_uri: oauth_client_alpha
.redirect_uris
.first()
.unwrap()
.uri
.clone(),
},
}
}
}
#[derive(Clone)]
pub struct DummyProjectAlpha {
pub project_id: String,
pub project_slug: String,
pub project_id_parsed: ProjectId,
pub version_id: String,
pub thread_id: String,
pub file_hash: String,
pub team_id: String,
}
#[derive(Clone)]
pub struct DummyProjectBeta {
pub project_id: String,
pub project_slug: String,
pub project_id_parsed: ProjectId,
pub version_id: String,
pub thread_id: String,
pub file_hash: String,
pub team_id: String,
}
#[derive(Clone)]
pub struct DummyOrganizationZeta {
pub organization_id: String,
pub organization_title: String,
pub team_id: String,
}
#[derive(Clone)]
pub struct DummyOAuthClientAlpha {
pub client_id: String,
pub client_secret: String,
pub valid_redirect_uri: String,
}
pub async fn add_dummy_data(api: &ApiV3, db: TemporaryDatabase) -> DummyData {
// Adds basic dummy data to the database directly with sql (user, pats)
let pool = &db.pool.clone();
pool.execute(
include_str!("../files/dummy_data.sql")
.replace("$1", &Scopes::all().bits().to_string())
.as_str(),
)
.await
.unwrap();
let (alpha_project, alpha_version) = add_project_alpha(api).await;
let (beta_project, beta_version) = add_project_beta(api).await;
let zeta_organization = add_organization_zeta(api).await;
let oauth_client_alpha = get_oauth_client_alpha(api).await;
sqlx::query("INSERT INTO dummy_data (update_id) VALUES ($1)")
.bind(DUMMY_DATA_UPDATE)
.execute(pool)
.await
.unwrap();
DummyData::new(
alpha_project,
alpha_version,
beta_project,
beta_version,
zeta_organization,
oauth_client_alpha,
)
}
pub async fn get_dummy_data(api: &ApiV3) -> DummyData {
let (alpha_project, alpha_version) = get_project_alpha(api).await;
let (beta_project, beta_version) = get_project_beta(api).await;
let zeta_organization = get_organization_zeta(api).await;
let oauth_client_alpha = get_oauth_client_alpha(api).await;
DummyData::new(
alpha_project,
alpha_version,
beta_project,
beta_version,
zeta_organization,
oauth_client_alpha,
)
}
pub async fn add_project_alpha(api: &ApiV3) -> (CommonProject, CommonVersion) {
let (project, versions) = api
.add_public_project(
"alpha",
Some(TestFile::DummyProjectAlpha),
None,
USER_USER_PAT,
)
.await;
(project, versions.into_iter().next().unwrap())
}
pub async fn add_project_beta(api: &ApiV3) -> (CommonProject, CommonVersion) {
// Adds dummy data to the database with sqlx (projects, versions, threads)
// Generate test project data.
let jar = TestFile::DummyProjectBeta;
// TODO: this shouldnt be hardcoded (nor should other similar ones be)
let json_data = json!(
{
"title": "Test Project Beta",
"slug": "beta",
"description": "A dummy project for testing with.",
"body": "This project is not-yet-approved, and versions are draft.",
"initial_versions": [{
"file_parts": [jar.filename()],
"version_number": "1.2.3",
"version_title": "start",
"status": "unlisted",
"dependencies": [],
"client_side": "required",
"server_side": "optional",
"game_versions": ["1.20.1"] ,
"release_channel": "release",
"loaders": ["fabric"],
"featured": true
}],
"status": "private",
"requested_status": "private",
"categories": [],
"license_id": "MIT"
}
);
// Basic json
let json_segment = MultipartSegment {
name: "data".to_string(),
filename: None,
content_type: Some("application/json".to_string()),
data: MultipartSegmentData::Text(serde_json::to_string(&json_data).unwrap()),
};
// Basic file
let file_segment = MultipartSegment {
name: jar.filename(),
filename: Some(jar.filename()),
content_type: Some("application/java-archive".to_string()),
data: MultipartSegmentData::Binary(jar.bytes()),
};
// Add a project.
let req = TestRequest::post()
.uri("/v3/project")
.append_header(("Authorization", USER_USER_PAT))
.set_multipart(vec![json_segment.clone(), file_segment.clone()])
.to_request();
let resp = api.call(req).await;
assert_eq!(resp.status(), 200);
get_project_beta(api).await
}
pub async fn add_organization_zeta(api: &ApiV3) -> Organization {
// Add an organzation.
let req = TestRequest::post()
.uri("/v3/organization")
.append_header(("Authorization", USER_USER_PAT))
.set_json(json!({
"title": "zeta",
"description": "A dummy organization for testing with."
}))
.to_request();
let resp = api.call(req).await;
assert_eq!(resp.status(), 200);
get_organization_zeta(api).await
}
pub async fn get_project_alpha(api: &ApiV3) -> (CommonProject, CommonVersion) {
// Get project
let req = TestRequest::get()
.uri("/v3/project/alpha")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = api.call(req).await;
let project: CommonProject = test::read_body_json(resp).await;
// Get project's versions
let req = TestRequest::get()
.uri("/v3/project/alpha/version")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = api.call(req).await;
let versions: Vec<CommonVersion> = test::read_body_json(resp).await;
let version = versions.into_iter().next().unwrap();
(project, version)
}
pub async fn get_project_beta(api: &ApiV3) -> (CommonProject, CommonVersion) {
// Get project
let req = TestRequest::get()
.uri("/v3/project/beta")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = api.call(req).await;
assert_status(&resp, StatusCode::OK);
let project: serde_json::Value = test::read_body_json(resp).await;
let project: CommonProject = serde_json::from_value(project).unwrap();
// Get project's versions
let req = TestRequest::get()
.uri("/v3/project/beta/version")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = api.call(req).await;
assert_status(&resp, StatusCode::OK);
let versions: Vec<CommonVersion> = test::read_body_json(resp).await;
let version = versions.into_iter().next().unwrap();
(project, version)
}
pub async fn get_organization_zeta(api: &ApiV3) -> Organization {
// Get organization
let req = TestRequest::get()
.uri("/v3/organization/zeta")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = api.call(req).await;
let organization: Organization = test::read_body_json(resp).await;
organization
}
pub async fn get_oauth_client_alpha(api: &ApiV3) -> OAuthClient {
let oauth_clients = api
.get_user_oauth_clients(USER_USER_ID, USER_USER_PAT)
.await;
oauth_clients.into_iter().next().unwrap()
}
impl TestFile {
pub fn filename(&self) -> String {
match self {
TestFile::DummyProjectAlpha => "dummy-project-alpha.jar",
TestFile::DummyProjectBeta => "dummy-project-beta.jar",
TestFile::BasicMod => "basic-mod.jar",
TestFile::BasicModDifferent => "basic-mod-different.jar",
TestFile::BasicModRandom { filename, .. } => filename,
TestFile::BasicModpackRandom { filename, .. } => filename,
}
.to_string()
}
pub fn bytes(&self) -> Vec<u8> {
match self {
TestFile::DummyProjectAlpha => {
include_bytes!("../../tests/files/dummy-project-alpha.jar").to_vec()
}
TestFile::DummyProjectBeta => {
include_bytes!("../../tests/files/dummy-project-beta.jar").to_vec()
}
TestFile::BasicMod => include_bytes!("../../tests/files/basic-mod.jar").to_vec(),
TestFile::BasicModDifferent => {
include_bytes!("../../tests/files/basic-mod-different.jar").to_vec()
}
TestFile::BasicModRandom { bytes, .. } => bytes.clone(),
TestFile::BasicModpackRandom { bytes, .. } => bytes.clone(),
}
}
pub fn project_type(&self) -> String {
match self {
TestFile::DummyProjectAlpha => "mod",
TestFile::DummyProjectBeta => "mod",
TestFile::BasicMod => "mod",
TestFile::BasicModDifferent => "mod",
TestFile::BasicModRandom { .. } => "mod",
TestFile::BasicModpackRandom { .. } => "modpack",
}
.to_string()
}
}
impl DummyImage {
pub fn filename(&self) -> String {
match self {
DummyImage::SmallIcon => "200x200.png",
}
.to_string()
}
pub fn extension(&self) -> String {
match self {
DummyImage::SmallIcon => "png",
}
.to_string()
}
pub fn bytes(&self) -> Vec<u8> {
match self {
DummyImage::SmallIcon => include_bytes!("../../tests/files/200x200.png").to_vec(),
}
}
}