Search test + v3 (#731)

* search patch for accurate loader/gv filtering

* backup

* basic search test

* finished test

* incomplete commit; backing up

* Working multipat reroute backup

* working rough draft v3

* most tests passing

* works

* search v2 conversion

* added some tags.rs v2 conversions

* Worked through warnings, unwraps, prints

* refactors

* new search test

* version files changes fixes

* redesign to revs

* removed old caches

* removed games

* fmt clippy

* merge conflicts

* fmt, prepare

* moved v2 routes over to v3

* fixes; tests passing

* project type changes

* moved files over

* fmt, clippy, prepare, etc

* loaders to loader_fields, added tests

* fmt, clippy, prepare

* fixed sorting bug

* reversed back- wrong order for consistency

* fmt; clippy; prepare

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
Wyatt Verchere
2023-11-11 16:40:10 -08:00
committed by GitHub
parent 97ccb7df94
commit ae1c5342f2
133 changed files with 18153 additions and 11320 deletions

View File

@@ -1,82 +0,0 @@
use actix_web::test::TestRequest;
use bytes::{Bytes, BytesMut};
// Multipart functionality (actix-test does not innately support multipart)
#[derive(Debug, Clone)]
pub struct MultipartSegment {
pub name: String,
pub filename: Option<String>,
pub content_type: Option<String>,
pub data: MultipartSegmentData,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum MultipartSegmentData {
Text(String),
Binary(Vec<u8>),
}
pub trait AppendsMultipart {
fn set_multipart(self, data: impl IntoIterator<Item = MultipartSegment>) -> Self;
}
impl AppendsMultipart for TestRequest {
fn set_multipart(self, data: impl IntoIterator<Item = MultipartSegment>) -> Self {
let (boundary, payload) = generate_multipart(data);
self.append_header((
"Content-Type",
format!("multipart/form-data; boundary={}", boundary),
))
.set_payload(payload)
}
}
fn generate_multipart(data: impl IntoIterator<Item = MultipartSegment>) -> (String, Bytes) {
let mut boundary = String::from("----WebKitFormBoundary");
boundary.push_str(&rand::random::<u64>().to_string());
boundary.push_str(&rand::random::<u64>().to_string());
boundary.push_str(&rand::random::<u64>().to_string());
let mut payload = BytesMut::new();
for segment in data {
payload.extend_from_slice(
format!(
"--{boundary}\r\nContent-Disposition: form-data; name=\"{name}\"",
boundary = boundary,
name = segment.name
)
.as_bytes(),
);
if let Some(filename) = &segment.filename {
payload.extend_from_slice(
format!("; filename=\"{filename}\"", filename = filename).as_bytes(),
);
}
if let Some(content_type) = &segment.content_type {
payload.extend_from_slice(
format!(
"\r\nContent-Type: {content_type}",
content_type = content_type
)
.as_bytes(),
);
}
payload.extend_from_slice(b"\r\n\r\n");
match &segment.data {
MultipartSegmentData::Text(text) => {
payload.extend_from_slice(text.as_bytes());
}
MultipartSegmentData::Binary(binary) => {
payload.extend_from_slice(binary);
}
}
payload.extend_from_slice(b"\r\n");
}
payload.extend_from_slice(format!("--{boundary}--\r\n", boundary = boundary).as_bytes());
(boundary, Bytes::from(payload))
}

View File

@@ -6,6 +6,7 @@ use std::rc::Rc;
pub mod organization;
pub mod project;
pub mod tags;
pub mod team;
pub mod version;
@@ -18,4 +19,15 @@ impl ApiV2 {
pub async fn call(&self, req: actix_http::Request) -> ServiceResponse {
self.test_app.call(req).await.unwrap()
}
pub async fn reset_search_index(&self) -> ServiceResponse {
let req = actix_web::test::TestRequest::post()
.uri("/v2/admin/_force_reindex")
.append_header((
"Modrinth-Admin",
dotenvy::var("LABRINTH_ADMIN_KEY").unwrap(),
))
.to_request();
self.call(req).await
}
}

View File

@@ -3,7 +3,7 @@ use actix_web::{
test::{self, TestRequest},
};
use bytes::Bytes;
use labrinth::models::{organizations::Organization, projects::Project};
use labrinth::models::{organizations::Organization, v2::projects::LegacyProject};
use serde_json::json;
use crate::common::request_data::ImageData;
@@ -58,7 +58,7 @@ impl ApiV2 {
&self,
id_or_title: &str,
pat: &str,
) -> Vec<Project> {
) -> Vec<LegacyProject> {
let resp = self.get_organization_projects(id_or_title, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await

View File

@@ -7,12 +7,15 @@ use actix_web::{
};
use bytes::Bytes;
use chrono::{DateTime, Utc};
use labrinth::models::projects::{Project, Version};
use labrinth::{
models::v2::projects::{LegacyProject, LegacyVersion},
search::SearchResults,
util::actix::AppendsMultipart,
};
use rust_decimal::Decimal;
use serde_json::json;
use crate::common::{
actix::AppendsMultipart,
asserts::assert_status,
database::MOD_USER_PAT,
request_data::{ImageData, ProjectCreationRequestData},
@@ -25,7 +28,7 @@ impl ApiV2 {
&self,
creation_data: ProjectCreationRequestData,
pat: &str,
) -> (Project, Vec<Version>) {
) -> (LegacyProject, Vec<LegacyVersion>) {
// Add a project.
let req = TestRequest::post()
.uri("/v2/project")
@@ -58,7 +61,7 @@ impl ApiV2 {
.append_header(("Authorization", pat))
.to_request();
let resp = self.call(req).await;
let versions: Vec<Version> = test::read_body_json(resp).await;
let versions: Vec<LegacyVersion> = test::read_body_json(resp).await;
(project, versions)
}
@@ -80,7 +83,7 @@ impl ApiV2 {
.to_request();
self.call(req).await
}
pub async fn get_project_deserialized(&self, id_or_slug: &str, pat: &str) -> Project {
pub async fn get_project_deserialized(&self, id_or_slug: &str, pat: &str) -> LegacyProject {
let resp = self.get_project(id_or_slug, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
@@ -98,36 +101,12 @@ impl ApiV2 {
&self,
user_id_or_username: &str,
pat: &str,
) -> Vec<Project> {
) -> Vec<LegacyProject> {
let resp = self.get_user_projects(user_id_or_username, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn get_version_from_hash(
&self,
hash: &str,
algorithm: &str,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::get()
.uri(&format!("/v2/version_file/{hash}?algorithm={algorithm}"))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}
pub async fn get_version_from_hash_deserialized(
&self,
hash: &str,
algorithm: &str,
pat: &str,
) -> Version {
let resp = self.get_version_from_hash(hash, algorithm, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn edit_project(
&self,
id_or_slug: &str,
@@ -195,6 +174,34 @@ impl ApiV2 {
}
}
pub async fn search_deserialized(
&self,
query: Option<&str>,
facets: Option<serde_json::Value>,
pat: &str,
) -> SearchResults {
let query_field = if let Some(query) = query {
format!("&query={}", urlencoding::encode(query))
} else {
"".to_string()
};
let facets_field = if let Some(facets) = facets {
format!("&facets={}", urlencoding::encode(&facets.to_string()))
} else {
"".to_string()
};
let req = test::TestRequest::get()
.uri(&format!("/v2/search?{}{}", query_field, facets_field))
.append_header(("Authorization", pat))
.to_request();
let resp = self.call(req).await;
let status = resp.status();
assert_eq!(status, 200);
test::read_body_json(resp).await
}
pub async fn get_analytics_revenue(
&self,
id_or_slugs: Vec<&str>,

View File

@@ -0,0 +1,69 @@
use actix_web::{
dev::ServiceResponse,
test::{self, TestRequest},
};
use labrinth::routes::v2::tags::{CategoryData, GameVersionQueryData, LoaderData};
use crate::common::database::ADMIN_USER_PAT;
use super::ApiV2;
impl ApiV2 {
// Tag gets do not include PAT, as they are public.
pub async fn get_side_types(&self) -> ServiceResponse {
let req = TestRequest::get()
.uri("/v2/tag/side_type")
.append_header(("Authorization", ADMIN_USER_PAT))
.to_request();
self.call(req).await
}
pub async fn get_side_types_deserialized(&self) -> Vec<String> {
let resp = self.get_side_types().await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn get_loaders(&self) -> ServiceResponse {
let req = TestRequest::get()
.uri("/v2/tag/loader")
.append_header(("Authorization", ADMIN_USER_PAT))
.to_request();
self.call(req).await
}
pub async fn get_loaders_deserialized(&self) -> Vec<LoaderData> {
let resp = self.get_loaders().await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn get_categories(&self) -> ServiceResponse {
let req = TestRequest::get()
.uri("/v2/tag/category")
.append_header(("Authorization", ADMIN_USER_PAT))
.to_request();
self.call(req).await
}
pub async fn get_categories_deserialized(&self) -> Vec<CategoryData> {
let resp = self.get_categories().await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn get_game_versions(&self) -> ServiceResponse {
let req = TestRequest::get()
.uri("/v2/tag/game_version")
.append_header(("Authorization", ADMIN_USER_PAT))
.to_request();
self.call(req).await
}
pub async fn get_game_versions_deserialized(&self) -> Vec<GameVersionQueryData> {
let resp = self.get_game_versions().await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
}

View File

@@ -1,9 +1,18 @@
use std::collections::HashMap;
use actix_http::{header::AUTHORIZATION, StatusCode};
use actix_web::{dev::ServiceResponse, test};
use labrinth::models::projects::Version;
use actix_web::{
dev::ServiceResponse,
test::{self, TestRequest},
};
use labrinth::{
models::{projects::VersionType, v2::projects::LegacyVersion},
routes::v2::version_file::FileUpdateData,
util::actix::AppendsMultipart,
};
use serde_json::json;
use crate::common::{self, actix::AppendsMultipart, asserts::assert_status};
use crate::common::{asserts::assert_status, request_data::VersionCreationRequestData};
use super::ApiV2;
@@ -13,12 +22,319 @@ pub fn url_encode_json_serialized_vec(elements: &[String]) -> String {
}
impl ApiV2 {
pub async fn add_public_version(
&self,
creation_data: VersionCreationRequestData,
pat: &str,
) -> LegacyVersion {
// Add a project.
let req = TestRequest::post()
.uri("/v2/version")
.append_header(("Authorization", pat))
.set_multipart(creation_data.segment_data)
.to_request();
let resp = self.call(req).await;
assert_status(&resp, StatusCode::OK);
let value: serde_json::Value = test::read_body_json(resp).await;
let version_id = value["id"].as_str().unwrap();
// // Approve as a moderator.
// let req = TestRequest::patch()
// .uri(&format!("/v2/project/{}", creation_data.slug))
// .append_header(("Authorization", MOD_USER_PAT))
// .set_json(json!(
// {
// "status": "approved"
// }
// ))
// .to_request();
// let resp = self.call(req).await;
// assert_status(resp, StatusCode::NO_CONTENT);
self.get_version_deserialized(version_id, pat).await
}
pub async fn get_version(&self, id: &str, pat: &str) -> ServiceResponse {
let req = TestRequest::get()
.uri(&format!("/v2/version/{id}"))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}
pub async fn get_version_deserialized(&self, id: &str, pat: &str) -> LegacyVersion {
let resp = self.get_version(id, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn edit_version(
&self,
version_id: &str,
patch: serde_json::Value,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::patch()
.uri(&format!("/v2/version/{version_id}"))
.append_header(("Authorization", pat))
.set_json(patch)
.to_request();
self.call(req).await
}
pub async fn get_version_from_hash(
&self,
hash: &str,
algorithm: &str,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::get()
.uri(&format!("/v2/version_file/{hash}?algorithm={algorithm}"))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}
pub async fn get_version_from_hash_deserialized(
&self,
hash: &str,
algorithm: &str,
pat: &str,
) -> LegacyVersion {
let resp = self.get_version_from_hash(hash, algorithm, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn get_versions_from_hashes(
&self,
hashes: &[&str],
algorithm: &str,
pat: &str,
) -> ServiceResponse {
let req = TestRequest::post()
.uri("/v2/version_files")
.append_header(("Authorization", pat))
.set_json(json!({
"hashes": hashes,
"algorithm": algorithm,
}))
.to_request();
self.call(req).await
}
pub async fn get_versions_from_hashes_deserialized(
&self,
hashes: &[&str],
algorithm: &str,
pat: &str,
) -> HashMap<String, LegacyVersion> {
let resp = self.get_versions_from_hashes(hashes, algorithm, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn get_update_from_hash(
&self,
hash: &str,
algorithm: &str,
loaders: Option<Vec<String>>,
game_versions: Option<Vec<String>>,
version_types: Option<Vec<String>>,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::post()
.uri(&format!(
"/v2/version_file/{hash}/update?algorithm={algorithm}"
))
.append_header(("Authorization", pat))
.set_json(json!({
"loaders": loaders,
"game_versions": game_versions,
"version_types": version_types,
}))
.to_request();
self.call(req).await
}
pub async fn get_update_from_hash_deserialized(
&self,
hash: &str,
algorithm: &str,
loaders: Option<Vec<String>>,
game_versions: Option<Vec<String>>,
version_types: Option<Vec<String>>,
pat: &str,
) -> LegacyVersion {
let resp = self
.get_update_from_hash(hash, algorithm, loaders, game_versions, version_types, pat)
.await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn update_files(
&self,
algorithm: &str,
hashes: Vec<String>,
loaders: Option<Vec<String>>,
game_versions: Option<Vec<String>>,
version_types: Option<Vec<String>>,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::post()
.uri("/v2/version_files/update")
.append_header(("Authorization", pat))
.set_json(json!({
"algorithm": algorithm,
"hashes": hashes,
"loaders": loaders,
"game_versions": game_versions,
"version_types": version_types,
}))
.to_request();
self.call(req).await
}
pub async fn update_files_deserialized(
&self,
algorithm: &str,
hashes: Vec<String>,
loaders: Option<Vec<String>>,
game_versions: Option<Vec<String>>,
version_types: Option<Vec<String>>,
pat: &str,
) -> HashMap<String, LegacyVersion> {
let resp = self
.update_files(
algorithm,
hashes,
loaders,
game_versions,
version_types,
pat,
)
.await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
pub async fn update_individual_files(
&self,
algorithm: &str,
hashes: Vec<FileUpdateData>,
pat: &str,
) -> ServiceResponse {
let req = test::TestRequest::post()
.uri("/v2/version_files/update_individual")
.append_header(("Authorization", pat))
.set_json(json!({
"algorithm": algorithm,
"hashes": hashes
}))
.to_request();
self.call(req).await
}
pub async fn update_individual_files_deserialized(
&self,
algorithm: &str,
hashes: Vec<FileUpdateData>,
pat: &str,
) -> HashMap<String, LegacyVersion> {
let resp = self.update_individual_files(algorithm, hashes, pat).await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
// TODO: Not all fields are tested currently in the V2 tests, only the v2-v3 relevant ones are
#[allow(clippy::too_many_arguments)]
pub async fn get_project_versions(
&self,
project_id_slug: &str,
game_versions: Option<Vec<String>>,
loaders: Option<Vec<String>>,
featured: Option<bool>,
version_type: Option<VersionType>,
limit: Option<usize>,
offset: Option<usize>,
pat: &str,
) -> ServiceResponse {
let mut query_string = String::new();
if let Some(game_versions) = game_versions {
query_string.push_str(&format!(
"&game_versions={}",
urlencoding::encode(&serde_json::to_string(&game_versions).unwrap())
));
}
if let Some(loaders) = loaders {
query_string.push_str(&format!(
"&loaders={}",
urlencoding::encode(&serde_json::to_string(&loaders).unwrap())
));
}
if let Some(featured) = featured {
query_string.push_str(&format!("&featured={}", featured));
}
if let Some(version_type) = version_type {
query_string.push_str(&format!("&version_type={}", version_type));
}
if let Some(limit) = limit {
let limit = limit.to_string();
query_string.push_str(&format!("&limit={}", limit));
}
if let Some(offset) = offset {
let offset = offset.to_string();
query_string.push_str(&format!("&offset={}", offset));
}
let req = test::TestRequest::get()
.uri(&format!(
"/v2/project/{project_id_slug}/version?{}",
query_string.trim_start_matches('&')
))
.append_header(("Authorization", pat))
.to_request();
self.call(req).await
}
#[allow(clippy::too_many_arguments)]
pub async fn get_project_versions_deserialized(
&self,
slug: &str,
game_versions: Option<Vec<String>>,
loaders: Option<Vec<String>>,
featured: Option<bool>,
version_type: Option<VersionType>,
limit: Option<usize>,
offset: Option<usize>,
pat: &str,
) -> Vec<LegacyVersion> {
let resp = self
.get_project_versions(
slug,
game_versions,
loaders,
featured,
version_type,
limit,
offset,
pat,
)
.await;
assert_eq!(resp.status(), 200);
test::read_body_json(resp).await
}
// TODO: remove redundancy in these functions
pub async fn create_default_version(
&self,
project_id: &str,
ordering: Option<i32>,
pat: &str,
) -> Version {
) -> LegacyVersion {
let json_data = json!(
{
"project_id": project_id,
@@ -33,19 +349,19 @@ impl ApiV2 {
"ordering": ordering,
}
);
let json_segment = common::actix::MultipartSegment {
let json_segment = labrinth::util::actix::MultipartSegment {
name: "data".to_string(),
filename: None,
content_type: Some("application/json".to_string()),
data: common::actix::MultipartSegmentData::Text(
data: labrinth::util::actix::MultipartSegmentData::Text(
serde_json::to_string(&json_data).unwrap(),
),
};
let file_segment = common::actix::MultipartSegment {
let file_segment = labrinth::util::actix::MultipartSegment {
name: "basic-mod-different.jar".to_string(),
filename: Some("basic-mod.jar".to_string()),
content_type: Some("application/java-archive".to_string()),
data: common::actix::MultipartSegmentData::Binary(
data: labrinth::util::actix::MultipartSegmentData::Binary(
include_bytes!("../../../tests/files/basic-mod-different.jar").to_vec(),
),
};
@@ -60,7 +376,7 @@ impl ApiV2 {
test::read_body_json(resp).await
}
pub async fn get_versions(&self, version_ids: Vec<String>, pat: &str) -> Vec<Version> {
pub async fn get_versions(&self, version_ids: Vec<String>, pat: &str) -> Vec<LegacyVersion> {
let ids = url_encode_json_serialized_vec(&version_ids);
let request = test::TestRequest::get()
.uri(&format!("/v2/versions?ids={}", ids))

View File

@@ -114,7 +114,6 @@ pub fn generate_authorize_uri(
optional_query_param("scope", scope),
optional_query_param("state", state),
)
.to_string()
}
pub async fn get_authorize_accept_flow_id(response: ServiceResponse) -> String {

View File

@@ -2,15 +2,13 @@
use crate::common::get_json_val_str;
use itertools::Itertools;
use labrinth::models::v2::projects::LegacyVersion;
pub fn assert_status(response: &actix_web::dev::ServiceResponse, status: actix_http::StatusCode) {
assert_eq!(response.status(), status, "{:#?}", response.response());
}
pub fn assert_version_ids(
versions: &[labrinth::models::projects::Version],
expected_ids: Vec<String>,
) {
pub fn assert_version_ids(versions: &[LegacyVersion], expected_ids: Vec<String>) {
let version_ids = versions
.iter()
.map(|v| get_json_val_str(v.id))

View File

@@ -1,25 +1,24 @@
#![allow(dead_code)]
use std::io::{Cursor, Write};
use actix_http::StatusCode;
use actix_web::test::{self, TestRequest};
use labrinth::{
models::projects::Project,
models::{
oauth_clients::OAuthClient, organizations::Organization, pats::Scopes, projects::Version,
},
use labrinth::models::{
oauth_clients::OAuthClient,
organizations::Organization,
pats::Scopes,
v2::projects::{LegacyProject, LegacyVersion},
};
use serde_json::json;
use sqlx::Executor;
use zip::{write::FileOptions, CompressionMethod, ZipWriter};
use crate::common::{actix::AppendsMultipart, database::USER_USER_PAT};
use crate::common::database::USER_USER_PAT;
use labrinth::util::actix::{AppendsMultipart, MultipartSegment, MultipartSegmentData};
use super::{
actix::{MultipartSegment, MultipartSegmentData},
asserts::assert_status,
database::USER_USER_ID,
environment::TestEnvironment,
get_json_val_str,
request_data::get_public_project_creation_data,
};
use super::{environment::TestEnvironment, request_data::get_public_project_creation_data};
use super::{asserts::assert_status, database::USER_USER_ID, get_json_val_str};
pub const DUMMY_DATA_UPDATE: i64 = 3;
@@ -37,13 +36,107 @@ pub const DUMMY_CATEGORIES: &[&str] = &[
pub const DUMMY_OAUTH_CLIENT_ALPHA_SECRET: &str = "abcdefghijklmnopqrstuvwxyz";
#[allow(dead_code)]
pub enum DummyJarFile {
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
@@ -77,10 +170,10 @@ pub struct DummyData {
impl DummyData {
pub fn new(
project_alpha: Project,
project_alpha_version: Version,
project_beta: Project,
project_beta_version: Version,
project_alpha: LegacyProject,
project_alpha_version: LegacyVersion,
project_beta: LegacyProject,
project_beta_version: LegacyVersion,
organization_zeta: Organization,
oauth_client_alpha: OAuthClient,
) -> Self {
@@ -210,21 +303,21 @@ pub async fn get_dummy_data(test_env: &TestEnvironment) -> DummyData {
)
}
pub async fn add_project_alpha(test_env: &TestEnvironment) -> (Project, Version) {
pub async fn add_project_alpha(test_env: &TestEnvironment) -> (LegacyProject, LegacyVersion) {
let (project, versions) = test_env
.v2
.add_public_project(
get_public_project_creation_data("alpha", Some(DummyJarFile::DummyProjectAlpha)),
get_public_project_creation_data("alpha", Some(TestFile::DummyProjectAlpha)),
USER_USER_PAT,
)
.await;
(project, versions.into_iter().next().unwrap())
}
pub async fn add_project_beta(test_env: &TestEnvironment) -> (Project, Version) {
pub async fn add_project_beta(test_env: &TestEnvironment) -> (LegacyProject, LegacyVersion) {
// Adds dummy data to the database with sqlx (projects, versions, threads)
// Generate test project data.
let jar = DummyJarFile::DummyProjectBeta;
let jar = TestFile::DummyProjectBeta;
let json_data = json!(
{
"title": "Test Project Beta",
@@ -298,14 +391,14 @@ pub async fn add_organization_zeta(test_env: &TestEnvironment) -> Organization {
get_organization_zeta(test_env).await
}
pub async fn get_project_alpha(test_env: &TestEnvironment) -> (Project, Version) {
pub async fn get_project_alpha(test_env: &TestEnvironment) -> (LegacyProject, LegacyVersion) {
// Get project
let req = TestRequest::get()
.uri("/v2/project/alpha")
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = test_env.call(req).await;
let project: Project = test::read_body_json(resp).await;
let project: LegacyProject = test::read_body_json(resp).await;
// Get project's versions
let req = TestRequest::get()
@@ -313,13 +406,13 @@ pub async fn get_project_alpha(test_env: &TestEnvironment) -> (Project, Version)
.append_header(("Authorization", USER_USER_PAT))
.to_request();
let resp = test_env.call(req).await;
let versions: Vec<Version> = test::read_body_json(resp).await;
let versions: Vec<LegacyVersion> = test::read_body_json(resp).await;
let version = versions.into_iter().next().unwrap();
(project, version)
}
pub async fn get_project_beta(test_env: &TestEnvironment) -> (Project, Version) {
pub async fn get_project_beta(test_env: &TestEnvironment) -> (LegacyProject, LegacyVersion) {
// Get project
let req = TestRequest::get()
.uri("/v2/project/beta")
@@ -327,7 +420,8 @@ pub async fn get_project_beta(test_env: &TestEnvironment) -> (Project, Version)
.to_request();
let resp = test_env.call(req).await;
assert_status(&resp, StatusCode::OK);
let project: Project = test::read_body_json(resp).await;
let project: serde_json::Value = test::read_body_json(resp).await;
let project: LegacyProject = serde_json::from_value(project).unwrap();
// Get project's versions
let req = TestRequest::get()
@@ -336,7 +430,7 @@ pub async fn get_project_beta(test_env: &TestEnvironment) -> (Project, Version)
.to_request();
let resp = test_env.call(req).await;
assert_status(&resp, StatusCode::OK);
let versions: Vec<Version> = test::read_body_json(resp).await;
let versions: Vec<LegacyVersion> = test::read_body_json(resp).await;
let version = versions.into_iter().next().unwrap();
(project, version)
@@ -362,31 +456,48 @@ pub async fn get_oauth_client_alpha(test_env: &TestEnvironment) -> OAuthClient {
oauth_clients.into_iter().next().unwrap()
}
impl DummyJarFile {
impl TestFile {
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",
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 {
DummyJarFile::DummyProjectAlpha => {
TestFile::DummyProjectAlpha => {
include_bytes!("../../tests/files/dummy-project-alpha.jar").to_vec()
}
DummyJarFile::DummyProjectBeta => {
TestFile::DummyProjectBeta => {
include_bytes!("../../tests/files/dummy-project-beta.jar").to_vec()
}
DummyJarFile::BasicMod => include_bytes!("../../tests/files/basic-mod.jar").to_vec(),
DummyJarFile::BasicModDifferent => {
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 {

View File

@@ -2,9 +2,6 @@ use labrinth::{check_env_vars, clickhouse};
use labrinth::{file_hosting, queue, LabrinthConfig};
use std::sync::Arc;
use self::database::TemporaryDatabase;
pub mod actix;
pub mod api_v2;
pub mod api_v3;
pub mod asserts;
@@ -18,7 +15,7 @@ pub mod scopes;
// Testing equivalent to 'setup' function, producing a LabrinthConfig
// If making a test, you should probably use environment::TestEnvironment::build() (which calls this)
pub async fn setup(db: &TemporaryDatabase) -> LabrinthConfig {
pub async fn setup(db: &database::TemporaryDatabase) -> LabrinthConfig {
println!("Setting up labrinth config");
dotenvy::dotenv().ok();
@@ -40,7 +37,7 @@ pub async fn setup(db: &TemporaryDatabase) -> LabrinthConfig {
redis_pool.clone(),
&mut clickhouse,
file_host.clone(),
maxmind_reader.clone(),
maxmind_reader,
)
}

View File

@@ -175,6 +175,7 @@ impl<'a> PermissionsTest<'a> {
let resp = test_env.call(request).await;
if !self.allowed_failure_codes.contains(&resp.status().as_u16()) {
println!("Body: {:?}", resp.response().body());
return Err(format!(
"Failure permissions test failed. Expected failure codes {} got {}",
self.allowed_failure_codes
@@ -206,6 +207,7 @@ impl<'a> PermissionsTest<'a> {
let resp = test_env.call(request).await;
if !resp.status().is_success() {
println!("Body: {:?}", resp.response().body());
return Err(format!(
"Success permissions test failed. Expected success, got {}",
resp.status().as_u16()
@@ -673,8 +675,7 @@ impl<'a> PermissionsTest<'a> {
Ok(())
};
tokio::try_join!(test_1, test_2, test_3, test_4, test_5, test_6, test_7,)
.map_err(|e| e.to_string())?;
tokio::try_join!(test_1, test_2, test_3, test_4, test_5, test_6, test_7,).map_err(|e| e)?;
Ok(())
}
@@ -837,7 +838,7 @@ impl<'a> PermissionsTest<'a> {
Ok(())
};
tokio::try_join!(test_1, test_2, test_3,).map_err(|e| e.to_string())?;
tokio::try_join!(test_1, test_2, test_3,).map_err(|e| e)?;
Ok(())
}

View File

@@ -1,15 +1,21 @@
#![allow(dead_code)]
use serde_json::json;
use super::{
actix::MultipartSegment,
dummy_data::{DummyImage, DummyJarFile},
use super::dummy_data::{DummyImage, TestFile};
use labrinth::{
models::projects::ProjectId,
util::actix::{MultipartSegment, MultipartSegmentData},
};
use crate::common::actix::MultipartSegmentData;
pub struct ProjectCreationRequestData {
pub slug: String,
pub jar: Option<DummyJarFile>,
pub jar: Option<TestFile>,
pub segment_data: Vec<MultipartSegment>,
}
pub struct VersionCreationRequestData {
pub version: String,
pub jar: Option<TestFile>,
pub segment_data: Vec<MultipartSegment>,
}
@@ -21,29 +27,64 @@ pub struct ImageData {
pub fn get_public_project_creation_data(
slug: &str,
version_jar: Option<DummyJarFile>,
version_jar: Option<TestFile>,
) -> ProjectCreationRequestData {
let initial_versions = if let Some(ref jar) = version_jar {
json!([{
"file_parts": [jar.filename()],
"version_number": "1.2.3",
"version_title": "start",
"dependencies": [],
"game_versions": ["1.20.1"] ,
"release_channel": "release",
"loaders": ["fabric"],
"featured": true
}])
let json_data = get_public_project_creation_data_json(slug, version_jar.as_ref());
let multipart_data = get_public_creation_data_multipart(&json_data, version_jar.as_ref());
ProjectCreationRequestData {
slug: slug.to_string(),
jar: version_jar,
segment_data: multipart_data,
}
}
pub fn get_public_version_creation_data(
project_id: ProjectId,
version_number: &str,
version_jar: TestFile,
) -> VersionCreationRequestData {
let mut json_data = get_public_version_creation_data_json(version_number, &version_jar);
json_data["project_id"] = json!(project_id);
let multipart_data = get_public_creation_data_multipart(&json_data, Some(&version_jar));
VersionCreationRequestData {
version: version_number.to_string(),
jar: Some(version_jar),
segment_data: multipart_data,
}
}
pub fn get_public_version_creation_data_json(
version_number: &str,
version_jar: &TestFile,
) -> serde_json::Value {
json!({
"file_parts": [version_jar.filename()],
"version_number": version_number,
"version_title": "start",
"dependencies": [],
"game_versions": ["1.20.1"] ,
"release_channel": "release",
"loaders": ["fabric"],
"featured": true
})
}
pub fn get_public_project_creation_data_json(
slug: &str,
version_jar: Option<&TestFile>,
) -> serde_json::Value {
let initial_versions = if let Some(jar) = version_jar {
json!([get_public_version_creation_data_json("1.2.3", jar)])
} else {
json!([])
};
let is_draft = version_jar.is_none();
let json_data = json!(
json!(
{
"title": format!("Test Project {slug}"),
"slug": slug,
"project_type": version_jar.as_ref().map(|f| f.project_type()).unwrap_or("mod".to_string()),
"description": "A dummy project for testing with.",
"body": "This project is approved, and versions are listed.",
"client_side": "required",
@@ -51,19 +92,24 @@ pub fn get_public_project_creation_data(
"initial_versions": initial_versions,
"is_draft": is_draft,
"categories": [],
"license_id": "MIT"
"license_id": "MIT",
}
);
)
}
pub fn get_public_creation_data_multipart(
json_data: &serde_json::Value,
version_jar: Option<&TestFile>,
) -> Vec<MultipartSegment> {
// 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()),
data: MultipartSegmentData::Text(serde_json::to_string(json_data).unwrap()),
};
let segment_data = if let Some(ref jar) = version_jar {
if let Some(jar) = version_jar {
// Basic file
let file_segment = MultipartSegment {
name: jar.filename(),
@@ -72,15 +118,9 @@ pub fn get_public_project_creation_data(
data: MultipartSegmentData::Binary(jar.bytes()),
};
vec![json_segment.clone(), file_segment]
vec![json_segment, file_segment]
} else {
vec![json_segment.clone()]
};
ProjectCreationRequestData {
slug: slug.to_string(),
jar: version_jar,
segment_data,
vec![json_segment]
}
}