OneLauncher-Cracked/packages/oneclient_core/tests/bundle_updates.rs
Maciek 0ab544f265
feat: custom cluster instances (#859)
* feat: added adding new instances of a cluster with 'start from scratch' option enabling user to create a vanilla/modded cluster; fixed couple pre-existing bugs that wouldn't let other loaders boot up

* feat: fully implemented cluster creation modal design

* chore: refactoring

* feat: expanded the tags section on customization step; found that fabric doesnt work with 1.8.9

* feat: added gallery covers handling for new cluster instances

* feat: added 'redirecting' from fabric to orinthe on versions pre-1.14

* feat: added java-version-override

* feat: added loader types migrations (right now they dont migrate mods because there are no bundles)

* chore: wider loader dropdown for multiloader cluster (if it will be used in the future)

* chore: fixed more issues with isolated and shared dir, with migrations and instances naming

* chore: refactor; taken the loader version dropdown from version picker view back to loader selector

* chore: deleted Quilt from UI

---------

Co-authored-by: LynithDev <61880709+LynithDev@users.noreply.github.com>
2026-09-29 19:08:21 +02:00

489 lines
13 KiB
Rust

use oneclient_common::domain::{ContentType, GameLoader, ProviderId};
use oneclient_content::bundles::{
BundleFile, BundleFileKind, BundleFileType, BundleManifest, check_bundle_updates,
};
use oneclient_core::LauncherState;
use oneclient_core::clusters::CreateClusterOptions;
use oneclient_db::dao::{artifact as artifact_dao, cluster_bundle as bundle_dao};
use oneclient_db::models::OverrideType;
const BUNDLE: &str = "Test Bundle";
const MC_VERSION: &str = "1.21.1";
const PROJECT_ID: &str = "sodium";
const VERSION_ID: &str = "v1";
const HASH: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
fn managed_file(enabled: bool) -> BundleFile {
BundleFile {
enabled,
hidden: false,
path: "mods/sodium.jar".to_string(),
size: 1,
file_type: BundleFileType::Normal,
kind: BundleFileKind::Managed {
provider: ProviderId::Modrinth,
project_id: PROJECT_ID.to_string(),
version_id: VERSION_ID.to_string(),
sha1: HASH.to_string(),
},
}
}
/// Added by the catalog after the last sync untracked so it has no override
fn newly_shipped_file() -> BundleFile {
BundleFile {
enabled: true,
hidden: false,
path: "mods/newcomer.jar".to_string(),
size: 1,
file_type: BundleFileType::Normal,
kind: BundleFileKind::Managed {
provider: ProviderId::Modrinth,
project_id: "newcomer".to_string(),
version_id: "v1".to_string(),
sha1: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
},
}
}
fn manifest(files: Vec<BundleFile>) -> BundleManifest {
BundleManifest {
name: BUNDLE.to_string(),
version_id: "1".to_string(),
category: "test".to_string(),
mc_version: MC_VERSION.to_string(),
loader: GameLoader::Fabric,
loader_version: "0.16.0".to_string(),
enabled: true,
java_version_override: None,
files,
}
}
async fn cluster_with_tracked_mod(state: &LauncherState) -> i64 {
let global = state.settings.read().global_game_settings.clone();
let cluster = state
.clusters
.create(
&global,
CreateClusterOptions::new("Bundle Cluster", MC_VERSION, GameLoader::Fabric),
)
.await
.unwrap();
artifact_dao::insert_artifact(
&state.services.db,
HASH,
ContentType::Mod as i64,
"artifacts/sodium.jar",
"sodium.jar",
Some(1),
)
.await
.unwrap();
artifact_dao::link_cluster_artifact(&state.services.db, cluster.id, HASH, "sodium.jar")
.await
.unwrap();
artifact_dao::upsert_provider_release(
&state.services.db,
ProviderId::Modrinth as i64,
PROJECT_ID,
VERSION_ID,
HASH,
"Sodium",
"1.0.0",
None,
MC_VERSION,
"fabric",
)
.await
.unwrap();
bundle_dao::track_bundle_artifact(
&state.services.db,
cluster.id,
HASH,
BUNDLE,
VERSION_ID,
PROJECT_ID,
)
.await
.unwrap();
cluster.id
}
#[tokio::test]
async fn mod_still_in_manifest_is_not_removed() {
let state = oneclient_core::dev::ephemeral_state().await.unwrap();
oneclient_core::dev::seed_bundle_archive(&state, manifest(vec![managed_file(true)]))
.await
.unwrap();
let cluster_id = cluster_with_tracked_mod(&state).await;
let check = check_bundle_updates(
cluster_id,
state.bundles.as_ref(),
&state.services.content(),
)
.await
.unwrap();
assert!(
check.removals_available.is_empty(),
"an unchanged tracked mod was flagged for removal: {:?}",
check.removals_available
);
assert!(check.updates_available.is_empty());
}
#[tokio::test]
async fn mod_dropped_from_manifest_is_removed() {
let state = oneclient_core::dev::ephemeral_state().await.unwrap();
oneclient_core::dev::seed_bundle_archive(&state, manifest(vec![]))
.await
.unwrap();
let cluster_id = cluster_with_tracked_mod(&state).await;
let check = check_bundle_updates(
cluster_id,
state.bundles.as_ref(),
&state.services.content(),
)
.await
.unwrap();
assert_eq!(
check.removals_available.len(),
1,
"a mod the bundle no longer ships should be removed"
);
assert_eq!(check.removals_available[0].package_id, PROJECT_ID);
}
#[tokio::test]
async fn disabled_mod_dropped_from_manifest_is_still_removed() {
let state = oneclient_core::dev::ephemeral_state().await.unwrap();
oneclient_core::dev::seed_bundle_archive(&state, manifest(vec![]))
.await
.unwrap();
let cluster_id = cluster_with_tracked_mod(&state).await;
artifact_dao::update_cluster_artifact(&state.services.db, cluster_id, HASH, "sodium.jar", 0)
.await
.unwrap();
bundle_dao::save_override(
&state.services.db,
cluster_id,
BUNDLE,
PROJECT_ID,
OverrideType::Disabled,
)
.await
.unwrap();
let check = check_bundle_updates(
cluster_id,
state.bundles.as_ref(),
&state.services.content(),
)
.await
.unwrap();
assert_eq!(
check.removals_available.len(),
1,
"a disabled mod the bundle no longer ships should still be removed"
);
}
#[tokio::test]
async fn user_disabled_mod_is_not_treated_as_a_removal() {
let state = oneclient_core::dev::ephemeral_state().await.unwrap();
oneclient_core::dev::seed_bundle_archive(&state, manifest(vec![managed_file(true)]))
.await
.unwrap();
let cluster_id = cluster_with_tracked_mod(&state).await;
artifact_dao::update_cluster_artifact(&state.services.db, cluster_id, HASH, "sodium.jar", 0)
.await
.unwrap();
bundle_dao::save_override(
&state.services.db,
cluster_id,
BUNDLE,
PROJECT_ID,
OverrideType::Disabled,
)
.await
.unwrap();
let check = check_bundle_updates(
cluster_id,
state.bundles.as_ref(),
&state.services.content(),
)
.await
.unwrap();
assert!(
check.removals_available.is_empty(),
"a user-disabled mod is still shipped by the bundle and must not be removed: {:?}",
check.removals_available
);
}
#[tokio::test]
async fn live_bundle_takes_on_new_catalog_files() {
let state = oneclient_core::dev::ephemeral_state().await.unwrap();
oneclient_core::dev::seed_bundle_archive(
&state,
manifest(vec![managed_file(true), newly_shipped_file()]),
)
.await
.unwrap();
let cluster_id = cluster_with_tracked_mod(&state).await;
let check = check_bundle_updates(
cluster_id,
state.bundles.as_ref(),
&state.services.content(),
)
.await
.unwrap();
assert_eq!(
check.additions_available.len(),
1,
"a bundle the user still has enabled should pick up newly shipped files"
);
assert_eq!(
check.additions_available[0].new_file.kind.package_id(),
"newcomer"
);
}
#[tokio::test]
async fn emptied_bundle_does_not_take_on_new_catalog_files() {
let state = oneclient_core::dev::ephemeral_state().await.unwrap();
oneclient_core::dev::seed_bundle_archive(
&state,
manifest(vec![managed_file(true), newly_shipped_file()]),
)
.await
.unwrap();
let cluster_id = cluster_with_tracked_mod(&state).await;
// Disabling everything is how you opt out
// The newcomer lacks an override only
// because it did not exist yet that absence must not read as consent
artifact_dao::update_cluster_artifact(&state.services.db, cluster_id, HASH, "sodium.jar", 0)
.await
.unwrap();
bundle_dao::save_override(
&state.services.db,
cluster_id,
BUNDLE,
PROJECT_ID,
OverrideType::Disabled,
)
.await
.unwrap();
let check = check_bundle_updates(
cluster_id,
state.bundles.as_ref(),
&state.services.content(),
)
.await
.unwrap();
assert!(
check.additions_available.is_empty(),
"a bundle whose content the user disabled must not reinstate itself: {:?}",
check
.additions_available
.iter()
.map(|a| a.new_file.kind.package_id())
.collect::<Vec<_>>()
);
}
#[tokio::test]
async fn removed_bundle_content_does_not_take_on_new_catalog_files() {
let state = oneclient_core::dev::ephemeral_state().await.unwrap();
oneclient_core::dev::seed_bundle_archive(
&state,
manifest(vec![managed_file(true), newly_shipped_file()]),
)
.await
.unwrap();
let cluster_id = cluster_with_tracked_mod(&state).await;
oneclient_content::bundles::remove_artifact_from_cluster(
cluster_id,
HASH,
true,
&state.services.content(),
)
.await
.unwrap();
let check = check_bundle_updates(
cluster_id,
state.bundles.as_ref(),
&state.services.content(),
)
.await
.unwrap();
assert!(
check.additions_available.is_empty(),
"a bundle the user emptied by removal must not reinstate itself: {:?}",
check
.additions_available
.iter()
.map(|a| a.new_file.kind.package_id())
.collect::<Vec<_>>()
);
}
#[tokio::test]
async fn opting_a_single_file_in_keeps_the_bundle_live() {
let state = oneclient_core::dev::ephemeral_state().await.unwrap();
oneclient_core::dev::seed_bundle_archive(
&state,
manifest(vec![managed_file(true), newly_shipped_file()]),
)
.await
.unwrap();
let cluster_id = cluster_with_tracked_mod(&state).await;
artifact_dao::update_cluster_artifact(&state.services.db, cluster_id, HASH, "sodium.jar", 0)
.await
.unwrap();
bundle_dao::save_override(
&state.services.db,
cluster_id,
BUNDLE,
PROJECT_ID,
OverrideType::Disabled,
)
.await
.unwrap();
bundle_dao::save_override(
&state.services.db,
cluster_id,
BUNDLE,
"newcomer",
OverrideType::Enabled,
)
.await
.unwrap();
let check = check_bundle_updates(
cluster_id,
state.bundles.as_ref(),
&state.services.content(),
)
.await
.unwrap();
assert_eq!(
check.additions_available.len(),
1,
"an explicit opt-in is consent, even with the rest of the bundle disabled"
);
}
async fn seed_delisted_bundle(state: &LauncherState) {
oneclient_db::dao::bundle::upsert_bundle(
&state.services.db,
oneclient_db::models::NewBundle {
remote_path: "bundles/delisted.mrpack",
mc_version: MC_VERSION,
mc_loader: GameLoader::Fabric as i64,
file_name: "delisted.mrpack",
name: Some(BUNDLE),
version_id: Some("1"),
category: Some("test"),
loader_version: Some("0.16.0"),
disk_path: "bundles/delisted.mrpack",
hidden: true,
etag: None,
synced_at: None,
},
)
.await
.unwrap();
}
#[tokio::test]
async fn delisted_bundle_content_is_removed() {
let state = oneclient_core::dev::ephemeral_state().await.unwrap();
seed_delisted_bundle(&state).await;
let cluster_id = cluster_with_tracked_mod(&state).await;
let check = check_bundle_updates(
cluster_id,
state.bundles.as_ref(),
&state.services.content(),
)
.await
.unwrap();
assert_eq!(
check.removals_available.len(),
1,
"content exclusive to a bundle the catalog dropped should be removed"
);
assert_eq!(check.removals_available[0].package_id, PROJECT_ID);
}
#[tokio::test]
async fn delisted_bundle_content_another_bundle_still_ships_is_kept() {
let state = oneclient_core::dev::ephemeral_state().await.unwrap();
seed_delisted_bundle(&state).await;
let mut successor = manifest(vec![managed_file(true)]);
successor.name = "Successor Bundle".to_string();
oneclient_core::dev::seed_bundle_archive(&state, successor)
.await
.unwrap();
let cluster_id = cluster_with_tracked_mod(&state).await;
let check = check_bundle_updates(
cluster_id,
state.bundles.as_ref(),
&state.services.content(),
)
.await
.unwrap();
assert!(
check.removals_available.is_empty(),
"a mod another live bundle still ships is not exclusive: {:?}",
check.removals_available
);
}
#[tokio::test]
async fn tracked_bundle_that_never_synced_is_not_removed() {
let state = oneclient_core::dev::ephemeral_state().await.unwrap();
let cluster_id = cluster_with_tracked_mod(&state).await;
let check = check_bundle_updates(
cluster_id,
state.bundles.as_ref(),
&state.services.content(),
)
.await
.unwrap();
assert!(
check.removals_available.is_empty(),
"an absent catalog is not a delisting and must not take content down: {:?}",
check.removals_available
);
}