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