• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

endoze / valheim-mod-manager / 23714700391

29 Mar 2026 05:22PM UTC coverage: 92.604% (+0.2%) from 92.43%
23714700391

push

github

web-flow
Merge pull request #15 from endoze/add-string-interning

Implement string interning for package manifest

500 of 531 new or added lines in 3 files covered. (94.16%)

9 existing lines in 3 files now uncovered.

889 of 960 relevant lines covered (92.6%)

3.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.65
/src/package.rs
1
use crate::intern::{
2
  InternKey, StringInterner, intern_option, intern_vec, resolve_option, resolve_vec,
3
};
4
use lasso::Key;
5
use serde::{Deserialize, Serialize};
6
use std::collections::BTreeSet;
7
use std::collections::HashMap;
8
use time::OffsetDateTime;
9

10
/// Struct-of-Arrays representation of the package manifest for efficient storage.
11
///
12
/// This structure organizes package data in a columnar format to minimize duplication
13
/// and improve serialization efficiency. All packages are stored with parallel vectors,
14
/// and version data is stored separately with indices linking back to packages.
15
#[derive(Serialize, Deserialize, Debug, Clone)]
16
pub struct PackageManifest {
17
  pub names: Vec<Option<String>>,
18
  pub full_names: Vec<Option<String>>,
19
  pub owners: Vec<Option<String>>,
20
  pub package_urls: Vec<Option<String>>,
21
  pub dates_created: Vec<OffsetDateTime>,
22
  pub dates_updated: Vec<OffsetDateTime>,
23
  pub uuid4s: Vec<Option<String>>,
24
  pub rating_scores: Vec<Option<u32>>,
25
  pub is_pinned: Vec<Option<bool>>,
26
  pub is_deprecated: Vec<Option<bool>>,
27
  pub has_nsfw_content: Vec<Option<bool>>,
28
  pub categories: Vec<Vec<String>>,
29
  pub version_ranges: Vec<(usize, usize)>,
30
  pub versions: VersionManifest,
31
}
32

33
/// Struct-of-Arrays representation of version data.
34
///
35
/// Separates frequently accessed "hot" data from rarely accessed "cold" metadata
36
/// for better cache performance and memory efficiency.
37
#[derive(Serialize, Deserialize, Debug, Clone)]
38
pub struct VersionManifest {
39
  pub package_indices: Vec<usize>,
40
  pub version_numbers: Vec<Option<String>>,
41
  pub download_urls: Vec<Option<String>>,
42
  pub dependencies: Vec<Vec<String>>,
43
  pub dates_created: Vec<OffsetDateTime>,
44
  pub descriptions: Vec<Option<String>>,
45
  pub icons: Vec<Option<String>>,
46
  pub downloads: Vec<Option<u32>>,
47
  pub website_urls: Vec<Option<String>>,
48
  pub is_active: Vec<Option<bool>>,
49
  pub uuid4s: Vec<Option<String>>,
50
  pub file_sizes: Vec<Option<u64>>,
51
}
52

53
/// Struct-of-Arrays representation of the package manifest with interned strings.
54
///
55
/// This structure builds upon the PackageManifest format by adding string interning
56
/// to further reduce memory usage and improve serialization efficiency. Repeated strings
57
/// like author names, categories, and URLs are stored once in the interner and referenced
58
/// by integer keys throughout the structure.
59
///
60
/// String interning is particularly beneficial for the Valheim mod ecosystem where the same
61
/// authors maintain multiple packages and common patterns appear frequently in metadata.
62
#[derive(Debug, Clone)]
63
pub struct InternedPackageManifest {
64
  pub interner: StringInterner,
65
  pub names: Vec<Option<InternKey>>,
66
  pub full_names: Vec<Option<InternKey>>,
67
  pub owners: Vec<Option<InternKey>>,
68
  pub package_urls: Vec<Option<InternKey>>,
69
  pub dates_created: Vec<OffsetDateTime>,
70
  pub dates_updated: Vec<OffsetDateTime>,
71
  pub uuid4s: Vec<Option<InternKey>>,
72
  pub rating_scores: Vec<Option<u32>>,
73
  pub is_pinned: Vec<Option<bool>>,
74
  pub is_deprecated: Vec<Option<bool>>,
75
  pub has_nsfw_content: Vec<Option<bool>>,
76
  pub categories: Vec<Vec<InternKey>>,
77
  pub version_ranges: Vec<(usize, usize)>,
78
  pub versions: InternedVersionManifest,
79
}
80

81
/// Struct-of-Arrays representation of version data with interned strings.
82
///
83
/// Separates frequently accessed "hot" data from rarely accessed "cold" metadata
84
/// for better cache performance and memory efficiency. String values are stored as
85
/// interned keys that reference the parent manifest's string interner.
86
#[derive(Debug, Clone)]
87
pub struct InternedVersionManifest {
88
  pub package_indices: Vec<usize>,
89
  pub version_numbers: Vec<Option<InternKey>>,
90
  pub download_urls: Vec<Option<InternKey>>,
91
  pub dependencies: Vec<Vec<InternKey>>,
92
  pub dates_created: Vec<OffsetDateTime>,
93
  pub descriptions: Vec<Option<InternKey>>,
94
  pub icons: Vec<Option<InternKey>>,
95
  pub downloads: Vec<Option<u32>>,
96
  pub website_urls: Vec<Option<InternKey>>,
97
  pub is_active: Vec<Option<bool>>,
98
  pub uuid4s: Vec<Option<InternKey>>,
99
  pub file_sizes: Vec<Option<u64>>,
100
}
101

102
/// Serializable representation of an interned package manifest.
103
///
104
/// This structure converts the runtime InternedPackageManifest into a format that can be
105
/// efficiently serialized and deserialized. The string interner is converted to a simple
106
/// vector of strings (the string table), and all interned keys become u32 indices into
107
/// this table.
108
///
109
/// This separation allows for optimal serialization performance while maintaining the
110
/// memory efficiency benefits of string interning at runtime.
111
#[derive(Serialize, Deserialize, Debug, Clone)]
112
pub struct SerializableInternedManifest {
113
  pub string_table: Vec<String>,
114
  pub names: Vec<Option<u32>>,
115
  pub full_names: Vec<Option<u32>>,
116
  pub owners: Vec<Option<u32>>,
117
  pub package_urls: Vec<Option<u32>>,
118
  pub dates_created: Vec<OffsetDateTime>,
119
  pub dates_updated: Vec<OffsetDateTime>,
120
  pub uuid4s: Vec<Option<u32>>,
121
  pub rating_scores: Vec<Option<u32>>,
122
  pub is_pinned: Vec<Option<bool>>,
123
  pub is_deprecated: Vec<Option<bool>>,
124
  pub has_nsfw_content: Vec<Option<bool>>,
125
  pub categories: Vec<Vec<u32>>,
126
  pub version_ranges: Vec<(usize, usize)>,
127
  pub versions: SerializableInternedVersionManifest,
128
}
129

130
/// Serializable representation of interned version data.
131
///
132
/// Contains version information with string values represented as u32 indices into
133
/// the parent manifest's string table. This allows efficient serialization while
134
/// maintaining the benefits of string interning.
135
#[derive(Serialize, Deserialize, Debug, Clone)]
136
pub struct SerializableInternedVersionManifest {
137
  pub package_indices: Vec<usize>,
138
  pub version_numbers: Vec<Option<u32>>,
139
  pub download_urls: Vec<Option<u32>>,
140
  pub dependencies: Vec<Vec<u32>>,
141
  pub dates_created: Vec<OffsetDateTime>,
142
  pub descriptions: Vec<Option<u32>>,
143
  pub icons: Vec<Option<u32>>,
144
  pub downloads: Vec<Option<u32>>,
145
  pub website_urls: Vec<Option<u32>>,
146
  pub is_active: Vec<Option<bool>>,
147
  pub uuid4s: Vec<Option<u32>>,
148
  pub file_sizes: Vec<Option<u64>>,
149
}
150

151
#[allow(dead_code)]
152
impl PackageManifest {
153
  /// Returns the number of packages in the manifest.
154
  ///
155
  /// # Returns
156
  ///
157
  /// The total count of packages
158
  pub fn len(&self) -> usize {
2✔
159
    self.names.len()
2✔
160
  }
161

162
  /// Returns true if the manifest contains no packages.
163
  ///
164
  /// # Returns
165
  ///
166
  /// `true` if empty, `false` otherwise
167
  pub fn is_empty(&self) -> bool {
2✔
168
    self.names.is_empty()
2✔
169
  }
170

171
  /// Validates the internal consistency of the manifest structure.
172
  ///
173
  /// Checks that all parallel vectors have matching lengths and that version
174
  /// ranges are valid.
175
  ///
176
  /// # Returns
177
  ///
178
  /// Ok if the manifest is valid, otherwise an error describing the problem.
179
  pub fn validate(&self) -> Result<(), String> {
2✔
180
    let pkg_count = self.names.len();
2✔
181

182
    if self.full_names.len() != pkg_count {
2✔
183
      return Err(format!(
4✔
184
        "full_names length {} != {}",
185
        self.full_names.len(),
2✔
186
        pkg_count
187
      ));
188
    }
189

190
    if self.owners.len() != pkg_count {
2✔
191
      return Err(format!(
4✔
192
        "owners length {} != {}",
193
        self.owners.len(),
2✔
194
        pkg_count
195
      ));
196
    }
197

198
    if self.package_urls.len() != pkg_count {
2✔
199
      return Err(format!(
4✔
200
        "package_urls length {} != {}",
201
        self.package_urls.len(),
2✔
202
        pkg_count
203
      ));
204
    }
205

206
    if self.dates_created.len() != pkg_count {
2✔
207
      return Err(format!(
4✔
208
        "dates_created length {} != {}",
209
        self.dates_created.len(),
2✔
210
        pkg_count
211
      ));
212
    }
213

214
    if self.dates_updated.len() != pkg_count {
2✔
215
      return Err(format!(
4✔
216
        "dates_updated length {} != {}",
217
        self.dates_updated.len(),
2✔
218
        pkg_count
219
      ));
220
    }
221

222
    if self.uuid4s.len() != pkg_count {
2✔
223
      return Err(format!(
4✔
224
        "uuid4s length {} != {}",
225
        self.uuid4s.len(),
2✔
226
        pkg_count
227
      ));
228
    }
229

230
    if self.rating_scores.len() != pkg_count {
2✔
231
      return Err(format!(
4✔
232
        "rating_scores length {} != {}",
233
        self.rating_scores.len(),
2✔
234
        pkg_count
235
      ));
236
    }
237

238
    if self.is_pinned.len() != pkg_count {
2✔
239
      return Err(format!(
4✔
240
        "is_pinned length {} != {}",
241
        self.is_pinned.len(),
2✔
242
        pkg_count
243
      ));
244
    }
245

246
    if self.is_deprecated.len() != pkg_count {
2✔
247
      return Err(format!(
4✔
248
        "is_deprecated length {} != {}",
249
        self.is_deprecated.len(),
2✔
250
        pkg_count
251
      ));
252
    }
253

254
    if self.has_nsfw_content.len() != pkg_count {
2✔
255
      return Err(format!(
4✔
256
        "has_nsfw_content length {} != {}",
257
        self.has_nsfw_content.len(),
2✔
258
        pkg_count
259
      ));
260
    }
261

262
    if self.categories.len() != pkg_count {
2✔
263
      return Err(format!(
4✔
264
        "categories length {} != {}",
265
        self.categories.len(),
2✔
266
        pkg_count
267
      ));
268
    }
269

270
    if self.version_ranges.len() != pkg_count {
2✔
271
      return Err(format!(
4✔
272
        "version_ranges length {} != {}",
273
        self.version_ranges.len(),
2✔
274
        pkg_count
275
      ));
276
    }
277

278
    let version_count = self.versions.version_numbers.len();
2✔
279

280
    if self.versions.package_indices.len() != version_count {
2✔
281
      return Err(format!(
4✔
282
        "versions.package_indices length {} != {}",
283
        self.versions.package_indices.len(),
2✔
284
        version_count
285
      ));
286
    }
287

288
    if self.versions.download_urls.len() != version_count {
2✔
289
      return Err(format!(
4✔
290
        "versions.download_urls length {} != {}",
291
        self.versions.download_urls.len(),
2✔
292
        version_count
293
      ));
294
    }
295

296
    if self.versions.dependencies.len() != version_count {
2✔
297
      return Err(format!(
4✔
298
        "versions.dependencies length {} != {}",
299
        self.versions.dependencies.len(),
2✔
300
        version_count
301
      ));
302
    }
303

304
    if self.versions.dates_created.len() != version_count {
2✔
305
      return Err(format!(
4✔
306
        "versions.dates_created length {} != {}",
307
        self.versions.dates_created.len(),
2✔
308
        version_count
309
      ));
310
    }
311

312
    if self.versions.descriptions.len() != version_count {
2✔
313
      return Err(format!(
4✔
314
        "versions.descriptions length {} != {}",
315
        self.versions.descriptions.len(),
2✔
316
        version_count
317
      ));
318
    }
319

320
    if self.versions.icons.len() != version_count {
2✔
321
      return Err(format!(
4✔
322
        "versions.icons length {} != {}",
323
        self.versions.icons.len(),
2✔
324
        version_count
325
      ));
326
    }
327

328
    if self.versions.downloads.len() != version_count {
2✔
329
      return Err(format!(
4✔
330
        "versions.downloads length {} != {}",
331
        self.versions.downloads.len(),
2✔
332
        version_count
333
      ));
334
    }
335

336
    if self.versions.website_urls.len() != version_count {
3✔
337
      return Err(format!(
4✔
338
        "versions.website_urls length {} != {}",
339
        self.versions.website_urls.len(),
2✔
340
        version_count
341
      ));
342
    }
343

344
    if self.versions.is_active.len() != version_count {
3✔
345
      return Err(format!(
4✔
346
        "versions.is_active length {} != {}",
347
        self.versions.is_active.len(),
2✔
348
        version_count
349
      ));
350
    }
351

352
    if self.versions.uuid4s.len() != version_count {
3✔
353
      return Err(format!(
4✔
354
        "versions.uuid4s length {} != {}",
355
        self.versions.uuid4s.len(),
2✔
356
        version_count
357
      ));
358
    }
359

360
    if self.versions.file_sizes.len() != version_count {
3✔
361
      return Err(format!(
4✔
362
        "versions.file_sizes length {} != {}",
363
        self.versions.file_sizes.len(),
2✔
364
        version_count
365
      ));
366
    }
367

368
    for (idx, (start, end)) in self.version_ranges.iter().enumerate() {
8✔
369
      if start > end {
2✔
370
        return Err(format!(
4✔
371
          "Invalid version range at package {}: {} > {}",
372
          idx, start, end
373
        ));
374
      }
375

376
      if *end > version_count {
2✔
377
        return Err(format!(
4✔
378
          "Version range at package {} ends at {} but only {} versions exist",
379
          idx, end, version_count
380
        ));
381
      }
382
    }
383

384
    Ok(())
2✔
385
  }
386

387
  /// Retrieves a package by its full name.
388
  ///
389
  /// # Parameters
390
  ///
391
  /// * `full_name` - The full name of the package to find
392
  ///
393
  /// # Returns
394
  ///
395
  /// The package if found, otherwise None.
396
  #[allow(dead_code)]
397
  pub fn get_package_by_full_name(&self, full_name: &str) -> Option<Package> {
2✔
398
    let idx = self.find_index_by_full_name(full_name)?;
2✔
399
    Some(self.get_package_at(idx))
3✔
400
  }
401

402
  /// Finds the index of a package by its full name.
403
  ///
404
  /// # Parameters
405
  ///
406
  /// * `full_name` - The full name to search for
407
  ///
408
  /// # Returns
409
  ///
410
  /// The index of the package if found, otherwise None.
411
  #[allow(dead_code)]
412
  pub fn find_index_by_full_name(&self, full_name: &str) -> Option<usize> {
2✔
413
    self
2✔
414
      .full_names
415
      .iter()
416
      .position(|name| name.as_ref().map(|n| n.as_str()) == Some(full_name))
11✔
417
  }
418

419
  /// Reconstructs a Package struct from the manifest data at the given index.
420
  ///
421
  /// This method gathers all package data and its versions from the SoA structure
422
  /// and reconstructs a complete Package object.
423
  ///
424
  /// # Parameters
425
  ///
426
  /// * `idx` - The index of the package in the manifest
427
  ///
428
  /// # Returns
429
  ///
430
  /// A fully materialized Package struct.
431
  #[allow(dead_code)]
432
  pub fn get_package_at(&self, idx: usize) -> Package {
3✔
433
    let (version_start, version_end) = self.version_ranges[idx];
3✔
434

435
    let mut versions = Vec::with_capacity(version_end - version_start);
3✔
436

437
    for ver_idx in version_start..version_end {
8✔
438
      versions.push(Version {
2✔
439
        name: self.names[idx].clone(),
6✔
440
        full_name: self.full_names[idx].clone(),
6✔
441
        description: self.versions.descriptions[ver_idx].clone(),
6✔
442
        icon: self.versions.icons[ver_idx].clone(),
6✔
443
        version_number: self.versions.version_numbers[ver_idx].clone(),
5✔
444
        dependencies: self.versions.dependencies[ver_idx].clone(),
4✔
445
        download_url: self.versions.download_urls[ver_idx].clone(),
5✔
446
        downloads: self.versions.downloads[ver_idx],
4✔
447
        date_created: self.versions.dates_created[ver_idx],
2✔
448
        website_url: self.versions.website_urls[ver_idx].clone(),
2✔
449
        is_active: self.versions.is_active[ver_idx],
4✔
450
        uuid4: self.versions.uuid4s[ver_idx].clone(),
2✔
451
        file_size: self.versions.file_sizes[ver_idx],
4✔
452
      });
453
    }
454

455
    Package {
456
      name: self.names[idx].clone(),
4✔
457
      full_name: self.full_names[idx].clone(),
4✔
458
      owner: self.owners[idx].clone(),
4✔
459
      package_url: self.package_urls[idx].clone(),
4✔
460
      date_created: self.dates_created[idx],
4✔
461
      date_updated: self.dates_updated[idx],
3✔
462
      uuid4: self.uuid4s[idx].clone(),
3✔
463
      rating_score: self.rating_scores[idx],
7✔
464
      is_pinned: self.is_pinned[idx],
4✔
465
      is_deprecated: self.is_deprecated[idx],
4✔
466
      has_nsfw_content: self.has_nsfw_content[idx],
2✔
467
      categories: self.categories[idx].clone(),
2✔
468
      versions,
469
    }
470
  }
471

472
  /// Returns the index of the latest version for a package.
473
  ///
474
  /// The latest version is determined by comparing the date_created field of all versions.
475
  ///
476
  /// # Parameters
477
  ///
478
  /// * `idx` - The index of the package in the manifest
479
  ///
480
  /// # Returns
481
  ///
482
  /// The index of the latest version, or None if the package has no versions.
483
  pub fn get_latest_version_at(&self, idx: usize) -> Option<usize> {
2✔
484
    let (ver_start, ver_end) = self.version_ranges[idx];
4✔
485

486
    if ver_start >= ver_end {
2✔
487
      return None;
2✔
488
    }
489

490
    (ver_start..ver_end).max_by_key(|&ver_idx| self.versions.dates_created[ver_idx])
10✔
491
  }
492

493
  /// Builds an index mapping full package names to their indices.
494
  ///
495
  /// This is useful for fast lookups of packages by name.
496
  ///
497
  /// # Returns
498
  ///
499
  /// A HashMap mapping full names to package indices.
500
  pub fn build_name_index(&self) -> HashMap<String, usize> {
3✔
501
    self
2✔
502
      .full_names
503
      .iter()
504
      .enumerate()
505
      .filter_map(|(idx, name)| Some((name.as_ref()?.clone(), idx)))
5✔
506
      .collect()
507
  }
508
}
509

510
/// Implementation block for VersionManifest.
511
///
512
/// Currently empty but reserved for future version-specific methods.
513
impl VersionManifest {}
514

515
impl InternedPackageManifest {
516
  /// Returns the number of packages in the manifest.
517
  pub fn len(&self) -> usize {
2✔
518
    self.names.len()
2✔
519
  }
520

521
  /// Returns true if the manifest contains no packages.
522
  #[allow(dead_code)]
523
  pub fn is_empty(&self) -> bool {
2✔
524
    self.names.is_empty()
2✔
525
  }
526

527
  /// Resolves the package name at the given index from the string interner.
528
  ///
529
  /// # Returns
530
  ///
531
  /// The resolved string if a name exists at the index, otherwise None.
532
  pub fn resolve_name_at(&self, idx: usize) -> Option<String> {
2✔
533
    resolve_option(&self.interner, self.names[idx])
2✔
534
  }
535

536
  /// Resolves the full package name at the given index from the string interner.
537
  ///
538
  /// # Returns
539
  ///
540
  /// The resolved string if a full name exists at the index, otherwise None.
541
  pub fn resolve_full_name_at(&self, idx: usize) -> Option<String> {
2✔
542
    resolve_option(&self.interner, self.full_names[idx])
2✔
543
  }
544

545
  /// Resolves the package owner at the given index from the string interner.
546
  ///
547
  /// # Returns
548
  ///
549
  /// The resolved string if an owner exists at the index, otherwise None.
550
  pub fn resolve_owner_at(&self, idx: usize) -> Option<String> {
2✔
551
    resolve_option(&self.interner, self.owners[idx])
2✔
552
  }
553

554
  /// Retrieves a package by its full name.
555
  ///
556
  /// # Parameters
557
  ///
558
  /// * `full_name` - The full name of the package to find
559
  ///
560
  /// # Returns
561
  ///
562
  /// The package if found, otherwise None.
563
  #[allow(dead_code)]
564
  pub fn get_package_by_full_name(&self, full_name: &str) -> Option<Package> {
2✔
565
    let idx = self.find_index_by_full_name(full_name)?;
2✔
566
    Some(self.get_package_at(idx))
2✔
567
  }
568

569
  /// Finds the index of a package by its full name.
570
  ///
571
  /// # Parameters
572
  ///
573
  /// * `full_name` - The full name to search for
574
  ///
575
  /// # Returns
576
  ///
577
  /// The index of the package if found, otherwise None.
578
  #[allow(dead_code)]
579
  pub fn find_index_by_full_name(&self, full_name: &str) -> Option<usize> {
2✔
580
    self.full_names.iter().position(|key| {
4✔
581
      key
4✔
582
        .map(|k| self.interner.resolve(&k) == full_name)
6✔
583
        .unwrap_or(false)
584
    })
585
  }
586

587
  /// Reconstructs a Package struct from the manifest data at the given index.
588
  ///
589
  /// This method resolves all interned strings and constructs a complete Package
590
  /// with all its versions.
591
  ///
592
  /// # Parameters
593
  ///
594
  /// * `idx` - The index of the package in the manifest
595
  ///
596
  /// # Returns
597
  ///
598
  /// A fully materialized Package struct.
599
  #[allow(dead_code)]
600
  pub fn get_package_at(&self, idx: usize) -> Package {
2✔
601
    let (version_start, version_end) = self.version_ranges[idx];
2✔
602
    let mut versions = Vec::with_capacity(version_end - version_start);
2✔
603

604
    for ver_idx in version_start..version_end {
6✔
605
      versions.push(Version {
4✔
606
        name: resolve_option(&self.interner, self.names[idx]),
4✔
607
        full_name: resolve_option(&self.interner, self.full_names[idx]),
4✔
608
        description: resolve_option(&self.interner, self.versions.descriptions[ver_idx]),
6✔
609
        icon: resolve_option(&self.interner, self.versions.icons[ver_idx]),
8✔
610
        version_number: resolve_option(&self.interner, self.versions.version_numbers[ver_idx]),
8✔
611
        dependencies: resolve_vec(&self.interner, &self.versions.dependencies[ver_idx]),
8✔
612
        download_url: resolve_option(&self.interner, self.versions.download_urls[ver_idx]),
8✔
613
        downloads: self.versions.downloads[ver_idx],
8✔
614
        date_created: self.versions.dates_created[ver_idx],
4✔
615
        website_url: resolve_option(&self.interner, self.versions.website_urls[ver_idx]),
4✔
616
        is_active: self.versions.is_active[ver_idx],
8✔
617
        uuid4: resolve_option(&self.interner, self.versions.uuid4s[ver_idx]),
4✔
618
        file_size: self.versions.file_sizes[ver_idx],
8✔
619
      });
620
    }
621

622
    Package {
623
      name: resolve_option(&self.interner, self.names[idx]),
8✔
624
      full_name: resolve_option(&self.interner, self.full_names[idx]),
9✔
625
      owner: resolve_option(&self.interner, self.owners[idx]),
10✔
626
      package_url: resolve_option(&self.interner, self.package_urls[idx]),
9✔
627
      date_created: self.dates_created[idx],
9✔
628
      date_updated: self.dates_updated[idx],
4✔
629
      uuid4: resolve_option(&self.interner, self.uuid4s[idx]),
5✔
630
      rating_score: self.rating_scores[idx],
9✔
631
      is_pinned: self.is_pinned[idx],
4✔
632
      is_deprecated: self.is_deprecated[idx],
5✔
633
      has_nsfw_content: self.has_nsfw_content[idx],
3✔
634
      categories: resolve_vec(&self.interner, &self.categories[idx]),
3✔
635
      versions,
636
    }
637
  }
638

639
  /// Returns the index of the latest version for a package.
640
  ///
641
  /// The latest version is determined by comparing the date_created field of all versions.
642
  ///
643
  /// # Parameters
644
  ///
645
  /// * `idx` - The index of the package in the manifest
646
  ///
647
  /// # Returns
648
  ///
649
  /// The index of the latest version, or None if the package has no versions.
650
  pub fn get_latest_version_at(&self, idx: usize) -> Option<usize> {
2✔
651
    let (ver_start, ver_end) = self.version_ranges[idx];
2✔
652

653
    if ver_start >= ver_end {
2✔
654
      return None;
2✔
655
    }
656

657
    (ver_start..ver_end).max_by_key(|&ver_idx| self.versions.dates_created[ver_idx])
8✔
658
  }
659

660
  /// Builds an index mapping full package names to their indices.
661
  ///
662
  /// This is useful for fast lookups of packages by name.
663
  ///
664
  /// # Returns
665
  ///
666
  /// A HashMap mapping resolved full names to package indices.
667
  pub fn build_name_index(&self) -> HashMap<String, usize> {
2✔
668
    self
2✔
669
      .full_names
670
      .iter()
671
      .enumerate()
672
      .filter_map(|(idx, key)| {
4✔
673
        let name = resolve_option(&self.interner, *key)?;
2✔
674
        Some((name, idx))
2✔
675
      })
676
      .collect()
677
  }
678

679
  /// Validates the internal consistency of the manifest structure.
680
  ///
681
  /// Checks that all parallel vectors have matching lengths and that version
682
  /// ranges are valid.
683
  ///
684
  /// # Returns
685
  ///
686
  /// Ok if the manifest is valid, otherwise an error describing the problem.
687
  pub fn validate(&self) -> Result<(), String> {
2✔
688
    let pkg_count = self.names.len();
3✔
689

690
    if self.full_names.len() != pkg_count {
3✔
691
      return Err(format!(
4✔
692
        "full_names length {} != {}",
693
        self.full_names.len(),
2✔
694
        pkg_count
695
      ));
696
    }
697

698
    if self.owners.len() != pkg_count {
3✔
699
      return Err(format!(
4✔
700
        "owners length {} != {}",
701
        self.owners.len(),
2✔
702
        pkg_count
703
      ));
704
    }
705

706
    if self.package_urls.len() != pkg_count {
3✔
707
      return Err(format!(
4✔
708
        "package_urls length {} != {}",
709
        self.package_urls.len(),
2✔
710
        pkg_count
711
      ));
712
    }
713

714
    if self.dates_created.len() != pkg_count {
3✔
715
      return Err(format!(
4✔
716
        "dates_created length {} != {}",
717
        self.dates_created.len(),
2✔
718
        pkg_count
719
      ));
720
    }
721

722
    if self.dates_updated.len() != pkg_count {
3✔
723
      return Err(format!(
4✔
724
        "dates_updated length {} != {}",
725
        self.dates_updated.len(),
2✔
726
        pkg_count
727
      ));
728
    }
729

730
    if self.uuid4s.len() != pkg_count {
3✔
731
      return Err(format!(
4✔
732
        "uuid4s length {} != {}",
733
        self.uuid4s.len(),
2✔
734
        pkg_count
735
      ));
736
    }
737

738
    if self.rating_scores.len() != pkg_count {
3✔
739
      return Err(format!(
4✔
740
        "rating_scores length {} != {}",
741
        self.rating_scores.len(),
2✔
742
        pkg_count
743
      ));
744
    }
745

746
    if self.is_pinned.len() != pkg_count {
3✔
747
      return Err(format!(
4✔
748
        "is_pinned length {} != {}",
749
        self.is_pinned.len(),
2✔
750
        pkg_count
751
      ));
752
    }
753

754
    if self.is_deprecated.len() != pkg_count {
3✔
755
      return Err(format!(
4✔
756
        "is_deprecated length {} != {}",
757
        self.is_deprecated.len(),
2✔
758
        pkg_count
759
      ));
760
    }
761

762
    if self.has_nsfw_content.len() != pkg_count {
3✔
763
      return Err(format!(
4✔
764
        "has_nsfw_content length {} != {}",
765
        self.has_nsfw_content.len(),
2✔
766
        pkg_count
767
      ));
768
    }
769

770
    if self.categories.len() != pkg_count {
3✔
771
      return Err(format!(
4✔
772
        "categories length {} != {}",
773
        self.categories.len(),
2✔
774
        pkg_count
775
      ));
776
    }
777

778
    if self.version_ranges.len() != pkg_count {
3✔
779
      return Err(format!(
4✔
780
        "version_ranges length {} != {}",
781
        self.version_ranges.len(),
2✔
782
        pkg_count
783
      ));
784
    }
785

786
    let version_count = self.versions.package_indices.len();
3✔
787

788
    if self.versions.version_numbers.len() != version_count {
2✔
789
      return Err(format!(
4✔
790
        "versions.version_numbers length {} != {}",
791
        self.versions.version_numbers.len(),
2✔
792
        version_count
793
      ));
794
    }
795

796
    if self.versions.download_urls.len() != version_count {
2✔
797
      return Err(format!(
4✔
798
        "versions.download_urls length {} != {}",
799
        self.versions.download_urls.len(),
2✔
800
        version_count
801
      ));
802
    }
803

804
    if self.versions.dependencies.len() != version_count {
2✔
805
      return Err(format!(
4✔
806
        "versions.dependencies length {} != {}",
807
        self.versions.dependencies.len(),
2✔
808
        version_count
809
      ));
810
    }
811

812
    if self.versions.dates_created.len() != version_count {
2✔
813
      return Err(format!(
4✔
814
        "versions.dates_created length {} != {}",
815
        self.versions.dates_created.len(),
2✔
816
        version_count
817
      ));
818
    }
819

820
    if self.versions.descriptions.len() != version_count {
2✔
821
      return Err(format!(
4✔
822
        "versions.descriptions length {} != {}",
823
        self.versions.descriptions.len(),
2✔
824
        version_count
825
      ));
826
    }
827

828
    if self.versions.icons.len() != version_count {
2✔
829
      return Err(format!(
4✔
830
        "versions.icons length {} != {}",
831
        self.versions.icons.len(),
2✔
832
        version_count
833
      ));
834
    }
835

836
    if self.versions.downloads.len() != version_count {
2✔
837
      return Err(format!(
4✔
838
        "versions.downloads length {} != {}",
839
        self.versions.downloads.len(),
2✔
840
        version_count
841
      ));
842
    }
843

844
    if self.versions.website_urls.len() != version_count {
2✔
845
      return Err(format!(
4✔
846
        "versions.website_urls length {} != {}",
847
        self.versions.website_urls.len(),
2✔
848
        version_count
849
      ));
850
    }
851

852
    if self.versions.is_active.len() != version_count {
2✔
853
      return Err(format!(
4✔
854
        "versions.is_active length {} != {}",
855
        self.versions.is_active.len(),
2✔
856
        version_count
857
      ));
858
    }
859

860
    if self.versions.uuid4s.len() != version_count {
2✔
861
      return Err(format!(
4✔
862
        "versions.uuid4s length {} != {}",
863
        self.versions.uuid4s.len(),
2✔
864
        version_count
865
      ));
866
    }
867

868
    if self.versions.file_sizes.len() != version_count {
2✔
869
      return Err(format!(
4✔
870
        "versions.file_sizes length {} != {}",
871
        self.versions.file_sizes.len(),
2✔
872
        version_count
873
      ));
874
    }
875

876
    for (idx, (start, end)) in self.version_ranges.iter().enumerate() {
6✔
877
      if start > end {
2✔
878
        return Err(format!(
4✔
879
          "Invalid version range at package {}: {} > {}",
880
          idx, start, end
881
        ));
882
      }
883

884
      if *end > version_count {
3✔
885
        return Err(format!(
4✔
886
          "Version range at package {} ends at {} but only {} versions exist",
887
          idx, end, version_count
888
        ));
889
      }
890
    }
891

892
    Ok(())
3✔
893
  }
894
}
895

896
/// Implementation block for InternedVersionManifest.
897
///
898
/// Currently empty but reserved for future version-specific methods.
899
impl InternedVersionManifest {}
900

901
/// Represents a mod package from the Thunderstore API.
902
///
903
/// This struct contains information about a mod package, including its metadata
904
/// and all available versions.
905
#[derive(Serialize, Deserialize, Clone)]
906
pub struct Package {
907
  /// The short name of the package
908
  pub name: Option<String>,
909
  /// The full name of the package, typically in format "Owner-Name"
910
  pub full_name: Option<String>,
911
  /// The username of the package owner
912
  pub owner: Option<String>,
913
  /// The URL to the package's page on Thunderstore
914
  pub package_url: Option<String>,
915
  /// When the package was first published
916
  #[serde(with = "time::serde::rfc3339")]
917
  pub date_created: time::OffsetDateTime,
918
  /// When the package was last updated
919
  #[serde(with = "time::serde::rfc3339")]
920
  pub date_updated: time::OffsetDateTime,
921
  /// Unique identifier for the package
922
  pub uuid4: Option<String>,
923
  /// User rating score for the package
924
  pub rating_score: Option<u32>,
925
  /// Whether the package is pinned by Thunderstore
926
  pub is_pinned: Option<bool>,
927
  /// Whether the package is marked as deprecated
928
  pub is_deprecated: Option<bool>,
929
  /// Whether the package contains NSFW content
930
  pub has_nsfw_content: Option<bool>,
931
  /// List of categories the package belongs to
932
  pub categories: Vec<String>,
933
  /// All available versions of the package
934
  pub versions: Vec<Version>,
935
}
936

937
/// Represents a specific version of a mod package.
938
///
939
/// This struct contains information about one version of a package,
940
/// including its version number, dependencies, and download information.
941
#[derive(Serialize, Deserialize, Debug, Clone)]
942
pub struct Version {
943
  /// The name of this version
944
  pub name: Option<String>,
945
  /// The full name of this version
946
  pub full_name: Option<String>,
947
  /// The description of this version
948
  pub description: Option<String>,
949
  /// URL to the icon for this version
950
  pub icon: Option<String>,
951
  /// The version number (e.g., "1.0.0")
952
  pub version_number: Option<String>,
953
  /// List of dependencies required by this version
954
  pub dependencies: Vec<String>,
955
  /// URL to download this version
956
  pub download_url: Option<String>,
957
  /// Number of times this version has been downloaded
958
  pub downloads: Option<u32>,
959
  /// When this version was published
960
  #[serde(with = "time::serde::rfc3339")]
961
  pub date_created: time::OffsetDateTime,
962
  /// URL to the website for this version
963
  pub website_url: Option<String>,
964
  /// Whether this version is active
965
  pub is_active: Option<bool>,
966
  /// Unique identifier for this version
967
  pub uuid4: Option<String>,
968
  /// Size of the download file in bytes
969
  pub file_size: Option<u64>,
970
}
971

972
impl Package {
973
  /// Returns the latest version of the package based on creation date.
974
  ///
975
  /// # Returns
976
  ///
977
  /// The most recent version of the package, or `None` if there are no versions.
978
  pub fn latest_version(&self) -> Option<&Version> {
2✔
979
    self
2✔
980
      .versions
981
      .iter()
982
      .max_by(|a, b| a.date_created.partial_cmp(&b.date_created).unwrap())
6✔
983
  }
984

985
  /// Constructs a filename and download URL for the latest version of the package.
986
  ///
987
  /// # Returns
988
  ///
989
  /// A tuple containing:
990
  /// - The formatted zip filename (e.g., "Owner-PackageName-1.0.0.zip")
991
  /// - The download URL
992
  ///
993
  /// Returns `None` if any required information is missing (latest version, download URL, etc.)
994
  #[cfg(test)]
995
  pub fn zip_and_url(&self) -> Option<(String, String)> {
2✔
996
    let pkg = self.latest_version()?;
4✔
997
    let url: String = pkg.download_url.clone()?;
10✔
998
    let version_number = pkg.version_number.as_ref()?;
12✔
999
    let package_name = &self.full_name.as_ref()?;
8✔
1000
    let zip_name = format!("{}-{}.zip", package_name, version_number);
4✔
1001

1002
    Some((zip_name, url))
2✔
1003
  }
1004
}
1005

1006
/// Custom debug implementation for Package to show relevant information.
1007
impl std::fmt::Debug for Package {
1008
  /// Formats the Package for debugging, including key information like name and dependencies.
1009
  ///
1010
  /// # Parameters
1011
  ///
1012
  /// * `f` - The formatter to write to
1013
  ///
1014
  /// # Returns
1015
  ///
1016
  /// A Result indicating whether the formatting succeeded
1017
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2✔
1018
    f.debug_struct("Package")
8✔
1019
      .field("full_name", &self.full_name)
2✔
1020
      .field(
1021
        "dependencies",
1022
        &self.latest_version().map(|v| &v.dependencies),
6✔
1023
      )
1024
      .field(
1025
        "download_url",
1026
        &self.latest_version().map(|v| &v.download_url),
6✔
1027
      )
1028
      .finish()
1029
  }
1030
}
1031

1032
/// Represents a graph of package dependencies to be resolved and installed.
1033
///
1034
/// This structure helps determine which packages need to be installed,
1035
/// including any dependencies they require.
1036
#[derive(Debug, Deserialize, Serialize)]
1037
pub struct DependencyGraph {
1038
  /// The list of package names that the user wants to install
1039
  install_packages: Vec<String>,
1040
}
1041

1042
impl DependencyGraph {
1043
  /// Creates a new DependencyGraph with the specified packages to install.
1044
  ///
1045
  /// # Parameters
1046
  ///
1047
  /// * `install_packages` - List of package names to install
1048
  ///
1049
  /// # Returns
1050
  ///
1051
  /// A new `DependencyGraph` instance
1052
  pub fn new(install_packages: Vec<String>) -> Self {
3✔
1053
    Self { install_packages }
1054
  }
1055

1056
  /// Resolves all dependencies for the requested packages.
1057
  ///
1058
  /// This method:
1059
  /// 1. Starts with the user-requested packages
1060
  /// 2. For each package, adds its dependencies to the list to process
1061
  /// 3. Continues until all dependencies are resolved
1062
  ///
1063
  /// # Parameters
1064
  ///
1065
  /// * `manifest` - A PackageManifest of all available packages
1066
  ///
1067
  /// # Returns
1068
  ///
1069
  /// A HashMap where keys are filenames and values are download URLs for all required packages
1070
  #[allow(dead_code)]
1071
  pub fn resolve(&self, manifest: &PackageManifest) -> HashMap<String, String> {
3✔
1072
    let name_index = manifest.build_name_index();
3✔
1073
    let mut sorted_set: BTreeSet<&str> = self.install_packages.iter().map(String::as_str).collect();
5✔
1074
    let mut install_indices = Vec::new();
3✔
1075

1076
    tracing::debug!("Starting with mod list of: {:#?}", sorted_set);
7✔
1077

1078
    while !sorted_set.is_empty() {
5✔
1079
      let Some(item) = sorted_set.pop_first() else {
5✔
1080
        continue;
1081
      };
1082

1083
      let item = item.split('-').take(2).collect::<Vec<&str>>().join("-");
4✔
1084
      let Some(&pkg_idx) = name_index.get(&item) else {
2✔
1085
        continue;
1086
      };
1087

1088
      if let Some(latest_ver_idx) = manifest.get_latest_version_at(pkg_idx) {
6✔
1089
        manifest.versions.dependencies[latest_ver_idx]
8✔
1090
          .iter()
1091
          .for_each(|dep| {
6✔
1092
            let dep_str = dep.as_str();
2✔
1093

1094
            tracing::debug!(
6✔
1095
              "Found dependency of {:#?} for {:#?} mod",
1096
              dep_str,
NEW
1097
              manifest.names[pkg_idx].as_ref().unwrap(),
×
1098
            );
1099
            sorted_set.insert(dep_str);
2✔
1100
          });
1101
      }
1102

1103
      install_indices.push(pkg_idx);
3✔
1104
    }
1105

1106
    Self::package_indices_to_urls(manifest, &install_indices)
6✔
1107
  }
1108

1109
  /// Converts package indices to a HashMap of filenames and download URLs.
1110
  ///
1111
  /// # Parameters
1112
  ///
1113
  /// * `manifest` - The PackageManifest
1114
  /// * `indices` - Vector of package indices
1115
  ///
1116
  /// # Returns
1117
  ///
1118
  /// A HashMap mapping filenames to download URLs
1119
  #[allow(dead_code)]
1120
  fn package_indices_to_urls(
3✔
1121
    manifest: &PackageManifest,
1122
    indices: &[usize],
1123
  ) -> HashMap<String, String> {
1124
    indices
3✔
1125
      .iter()
1126
      .filter_map(|&idx| {
6✔
1127
        let latest_ver_idx = manifest.get_latest_version_at(idx)?;
2✔
1128
        let url = manifest.versions.download_urls[latest_ver_idx].as_ref()?;
5✔
1129
        let version_number = manifest.versions.version_numbers[latest_ver_idx].as_ref()?;
5✔
1130
        let package_name = manifest.full_names[idx].as_ref()?;
6✔
1131
        let zip_name = format!("{}-{}.zip", package_name, version_number);
4✔
1132

1133
        Some((zip_name, url.clone()))
2✔
1134
      })
1135
      .collect()
1136
  }
1137

1138
  /// Resolves all dependencies for the requested packages using an interned manifest.
1139
  ///
1140
  /// This method:
1141
  /// 1. Starts with the user-requested packages
1142
  /// 2. For each package, adds its dependencies to the list to process
1143
  /// 3. Continues until all dependencies are resolved
1144
  ///
1145
  /// # Parameters
1146
  ///
1147
  /// * `manifest` - An InternedPackageManifest of all available packages
1148
  ///
1149
  /// # Returns
1150
  ///
1151
  /// A HashMap where keys are filenames and values are download URLs for all required packages
1152
  pub fn resolve_interned(&self, manifest: &InternedPackageManifest) -> HashMap<String, String> {
2✔
1153
    let name_index = manifest.build_name_index();
3✔
1154
    let mut sorted_set: BTreeSet<String> = self.install_packages.iter().cloned().collect();
6✔
1155
    let mut install_indices = Vec::new();
3✔
1156

1157
    tracing::debug!("Starting with mod list of: {:#?}", sorted_set);
8✔
1158

1159
    while !sorted_set.is_empty() {
4✔
1160
      let Some(item) = sorted_set.pop_first() else {
4✔
1161
        continue;
1162
      };
1163

1164
      let item = item.split('-').take(2).collect::<Vec<&str>>().join("-");
4✔
1165
      let Some(&pkg_idx) = name_index.get(&item) else {
2✔
1166
        continue;
1167
      };
1168

1169
      if let Some(latest_ver_idx) = manifest.get_latest_version_at(pkg_idx) {
5✔
1170
        let deps = resolve_vec(
1171
          &manifest.interner,
1172
          &manifest.versions.dependencies[latest_ver_idx],
5✔
1173
        );
1174

1175
        for dep in deps {
8✔
NEW
1176
          tracing::debug!(
×
1177
            "Found dependency of {:#?} for {:#?} mod",
1178
            dep,
1179
            manifest.resolve_name_at(pkg_idx).unwrap_or_default(),
1180
          );
1181
          sorted_set.insert(dep);
2✔
1182
        }
1183
      }
1184

1185
      install_indices.push(pkg_idx);
2✔
1186
    }
1187

1188
    Self::package_indices_to_urls_interned(manifest, &install_indices)
5✔
1189
  }
1190

1191
  /// Converts package indices to a HashMap of filenames and download URLs using an interned manifest.
1192
  ///
1193
  /// # Parameters
1194
  ///
1195
  /// * `manifest` - The InternedPackageManifest
1196
  /// * `indices` - Vector of package indices
1197
  ///
1198
  /// # Returns
1199
  ///
1200
  /// A HashMap mapping filenames to download URLs
1201
  fn package_indices_to_urls_interned(
4✔
1202
    manifest: &InternedPackageManifest,
1203
    indices: &[usize],
1204
  ) -> HashMap<String, String> {
1205
    indices
3✔
1206
      .iter()
1207
      .filter_map(|&idx| {
6✔
1208
        let latest_ver_idx = manifest.get_latest_version_at(idx)?;
4✔
1209
        let url = resolve_option(
6✔
1210
          &manifest.interner,
4✔
1211
          manifest.versions.download_urls[latest_ver_idx],
4✔
1212
        )?;
1213
        let version_number = resolve_option(
3✔
1214
          &manifest.interner,
3✔
1215
          manifest.versions.version_numbers[latest_ver_idx],
6✔
1216
        )?;
1217
        let package_name = manifest.resolve_full_name_at(idx)?;
6✔
1218
        let zip_name = format!("{}-{}.zip", package_name, version_number);
7✔
1219

1220
        Some((zip_name, url))
3✔
1221
      })
1222
      .collect()
1223
  }
1224
}
1225

1226
impl From<Vec<Package>> for PackageManifest {
1227
  fn from(packages: Vec<Package>) -> Self {
2✔
1228
    let num_packages = packages.len();
4✔
1229

1230
    let total_versions: usize = packages.iter().map(|p| p.versions.len()).sum();
6✔
1231

1232
    let mut names = Vec::with_capacity(num_packages);
2✔
1233
    let mut full_names = Vec::with_capacity(num_packages);
2✔
1234
    let mut owners = Vec::with_capacity(num_packages);
2✔
1235
    let mut package_urls = Vec::with_capacity(num_packages);
2✔
1236
    let mut dates_created = Vec::with_capacity(num_packages);
2✔
1237
    let mut dates_updated = Vec::with_capacity(num_packages);
2✔
1238
    let mut uuid4s = Vec::with_capacity(num_packages);
2✔
1239
    let mut rating_scores = Vec::with_capacity(num_packages);
2✔
1240
    let mut is_pinned = Vec::with_capacity(num_packages);
2✔
1241
    let mut is_deprecated = Vec::with_capacity(num_packages);
2✔
1242
    let mut has_nsfw_content = Vec::with_capacity(num_packages);
2✔
1243
    let mut categories = Vec::with_capacity(num_packages);
2✔
1244
    let mut version_ranges = Vec::with_capacity(num_packages);
2✔
1245

1246
    let mut package_indices = Vec::with_capacity(total_versions);
2✔
1247
    let mut version_numbers = Vec::with_capacity(total_versions);
2✔
1248
    let mut download_urls = Vec::with_capacity(total_versions);
2✔
1249
    let mut dependencies = Vec::with_capacity(total_versions);
2✔
1250
    let mut version_dates_created = Vec::with_capacity(total_versions);
2✔
1251
    let mut descriptions = Vec::with_capacity(total_versions);
2✔
1252
    let mut icons = Vec::with_capacity(total_versions);
2✔
1253
    let mut downloads = Vec::with_capacity(total_versions);
2✔
1254
    let mut website_urls = Vec::with_capacity(total_versions);
2✔
1255
    let mut is_active = Vec::with_capacity(total_versions);
2✔
1256
    let mut version_uuid4s = Vec::with_capacity(total_versions);
2✔
1257
    let mut file_sizes = Vec::with_capacity(total_versions);
2✔
1258

1259
    let mut version_offset = 0;
3✔
1260

1261
    for (pkg_idx, package) in packages.into_iter().enumerate() {
14✔
1262
      names.push(package.name);
2✔
1263
      full_names.push(package.full_name);
4✔
1264
      owners.push(package.owner);
4✔
1265
      package_urls.push(package.package_url);
4✔
1266
      dates_created.push(package.date_created);
4✔
1267
      dates_updated.push(package.date_updated);
4✔
1268
      uuid4s.push(package.uuid4);
3✔
1269
      rating_scores.push(package.rating_score);
2✔
1270
      is_pinned.push(package.is_pinned);
3✔
1271
      is_deprecated.push(package.is_deprecated);
2✔
1272
      has_nsfw_content.push(package.has_nsfw_content);
4✔
1273
      categories.push(package.categories);
3✔
1274

1275
      let version_count = package.versions.len();
5✔
1276
      version_ranges.push((version_offset, version_offset + version_count));
4✔
1277

1278
      for version in package.versions {
17✔
1279
        package_indices.push(pkg_idx);
3✔
1280
        version_numbers.push(version.version_number);
3✔
1281
        download_urls.push(version.download_url);
3✔
1282
        dependencies.push(version.dependencies);
3✔
1283
        version_dates_created.push(version.date_created);
3✔
1284
        descriptions.push(version.description);
3✔
1285
        icons.push(version.icon);
3✔
1286
        downloads.push(version.downloads);
3✔
1287
        website_urls.push(version.website_url);
3✔
1288
        is_active.push(version.is_active);
3✔
1289
        version_uuid4s.push(version.uuid4);
3✔
1290
        file_sizes.push(version.file_size);
3✔
1291
      }
1292

1293
      version_offset += version_count;
2✔
1294
    }
1295

1296
    PackageManifest {
1297
      names,
1298
      full_names,
1299
      owners,
1300
      package_urls,
1301
      dates_created,
1302
      dates_updated,
1303
      uuid4s,
1304
      rating_scores,
1305
      is_pinned,
1306
      is_deprecated,
1307
      has_nsfw_content,
1308
      categories,
1309
      version_ranges,
1310
      versions: VersionManifest {
2✔
1311
        package_indices,
1312
        version_numbers,
1313
        download_urls,
1314
        dependencies,
1315
        dates_created: version_dates_created,
1316
        descriptions,
1317
        icons,
1318
        downloads,
1319
        website_urls,
1320
        is_active,
1321
        uuid4s: version_uuid4s,
1322
        file_sizes,
1323
      },
1324
    }
1325
  }
1326
}
1327

1328
impl From<Vec<Package>> for InternedPackageManifest {
1329
  fn from(packages: Vec<Package>) -> Self {
2✔
1330
    let num_packages = packages.len();
4✔
1331
    let total_versions: usize = packages.iter().map(|p| p.versions.len()).sum();
6✔
1332

1333
    let mut interner = StringInterner::default();
2✔
1334

1335
    let mut names = Vec::with_capacity(num_packages);
2✔
1336
    let mut full_names = Vec::with_capacity(num_packages);
2✔
1337
    let mut owners = Vec::with_capacity(num_packages);
2✔
1338
    let mut package_urls = Vec::with_capacity(num_packages);
2✔
1339
    let mut dates_created = Vec::with_capacity(num_packages);
2✔
1340
    let mut dates_updated = Vec::with_capacity(num_packages);
2✔
1341
    let mut uuid4s = Vec::with_capacity(num_packages);
2✔
1342
    let mut rating_scores = Vec::with_capacity(num_packages);
2✔
1343
    let mut is_pinned = Vec::with_capacity(num_packages);
2✔
1344
    let mut is_deprecated = Vec::with_capacity(num_packages);
2✔
1345
    let mut has_nsfw_content = Vec::with_capacity(num_packages);
2✔
1346
    let mut categories = Vec::with_capacity(num_packages);
2✔
1347
    let mut version_ranges = Vec::with_capacity(num_packages);
2✔
1348

1349
    let mut package_indices = Vec::with_capacity(total_versions);
2✔
1350
    let mut version_numbers = Vec::with_capacity(total_versions);
2✔
1351
    let mut download_urls = Vec::with_capacity(total_versions);
2✔
1352
    let mut dependencies = Vec::with_capacity(total_versions);
2✔
1353
    let mut version_dates_created = Vec::with_capacity(total_versions);
2✔
1354
    let mut descriptions = Vec::with_capacity(total_versions);
2✔
1355
    let mut icons = Vec::with_capacity(total_versions);
2✔
1356
    let mut downloads = Vec::with_capacity(total_versions);
2✔
1357
    let mut website_urls = Vec::with_capacity(total_versions);
2✔
1358
    let mut is_active = Vec::with_capacity(total_versions);
2✔
1359
    let mut version_uuid4s = Vec::with_capacity(total_versions);
2✔
1360
    let mut file_sizes = Vec::with_capacity(total_versions);
2✔
1361

1362
    let mut version_offset = 0;
2✔
1363

1364
    for (pkg_idx, package) in packages.into_iter().enumerate() {
10✔
1365
      names.push(intern_option(&mut interner, package.name.as_deref()));
4✔
1366
      full_names.push(intern_option(&mut interner, package.full_name.as_deref()));
2✔
1367
      owners.push(intern_option(&mut interner, package.owner.as_deref()));
2✔
1368
      package_urls.push(intern_option(&mut interner, package.package_url.as_deref()));
2✔
1369
      dates_created.push(package.date_created);
2✔
1370
      dates_updated.push(package.date_updated);
2✔
1371
      uuid4s.push(intern_option(&mut interner, package.uuid4.as_deref()));
2✔
1372
      rating_scores.push(package.rating_score);
2✔
1373
      is_pinned.push(package.is_pinned);
2✔
1374
      is_deprecated.push(package.is_deprecated);
2✔
1375
      has_nsfw_content.push(package.has_nsfw_content);
2✔
1376
      categories.push(intern_vec(&mut interner, &package.categories));
2✔
1377

1378
      let version_count = package.versions.len();
2✔
1379
      version_ranges.push((version_offset, version_offset + version_count));
2✔
1380

1381
      for version in package.versions {
6✔
1382
        package_indices.push(pkg_idx);
2✔
1383
        version_numbers.push(intern_option(
2✔
1384
          &mut interner,
1385
          version.version_number.as_deref(),
2✔
1386
        ));
1387
        download_urls.push(intern_option(
2✔
1388
          &mut interner,
1389
          version.download_url.as_deref(),
2✔
1390
        ));
1391
        dependencies.push(intern_vec(&mut interner, &version.dependencies));
2✔
1392
        version_dates_created.push(version.date_created);
2✔
1393
        descriptions.push(intern_option(&mut interner, version.description.as_deref()));
2✔
1394
        icons.push(intern_option(&mut interner, version.icon.as_deref()));
2✔
1395
        downloads.push(version.downloads);
2✔
1396
        website_urls.push(intern_option(&mut interner, version.website_url.as_deref()));
2✔
1397
        is_active.push(version.is_active);
2✔
1398
        version_uuid4s.push(intern_option(&mut interner, version.uuid4.as_deref()));
2✔
1399
        file_sizes.push(version.file_size);
2✔
1400
      }
1401

1402
      version_offset += version_count;
2✔
1403
    }
1404

1405
    InternedPackageManifest {
1406
      interner,
1407
      names,
1408
      full_names,
1409
      owners,
1410
      package_urls,
1411
      dates_created,
1412
      dates_updated,
1413
      uuid4s,
1414
      rating_scores,
1415
      is_pinned,
1416
      is_deprecated,
1417
      has_nsfw_content,
1418
      categories,
1419
      version_ranges,
1420
      versions: InternedVersionManifest {
2✔
1421
        package_indices,
1422
        version_numbers,
1423
        download_urls,
1424
        dependencies,
1425
        dates_created: version_dates_created,
1426
        descriptions,
1427
        icons,
1428
        downloads,
1429
        website_urls,
1430
        is_active,
1431
        uuid4s: version_uuid4s,
1432
        file_sizes,
1433
      },
1434
    }
1435
  }
1436
}
1437

1438
impl From<PackageManifest> for InternedPackageManifest {
1439
  fn from(manifest: PackageManifest) -> Self {
2✔
1440
    let mut interner = StringInterner::default();
2✔
1441

1442
    let names: Vec<Option<InternKey>> = manifest
4✔
1443
      .names
1444
      .iter()
1445
      .map(|s| intern_option(&mut interner, s.as_deref()))
4✔
1446
      .collect();
1447

1448
    let full_names: Vec<Option<InternKey>> = manifest
4✔
1449
      .full_names
1450
      .iter()
1451
      .map(|s| intern_option(&mut interner, s.as_deref()))
4✔
1452
      .collect();
1453

1454
    let owners: Vec<Option<InternKey>> = manifest
4✔
1455
      .owners
1456
      .iter()
1457
      .map(|s| intern_option(&mut interner, s.as_deref()))
4✔
1458
      .collect();
1459

1460
    let package_urls: Vec<Option<InternKey>> = manifest
4✔
1461
      .package_urls
1462
      .iter()
1463
      .map(|s| intern_option(&mut interner, s.as_deref()))
4✔
1464
      .collect();
1465

1466
    let uuid4s: Vec<Option<InternKey>> = manifest
4✔
1467
      .uuid4s
1468
      .iter()
1469
      .map(|s| intern_option(&mut interner, s.as_deref()))
4✔
1470
      .collect();
1471

1472
    let categories: Vec<Vec<InternKey>> = manifest
4✔
1473
      .categories
1474
      .iter()
1475
      .map(|c| intern_vec(&mut interner, c))
4✔
1476
      .collect();
1477

1478
    let version_numbers: Vec<Option<InternKey>> = manifest
4✔
1479
      .versions
1480
      .version_numbers
1481
      .iter()
1482
      .map(|s| intern_option(&mut interner, s.as_deref()))
4✔
1483
      .collect();
1484

1485
    let download_urls: Vec<Option<InternKey>> = manifest
4✔
1486
      .versions
1487
      .download_urls
1488
      .iter()
1489
      .map(|s| intern_option(&mut interner, s.as_deref()))
4✔
1490
      .collect();
1491

1492
    let dependencies: Vec<Vec<InternKey>> = manifest
4✔
1493
      .versions
1494
      .dependencies
1495
      .iter()
1496
      .map(|d| intern_vec(&mut interner, d))
4✔
1497
      .collect();
1498

1499
    let descriptions: Vec<Option<InternKey>> = manifest
4✔
1500
      .versions
1501
      .descriptions
1502
      .iter()
1503
      .map(|s| intern_option(&mut interner, s.as_deref()))
4✔
1504
      .collect();
1505

1506
    let icons: Vec<Option<InternKey>> = manifest
4✔
1507
      .versions
1508
      .icons
1509
      .iter()
1510
      .map(|s| intern_option(&mut interner, s.as_deref()))
4✔
1511
      .collect();
1512

1513
    let website_urls: Vec<Option<InternKey>> = manifest
4✔
1514
      .versions
1515
      .website_urls
1516
      .iter()
1517
      .map(|s| intern_option(&mut interner, s.as_deref()))
4✔
1518
      .collect();
1519

1520
    let version_uuid4s: Vec<Option<InternKey>> = manifest
4✔
1521
      .versions
1522
      .uuid4s
1523
      .iter()
1524
      .map(|s| intern_option(&mut interner, s.as_deref()))
4✔
1525
      .collect();
1526

1527
    InternedPackageManifest {
1528
      interner,
1529
      names,
1530
      full_names,
1531
      owners,
1532
      package_urls,
1533
      dates_created: manifest.dates_created,
2✔
1534
      dates_updated: manifest.dates_updated,
2✔
1535
      uuid4s,
1536
      rating_scores: manifest.rating_scores,
2✔
1537
      is_pinned: manifest.is_pinned,
2✔
1538
      is_deprecated: manifest.is_deprecated,
2✔
1539
      has_nsfw_content: manifest.has_nsfw_content,
2✔
1540
      categories,
1541
      version_ranges: manifest.version_ranges,
2✔
1542
      versions: InternedVersionManifest {
2✔
1543
        package_indices: manifest.versions.package_indices,
1544
        version_numbers,
1545
        download_urls,
1546
        dependencies,
1547
        dates_created: manifest.versions.dates_created,
1548
        descriptions,
1549
        icons,
1550
        downloads: manifest.versions.downloads,
1551
        website_urls,
1552
        is_active: manifest.versions.is_active,
1553
        uuid4s: version_uuid4s,
1554
        file_sizes: manifest.versions.file_sizes,
1555
      },
1556
    }
1557
  }
1558
}
1559

1560
/// Converts an InternKey to its u32 index representation for serialization.
1561
///
1562
/// # Parameters
1563
///
1564
/// * `_interner` - The StringInterner (unused but kept for API symmetry)
1565
/// * `key` - The InternKey to convert
1566
///
1567
/// # Returns
1568
///
1569
/// The u32 index corresponding to the key
1570
fn key_to_index(_interner: &StringInterner, key: InternKey) -> u32 {
2✔
1571
  key.into_usize() as u32
2✔
1572
}
1573

1574
/// Converts a u32 index back to an InternKey for deserialization.
1575
///
1576
/// # Parameters
1577
///
1578
/// * `index` - The u32 index to convert
1579
///
1580
/// # Returns
1581
///
1582
/// The InternKey corresponding to the index
1583
///
1584
/// # Panics
1585
///
1586
/// Panics if the index cannot be converted to a valid InternKey
1587
fn index_to_key(index: u32) -> InternKey {
2✔
1588
  InternKey::try_from_usize(index as usize).expect("Invalid intern key index")
2✔
1589
}
1590

1591
impl From<&InternedPackageManifest> for SerializableInternedManifest {
1592
  fn from(manifest: &InternedPackageManifest) -> Self {
2✔
1593
    let string_table: Vec<String> = manifest.interner.strings().map(|s| s.to_string()).collect();
6✔
1594

1595
    let names: Vec<Option<u32>> = manifest
4✔
1596
      .names
1597
      .iter()
1598
      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
9✔
1599
      .collect();
1600

1601
    let full_names: Vec<Option<u32>> = manifest
4✔
1602
      .full_names
1603
      .iter()
1604
      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
8✔
1605
      .collect();
1606

1607
    let owners: Vec<Option<u32>> = manifest
4✔
1608
      .owners
1609
      .iter()
1610
      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
8✔
1611
      .collect();
1612

1613
    let package_urls: Vec<Option<u32>> = manifest
4✔
1614
      .package_urls
1615
      .iter()
1616
      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
8✔
1617
      .collect();
1618

1619
    let uuid4s: Vec<Option<u32>> = manifest
4✔
1620
      .uuid4s
1621
      .iter()
1622
      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
8✔
1623
      .collect();
1624

1625
    let categories: Vec<Vec<u32>> = manifest
6✔
1626
      .categories
1627
      .iter()
1628
      .map(|keys| {
2✔
1629
        keys
4✔
1630
          .iter()
1631
          .map(|k| key_to_index(&manifest.interner, *k))
7✔
1632
          .collect()
1633
      })
1634
      .collect();
1635

1636
    let version_numbers: Vec<Option<u32>> = manifest
6✔
1637
      .versions
1638
      .version_numbers
1639
      .iter()
1640
      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
8✔
1641
      .collect();
1642

1643
    let download_urls: Vec<Option<u32>> = manifest
6✔
1644
      .versions
1645
      .download_urls
1646
      .iter()
1647
      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
8✔
1648
      .collect();
1649

1650
    let dependencies: Vec<Vec<u32>> = manifest
6✔
1651
      .versions
1652
      .dependencies
1653
      .iter()
1654
      .map(|keys| {
2✔
1655
        keys
4✔
1656
          .iter()
1657
          .map(|k| key_to_index(&manifest.interner, *k))
2✔
1658
          .collect()
1659
      })
1660
      .collect();
1661

1662
    let descriptions: Vec<Option<u32>> = manifest
6✔
1663
      .versions
1664
      .descriptions
1665
      .iter()
1666
      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
8✔
1667
      .collect();
1668

1669
    let icons: Vec<Option<u32>> = manifest
6✔
1670
      .versions
1671
      .icons
1672
      .iter()
1673
      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
8✔
1674
      .collect();
1675

1676
    let website_urls: Vec<Option<u32>> = manifest
6✔
1677
      .versions
1678
      .website_urls
1679
      .iter()
1680
      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
8✔
1681
      .collect();
1682

1683
    let version_uuid4s: Vec<Option<u32>> = manifest
6✔
1684
      .versions
1685
      .uuid4s
1686
      .iter()
1687
      .map(|k| k.map(|key| key_to_index(&manifest.interner, key)))
8✔
1688
      .collect();
1689

1690
    SerializableInternedManifest {
1691
      string_table,
1692
      names,
1693
      full_names,
1694
      owners,
1695
      package_urls,
1696
      dates_created: manifest.dates_created.clone(),
3✔
1697
      dates_updated: manifest.dates_updated.clone(),
3✔
1698
      uuid4s,
1699
      rating_scores: manifest.rating_scores.clone(),
3✔
1700
      is_pinned: manifest.is_pinned.clone(),
3✔
1701
      is_deprecated: manifest.is_deprecated.clone(),
3✔
1702
      has_nsfw_content: manifest.has_nsfw_content.clone(),
3✔
1703
      categories,
1704
      version_ranges: manifest.version_ranges.clone(),
3✔
1705
      versions: SerializableInternedVersionManifest {
3✔
1706
        package_indices: manifest.versions.package_indices.clone(),
1707
        version_numbers,
1708
        download_urls,
1709
        dependencies,
1710
        dates_created: manifest.versions.dates_created.clone(),
1711
        descriptions,
1712
        icons,
1713
        downloads: manifest.versions.downloads.clone(),
1714
        website_urls,
1715
        is_active: manifest.versions.is_active.clone(),
1716
        uuid4s: version_uuid4s,
1717
        file_sizes: manifest.versions.file_sizes.clone(),
1718
      },
1719
    }
1720
  }
1721
}
1722

1723
impl From<SerializableInternedManifest> for InternedPackageManifest {
1724
  fn from(manifest: SerializableInternedManifest) -> Self {
2✔
1725
    let mut interner = StringInterner::default();
2✔
1726

1727
    for s in &manifest.string_table {
6✔
1728
      interner.get_or_intern(s);
4✔
1729
    }
1730

1731
    let names: Vec<Option<InternKey>> = manifest
4✔
1732
      .names
1733
      .iter()
1734
      .map(|idx| idx.map(index_to_key))
4✔
1735
      .collect();
1736

1737
    let full_names: Vec<Option<InternKey>> = manifest
4✔
1738
      .full_names
1739
      .iter()
1740
      .map(|idx| idx.map(index_to_key))
4✔
1741
      .collect();
1742

1743
    let owners: Vec<Option<InternKey>> = manifest
4✔
1744
      .owners
1745
      .iter()
1746
      .map(|idx| idx.map(index_to_key))
4✔
1747
      .collect();
1748

1749
    let package_urls: Vec<Option<InternKey>> = manifest
4✔
1750
      .package_urls
1751
      .iter()
1752
      .map(|idx| idx.map(index_to_key))
4✔
1753
      .collect();
1754

1755
    let uuid4s: Vec<Option<InternKey>> = manifest
4✔
1756
      .uuid4s
1757
      .iter()
1758
      .map(|idx| idx.map(index_to_key))
4✔
1759
      .collect();
1760

1761
    let categories: Vec<Vec<InternKey>> = manifest
4✔
1762
      .categories
1763
      .iter()
1764
      .map(|indices| indices.iter().map(|&i| index_to_key(i)).collect())
8✔
1765
      .collect();
1766

1767
    let version_numbers: Vec<Option<InternKey>> = manifest
4✔
1768
      .versions
1769
      .version_numbers
1770
      .iter()
1771
      .map(|idx| idx.map(index_to_key))
4✔
1772
      .collect();
1773

1774
    let download_urls: Vec<Option<InternKey>> = manifest
4✔
1775
      .versions
1776
      .download_urls
1777
      .iter()
1778
      .map(|idx| idx.map(index_to_key))
4✔
1779
      .collect();
1780

1781
    let dependencies: Vec<Vec<InternKey>> = manifest
4✔
1782
      .versions
1783
      .dependencies
1784
      .iter()
1785
      .map(|indices| indices.iter().map(|&i| index_to_key(i)).collect())
4✔
1786
      .collect();
1787

1788
    let descriptions: Vec<Option<InternKey>> = manifest
4✔
1789
      .versions
1790
      .descriptions
1791
      .iter()
1792
      .map(|idx| idx.map(index_to_key))
4✔
1793
      .collect();
1794

1795
    let icons: Vec<Option<InternKey>> = manifest
4✔
1796
      .versions
1797
      .icons
1798
      .iter()
1799
      .map(|idx| idx.map(index_to_key))
4✔
1800
      .collect();
1801

1802
    let website_urls: Vec<Option<InternKey>> = manifest
4✔
1803
      .versions
1804
      .website_urls
1805
      .iter()
1806
      .map(|idx| idx.map(index_to_key))
4✔
1807
      .collect();
1808

1809
    let version_uuid4s: Vec<Option<InternKey>> = manifest
4✔
1810
      .versions
1811
      .uuid4s
1812
      .iter()
1813
      .map(|idx| idx.map(index_to_key))
4✔
1814
      .collect();
1815

1816
    InternedPackageManifest {
1817
      interner,
1818
      names,
1819
      full_names,
1820
      owners,
1821
      package_urls,
1822
      dates_created: manifest.dates_created,
2✔
1823
      dates_updated: manifest.dates_updated,
2✔
1824
      uuid4s,
1825
      rating_scores: manifest.rating_scores,
2✔
1826
      is_pinned: manifest.is_pinned,
2✔
1827
      is_deprecated: manifest.is_deprecated,
2✔
1828
      has_nsfw_content: manifest.has_nsfw_content,
2✔
1829
      categories,
1830
      version_ranges: manifest.version_ranges,
2✔
1831
      versions: InternedVersionManifest {
2✔
1832
        package_indices: manifest.versions.package_indices,
1833
        version_numbers,
1834
        download_urls,
1835
        dependencies,
1836
        dates_created: manifest.versions.dates_created,
1837
        descriptions,
1838
        icons,
1839
        downloads: manifest.versions.downloads,
1840
        website_urls,
1841
        is_active: manifest.versions.is_active,
1842
        uuid4s: version_uuid4s,
1843
        file_sizes: manifest.versions.file_sizes,
1844
      },
1845
    }
1846
  }
1847
}
1848

1849
#[cfg(test)]
1850
mod tests {
1851
  use super::*;
1852
  use time::OffsetDateTime;
1853

1854
  fn create_test_package(name: &str, owner: &str, version: &str) -> Package {
1855
    let full_name = format!("{}-{}", owner, name);
1856
    Package {
1857
      name: Some(name.to_string()),
1858
      full_name: Some(full_name.clone()),
1859
      owner: Some(owner.to_string()),
1860
      package_url: Some(format!("https://example.com/{}", name)),
1861
      date_created: OffsetDateTime::now_utc(),
1862
      date_updated: OffsetDateTime::now_utc(),
1863
      uuid4: Some("test-uuid".to_string()),
1864
      rating_score: Some(5),
1865
      is_pinned: Some(false),
1866
      is_deprecated: Some(false),
1867
      has_nsfw_content: Some(false),
1868
      categories: vec!["category1".to_string()],
1869
      versions: vec![Version {
1870
        name: Some(name.to_string()),
1871
        full_name: Some(full_name.clone()),
1872
        description: Some("Test description".to_string()),
1873
        icon: Some("icon.png".to_string()),
1874
        version_number: Some(version.to_string()),
1875
        dependencies: vec![],
1876
        download_url: Some(format!("https://example.com/{}/download", name)),
1877
        downloads: Some(100),
1878
        date_created: OffsetDateTime::now_utc(),
1879
        website_url: Some("https://example.com".to_string()),
1880
        is_active: Some(true),
1881
        uuid4: Some("test-version-uuid".to_string()),
1882
        file_size: Some(1024),
1883
      }],
1884
    }
1885
  }
1886

1887
  fn create_test_package_with_dependencies(
1888
    name: &str,
1889
    owner: &str,
1890
    version: &str,
1891
    dependencies: Vec<String>,
1892
  ) -> Package {
1893
    let mut pkg = create_test_package(name, owner, version);
1894
    if let Some(latest_version) = pkg.versions.first_mut() {
1895
      latest_version.dependencies = dependencies;
1896
    }
1897
    pkg
1898
  }
1899

1900
  #[test]
1901
  fn test_latest_version() {
1902
    let mut pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
1903

1904
    let older_version = Version {
1905
      name: Some("TestMod".to_string()),
1906
      full_name: Some("TestOwner-TestMod".to_string()),
1907
      description: Some("Older version".to_string()),
1908
      icon: Some("icon.png".to_string()),
1909
      version_number: Some("0.9.0".to_string()),
1910
      dependencies: vec![],
1911
      download_url: Some("https://example.com/TestMod/download-old".to_string()),
1912
      downloads: Some(50),
1913
      date_created: OffsetDateTime::now_utc().saturating_sub(time::Duration::days(30)),
1914
      website_url: Some("https://example.com".to_string()),
1915
      is_active: Some(true),
1916
      uuid4: Some("old-version-uuid".to_string()),
1917
      file_size: Some(512),
1918
    };
1919
    pkg.versions.push(older_version);
1920

1921
    let newer_version = Version {
1922
      name: Some("TestMod".to_string()),
1923
      full_name: Some("TestOwner-TestMod".to_string()),
1924
      description: Some("Newer version".to_string()),
1925
      icon: Some("icon.png".to_string()),
1926
      version_number: Some("1.1.0".to_string()),
1927
      dependencies: vec![],
1928
      download_url: Some("https://example.com/TestMod/download-new".to_string()),
1929
      downloads: Some(150),
1930
      date_created: OffsetDateTime::now_utc().saturating_add(time::Duration::days(30)),
1931
      website_url: Some("https://example.com".to_string()),
1932
      is_active: Some(true),
1933
      uuid4: Some("new-version-uuid".to_string()),
1934
      file_size: Some(2048),
1935
    };
1936
    pkg.versions.push(newer_version);
1937

1938
    let latest = pkg.latest_version().unwrap();
1939
    assert_eq!(latest.version_number, Some("1.1.0".to_string()));
1940
    assert_eq!(latest.description, Some("Newer version".to_string()));
1941
  }
1942

1943
  #[test]
1944
  fn test_zip_and_url() {
1945
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
1946
    let (filename, url) = pkg.zip_and_url().unwrap();
1947

1948
    assert_eq!(filename, "TestOwner-TestMod-1.0.0.zip");
1949
    assert_eq!(url, "https://example.com/TestMod/download");
1950
  }
1951

1952
  #[test]
1953
  fn test_zip_and_url_missing_data() {
1954
    let mut pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
1955

1956
    if let Some(version) = pkg.versions.first_mut() {
1957
      version.version_number = None;
1958
    }
1959

1960
    assert!(pkg.zip_and_url().is_none());
1961

1962
    let mut pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
1963

1964
    if let Some(version) = pkg.versions.first_mut() {
1965
      version.download_url = None;
1966
    }
1967

1968
    assert!(pkg.zip_and_url().is_none());
1969

1970
    let mut pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
1971
    pkg.full_name = None;
1972

1973
    assert!(pkg.zip_and_url().is_none());
1974
  }
1975

1976
  #[test]
1977
  fn test_dependency_graph_resolve() {
1978
    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
1979
    let pkg2 = create_test_package_with_dependencies(
1980
      "ModB",
1981
      "Owner2",
1982
      "2.0.0",
1983
      vec!["Owner3-ModC".to_string()],
1984
    );
1985
    let pkg3 = create_test_package_with_dependencies(
1986
      "ModC",
1987
      "Owner3",
1988
      "1.5.0",
1989
      vec!["Owner4-ModD".to_string()],
1990
    );
1991
    let pkg4 = create_test_package("ModD", "Owner4", "0.9.0");
1992

1993
    let packages = vec![pkg1, pkg2, pkg3, pkg4];
1994
    let manifest: PackageManifest = packages.into();
1995

1996
    let dg1 = DependencyGraph::new(vec!["Owner1-ModA".to_string()]);
1997
    let result1 = dg1.resolve(&manifest);
1998
    assert_eq!(result1.len(), 1);
1999
    assert!(result1.contains_key("Owner1-ModA-1.0.0.zip"));
2000

2001
    let dg2 = DependencyGraph::new(vec!["Owner2-ModB".to_string()]);
2002
    let result2 = dg2.resolve(&manifest);
2003
    assert_eq!(result2.len(), 3);
2004
    assert!(result2.contains_key("Owner2-ModB-2.0.0.zip"));
2005
    assert!(result2.contains_key("Owner3-ModC-1.5.0.zip"));
2006
    assert!(result2.contains_key("Owner4-ModD-0.9.0.zip"));
2007

2008
    let dg3 = DependencyGraph::new(vec!["Owner1-ModA".to_string(), "Owner3-ModC".to_string()]);
2009
    let result3 = dg3.resolve(&manifest);
2010
    assert_eq!(result3.len(), 3);
2011
    assert!(result3.contains_key("Owner1-ModA-1.0.0.zip"));
2012
    assert!(result3.contains_key("Owner3-ModC-1.5.0.zip"));
2013
    assert!(result3.contains_key("Owner4-ModD-0.9.0.zip"));
2014

2015
    let dg4 = DependencyGraph::new(vec!["Owner5-ModE".to_string()]);
2016
    let result4 = dg4.resolve(&manifest);
2017
    assert_eq!(result4.len(), 0);
2018
  }
2019

2020
  #[test]
2021
  fn test_package_debug() {
2022
    let pkg = create_test_package("ModTest", "OwnerTest", "1.2.3");
2023
    let debug_output = format!("{:?}", pkg);
2024

2025
    assert!(debug_output.contains("Package"));
2026
    assert!(debug_output.contains("full_name: Some(\"OwnerTest-ModTest\")"));
2027
    assert!(debug_output.contains("dependencies: Some([])"));
2028
    assert!(
2029
      debug_output.contains("download_url: Some(Some(\"https://example.com/ModTest/download\"))")
2030
    );
2031

2032
    let mut pkg_with_deps = create_test_package_with_dependencies(
2033
      "ModWithDeps",
2034
      "OwnerTest",
2035
      "2.0.0",
2036
      vec!["Dep1-Mod1".to_string(), "Dep2-Mod2".to_string()],
2037
    );
2038
    let debug_with_deps = format!("{:?}", pkg_with_deps);
2039

2040
    assert!(debug_with_deps.contains("dependencies: Some([\"Dep1-Mod1\", \"Dep2-Mod2\"])"));
2041

2042
    pkg_with_deps.full_name = None;
2043
    let debug_missing_name = format!("{:?}", pkg_with_deps);
2044
    assert!(debug_missing_name.contains("full_name: None"));
2045

2046
    let mut pkg_no_version = create_test_package("NoVersion", "TestOwner", "1.0.0");
2047
    pkg_no_version.versions.clear();
2048
    let debug_no_version = format!("{:?}", pkg_no_version);
2049

2050
    assert!(debug_no_version.contains("dependencies: None"));
2051
    assert!(debug_no_version.contains("download_url: None"));
2052
  }
2053

2054
  #[test]
2055
  fn test_package_to_manifest_conversion() {
2056
    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
2057
    let pkg2 = create_test_package_with_dependencies(
2058
      "ModB",
2059
      "Owner2",
2060
      "2.0.0",
2061
      vec!["Owner3-ModC".to_string()],
2062
    );
2063

2064
    let packages = vec![pkg1, pkg2];
2065
    let manifest: PackageManifest = packages.clone().into();
2066

2067
    assert_eq!(manifest.len(), 2);
2068
    assert_eq!(manifest.names[0], Some("ModA".to_string()));
2069
    assert_eq!(manifest.names[1], Some("ModB".to_string()));
2070
    assert_eq!(manifest.full_names[0], Some("Owner1-ModA".to_string()));
2071
    assert_eq!(manifest.full_names[1], Some("Owner2-ModB".to_string()));
2072

2073
    assert_eq!(manifest.versions.version_numbers.len(), 2);
2074

2075
    assert_eq!(manifest.version_ranges[0], (0, 1));
2076
    assert_eq!(manifest.version_ranges[1], (1, 2));
2077

2078
    assert_eq!(
2079
      manifest.versions.version_numbers[0],
2080
      Some("1.0.0".to_string())
2081
    );
2082
    assert_eq!(
2083
      manifest.versions.version_numbers[1],
2084
      Some("2.0.0".to_string())
2085
    );
2086

2087
    assert_eq!(manifest.versions.dependencies[0], Vec::<String>::new());
2088
    assert_eq!(
2089
      manifest.versions.dependencies[1],
2090
      vec!["Owner3-ModC".to_string()]
2091
    );
2092
  }
2093

2094
  #[test]
2095
  fn test_manifest_get_package_methods() {
2096
    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
2097
    let pkg2 = create_test_package_with_dependencies(
2098
      "ModB",
2099
      "Owner2",
2100
      "2.0.0",
2101
      vec!["Owner3-ModC".to_string()],
2102
    );
2103

2104
    let original_packages = vec![pkg1, pkg2];
2105
    let manifest: PackageManifest = original_packages.clone().into();
2106

2107
    assert_eq!(manifest.len(), 2);
2108

2109
    let pkg_a = manifest.get_package_by_full_name("Owner1-ModA").unwrap();
2110
    assert_eq!(pkg_a.name, Some("ModA".to_string()));
2111
    assert_eq!(pkg_a.owner, Some("Owner1".to_string()));
2112
    assert_eq!(pkg_a.versions.len(), 1);
2113
    assert_eq!(pkg_a.versions[0].version_number, Some("1.0.0".to_string()));
2114

2115
    let pkg_b = manifest.get_package_by_full_name("Owner2-ModB").unwrap();
2116
    assert_eq!(pkg_b.name, Some("ModB".to_string()));
2117
    assert_eq!(pkg_b.owner, Some("Owner2".to_string()));
2118
    assert_eq!(pkg_b.versions.len(), 1);
2119
    assert_eq!(pkg_b.versions[0].version_number, Some("2.0.0".to_string()));
2120
    assert_eq!(
2121
      pkg_b.versions[0].dependencies,
2122
      vec!["Owner3-ModC".to_string()]
2123
    );
2124

2125
    let idx = manifest.find_index_by_full_name("Owner1-ModA").unwrap();
2126
    assert_eq!(manifest.names[idx], Some("ModA".to_string()));
2127
  }
2128

2129
  #[test]
2130
  fn test_round_trip_conversion() {
2131
    let mut pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2132

2133
    pkg.versions.push(Version {
2134
      name: Some("TestMod".to_string()),
2135
      full_name: Some("TestOwner-TestMod".to_string()),
2136
      description: Some("Version 2".to_string()),
2137
      icon: Some("icon2.png".to_string()),
2138
      version_number: Some("2.0.0".to_string()),
2139
      dependencies: vec!["Dep1-Mod1".to_string()],
2140
      download_url: Some("https://example.com/TestMod/download-v2".to_string()),
2141
      downloads: Some(200),
2142
      date_created: OffsetDateTime::now_utc(),
2143
      website_url: Some("https://example.com".to_string()),
2144
      is_active: Some(true),
2145
      uuid4: Some("test-version-uuid-2".to_string()),
2146
      file_size: Some(2048),
2147
    });
2148

2149
    let original_full_name = pkg.full_name.clone().unwrap();
2150
    let original_version_count = pkg.versions.len();
2151

2152
    let packages = vec![pkg];
2153
    let manifest: PackageManifest = packages.into();
2154

2155
    let reconstructed = manifest
2156
      .get_package_by_full_name(&original_full_name)
2157
      .unwrap();
2158
    assert_eq!(reconstructed.full_name, Some(original_full_name));
2159
    assert_eq!(reconstructed.versions.len(), original_version_count);
2160
    assert_eq!(
2161
      reconstructed.versions[0].version_number,
2162
      Some("1.0.0".to_string())
2163
    );
2164
    assert_eq!(
2165
      reconstructed.versions[1].version_number,
2166
      Some("2.0.0".to_string())
2167
    );
2168
    assert_eq!(
2169
      reconstructed.versions[1].dependencies,
2170
      vec!["Dep1-Mod1".to_string()]
2171
    );
2172
  }
2173

2174
  #[test]
2175
  fn test_validate_valid_manifest() {
2176
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2177
    let packages = vec![pkg];
2178
    let manifest: PackageManifest = packages.into();
2179

2180
    assert!(manifest.validate().is_ok());
2181
  }
2182

2183
  #[test]
2184
  fn test_validate_mismatched_full_names_length() {
2185
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2186
    let packages = vec![pkg];
2187
    let mut manifest: PackageManifest = packages.into();
2188

2189
    manifest.full_names.pop();
2190

2191
    let result = manifest.validate();
2192
    assert!(result.is_err());
2193
    assert!(result.unwrap_err().contains("full_names length"));
2194
  }
2195

2196
  #[test]
2197
  fn test_validate_mismatched_owners_length() {
2198
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2199
    let packages = vec![pkg];
2200
    let mut manifest: PackageManifest = packages.into();
2201

2202
    manifest.owners.push(Some("Extra".to_string()));
2203

2204
    let result = manifest.validate();
2205
    assert!(result.is_err());
2206
    assert!(result.unwrap_err().contains("owners length"));
2207
  }
2208

2209
  #[test]
2210
  fn test_validate_invalid_version_range() {
2211
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2212
    let packages = vec![pkg];
2213
    let mut manifest: PackageManifest = packages.into();
2214

2215
    manifest.version_ranges[0] = (5, 3);
2216

2217
    let result = manifest.validate();
2218
    assert!(result.is_err());
2219
    assert!(result.unwrap_err().contains("Invalid version range"));
2220
  }
2221

2222
  #[test]
2223
  fn test_validate_version_range_out_of_bounds() {
2224
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2225
    let packages = vec![pkg];
2226
    let mut manifest: PackageManifest = packages.into();
2227

2228
    manifest.version_ranges[0] = (0, 999);
2229

2230
    let result = manifest.validate();
2231
    assert!(result.is_err());
2232
    let err_msg = result.unwrap_err();
2233
    assert!(err_msg.contains("only"));
2234
    assert!(err_msg.contains("versions exist"));
2235
  }
2236

2237
  #[test]
2238
  fn test_validate_mismatched_version_arrays() {
2239
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2240
    let packages = vec![pkg];
2241
    let mut manifest: PackageManifest = packages.into();
2242

2243
    manifest.versions.download_urls.pop();
2244

2245
    let result = manifest.validate();
2246
    assert!(result.is_err());
2247
    assert!(
2248
      result
2249
        .unwrap_err()
2250
        .contains("versions.download_urls length")
2251
    );
2252
  }
2253

2254
  #[test]
2255
  fn test_validate_empty_manifest() {
2256
    let manifest = PackageManifest {
2257
      names: vec![],
2258
      full_names: vec![],
2259
      owners: vec![],
2260
      package_urls: vec![],
2261
      dates_created: vec![],
2262
      dates_updated: vec![],
2263
      uuid4s: vec![],
2264
      rating_scores: vec![],
2265
      is_pinned: vec![],
2266
      is_deprecated: vec![],
2267
      has_nsfw_content: vec![],
2268
      categories: vec![],
2269
      version_ranges: vec![],
2270
      versions: VersionManifest {
2271
        package_indices: vec![],
2272
        version_numbers: vec![],
2273
        download_urls: vec![],
2274
        dependencies: vec![],
2275
        dates_created: vec![],
2276
        descriptions: vec![],
2277
        icons: vec![],
2278
        downloads: vec![],
2279
        website_urls: vec![],
2280
        is_active: vec![],
2281
        uuid4s: vec![],
2282
        file_sizes: vec![],
2283
      },
2284
    };
2285

2286
    assert!(manifest.validate().is_ok());
2287
    assert!(manifest.is_empty());
2288
  }
2289

2290
  #[test]
2291
  fn test_validate_mismatched_package_urls_length() {
2292
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2293
    let packages = vec![pkg];
2294
    let mut manifest: PackageManifest = packages.into();
2295

2296
    manifest.package_urls.pop();
2297

2298
    let result = manifest.validate();
2299
    assert!(result.is_err());
2300
    assert!(result.unwrap_err().contains("package_urls length"));
2301
  }
2302

2303
  #[test]
2304
  fn test_validate_mismatched_dates_created_length() {
2305
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2306
    let packages = vec![pkg];
2307
    let mut manifest: PackageManifest = packages.into();
2308

2309
    manifest.dates_created.pop();
2310

2311
    let result = manifest.validate();
2312
    assert!(result.is_err());
2313
    assert!(result.unwrap_err().contains("dates_created length"));
2314
  }
2315

2316
  #[test]
2317
  fn test_validate_mismatched_dates_updated_length() {
2318
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2319
    let packages = vec![pkg];
2320
    let mut manifest: PackageManifest = packages.into();
2321

2322
    manifest.dates_updated.pop();
2323

2324
    let result = manifest.validate();
2325
    assert!(result.is_err());
2326
    assert!(result.unwrap_err().contains("dates_updated length"));
2327
  }
2328

2329
  #[test]
2330
  fn test_validate_mismatched_uuid4s_length() {
2331
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2332
    let packages = vec![pkg];
2333
    let mut manifest: PackageManifest = packages.into();
2334

2335
    manifest.uuid4s.pop();
2336

2337
    let result = manifest.validate();
2338
    assert!(result.is_err());
2339
    assert!(result.unwrap_err().contains("uuid4s length"));
2340
  }
2341

2342
  #[test]
2343
  fn test_validate_mismatched_rating_scores_length() {
2344
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2345
    let packages = vec![pkg];
2346
    let mut manifest: PackageManifest = packages.into();
2347

2348
    manifest.rating_scores.pop();
2349

2350
    let result = manifest.validate();
2351
    assert!(result.is_err());
2352
    assert!(result.unwrap_err().contains("rating_scores length"));
2353
  }
2354

2355
  #[test]
2356
  fn test_validate_mismatched_is_pinned_length() {
2357
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2358
    let packages = vec![pkg];
2359
    let mut manifest: PackageManifest = packages.into();
2360

2361
    manifest.is_pinned.pop();
2362

2363
    let result = manifest.validate();
2364
    assert!(result.is_err());
2365
    assert!(result.unwrap_err().contains("is_pinned length"));
2366
  }
2367

2368
  #[test]
2369
  fn test_validate_mismatched_is_deprecated_length() {
2370
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2371
    let packages = vec![pkg];
2372
    let mut manifest: PackageManifest = packages.into();
2373

2374
    manifest.is_deprecated.pop();
2375

2376
    let result = manifest.validate();
2377
    assert!(result.is_err());
2378
    assert!(result.unwrap_err().contains("is_deprecated length"));
2379
  }
2380

2381
  #[test]
2382
  fn test_validate_mismatched_has_nsfw_content_length() {
2383
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2384
    let packages = vec![pkg];
2385
    let mut manifest: PackageManifest = packages.into();
2386

2387
    manifest.has_nsfw_content.pop();
2388

2389
    let result = manifest.validate();
2390
    assert!(result.is_err());
2391
    assert!(result.unwrap_err().contains("has_nsfw_content length"));
2392
  }
2393

2394
  #[test]
2395
  fn test_validate_mismatched_categories_length() {
2396
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2397
    let packages = vec![pkg];
2398
    let mut manifest: PackageManifest = packages.into();
2399

2400
    manifest.categories.pop();
2401

2402
    let result = manifest.validate();
2403
    assert!(result.is_err());
2404
    assert!(result.unwrap_err().contains("categories length"));
2405
  }
2406

2407
  #[test]
2408
  fn test_validate_mismatched_version_ranges_length() {
2409
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2410
    let packages = vec![pkg];
2411
    let mut manifest: PackageManifest = packages.into();
2412

2413
    manifest.version_ranges.pop();
2414

2415
    let result = manifest.validate();
2416
    assert!(result.is_err());
2417
    assert!(result.unwrap_err().contains("version_ranges length"));
2418
  }
2419

2420
  #[test]
2421
  fn test_validate_mismatched_version_package_indices_length() {
2422
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2423
    let packages = vec![pkg];
2424
    let mut manifest: PackageManifest = packages.into();
2425

2426
    manifest.versions.package_indices.pop();
2427

2428
    let result = manifest.validate();
2429
    assert!(result.is_err());
2430
    assert!(
2431
      result
2432
        .unwrap_err()
2433
        .contains("versions.package_indices length")
2434
    );
2435
  }
2436

2437
  #[test]
2438
  fn test_validate_mismatched_version_dependencies_length() {
2439
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2440
    let packages = vec![pkg];
2441
    let mut manifest: PackageManifest = packages.into();
2442

2443
    manifest.versions.dependencies.pop();
2444

2445
    let result = manifest.validate();
2446
    assert!(result.is_err());
2447
    assert!(result.unwrap_err().contains("versions.dependencies length"));
2448
  }
2449

2450
  #[test]
2451
  fn test_validate_mismatched_version_dates_created_length() {
2452
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2453
    let packages = vec![pkg];
2454
    let mut manifest: PackageManifest = packages.into();
2455

2456
    manifest.versions.dates_created.pop();
2457

2458
    let result = manifest.validate();
2459
    assert!(result.is_err());
2460
    assert!(
2461
      result
2462
        .unwrap_err()
2463
        .contains("versions.dates_created length")
2464
    );
2465
  }
2466

2467
  #[test]
2468
  fn test_validate_mismatched_version_descriptions_length() {
2469
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2470
    let packages = vec![pkg];
2471
    let mut manifest: PackageManifest = packages.into();
2472

2473
    manifest.versions.descriptions.pop();
2474

2475
    let result = manifest.validate();
2476
    assert!(result.is_err());
2477
    assert!(result.unwrap_err().contains("versions.descriptions length"));
2478
  }
2479

2480
  #[test]
2481
  fn test_validate_mismatched_version_icons_length() {
2482
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2483
    let packages = vec![pkg];
2484
    let mut manifest: PackageManifest = packages.into();
2485

2486
    manifest.versions.icons.pop();
2487

2488
    let result = manifest.validate();
2489
    assert!(result.is_err());
2490
    assert!(result.unwrap_err().contains("versions.icons length"));
2491
  }
2492

2493
  #[test]
2494
  fn test_validate_mismatched_version_downloads_length() {
2495
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2496
    let packages = vec![pkg];
2497
    let mut manifest: PackageManifest = packages.into();
2498

2499
    manifest.versions.downloads.pop();
2500

2501
    let result = manifest.validate();
2502
    assert!(result.is_err());
2503
    assert!(result.unwrap_err().contains("versions.downloads length"));
2504
  }
2505

2506
  #[test]
2507
  fn test_validate_mismatched_version_website_urls_length() {
2508
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2509
    let packages = vec![pkg];
2510
    let mut manifest: PackageManifest = packages.into();
2511

2512
    manifest.versions.website_urls.pop();
2513

2514
    let result = manifest.validate();
2515
    assert!(result.is_err());
2516
    assert!(result.unwrap_err().contains("versions.website_urls length"));
2517
  }
2518

2519
  #[test]
2520
  fn test_validate_mismatched_version_is_active_length() {
2521
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2522
    let packages = vec![pkg];
2523
    let mut manifest: PackageManifest = packages.into();
2524

2525
    manifest.versions.is_active.pop();
2526

2527
    let result = manifest.validate();
2528
    assert!(result.is_err());
2529
    assert!(result.unwrap_err().contains("versions.is_active length"));
2530
  }
2531

2532
  #[test]
2533
  fn test_validate_mismatched_version_uuid4s_length() {
2534
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2535
    let packages = vec![pkg];
2536
    let mut manifest: PackageManifest = packages.into();
2537

2538
    manifest.versions.uuid4s.pop();
2539

2540
    let result = manifest.validate();
2541
    assert!(result.is_err());
2542
    assert!(result.unwrap_err().contains("versions.uuid4s length"));
2543
  }
2544

2545
  #[test]
2546
  fn test_validate_mismatched_version_file_sizes_length() {
2547
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2548
    let packages = vec![pkg];
2549
    let mut manifest: PackageManifest = packages.into();
2550

2551
    manifest.versions.file_sizes.pop();
2552

2553
    let result = manifest.validate();
2554
    assert!(result.is_err());
2555
    assert!(result.unwrap_err().contains("versions.file_sizes length"));
2556
  }
2557

2558
  #[test]
2559
  fn test_get_latest_version_at_empty_range() {
2560
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2561
    let packages = vec![pkg];
2562
    let mut manifest: PackageManifest = packages.into();
2563

2564
    manifest.version_ranges[0] = (0, 0);
2565

2566
    let result = manifest.get_latest_version_at(0);
2567
    assert!(result.is_none());
2568
  }
2569

2570
  #[test]
2571
  fn test_dependency_graph_resolve_with_missing_dependency() {
2572
    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
2573
    let pkg2 = create_test_package_with_dependencies(
2574
      "ModB",
2575
      "Owner2",
2576
      "2.0.0",
2577
      vec!["Owner3-NonExistent".to_string()],
2578
    );
2579

2580
    let packages = vec![pkg1, pkg2];
2581
    let manifest: PackageManifest = packages.into();
2582

2583
    let dg = DependencyGraph::new(vec!["Owner2-ModB".to_string()]);
2584
    let result = dg.resolve(&manifest);
2585

2586
    assert_eq!(result.len(), 1);
2587
    assert!(result.contains_key("Owner2-ModB-2.0.0.zip"));
2588
  }
2589

2590
  #[test]
2591
  fn test_dependency_graph_resolve_with_version_suffix() {
2592
    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
2593

2594
    let packages = vec![pkg1];
2595
    let manifest: PackageManifest = packages.into();
2596

2597
    let dg = DependencyGraph::new(vec!["Owner1-ModA-1.0.0".to_string()]);
2598
    let result = dg.resolve(&manifest);
2599

2600
    assert_eq!(result.len(), 1);
2601
    assert!(result.contains_key("Owner1-ModA-1.0.0.zip"));
2602
  }
2603

2604
  #[test]
2605
  fn test_interned_manifest_basic_operations() {
2606
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2607
    let packages = vec![pkg];
2608
    let interned: InternedPackageManifest = packages.into();
2609

2610
    assert_eq!(interned.len(), 1);
2611

2612
    assert!(!interned.is_empty());
2613

2614
    assert_eq!(interned.resolve_name_at(0), Some("TestMod".to_string()));
2615

2616
    assert_eq!(
2617
      interned.resolve_full_name_at(0),
2618
      Some("TestOwner-TestMod".to_string())
2619
    );
2620

2621
    assert_eq!(interned.resolve_owner_at(0), Some("TestOwner".to_string()));
2622
  }
2623

2624
  #[test]
2625
  fn test_interned_manifest_get_package_by_full_name() {
2626
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2627
    let packages = vec![pkg];
2628
    let interned: InternedPackageManifest = packages.into();
2629

2630
    let found = interned.get_package_by_full_name("TestOwner-TestMod");
2631

2632
    assert!(found.is_some());
2633

2634
    let package = found.unwrap();
2635

2636
    assert_eq!(package.name, Some("TestMod".to_string()));
2637

2638
    assert_eq!(package.owner, Some("TestOwner".to_string()));
2639

2640
    assert_eq!(package.versions.len(), 1);
2641
  }
2642

2643
  #[test]
2644
  fn test_interned_manifest_find_index_by_full_name() {
2645
    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
2646
    let pkg2 = create_test_package("ModB", "Owner2", "2.0.0");
2647
    let packages = vec![pkg1, pkg2];
2648
    let interned: InternedPackageManifest = packages.into();
2649

2650
    assert_eq!(interned.find_index_by_full_name("Owner1-ModA"), Some(0));
2651

2652
    assert_eq!(interned.find_index_by_full_name("Owner2-ModB"), Some(1));
2653

2654
    assert_eq!(interned.find_index_by_full_name("NonExistent"), None);
2655
  }
2656

2657
  #[test]
2658
  fn test_interned_manifest_get_package_at() {
2659
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2660
    let packages = vec![pkg];
2661
    let interned: InternedPackageManifest = packages.into();
2662

2663
    let package = interned.get_package_at(0);
2664

2665
    assert_eq!(package.name, Some("TestMod".to_string()));
2666

2667
    assert_eq!(package.full_name, Some("TestOwner-TestMod".to_string()));
2668

2669
    assert_eq!(package.owner, Some("TestOwner".to_string()));
2670

2671
    assert_eq!(package.versions.len(), 1);
2672

2673
    assert_eq!(
2674
      package.versions[0].version_number,
2675
      Some("1.0.0".to_string())
2676
    );
2677
  }
2678

2679
  #[test]
2680
  fn test_interned_manifest_get_latest_version_at() {
2681
    let mut pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2682

2683
    let older_version = Version {
2684
      name: Some("TestMod".to_string()),
2685
      full_name: Some("TestOwner-TestMod".to_string()),
2686
      description: Some("Older version".to_string()),
2687
      icon: Some("icon.png".to_string()),
2688
      version_number: Some("0.9.0".to_string()),
2689
      dependencies: vec![],
2690
      download_url: Some("https://example.com/TestMod/download-old".to_string()),
2691
      downloads: Some(50),
2692
      date_created: OffsetDateTime::now_utc().saturating_sub(time::Duration::days(30)),
2693
      website_url: Some("https://example.com".to_string()),
2694
      is_active: Some(true),
2695
      uuid4: Some("old-version-uuid".to_string()),
2696
      file_size: Some(512),
2697
    };
2698
    pkg.versions.insert(0, older_version);
2699

2700
    let packages = vec![pkg];
2701
    let interned: InternedPackageManifest = packages.into();
2702

2703
    let latest_idx = interned.get_latest_version_at(0);
2704

2705
    assert!(latest_idx.is_some());
2706

2707
    let version_idx = latest_idx.unwrap();
2708
    let version_number = interned.versions.version_numbers[version_idx]
2709
      .map(|key| interned.interner.resolve(&key).to_string());
2710

2711
    assert_eq!(version_number, Some("1.0.0".to_string()));
2712
  }
2713

2714
  #[test]
2715
  fn test_interned_manifest_get_latest_version_at_empty_range() {
2716
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2717
    let packages = vec![pkg];
2718
    let mut interned: InternedPackageManifest = packages.into();
2719

2720
    interned.version_ranges[0] = (0, 0);
2721

2722
    let latest = interned.get_latest_version_at(0);
2723

2724
    assert!(latest.is_none());
2725
  }
2726

2727
  #[test]
2728
  fn test_interned_manifest_build_name_index() {
2729
    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
2730
    let pkg2 = create_test_package("ModB", "Owner2", "2.0.0");
2731
    let packages = vec![pkg1, pkg2];
2732
    let interned: InternedPackageManifest = packages.into();
2733

2734
    let index = interned.build_name_index();
2735

2736
    assert_eq!(index.len(), 2);
2737

2738
    assert_eq!(index.get("Owner1-ModA"), Some(&0));
2739

2740
    assert_eq!(index.get("Owner2-ModB"), Some(&1));
2741
  }
2742

2743
  #[test]
2744
  fn test_interned_manifest_validate_valid() {
2745
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2746
    let packages = vec![pkg];
2747
    let interned: InternedPackageManifest = packages.into();
2748

2749
    let result = interned.validate();
2750

2751
    assert!(result.is_ok());
2752
  }
2753

2754
  #[test]
2755
  fn test_interned_manifest_validate_mismatched_full_names_length() {
2756
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2757
    let packages = vec![pkg];
2758
    let mut interned: InternedPackageManifest = packages.into();
2759

2760
    interned.full_names.pop();
2761

2762
    let result = interned.validate();
2763

2764
    assert!(result.is_err());
2765

2766
    assert!(result.unwrap_err().contains("full_names length"));
2767
  }
2768

2769
  #[test]
2770
  fn test_interned_manifest_validate_mismatched_owners_length() {
2771
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2772
    let packages = vec![pkg];
2773
    let mut interned: InternedPackageManifest = packages.into();
2774

2775
    interned.owners.pop();
2776

2777
    let result = interned.validate();
2778

2779
    assert!(result.is_err());
2780

2781
    assert!(result.unwrap_err().contains("owners length"));
2782
  }
2783

2784
  #[test]
2785
  fn test_interned_manifest_validate_mismatched_package_urls_length() {
2786
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2787
    let packages = vec![pkg];
2788
    let mut interned: InternedPackageManifest = packages.into();
2789

2790
    interned.package_urls.pop();
2791

2792
    let result = interned.validate();
2793

2794
    assert!(result.is_err());
2795

2796
    assert!(result.unwrap_err().contains("package_urls length"));
2797
  }
2798

2799
  #[test]
2800
  fn test_interned_manifest_validate_mismatched_dates_created_length() {
2801
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2802
    let packages = vec![pkg];
2803
    let mut interned: InternedPackageManifest = packages.into();
2804

2805
    interned.dates_created.pop();
2806

2807
    let result = interned.validate();
2808

2809
    assert!(result.is_err());
2810

2811
    assert!(result.unwrap_err().contains("dates_created length"));
2812
  }
2813

2814
  #[test]
2815
  fn test_interned_manifest_validate_mismatched_dates_updated_length() {
2816
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2817
    let packages = vec![pkg];
2818
    let mut interned: InternedPackageManifest = packages.into();
2819

2820
    interned.dates_updated.pop();
2821

2822
    let result = interned.validate();
2823

2824
    assert!(result.is_err());
2825

2826
    assert!(result.unwrap_err().contains("dates_updated length"));
2827
  }
2828

2829
  #[test]
2830
  fn test_interned_manifest_validate_mismatched_uuid4s_length() {
2831
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2832
    let packages = vec![pkg];
2833
    let mut interned: InternedPackageManifest = packages.into();
2834

2835
    interned.uuid4s.pop();
2836

2837
    let result = interned.validate();
2838

2839
    assert!(result.is_err());
2840

2841
    assert!(result.unwrap_err().contains("uuid4s length"));
2842
  }
2843

2844
  #[test]
2845
  fn test_interned_manifest_validate_mismatched_rating_scores_length() {
2846
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2847
    let packages = vec![pkg];
2848
    let mut interned: InternedPackageManifest = packages.into();
2849

2850
    interned.rating_scores.pop();
2851

2852
    let result = interned.validate();
2853

2854
    assert!(result.is_err());
2855

2856
    assert!(result.unwrap_err().contains("rating_scores length"));
2857
  }
2858

2859
  #[test]
2860
  fn test_interned_manifest_validate_mismatched_is_pinned_length() {
2861
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2862
    let packages = vec![pkg];
2863
    let mut interned: InternedPackageManifest = packages.into();
2864

2865
    interned.is_pinned.pop();
2866

2867
    let result = interned.validate();
2868

2869
    assert!(result.is_err());
2870

2871
    assert!(result.unwrap_err().contains("is_pinned length"));
2872
  }
2873

2874
  #[test]
2875
  fn test_interned_manifest_validate_mismatched_is_deprecated_length() {
2876
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2877
    let packages = vec![pkg];
2878
    let mut interned: InternedPackageManifest = packages.into();
2879

2880
    interned.is_deprecated.pop();
2881

2882
    let result = interned.validate();
2883

2884
    assert!(result.is_err());
2885

2886
    assert!(result.unwrap_err().contains("is_deprecated length"));
2887
  }
2888

2889
  #[test]
2890
  fn test_interned_manifest_validate_mismatched_has_nsfw_content_length() {
2891
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2892
    let packages = vec![pkg];
2893
    let mut interned: InternedPackageManifest = packages.into();
2894

2895
    interned.has_nsfw_content.pop();
2896

2897
    let result = interned.validate();
2898

2899
    assert!(result.is_err());
2900

2901
    assert!(result.unwrap_err().contains("has_nsfw_content length"));
2902
  }
2903

2904
  #[test]
2905
  fn test_interned_manifest_validate_mismatched_categories_length() {
2906
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2907
    let packages = vec![pkg];
2908
    let mut interned: InternedPackageManifest = packages.into();
2909

2910
    interned.categories.pop();
2911

2912
    let result = interned.validate();
2913

2914
    assert!(result.is_err());
2915

2916
    assert!(result.unwrap_err().contains("categories length"));
2917
  }
2918

2919
  #[test]
2920
  fn test_interned_manifest_validate_mismatched_version_ranges_length() {
2921
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2922
    let packages = vec![pkg];
2923
    let mut interned: InternedPackageManifest = packages.into();
2924

2925
    interned.version_ranges.pop();
2926

2927
    let result = interned.validate();
2928

2929
    assert!(result.is_err());
2930

2931
    assert!(result.unwrap_err().contains("version_ranges length"));
2932
  }
2933

2934
  #[test]
2935
  fn test_interned_manifest_validate_mismatched_version_numbers_length() {
2936
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2937
    let packages = vec![pkg];
2938
    let mut interned: InternedPackageManifest = packages.into();
2939

2940
    interned.versions.version_numbers.pop();
2941

2942
    let result = interned.validate();
2943

2944
    assert!(result.is_err());
2945

2946
    assert!(result.unwrap_err().contains("version_numbers length"));
2947
  }
2948

2949
  #[test]
2950
  fn test_interned_manifest_validate_mismatched_version_download_urls_length() {
2951
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2952
    let packages = vec![pkg];
2953
    let mut interned: InternedPackageManifest = packages.into();
2954

2955
    interned.versions.download_urls.pop();
2956

2957
    let result = interned.validate();
2958

2959
    assert!(result.is_err());
2960

2961
    assert!(result.unwrap_err().contains("download_urls length"));
2962
  }
2963

2964
  #[test]
2965
  fn test_interned_manifest_validate_mismatched_version_dependencies_length() {
2966
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2967
    let packages = vec![pkg];
2968
    let mut interned: InternedPackageManifest = packages.into();
2969

2970
    interned.versions.dependencies.pop();
2971

2972
    let result = interned.validate();
2973

2974
    assert!(result.is_err());
2975

2976
    assert!(result.unwrap_err().contains("dependencies length"));
2977
  }
2978

2979
  #[test]
2980
  fn test_interned_manifest_validate_mismatched_version_dates_created_length() {
2981
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2982
    let packages = vec![pkg];
2983
    let mut interned: InternedPackageManifest = packages.into();
2984

2985
    interned.versions.dates_created.pop();
2986

2987
    let result = interned.validate();
2988

2989
    assert!(result.is_err());
2990

2991
    assert!(result.unwrap_err().contains("dates_created length"));
2992
  }
2993

2994
  #[test]
2995
  fn test_interned_manifest_validate_mismatched_version_descriptions_length() {
2996
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
2997
    let packages = vec![pkg];
2998
    let mut interned: InternedPackageManifest = packages.into();
2999

3000
    interned.versions.descriptions.pop();
3001

3002
    let result = interned.validate();
3003

3004
    assert!(result.is_err());
3005

3006
    assert!(result.unwrap_err().contains("descriptions length"));
3007
  }
3008

3009
  #[test]
3010
  fn test_interned_manifest_validate_mismatched_version_icons_length() {
3011
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3012
    let packages = vec![pkg];
3013
    let mut interned: InternedPackageManifest = packages.into();
3014

3015
    interned.versions.icons.pop();
3016

3017
    let result = interned.validate();
3018

3019
    assert!(result.is_err());
3020

3021
    assert!(result.unwrap_err().contains("icons length"));
3022
  }
3023

3024
  #[test]
3025
  fn test_interned_manifest_validate_mismatched_version_downloads_length() {
3026
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3027
    let packages = vec![pkg];
3028
    let mut interned: InternedPackageManifest = packages.into();
3029

3030
    interned.versions.downloads.pop();
3031

3032
    let result = interned.validate();
3033

3034
    assert!(result.is_err());
3035

3036
    assert!(result.unwrap_err().contains("downloads length"));
3037
  }
3038

3039
  #[test]
3040
  fn test_interned_manifest_validate_mismatched_version_website_urls_length() {
3041
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3042
    let packages = vec![pkg];
3043
    let mut interned: InternedPackageManifest = packages.into();
3044

3045
    interned.versions.website_urls.pop();
3046

3047
    let result = interned.validate();
3048

3049
    assert!(result.is_err());
3050

3051
    assert!(result.unwrap_err().contains("website_urls length"));
3052
  }
3053

3054
  #[test]
3055
  fn test_interned_manifest_validate_mismatched_version_is_active_length() {
3056
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3057
    let packages = vec![pkg];
3058
    let mut interned: InternedPackageManifest = packages.into();
3059

3060
    interned.versions.is_active.pop();
3061

3062
    let result = interned.validate();
3063

3064
    assert!(result.is_err());
3065

3066
    assert!(result.unwrap_err().contains("is_active length"));
3067
  }
3068

3069
  #[test]
3070
  fn test_interned_manifest_validate_mismatched_version_uuid4s_length() {
3071
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3072
    let packages = vec![pkg];
3073
    let mut interned: InternedPackageManifest = packages.into();
3074

3075
    interned.versions.uuid4s.pop();
3076

3077
    let result = interned.validate();
3078

3079
    assert!(result.is_err());
3080

3081
    assert!(result.unwrap_err().contains("uuid4s length"));
3082
  }
3083

3084
  #[test]
3085
  fn test_interned_manifest_validate_mismatched_version_file_sizes_length() {
3086
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3087
    let packages = vec![pkg];
3088
    let mut interned: InternedPackageManifest = packages.into();
3089

3090
    interned.versions.file_sizes.pop();
3091

3092
    let result = interned.validate();
3093

3094
    assert!(result.is_err());
3095

3096
    assert!(result.unwrap_err().contains("file_sizes length"));
3097
  }
3098

3099
  #[test]
3100
  fn test_interned_manifest_validate_invalid_version_range() {
3101
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3102
    let packages = vec![pkg];
3103
    let mut interned: InternedPackageManifest = packages.into();
3104

3105
    interned.version_ranges[0] = (5, 3);
3106

3107
    let result = interned.validate();
3108

3109
    assert!(result.is_err());
3110

3111
    assert!(result.unwrap_err().contains("Invalid version range"));
3112
  }
3113

3114
  #[test]
3115
  fn test_interned_manifest_validate_version_range_out_of_bounds() {
3116
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3117
    let packages = vec![pkg];
3118
    let mut interned: InternedPackageManifest = packages.into();
3119

3120
    interned.version_ranges[0] = (0, 999);
3121

3122
    let result = interned.validate();
3123

3124
    assert!(result.is_err());
3125

3126
    assert!(result.unwrap_err().contains("ends at"));
3127
  }
3128

3129
  #[test]
3130
  fn test_interned_manifest_serialization_round_trip() {
3131
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3132
    let packages = vec![pkg.clone()];
3133
    let interned: InternedPackageManifest = packages.into();
3134

3135
    let serializable: SerializableInternedManifest = (&interned).into();
3136

3137
    let binary = bincode::serialize(&serializable).unwrap();
3138

3139
    let deserialized: SerializableInternedManifest = bincode::deserialize(&binary).unwrap();
3140

3141
    let recovered: InternedPackageManifest = deserialized.into();
3142

3143
    assert_eq!(recovered.len(), 1);
3144

3145
    assert_eq!(recovered.resolve_name_at(0), Some("TestMod".to_string()));
3146

3147
    assert_eq!(
3148
      recovered.resolve_full_name_at(0),
3149
      Some("TestOwner-TestMod".to_string())
3150
    );
3151

3152
    let recovered_pkg = recovered.get_package_at(0);
3153

3154
    assert_eq!(recovered_pkg.name, pkg.name);
3155

3156
    assert_eq!(recovered_pkg.full_name, pkg.full_name);
3157

3158
    assert_eq!(recovered_pkg.versions.len(), pkg.versions.len());
3159
  }
3160

3161
  #[test]
3162
  fn test_interned_manifest_conversion_from_package_manifest() {
3163
    let pkg = create_test_package("TestMod", "TestOwner", "1.0.0");
3164
    let packages = vec![pkg];
3165
    let v2_manifest: PackageManifest = packages.into();
3166

3167
    let interned: InternedPackageManifest = v2_manifest.clone().into();
3168

3169
    assert_eq!(interned.len(), v2_manifest.len());
3170

3171
    assert_eq!(interned.resolve_name_at(0), v2_manifest.names[0].clone());
3172

3173
    assert_eq!(
3174
      interned.resolve_full_name_at(0),
3175
      v2_manifest.full_names[0].clone()
3176
    );
3177
  }
3178

3179
  #[test]
3180
  fn test_interned_manifest_dependency_graph_resolve() {
3181
    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
3182
    let pkg2 = create_test_package_with_dependencies(
3183
      "ModB",
3184
      "Owner2",
3185
      "2.0.0",
3186
      vec!["Owner3-ModC".to_string()],
3187
    );
3188
    let pkg3 = create_test_package_with_dependencies(
3189
      "ModC",
3190
      "Owner3",
3191
      "1.5.0",
3192
      vec!["Owner4-ModD".to_string()],
3193
    );
3194
    let pkg4 = create_test_package("ModD", "Owner4", "0.9.0");
3195

3196
    let packages = vec![pkg1, pkg2, pkg3, pkg4];
3197
    let interned: InternedPackageManifest = packages.into();
3198

3199
    let dg1 = DependencyGraph::new(vec!["Owner1-ModA".to_string()]);
3200
    let result1 = dg1.resolve_interned(&interned);
3201

3202
    assert_eq!(result1.len(), 1);
3203

3204
    assert!(result1.contains_key("Owner1-ModA-1.0.0.zip"));
3205

3206
    let dg2 = DependencyGraph::new(vec!["Owner2-ModB".to_string()]);
3207
    let result2 = dg2.resolve_interned(&interned);
3208

3209
    assert_eq!(result2.len(), 3);
3210

3211
    assert!(result2.contains_key("Owner2-ModB-2.0.0.zip"));
3212

3213
    assert!(result2.contains_key("Owner3-ModC-1.5.0.zip"));
3214

3215
    assert!(result2.contains_key("Owner4-ModD-0.9.0.zip"));
3216
  }
3217

3218
  #[test]
3219
  fn test_interned_manifest_dependency_graph_resolve_with_missing_dependency() {
3220
    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
3221
    let pkg2 = create_test_package_with_dependencies(
3222
      "ModB",
3223
      "Owner2",
3224
      "2.0.0",
3225
      vec!["Owner3-NonExistent".to_string()],
3226
    );
3227

3228
    let packages = vec![pkg1, pkg2];
3229
    let interned: InternedPackageManifest = packages.into();
3230

3231
    let dg = DependencyGraph::new(vec!["Owner2-ModB".to_string()]);
3232
    let result = dg.resolve_interned(&interned);
3233

3234
    assert_eq!(result.len(), 1);
3235

3236
    assert!(result.contains_key("Owner2-ModB-2.0.0.zip"));
3237
  }
3238

3239
  #[test]
3240
  fn test_interned_manifest_dependency_graph_resolve_with_version_suffix() {
3241
    let pkg1 = create_test_package("ModA", "Owner1", "1.0.0");
3242

3243
    let packages = vec![pkg1];
3244
    let interned: InternedPackageManifest = packages.into();
3245

3246
    let dg = DependencyGraph::new(vec!["Owner1-ModA-1.0.0".to_string()]);
3247
    let result = dg.resolve_interned(&interned);
3248

3249
    assert_eq!(result.len(), 1);
3250

3251
    assert!(result.contains_key("Owner1-ModA-1.0.0.zip"));
3252
  }
3253
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc