spec_driven_docs/release/
fixture.rs1use std::collections::BTreeMap;
9
10use camino::Utf8Path;
11
12use crate::domain::ownership::Sha256;
13use crate::error::AppError;
14use crate::release::{
15 Provenance, ReleaseBundle, ReleaseManifest, ReleaseResolver, ResolvedRelease, Selector,
16 Version, blob_from, manifest_from,
17};
18
19#[derive(Debug, Clone)]
21pub struct FixtureReleaseBundle {
22 version: Version,
23 payload_schema: u32,
24 files: BTreeMap<String, Vec<u8>>,
25}
26
27impl FixtureReleaseBundle {
28 pub fn read(root: &Utf8Path, version: Version, payload_schema: u32) -> Result<Self, AppError> {
34 let mut files = BTreeMap::new();
35 for entry in walkdir::WalkDir::new(root)
36 .into_iter()
37 .filter_map(Result::ok)
38 {
39 if !entry.file_type().is_file() {
40 continue;
41 }
42 let Ok(path) = camino::Utf8PathBuf::from_path_buf(entry.path().to_path_buf()) else {
43 continue;
44 };
45 let Ok(relative) = path.strip_prefix(root) else {
46 continue;
47 };
48 files.insert(relative.to_string(), std::fs::read(&path)?);
49 }
50 Ok(Self {
51 version,
52 payload_schema,
53 files,
54 })
55 }
56
57 #[must_use]
59 pub const fn of(
60 version: Version,
61 payload_schema: u32,
62 files: BTreeMap<String, Vec<u8>>,
63 ) -> Self {
64 Self {
65 version,
66 payload_schema,
67 files,
68 }
69 }
70}
71
72impl ReleaseBundle for FixtureReleaseBundle {
73 fn manifest(&self) -> Result<ReleaseManifest, AppError> {
74 Ok(manifest_from(
75 self.version.clone(),
76 self.payload_schema,
77 Provenance::Native,
78 None,
79 &self.files,
80 &BTreeMap::new(),
81 ))
82 }
83
84 fn blob(&self, digest: &Sha256) -> Result<Vec<u8>, AppError> {
85 blob_from(&self.files, &BTreeMap::new(), digest)
86 }
87}
88
89impl ReleaseResolver for FixtureReleaseBundle {
90 fn resolve(&self, selector: &Selector) -> Result<ResolvedRelease, AppError> {
91 let manifest = self.manifest()?;
92 Ok(ResolvedRelease {
93 selector: selector.clone(),
94 version: self.version.clone(),
95 registry_checksum: None,
96 payload_sha256: manifest.payload_sha256,
97 yanked: false,
98 bundle: Box::new(self.clone()),
99 })
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 #![allow(
106 clippy::unwrap_used,
107 reason = "a test panics as its failure signal, not as control flow"
108 )]
109
110 use super::*;
111
112 #[test]
113 fn the_fixture_source_serves_a_directory() {
114 let dir = tempfile::tempdir().unwrap();
115 let root = camino::Utf8PathBuf::from(dir.path().to_str().unwrap());
116 crate::adapters::fs::write_file(&root.join("method/one.md"), b"one\n").unwrap();
117 crate::adapters::fs::write_file(&root.join("templates/two.md"), b"two\n").unwrap();
118
119 let version: Version = "0.1.0".parse().unwrap();
120 let bundle = FixtureReleaseBundle::read(&root, version.clone(), 1).unwrap();
121 let manifest = bundle.manifest().unwrap();
122 let paths: Vec<&str> = manifest
123 .artifacts
124 .iter()
125 .map(|artifact| artifact.path.as_str())
126 .collect();
127 assert_eq!(paths, ["method/one.md", "templates/two.md"]);
128 assert_eq!(bundle.artifact("method/one.md").unwrap(), b"one\n");
129 assert!(bundle.artifact("absent.md").is_err());
130
131 let resolved = bundle.resolve(&Selector::Exact(version.clone())).unwrap();
132 assert_eq!(resolved.version, version);
133 assert_eq!(resolved.payload_sha256, manifest.payload_sha256);
134 }
135}