pacenotes_server/store/
publish_guards.rs1pub fn unpublished_games(
9 current: &pacenotes_shared::Manifest,
10 next: &pacenotes_shared::Manifest,
11) -> Vec<String> {
12 let mut games: Vec<String> = current
13 .packages
14 .iter()
15 .map(|package| package.game.clone())
16 .filter(|game| !next.schemas.contains_key(game))
17 .filter(|game| !next.packages.iter().any(|package| &package.game == game))
18 .collect();
19 games.sort();
20 games.dedup();
21 games
22}
23
24pub fn metadata_losses(
31 current: &pacenotes_shared::Manifest,
32 next: &pacenotes_shared::Manifest,
33) -> Vec<String> {
34 let mut losses = Vec::new();
35 for old in ¤t.packages {
36 let Some(new) = next.packages.iter().find(|package| {
37 package.game == old.game
38 && package.directory == old.directory
39 && package.name == old.name
40 }) else {
41 continue;
42 };
43 let id = old.download_path();
44 if crate::store::is_chosen_version(&old.version)
45 && new
46 .version
47 .starts_with(crate::store::DERIVED_VERSION_PREFIX)
48 {
49 losses.push(format!("{id}: version {:?}", old.version));
50 }
51 let optional_fields = [
52 ("display_name", &old.display_name, &new.display_name),
53 ("description", &old.description, &new.description),
54 (
55 "exclusive_group",
56 &old.exclusive_group,
57 &new.exclusive_group,
58 ),
59 ];
60 for (field, before, after) in optional_fields {
61 if let (Some(value), None) = (before, after) {
62 losses.push(format!("{id}: {field} {value:?}"));
63 }
64 }
65 if !old.requires.is_empty() && new.requires.is_empty() {
66 losses.push(format!("{id}: requires {:?}", old.requires));
67 }
68 }
69 losses
70}
71
72#[cfg(test)]
73mod tests {
74 #[test]
76 fn a_live_game_losing_its_schema_is_detected() {
77 let tmp = tempfile::tempdir().unwrap();
78 let dir = tmp.path();
79 crate::store::fixture_tree::write(
80 &crate::store::fixture_tree::package_path(dir, "rbr", "public/language/polish.zip"),
81 b"x",
82 );
83 let next = crate::store::fixture_tree::generate(dir).manifest;
84 assert!(next.packages.is_empty());
85 let live = crate::store::fixture_tree::legacy_manifest();
86 assert_eq!(super::unpublished_games(&live, &next), vec!["rbr"]);
87 }
88
89 #[test]
91 fn a_game_emptied_on_purpose_is_not_unpublished() {
92 let tmp = tempfile::tempdir().unwrap();
93 let dir = tmp.path();
94 crate::store::fixture_tree::declare(dir, "rbr", &["language"]);
95 std::fs::create_dir_all(dir.join("rbr").join(crate::store::PACKAGES_DIR)).unwrap();
96 let next = crate::store::fixture_tree::generate(dir).manifest;
97 let live = crate::store::fixture_tree::legacy_manifest();
98 assert!(super::unpublished_games(&live, &next).is_empty());
99 }
100
101 fn basic_on_disk(dir: &std::path::Path, sidecar: Option<&[u8]>) -> pacenotes_shared::Manifest {
102 let zip = crate::store::fixture_tree::package_path(dir, "rbr", "public/basic.zip");
103 crate::store::fixture_tree::write_package(dir, "rbr", "public/basic.zip", b"hello");
104 let _ = std::fs::remove_file(zip.with_extension("toml"));
105 if let Some(sidecar) = sidecar {
106 crate::store::fixture_tree::write(&zip.with_extension("toml"), sidecar);
107 }
108 crate::store::fixture_tree::generate(dir).manifest
109 }
110
111 #[test]
112 fn dropping_manifest_only_metadata_is_detected() {
113 let tmp = tempfile::tempdir().unwrap();
114 let next = basic_on_disk(tmp.path(), None);
115
116 let losses = super::metadata_losses(&crate::store::fixture_tree::legacy_manifest(), &next)
117 .join("\n");
118 for field in [
119 "version",
120 "display_name",
121 "description",
122 "exclusive_group",
123 "requires",
124 ] {
125 assert!(losses.contains(field), "{field} missing from {losses}");
126 }
127 assert!(losses.contains("rbr/public/basic"), "{losses}");
128 }
129
130 #[test]
132 fn a_value_a_sidecar_changed_is_not_a_loss() {
133 let tmp = tempfile::tempdir().unwrap();
134 let next = basic_on_disk(
135 tmp.path(),
136 Some(
137 br#"
138 version = "3.0.0"
139 display_name = "Other"
140 description = "Other"
141 exclusive_group = "other"
142 requires = ["public/language/english"]
143 "#,
144 ),
145 );
146 let current = crate::store::fixture_tree::legacy_manifest();
147 assert!(super::metadata_losses(¤t, &next).is_empty());
148 }
149
150 #[test]
151 fn the_legacy_default_version_is_not_a_loss() {
152 let tmp = tempfile::tempdir().unwrap();
153 let next = basic_on_disk(tmp.path(), None);
154 let mut defaulted = crate::store::fixture_tree::legacy_manifest();
155 let package = &mut defaulted.packages[0];
156 package.version = "0.0.0".into();
157 package.display_name = None;
158 package.description = None;
159 package.exclusive_group = None;
160 package.requires.clear();
161 assert!(super::metadata_losses(&defaulted, &next).is_empty());
162 }
163
164 #[test]
166 fn a_package_whose_zip_is_gone_is_not_a_loss() {
167 let current = crate::store::fixture_tree::legacy_manifest();
168 assert!(super::metadata_losses(¤t, &Default::default()).is_empty());
169 }
170}