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 inherits_workspace_version(&self) -> bool {
102 false
103 }
104 fn workspace_root_path(&self) -> Option<&Path> {
105 None
106 }
107 }
108
109 #[derive(Debug)]
110 struct MockWorkspace {
111 name: Option<String>,
112 path: PathBuf,
113 relative_path: PathBuf,
114 changed: bool,
115 dependencies: HashSet<String>,
116 }
117
118 impl MockWorkspace {
119 fn new(name: &str, path: &str) -> Self {
120 Self {
121 name: Some(name.to_string()),
122 path: PathBuf::from(path),
123 relative_path: PathBuf::from(path),
124 changed: false,
125 dependencies: HashSet::new(),
126 }
127 }
128 }
129
130 #[async_trait]
131 impl Workspace for MockWorkspace {
132 fn name(&self) -> Option<&str> {
133 self.name.as_deref()
134 }
135 fn path(&self) -> &Path {
136 &self.path
137 }
138 fn relative_path(&self) -> &Path {
139 &self.relative_path
140 }
141 fn version(&self) -> Option<&str> {
142 Some("1.0.0")
143 }
144 async fn update_version(&mut self, _update_type: UpdateType) -> Result<()> {
145 Ok(())
146 }
147 fn language(&self) -> Language {
148 Language::Node
149 }
150 fn dependencies(&self) -> &HashSet<String> {
151 &self.dependencies
152 }
153 fn add_dependency(&mut self, dep: &str) {
154 self.dependencies.insert(dep.to_string());
155 }
156 fn is_changed(&self) -> bool {
157 self.changed
158 }
159 fn set_changed(&mut self, changed: bool) {
160 self.changed = changed;
161 }
162 fn default_publish_command(&self) -> String {
163 "echo test".to_string()
164 }
165 }
166
167 #[derive(Debug)]
168 struct MockProjectFinder {
169 projects: Vec<Project>,
170 }
171
172 impl MockProjectFinder {
173 fn new() -> Self {
174 Self { projects: vec![] }
175 }
176
177 fn with_package(mut self, package: MockPackage) -> Self {
178 self.projects.push(Project::Package(Box::new(package)));
179 self
180 }
181
182 fn with_workspace(mut self, workspace: MockWorkspace) -> Self {
183 self.projects.push(Project::Workspace(Box::new(workspace)));
184 self
185 }
186 }
187
188 #[async_trait]
189 impl ProjectFinder for MockProjectFinder {
190 fn projects(&self) -> Vec<&Project> {
191 self.projects.iter().collect()
192 }
193
194 fn projects_mut(&mut self) -> Vec<&mut Project> {
195 self.projects.iter_mut().collect()
196 }
197
198 fn project_files(&self) -> &[&str] {
199 &["package.json"]
200 }
201
202 async fn visit(&mut self, _path: &Path, _relative_path: &Path) -> Result<()> {
203 Ok(())
204 }
205 }
206
207 #[test]
208 fn test_project_finder_check_changed() {
209 let package = MockPackage::new("test", "/project/package.json");
210 let mut finder = MockProjectFinder::new().with_package(package);
211
212 finder
214 .check_changed(Path::new("/project/src/index.js"))
215 .unwrap();
216
217 assert!(finder.projects()[0].is_changed());
219 }
220
221 #[test]
222 fn test_project_finder_check_changed_multiple_projects() {
223 let package1 = MockPackage::new("pkg1", "/project1/package.json");
224 let package2 = MockPackage::new("pkg2", "/project2/package.json");
225 let mut finder = MockProjectFinder::new()
226 .with_package(package1)
227 .with_package(package2);
228
229 finder
231 .check_changed(Path::new("/project1/src/index.js"))
232 .unwrap();
233
234 assert!(finder.projects()[0].is_changed());
236 assert!(!finder.projects()[1].is_changed());
237 }
238
239 #[test]
240 fn test_project_finder_with_workspace() {
241 let workspace = MockWorkspace::new("root", "/project/package.json");
242 let mut finder = MockProjectFinder::new().with_workspace(workspace);
243
244 finder
245 .check_changed(Path::new("/project/src/index.js"))
246 .unwrap();
247
248 assert!(finder.projects()[0].is_changed());
249 }
250
251 #[tokio::test]
252 async fn test_project_finder_finalize() {
253 let mut finder = MockProjectFinder::new();
254 let result = finder.finalize().await;
255 assert!(result.is_ok());
256 }
257
258 #[tokio::test]
259 async fn test_project_finder_finalize_with_projects() {
260 let package = MockPackage::new("pkg1", "/project/package.json");
261 let mut finder = MockProjectFinder::new().with_package(package);
262 let result = finder.finalize().await;
263 assert!(result.is_ok());
264 assert_eq!(finder.projects().len(), 1);
265 }
266}