diff --git a/.sqlx/query-a62767e812783e8836a11b22878a4248123f3fe212a876e192f549acd6edcb39.json b/.sqlx/query-1e735a003ce305624ce8bbf181c99e41fbe8fcd836e926daf3e73aa3bb5552a6.json similarity index 56% rename from .sqlx/query-a62767e812783e8836a11b22878a4248123f3fe212a876e192f549acd6edcb39.json rename to .sqlx/query-1e735a003ce305624ce8bbf181c99e41fbe8fcd836e926daf3e73aa3bb5552a6.json index ad89e886a..bdf80942b 100644 --- a/.sqlx/query-a62767e812783e8836a11b22878a4248123f3fe212a876e192f549acd6edcb39.json +++ b/.sqlx/query-1e735a003ce305624ce8bbf181c99e41fbe8fcd836e926daf3e73aa3bb5552a6.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,\n v.changelog changelog, v.date_published date_published, v.downloads downloads,\n v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status,\n JSONB_AGG(DISTINCT jsonb_build_object('version', gv.version, 'created', gv.created)) filter (where gv.version is not null) game_versions,\n ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,\n JSONB_AGG(DISTINCT jsonb_build_object('id', f.id, 'url', f.url, 'filename', f.filename, 'primary', f.is_primary, 'size', f.size, 'file_type', f.file_type)) filter (where f.id is not null) files,\n JSONB_AGG(DISTINCT jsonb_build_object('algorithm', h.algorithm, 'hash', encode(h.hash, 'escape'), 'file_id', h.file_id)) filter (where h.hash is not null) hashes,\n JSONB_AGG(DISTINCT jsonb_build_object('project_id', d.mod_dependency_id, 'version_id', d.dependency_id, 'dependency_type', d.dependency_type,'file_name', dependency_file_name)) filter (where d.dependency_type is not null) dependencies\n FROM versions v\n LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id\n LEFT OUTER JOIN game_versions gv on gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv on v.id = lv.version_id\n LEFT OUTER JOIN loaders l on lv.loader_id = l.id\n LEFT OUTER JOIN files f on v.id = f.version_id\n LEFT OUTER JOIN hashes h on f.id = h.file_id\n LEFT OUTER JOIN dependencies d on v.id = d.dependent_id\n WHERE v.id = ANY($1)\n GROUP BY v.id\n ORDER BY v.date_published ASC;\n ", + "query": "\n SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number,\n v.changelog changelog, v.date_published date_published, v.downloads downloads,\n v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status, v.ordering ordering,\n JSONB_AGG(DISTINCT jsonb_build_object('version', gv.version, 'created', gv.created)) filter (where gv.version is not null) game_versions,\n ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders,\n JSONB_AGG(DISTINCT jsonb_build_object('id', f.id, 'url', f.url, 'filename', f.filename, 'primary', f.is_primary, 'size', f.size, 'file_type', f.file_type)) filter (where f.id is not null) files,\n JSONB_AGG(DISTINCT jsonb_build_object('algorithm', h.algorithm, 'hash', encode(h.hash, 'escape'), 'file_id', h.file_id)) filter (where h.hash is not null) hashes,\n JSONB_AGG(DISTINCT jsonb_build_object('project_id', d.mod_dependency_id, 'version_id', d.dependency_id, 'dependency_type', d.dependency_type,'file_name', dependency_file_name)) filter (where d.dependency_type is not null) dependencies\n FROM versions v\n LEFT OUTER JOIN game_versions_versions gvv on v.id = gvv.joining_version_id\n LEFT OUTER JOIN game_versions gv on gvv.game_version_id = gv.id\n LEFT OUTER JOIN loaders_versions lv on v.id = lv.version_id\n LEFT OUTER JOIN loaders l on lv.loader_id = l.id\n LEFT OUTER JOIN files f on v.id = f.version_id\n LEFT OUTER JOIN hashes h on f.id = h.file_id\n LEFT OUTER JOIN dependencies d on v.id = d.dependent_id\n WHERE v.id = ANY($1)\n GROUP BY v.id\n ORDER BY v.ordering ASC NULLS LAST, v.date_published ASC;\n ", "describe": { "columns": [ { @@ -65,26 +65,31 @@ }, { "ordinal": 12, + "name": "ordering", + "type_info": "Int4" + }, + { + "ordinal": 13, "name": "game_versions", "type_info": "Jsonb" }, { - "ordinal": 13, + "ordinal": 14, "name": "loaders", "type_info": "VarcharArray" }, { - "ordinal": 14, + "ordinal": 15, "name": "files", "type_info": "Jsonb" }, { - "ordinal": 15, + "ordinal": 16, "name": "hashes", "type_info": "Jsonb" }, { - "ordinal": 16, + "ordinal": 17, "name": "dependencies", "type_info": "Jsonb" } @@ -107,6 +112,7 @@ false, false, true, + true, null, null, null, @@ -114,5 +120,5 @@ null ] }, - "hash": "a62767e812783e8836a11b22878a4248123f3fe212a876e192f549acd6edcb39" + "hash": "1e735a003ce305624ce8bbf181c99e41fbe8fcd836e926daf3e73aa3bb5552a6" } diff --git a/.sqlx/query-54c6b31858b7bf383f9b7118583592d694ab2d80ac0f132c5b9bc42603f336c6.json b/.sqlx/query-54c6b31858b7bf383f9b7118583592d694ab2d80ac0f132c5b9bc42603f336c6.json new file mode 100644 index 000000000..d29360d16 --- /dev/null +++ b/.sqlx/query-54c6b31858b7bf383f9b7118583592d694ab2d80ac0f132c5b9bc42603f336c6.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE versions\n SET ordering = $1\n WHERE (id = $2)\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "54c6b31858b7bf383f9b7118583592d694ab2d80ac0f132c5b9bc42603f336c6" +} diff --git a/.sqlx/query-df871bd959ba97f105ac575f34d8d2a39cbc44a07e0339750a0e477e6fd582ed.json b/.sqlx/query-a4745a3dc87c3a858819b208b0c3a010dc297425883113565d934b8a834014ce.json similarity index 62% rename from .sqlx/query-df871bd959ba97f105ac575f34d8d2a39cbc44a07e0339750a0e477e6fd582ed.json rename to .sqlx/query-a4745a3dc87c3a858819b208b0c3a010dc297425883113565d934b8a834014ce.json index 7768dbbc3..10cba0801 100644 --- a/.sqlx/query-df871bd959ba97f105ac575f34d8d2a39cbc44a07e0339750a0e477e6fd582ed.json +++ b/.sqlx/query-a4745a3dc87c3a858819b208b0c3a010dc297425883113565d934b8a834014ce.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO versions (\n id, mod_id, author_id, name, version_number,\n changelog, date_published, downloads,\n version_type, featured, status\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7, $8,\n $9, $10, $11\n )\n ", + "query": "\n INSERT INTO versions (\n id, mod_id, author_id, name, version_number,\n changelog, date_published, downloads,\n version_type, featured, status, ordering\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7, $8,\n $9, $10, $11, $12\n )\n ", "describe": { "columns": [], "parameters": { @@ -15,10 +15,11 @@ "Int4", "Varchar", "Bool", - "Varchar" + "Varchar", + "Int4" ] }, "nullable": [] }, - "hash": "df871bd959ba97f105ac575f34d8d2a39cbc44a07e0339750a0e477e6fd582ed" + "hash": "a4745a3dc87c3a858819b208b0c3a010dc297425883113565d934b8a834014ce" } diff --git a/.sqlx/query-f3d7eb1b62f0b978787dba1132308d070d28911d6ddc380cedfa16e7baa3243a.json b/.sqlx/query-defc616ab6e602d87695371761563a023a96a860270a2f2afcdd48087e441dad.json similarity index 80% rename from .sqlx/query-f3d7eb1b62f0b978787dba1132308d070d28911d6ddc380cedfa16e7baa3243a.json rename to .sqlx/query-defc616ab6e602d87695371761563a023a96a860270a2f2afcdd48087e441dad.json index 4794dd9ab..151a7fa38 100644 --- a/.sqlx/query-f3d7eb1b62f0b978787dba1132308d070d28911d6ddc380cedfa16e7baa3243a.json +++ b/.sqlx/query-defc616ab6e602d87695371761563a023a96a860270a2f2afcdd48087e441dad.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, version_number, version_type\n FROM versions\n WHERE mod_id = $1 AND status = ANY($2)\n ORDER BY date_published ASC\n ", + "query": "\n SELECT id, version_number, version_type\n FROM versions\n WHERE mod_id = $1 AND status = ANY($2)\n ORDER BY ordering ASC NULLS LAST, date_published ASC\n ", "describe": { "columns": [ { @@ -31,5 +31,5 @@ false ] }, - "hash": "f3d7eb1b62f0b978787dba1132308d070d28911d6ddc380cedfa16e7baa3243a" + "hash": "defc616ab6e602d87695371761563a023a96a860270a2f2afcdd48087e441dad" } diff --git a/migrations/20231027195838_version_ordering.sql b/migrations/20231027195838_version_ordering.sql new file mode 100644 index 000000000..873f2c1bb --- /dev/null +++ b/migrations/20231027195838_version_ordering.sql @@ -0,0 +1 @@ +ALTER TABLE versions ADD COLUMN ordering int NULL; \ No newline at end of file diff --git a/src/database/models/version_item.rs b/src/database/models/version_item.rs index 7814698fc..565e3aae4 100644 --- a/src/database/models/version_item.rs +++ b/src/database/models/version_item.rs @@ -28,6 +28,7 @@ pub struct VersionBuilder { pub featured: bool, pub status: VersionStatus, pub requested_status: Option, + pub ordering: Option, } #[derive(Clone)] @@ -214,6 +215,7 @@ impl VersionBuilder { version_type: self.version_type, status: self.status, requested_status: self.requested_status, + ordering: self.ordering, }; version.insert(transaction).await?; @@ -317,7 +319,7 @@ impl VersionVersion { } } -#[derive(Clone, Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct Version { pub id: VersionId, pub project_id: ProjectId, @@ -332,6 +334,7 @@ pub struct Version { pub featured: bool, pub status: VersionStatus, pub requested_status: Option, + pub ordering: Option, } impl Version { @@ -344,12 +347,12 @@ impl Version { INSERT INTO versions ( id, mod_id, author_id, name, version_number, changelog, date_published, downloads, - version_type, featured, status + version_type, featured, status, ordering ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, - $9, $10, $11 + $9, $10, $11, $12 ) ", self.id as VersionId, @@ -362,7 +365,8 @@ impl Version { self.downloads, &self.version_type, self.featured, - self.status.as_str() + self.status.as_str(), + self.ordering ) .execute(&mut **transaction) .await?; @@ -554,7 +558,7 @@ impl Version { " SELECT v.id id, v.mod_id mod_id, v.author_id author_id, v.name version_name, v.version_number version_number, v.changelog changelog, v.date_published date_published, v.downloads downloads, - v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status, + v.version_type version_type, v.featured featured, v.status status, v.requested_status requested_status, v.ordering ordering, JSONB_AGG(DISTINCT jsonb_build_object('version', gv.version, 'created', gv.created)) filter (where gv.version is not null) game_versions, ARRAY_AGG(DISTINCT l.loader) filter (where l.loader is not null) loaders, JSONB_AGG(DISTINCT jsonb_build_object('id', f.id, 'url', f.url, 'filename', f.filename, 'primary', f.is_primary, 'size', f.size, 'file_type', f.file_type)) filter (where f.id is not null) files, @@ -570,7 +574,7 @@ impl Version { LEFT OUTER JOIN dependencies d on v.id = d.dependent_id WHERE v.id = ANY($1) GROUP BY v.id - ORDER BY v.date_published ASC; + ORDER BY v.ordering ASC NULLS LAST, v.date_published ASC; ", &version_ids_parsed ) @@ -593,6 +597,7 @@ impl Version { status: VersionStatus::from_string(&v.status), requested_status: v.requested_status .map(|x| VersionStatus::from_string(&x)), + ordering: v.ordering, }, files: { #[derive(Deserialize)] @@ -851,7 +856,7 @@ impl Version { } } -#[derive(Clone, Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct QueryVersion { pub inner: Version, @@ -861,7 +866,7 @@ pub struct QueryVersion { pub dependencies: Vec, } -#[derive(Clone, Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct QueryDependency { pub project_id: Option, pub version_id: Option, @@ -869,7 +874,7 @@ pub struct QueryDependency { pub dependency_type: String, } -#[derive(Clone, Deserialize, Serialize)] +#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct QueryFile { pub id: FileId, pub url: String, @@ -892,3 +897,84 @@ pub struct SingleFile { pub size: u32, pub file_type: Option, } + +impl std::cmp::Ord for QueryVersion { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.inner.cmp(&other.inner) + } +} + +impl std::cmp::PartialOrd for QueryVersion { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl std::cmp::Ord for Version { + fn cmp(&self, other: &Self) -> Ordering { + let ordering_order = match (self.ordering, other.ordering) { + (None, None) => Ordering::Equal, + (None, Some(_)) => Ordering::Greater, + (Some(_), None) => Ordering::Less, + (Some(a), Some(b)) => a.cmp(&b), + }; + + match ordering_order { + Ordering::Equal => self.date_published.cmp(&other.date_published), + ordering => ordering, + } + } +} + +impl std::cmp::PartialOrd for Version { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[cfg(test)] +mod tests { + use chrono::Months; + + use super::*; + + #[test] + fn test_version_sorting() { + let versions = vec![ + get_version(4, None, months_ago(6)), + get_version(3, None, months_ago(7)), + get_version(2, Some(1), months_ago(6)), + get_version(1, Some(0), months_ago(4)), + get_version(0, Some(0), months_ago(5)), + ]; + + let sorted = versions.iter().cloned().sorted().collect_vec(); + + let expected_sorted_ids = vec![0, 1, 2, 3, 4]; + let actual_sorted_ids = sorted.iter().map(|v| v.id.0).collect_vec(); + assert_eq!(expected_sorted_ids, actual_sorted_ids); + } + + fn months_ago(months: u32) -> DateTime { + Utc::now().checked_sub_months(Months::new(months)).unwrap() + } + + fn get_version(id: i64, ordering: Option, date_published: DateTime) -> Version { + Version { + id: VersionId(id), + ordering, + date_published, + project_id: ProjectId(0), + author_id: UserId(0), + name: Default::default(), + version_number: Default::default(), + changelog: Default::default(), + changelog_url: Default::default(), + downloads: Default::default(), + version_type: Default::default(), + featured: Default::default(), + status: VersionStatus::Listed, + requested_status: Default::default(), + } + } +} diff --git a/src/models/projects.rs b/src/models/projects.rs index 63f48f11b..51f1f675c 100644 --- a/src/models/projects.rs +++ b/src/models/projects.rs @@ -491,6 +491,8 @@ pub struct Version { pub game_versions: Vec, /// The loaders that this version works on pub loaders: Vec, + /// Ordering override, lower is returned first + pub ordering: Option, } impl From for Version { @@ -515,6 +517,7 @@ impl From for Version { "alpha" => VersionType::Alpha, _ => VersionType::Release, }, + ordering: v.ordering, status: v.status, requested_status: v.requested_status, @@ -729,7 +732,7 @@ impl DependencyType { } } -#[derive(Serialize, Deserialize, Copy, Clone, Debug)] +#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub enum FileType { RequiredResourcePack, diff --git a/src/routes/maven.rs b/src/routes/maven.rs index d3e7a3c5d..f719073a8 100644 --- a/src/routes/maven.rs +++ b/src/routes/maven.rs @@ -100,7 +100,7 @@ pub async fn maven_metadata( SELECT id, version_number, version_type FROM versions WHERE mod_id = $1 AND status = ANY($2) - ORDER BY date_published ASC + ORDER BY ordering ASC NULLS LAST, date_published ASC ", project.inner.id as database::models::ids::ProjectId, &*crate::models::projects::VersionStatus::iterator() diff --git a/src/routes/v2/project_creation.rs b/src/routes/v2/project_creation.rs index 21c49ed53..dadfc0965 100644 --- a/src/routes/v2/project_creation.rs +++ b/src/routes/v2/project_creation.rs @@ -964,6 +964,7 @@ async fn create_initial_version( status: VersionStatus::Listed, version_type: version_data.release_channel.to_string(), requested_status: None, + ordering: version_data.ordering, }; Ok(version) diff --git a/src/routes/v2/version_creation.rs b/src/routes/v2/version_creation.rs index 29e88ba95..e94284cfa 100644 --- a/src/routes/v2/version_creation.rs +++ b/src/routes/v2/version_creation.rs @@ -76,6 +76,9 @@ pub struct InitialVersionData { #[validate(length(max = 10))] #[serde(default)] pub uploaded_images: Vec, + + // The ordering relative to other versions + pub ordering: Option, } #[derive(Serialize, Deserialize, Clone)] @@ -316,6 +319,7 @@ async fn version_create_inner( featured: version_create_data.featured, status: version_create_data.status, requested_status: None, + ordering: version_create_data.ordering, }); return Ok(()); @@ -427,6 +431,7 @@ async fn version_create_inner( version_type: version_data.release_channel, status: builder.status, requested_status: builder.requested_status, + ordering: builder.ordering, files: builder .files .iter() diff --git a/src/routes/v2/version_file.rs b/src/routes/v2/version_file.rs index 171788b19..5d98dc4a1 100644 --- a/src/routes/v2/version_file.rs +++ b/src/routes/v2/version_file.rs @@ -323,7 +323,7 @@ pub async fn get_update_from_hash( bool }) - .sorted_by(|a, b| a.inner.date_published.cmp(&b.inner.date_published)) + .sorted() .collect::>(); if let Some(first) = versions.pop() { @@ -522,7 +522,7 @@ pub async fn update_files( bool }) - .sorted_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published)) + .sorted() .next(); if let Some(version) = version { @@ -629,7 +629,7 @@ pub async fn update_individual_files( bool }) - .sorted_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published)) + .sorted() .next(); if let Some(version) = version { diff --git a/src/routes/v2/versions.rs b/src/routes/v2/versions.rs index 218706e9b..44517a840 100644 --- a/src/routes/v2/versions.rs +++ b/src/routes/v2/versions.rs @@ -115,7 +115,7 @@ pub async fn version_list( .cloned() .collect::>(); - versions.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published)); + versions.sort(); // Attempt to populate versions with "auto featured" versions if response.is_empty() && !versions.is_empty() && filters.featured.unwrap_or(false) { @@ -155,7 +155,7 @@ pub async fn version_list( } } - response.sort_by(|a, b| b.inner.date_published.cmp(&a.inner.date_published)); + response.sort(); response.dedup_by(|a, b| a.inner.id == b.inner.id); let response = filter_authorized_versions(response, &user_option, &pool).await?; @@ -306,6 +306,7 @@ pub struct EditVersion { pub downloads: Option, pub status: Option, pub file_types: Option>, + pub ordering: Option>, //TODO: How do you actually pass this in json? } #[derive(Serialize, Deserialize)] @@ -684,6 +685,20 @@ pub async fn version_edit( } } + if let Some(ordering) = &new_version.ordering { + sqlx::query!( + " + UPDATE versions + SET ordering = $1 + WHERE (id = $2) + ", + ordering.to_owned() as Option, + id as database::models::ids::VersionId, + ) + .execute(&mut *transaction) + .await?; + } + // delete any images no longer in the changelog let checkable_strings: Vec<&str> = vec![&new_version.changelog] .into_iter() diff --git a/tests/common/api_v2/mod.rs b/tests/common/api_v2/mod.rs index 2ecc144e2..0f01b771b 100644 --- a/tests/common/api_v2/mod.rs +++ b/tests/common/api_v2/mod.rs @@ -7,6 +7,7 @@ use std::rc::Rc; pub mod organization; pub mod project; pub mod team; +pub mod version; #[derive(Clone)] pub struct ApiV2 { diff --git a/tests/common/api_v2/version.rs b/tests/common/api_v2/version.rs new file mode 100644 index 000000000..306db36ca --- /dev/null +++ b/tests/common/api_v2/version.rs @@ -0,0 +1,91 @@ +use actix_http::{header::AUTHORIZATION, StatusCode}; +use actix_web::{dev::ServiceResponse, test}; +use labrinth::models::projects::Version; +use serde_json::json; + +use crate::common::{self, actix::AppendsMultipart, asserts::assert_status}; + +use super::ApiV2; + +pub fn url_encode_json_serialized_vec(elements: &[String]) -> String { + let serialized = serde_json::to_string(&elements).unwrap(); + urlencoding::encode(&serialized).to_string() +} + +impl ApiV2 { + pub async fn create_default_version( + &self, + project_id: &str, + ordering: Option, + pat: &str, + ) -> Version { + let json_data = json!( + { + "project_id": project_id, + "file_parts": ["basic-mod-different.jar"], + "version_number": "1.2.3.4", + "version_title": "start", + "dependencies": [], + "game_versions": ["1.20.1"] , + "release_channel": "release", + "loaders": ["fabric"], + "featured": true, + "ordering": ordering, + } + ); + let json_segment = common::actix::MultipartSegment { + name: "data".to_string(), + filename: None, + content_type: Some("application/json".to_string()), + data: common::actix::MultipartSegmentData::Text( + serde_json::to_string(&json_data).unwrap(), + ), + }; + let file_segment = common::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( + include_bytes!("../../../tests/files/basic-mod-different.jar").to_vec(), + ), + }; + + let request = test::TestRequest::post() + .uri("/v2/version") + .set_multipart(vec![json_segment.clone(), file_segment.clone()]) + .append_header((AUTHORIZATION, pat)) + .to_request(); + let resp = self.call(request).await; + assert_status(&resp, StatusCode::OK); + test::read_body_json(resp).await + } + + pub async fn get_versions(&self, version_ids: Vec, pat: &str) -> Vec { + let ids = url_encode_json_serialized_vec(&version_ids); + let request = test::TestRequest::get() + .uri(&format!("/v2/versions?ids={}", ids)) + .append_header((AUTHORIZATION, pat)) + .to_request(); + let resp = self.call(request).await; + assert_status(&resp, StatusCode::OK); + test::read_body_json(resp).await + } + + pub async fn edit_version_ordering( + &self, + version_id: &str, + ordering: Option, + pat: &str, + ) -> ServiceResponse { + let request = test::TestRequest::patch() + .uri(&format!("/v2/version/{version_id}")) + .set_json(json!( + { + "ordering": ordering + } + )) + .append_header((AUTHORIZATION, pat)) + .to_request(); + self.call(request).await + } +} diff --git a/tests/common/asserts.rs b/tests/common/asserts.rs index 3c7f585ac..97885b8a2 100644 --- a/tests/common/asserts.rs +++ b/tests/common/asserts.rs @@ -1,9 +1,23 @@ #![allow(dead_code)] +use crate::common::get_json_val_str; +use itertools::Itertools; + 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, +) { + let version_ids = versions + .iter() + .map(|v| get_json_val_str(v.id)) + .collect_vec(); + assert_eq!(version_ids, expected_ids); +} + pub fn assert_any_status_except( response: &actix_web::dev::ServiceResponse, status: actix_http::StatusCode, diff --git a/tests/version.rs b/tests/version.rs new file mode 100644 index 000000000..a1a6d84d4 --- /dev/null +++ b/tests/version.rs @@ -0,0 +1,126 @@ +use crate::common::{asserts::assert_status, get_json_val_str}; +use actix_http::StatusCode; +use common::{ + asserts::assert_version_ids, database::USER_USER_PAT, environment::with_test_environment, +}; + +mod common; + +#[actix_rt::test] +async fn can_create_version_with_ordering() { + with_test_environment(|env| async move { + let alpha_project_id = env.dummy.as_ref().unwrap().project_alpha.project_id.clone(); + + let new_version_id = get_json_val_str( + env.v2 + .create_default_version(&alpha_project_id, Some(1), USER_USER_PAT) + .await + .id, + ); + + let versions = env + .v2 + .get_versions(vec![new_version_id.clone()], USER_USER_PAT) + .await; + assert_eq!(versions[0].ordering, Some(1)); + }) + .await; +} + +#[actix_rt::test] +async fn edit_version_ordering_works() { + with_test_environment(|env| async move { + let alpha_version_id = env.dummy.as_ref().unwrap().project_alpha.version_id.clone(); + + let resp = env + .v2 + .edit_version_ordering(&alpha_version_id, Some(10), USER_USER_PAT) + .await; + assert_status(&resp, StatusCode::NO_CONTENT); + + let versions = env + .v2 + .get_versions(vec![alpha_version_id.clone()], USER_USER_PAT) + .await; + assert_eq!(versions[0].ordering, Some(10)); + }) + .await; +} + +#[actix_rt::test] +async fn version_ordering_for_specified_orderings_orders_lower_order_first() { + with_test_environment(|env| async move { + let alpha_project_id = env.dummy.as_ref().unwrap().project_alpha.project_id.clone(); + let alpha_version_id = env.dummy.as_ref().unwrap().project_alpha.version_id.clone(); + let new_version_id = get_json_val_str( + env.v2 + .create_default_version(&alpha_project_id, Some(1), USER_USER_PAT) + .await + .id, + ); + env.v2 + .edit_version_ordering(&alpha_version_id, Some(10), USER_USER_PAT) + .await; + + let versions = env + .v2 + .get_versions( + vec![alpha_version_id.clone(), new_version_id.clone()], + USER_USER_PAT, + ) + .await; + assert_version_ids(&versions, vec![new_version_id, alpha_version_id]); + }) + .await; +} + +#[actix_rt::test] +async fn version_ordering_when_unspecified_orders_oldest_first() { + with_test_environment(|env| async move { + let alpha_project_id = &env.dummy.as_ref().unwrap().project_alpha.project_id.clone(); + let alpha_version_id = env.dummy.as_ref().unwrap().project_alpha.version_id.clone(); + let new_version_id = get_json_val_str( + env.v2 + .create_default_version(&alpha_project_id, None, USER_USER_PAT) + .await + .id, + ); + + let versions = env + .v2 + .get_versions( + vec![alpha_version_id.clone(), new_version_id.clone()], + USER_USER_PAT, + ) + .await; + assert_version_ids(&versions, vec![alpha_version_id, new_version_id]); + }) + .await +} + +#[actix_rt::test] +async fn version_ordering_when_specified_orders_specified_before_unspecified() { + with_test_environment(|env| async move { + let alpha_project_id = &env.dummy.as_ref().unwrap().project_alpha.project_id.clone(); + let alpha_version_id = env.dummy.as_ref().unwrap().project_alpha.version_id.clone(); + let new_version_id = get_json_val_str( + env.v2 + .create_default_version(&alpha_project_id, Some(10000), USER_USER_PAT) + .await + .id, + ); + env.v2 + .edit_version_ordering(&alpha_version_id, None, USER_USER_PAT) + .await; + + let versions = env + .v2 + .get_versions( + vec![alpha_version_id.clone(), new_version_id.clone()], + USER_USER_PAT, + ) + .await; + assert_version_ids(&versions, vec![new_version_id, alpha_version_id]); + }) + .await; +}