changepacks_core/
project_finder.rs1use std::path::Path;
2
3use crate::project::Project;
4use anyhow::Result;
5use async_trait::async_trait;
6
7#[async_trait]
12pub trait ProjectFinder: std::fmt::Debug + Send + Sync {
13 fn projects(&self) -> Vec<&Project>;
14 fn projects_mut(&mut self) -> Vec<&mut Project>;
15 fn project_files(&self) -> &[&str];
16 async fn visit(&mut self, path: &Path, relative_path: &Path) -> Result<()>;
19 fn check_changed(&mut self, path: &Path) -> Result<()> {
22 for project in self.projects_mut() {
23 project.check_changed(path)?;
24 }
25 Ok(())
26 }
27 #[cfg(not(tarpaulin_include))]
32 async fn finalize(&mut self) -> Result<()> {
33 Ok(())
34 }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40 use crate::{Language, Package, UpdateType, Workspace};
41 use async_trait::async_trait;
42 use std::collections::HashSet;
43 use std::path::PathBuf;
44
45 #[derive(Debug)]
46 struct MockPackage {
47 name: Option<String>,
48 path: PathBuf,
49 relative_path: PathBuf,
50 changed: bool,
51 dependencies: HashSet<String>,
52 }
53
54 impl MockPackage {
55 fn new(name: &str, path: &str) -> Self {
56 Self {
57 name: Some(name.to_string()),
58 path: PathBuf::from(path),
59 relative_path: PathBuf::from(path),
60 changed: false,
61 dependencies: HashSet::new(),
62 }
63 }
64 }
65
66 #[async_trait]
67 impl Package for MockPackage {
68 fn name(&self) -> Option<&str> {
69 self.name.as_deref()
70 }
71 fn version(&self) -> Option<&str> {
72 Some("1.0.0")
73 }
74 fn path(&self) -> &Path {
75 &self.path
76 }
77 fn relative_path(&self) -> &Path {
78 &self.relative_path
79 }
80 async fn update_version(&mut self, _update_type: UpdateType) -> Result<()> {
81 Ok(())
82 }
83 fn is_changed(&self) -> bool {
84 self.changed
85 }
86 fn language(&self) -> Language {
87 Language::Node
88 }
89 fn dependencies(&self) -> &HashSet<String> {
90 &self.dependencies
91 }
92 fn add_dependency(&mut self, dep: &str) {
93 self.dependencies.insert(dep.to_string());
94 }
95 fn set_changed(&mut self, changed: bool) {
96 self.changed = changed;
97 }
98 fn default_publish_command(&self) -> String {
99 "echo test".to_string()
100 }
101 fn default_dry_run_publish_command(&self) -> Option<String> {
102 Some("echo test --dry-run".to_string())
103 }
104 fn inherits_workspace_version(&self) -> bool {
105 false
106 }
107 fn workspace_root_path(&self) -> Option<&Path> {
108 None
109 }
110 }
111
112 #[derive(Debug)]
113 struct MockWorkspace {
114 name: Option<String>,
115 path: PathBuf,
116 relative_path: PathBuf,
117 changed: bool,
118 dependencies: HashSet<String>,
119 }
120
121 impl MockWorkspace {
122 fn new(name: &str, path: &str) -> Self {
123 Self {
124 name: Some(name.to_string()),
125 path: PathBuf::from(path),
126 relative_path: PathBuf::from(path),
127 changed: false,
128 dependencies: HashSet::new(),
129 }
130 }
131 }
132
133 #[async_trait]
134 impl Workspace for MockWorkspace {
135 fn name(&self) -> Option<&str> {
136 self.name.as_deref()
137 }
138 fn path(&self) -> &Path {
139 &self.path
140 }
141 fn relative_path(&self) -> &Path {
142 &self.relative_path
143 }
144 fn version(&self) -> Option<&str> {
145 Some("1.0.0")
146 }
147 async fn update_version(&mut self, _update_type: UpdateType) -> Result<()> {
148 Ok(())
149 }
150 fn language(&self) -> Language {
151 Language::Node
152 }
153 fn dependencies(&self) -> &HashSet<String> {
154 &self.dependencies
155 }
156 fn add_dependency(&mut self, dep: &str) {
157 self.dependencies.insert(dep.to_string());
158 }
159 fn is_changed(&self) -> bool {
160 self.changed
161 }
162 fn set_changed(&mut self, changed: bool) {
163 self.changed = changed;
164 }
165 fn default_publish_command(&self) -> String {
166 "echo test".to_string()
167 }
168 fn default_dry_run_publish_command(&self) -> Option<String> {
169 Some("echo test --dry-run".to_string())
170 }
171 }
172
173 #[derive(Debug)]
174 struct MockProjectFinder {
175 projects: Vec<Project>,
176 }
177
178 impl MockProjectFinder {
179 fn new() -> Self {
180 Self { projects: vec![] }
181 }
182
183 fn with_package(mut self, package: MockPackage) -> Self {
184 self.projects.push(Project::Package(Box::new(package)));
185 self
186 }
187
188 fn with_workspace(mut self, workspace: MockWorkspace) -> Self {
189 self.projects.push(Project::Workspace(Box::new(workspace)));
190 self
191 }
192 }
193
194 #[async_trait]
195 impl ProjectFinder for MockProjectFinder {
196 fn projects(&self) -> Vec<&Project> {
197 self.projects.iter().collect()
198 }
199
200 fn projects_mut(&mut self) -> Vec<&mut Project> {
201 self.projects.iter_mut().collect()
202 }
203
204 fn project_files(&self) -> &[&str] {
205 &["package.json"]
206 }
207
208 async fn visit(&mut self, _path: &Path, _relative_path: &Path) -> Result<()> {
209 Ok(())
210 }
211 }
212
213 #[test]
214 fn test_project_finder_check_changed() {
215 let package = MockPackage::new("test", "/project/package.json");
216 let mut finder = MockProjectFinder::new().with_package(package);
217
218 finder
220 .check_changed(Path::new("/project/src/index.js"))
221 .unwrap();
222
223 assert!(finder.projects()[0].is_changed());
225 }
226
227 #[test]
228 fn test_project_finder_check_changed_multiple_projects() {
229 let package1 = MockPackage::new("pkg1", "/project1/package.json");
230 let package2 = MockPackage::new("pkg2", "/project2/package.json");
231 let mut finder = MockProjectFinder::new()
232 .with_package(package1)
233 .with_package(package2);
234
235 finder
237 .check_changed(Path::new("/project1/src/index.js"))
238 .unwrap();
239
240 assert!(finder.projects()[0].is_changed());
242 assert!(!finder.projects()[1].is_changed());
243 }
244
245 #[test]
246 fn test_project_finder_with_workspace() {
247 let workspace = MockWorkspace::new("root", "/project/package.json");
248 let mut finder = MockProjectFinder::new().with_workspace(workspace);
249
250 finder
251 .check_changed(Path::new("/project/src/index.js"))
252 .unwrap();
253
254 assert!(finder.projects()[0].is_changed());
255 }
256
257 #[tokio::test]
258 async fn test_project_finder_finalize() {
259 let mut finder = MockProjectFinder::new();
260 let result = finder.finalize().await;
261 assert!(result.is_ok());
262 }
263
264 #[tokio::test]
265 async fn test_project_finder_finalize_with_projects() {
266 let package = MockPackage::new("pkg1", "/project/package.json");
267 let mut finder = MockProjectFinder::new().with_package(package);
268 let result = finder.finalize().await;
269 assert!(result.is_ok());
270 assert_eq!(finder.projects().len(), 1);
271 }
272}