* 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
531 lines
16 KiB
Rust
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(),
|
|
}
|
|
}
|
|
}
|