Skip to content

Commit 5e58309

Browse files
committed
test(core): refactor to use a TestBuilder
1 parent dc7ef37 commit 5e58309

7 files changed

Lines changed: 907 additions & 753 deletions

File tree

src/test/builder.rs

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
use {
2+
super::{mock, registry_client::MockRegistryClient},
3+
crate::{cli::UpdateTarget, context::Context, registry_client::RegistryClient, visit_packages::visit_packages},
4+
serde_json::{json, Value},
5+
std::sync::Arc,
6+
};
7+
8+
/// Builder pattern for creating test contexts with reduced boilerplate
9+
pub struct TestBuilder {
10+
config: Value,
11+
dependency_groups: Vec<Value>,
12+
packages: Vec<Value>,
13+
registry_updates: Option<Value>,
14+
semver_groups: Vec<Value>,
15+
strict: Option<bool>,
16+
update_target: Option<UpdateTarget>,
17+
version_groups: Vec<Value>,
18+
}
19+
20+
impl TestBuilder {
21+
pub fn new() -> Self {
22+
Self {
23+
config: json!({}),
24+
dependency_groups: vec![],
25+
packages: vec![],
26+
registry_updates: None,
27+
semver_groups: vec![],
28+
strict: None,
29+
update_target: None,
30+
version_groups: vec![],
31+
}
32+
}
33+
34+
pub fn with_package(mut self, package: Value) -> Self {
35+
self.packages.push(package);
36+
self
37+
}
38+
39+
pub fn with_packages(mut self, packages: Vec<Value>) -> Self {
40+
self.packages.extend(packages);
41+
self
42+
}
43+
44+
pub fn with_version_group(mut self, group: Value) -> Self {
45+
self.version_groups.push(group);
46+
self
47+
}
48+
49+
pub fn with_version_groups(mut self, groups: Vec<Value>) -> Self {
50+
self.version_groups.extend(groups);
51+
self
52+
}
53+
54+
pub fn with_semver_group(mut self, group: Value) -> Self {
55+
self.semver_groups.push(group);
56+
self
57+
}
58+
59+
pub fn with_semver_groups(mut self, groups: Vec<Value>) -> Self {
60+
self.semver_groups.extend(groups);
61+
self
62+
}
63+
64+
pub fn with_dependency_group(mut self, group: Value) -> Self {
65+
self.dependency_groups.push(group);
66+
self
67+
}
68+
69+
pub fn with_dependency_groups(mut self, groups: Vec<Value>) -> Self {
70+
self.dependency_groups.extend(groups);
71+
self
72+
}
73+
74+
pub fn with_strict(mut self, strict: bool) -> Self {
75+
self.strict = Some(strict);
76+
self
77+
}
78+
79+
pub fn with_update_target(mut self, target: UpdateTarget) -> Self {
80+
self.update_target = Some(target);
81+
self
82+
}
83+
84+
pub fn with_registry_updates(mut self, updates: Value) -> Self {
85+
self.registry_updates = Some(updates);
86+
self
87+
}
88+
89+
pub fn with_config(mut self, config: Value) -> Self {
90+
self.config = config;
91+
self
92+
}
93+
94+
/// Build the final configuration from all the builder settings
95+
fn build_config(&self) -> Value {
96+
let mut config = self.config.clone();
97+
if !self.version_groups.is_empty() {
98+
config["versionGroups"] = Value::Array(self.version_groups.clone());
99+
}
100+
if !self.semver_groups.is_empty() {
101+
config["semverGroups"] = Value::Array(self.semver_groups.clone());
102+
}
103+
if !self.dependency_groups.is_empty() {
104+
config["dependencyGroups"] = Value::Array(self.dependency_groups.clone());
105+
}
106+
if let Some(strict) = self.strict {
107+
config["strict"] = Value::Bool(strict);
108+
}
109+
config
110+
}
111+
112+
pub fn build(self) -> Context {
113+
let mut config = mock::config_from_mock(self.build_config());
114+
let registry_client = self.create_registry_client();
115+
let packages = mock::packages_from_mocks(self.packages);
116+
if let Some(target) = self.update_target {
117+
match target {
118+
UpdateTarget::Latest => config.cli.target = UpdateTarget::Latest,
119+
UpdateTarget::Minor => config.cli.target = UpdateTarget::Minor,
120+
UpdateTarget::Patch => config.cli.target = UpdateTarget::Patch,
121+
}
122+
}
123+
Context::create(config, packages, registry_client)
124+
}
125+
126+
pub fn build_and_visit(self) -> Context {
127+
let ctx = self.build();
128+
visit_packages(ctx)
129+
}
130+
131+
pub async fn build_with_registry_and_visit(self) -> Context {
132+
let mut config = mock::config_from_mock(self.build_config());
133+
let packages = mock::packages_from_mocks(self.packages);
134+
135+
if let Some(target) = self.update_target {
136+
match target {
137+
UpdateTarget::Latest => config.cli.target = UpdateTarget::Latest,
138+
UpdateTarget::Minor => config.cli.target = UpdateTarget::Minor,
139+
UpdateTarget::Patch => config.cli.target = UpdateTarget::Patch,
140+
}
141+
}
142+
143+
let ctx = if let Some(updates) = self.registry_updates {
144+
mock::context_with_registry_updates(config, packages, updates).await
145+
} else {
146+
Context::create(config, packages, None)
147+
};
148+
149+
visit_packages(ctx)
150+
}
151+
152+
/// Create registry client if updates are provided
153+
fn create_registry_client(&self) -> Option<Arc<dyn RegistryClient>> {
154+
self
155+
.registry_updates
156+
.as_ref()
157+
.map(|updates| Arc::new(MockRegistryClient::from_json(updates.clone())) as Arc<dyn RegistryClient>)
158+
}
159+
}
160+
161+
impl Default for TestBuilder {
162+
fn default() -> Self {
163+
Self::new()
164+
}
165+
}
166+
167+
#[cfg(test)]
168+
mod tests {
169+
use {
170+
super::*,
171+
crate::{
172+
instance_state::{InstanceState, ValidInstance::*},
173+
test::expect::{expect, ExpectedInstance},
174+
},
175+
};
176+
177+
#[test]
178+
fn test_builder_basic_usage() {
179+
let ctx = TestBuilder::new()
180+
.with_package(json!({
181+
"name": "package-a",
182+
"version": "1.0.0"
183+
}))
184+
.build_and_visit();
185+
186+
expect(&ctx).to_have_instances(vec![ExpectedInstance {
187+
state: InstanceState::valid(IsLocalAndValid),
188+
dependency_name: "package-a",
189+
id: "package-a in /version of package-a",
190+
actual: "1.0.0",
191+
expected: Some("1.0.0"),
192+
overridden: None,
193+
}]);
194+
}
195+
196+
#[test]
197+
fn test_builder_with_version_group() {
198+
let ctx = TestBuilder::new()
199+
.with_package(json!({
200+
"name": "package-a",
201+
"version": "1.0.0",
202+
"dependencies": {"foo": "1.0.0"}
203+
}))
204+
.with_version_group(json!({
205+
"dependencies": ["foo"],
206+
"pinVersion": "2.0.0"
207+
}))
208+
.build_and_visit();
209+
210+
// The test should show that foo gets pinned to 2.0.0
211+
assert!(ctx.instances.len() > 1);
212+
}
213+
214+
#[test]
215+
fn test_builder_with_multiple_packages() {
216+
let ctx = TestBuilder::new()
217+
.with_packages(vec![
218+
json!({"name": "package-a", "version": "1.0.0"}),
219+
json!({"name": "package-b", "version": "2.0.0"}),
220+
])
221+
.build_and_visit();
222+
223+
assert_eq!(ctx.instances.len(), 2);
224+
}
225+
226+
#[test]
227+
fn test_builder_with_strict_mode() {
228+
let ctx = TestBuilder::new()
229+
.with_package(json!({
230+
"name": "package-a",
231+
"version": "1.0.0",
232+
"dependencies": {"package-a": "workspace:*"}
233+
}))
234+
.with_strict(true)
235+
.build_and_visit();
236+
237+
// In strict mode, workspace protocol should be invalid when differs from local
238+
assert!(ctx.instances.iter().any(|i| i.state.borrow().is_invalid()));
239+
}
240+
241+
#[tokio::test]
242+
async fn test_builder_with_registry_updates() {
243+
let ctx = TestBuilder::new()
244+
.with_package(json!({
245+
"name": "package-a",
246+
"dependencies": {"foo": "1.0.0"}
247+
}))
248+
.with_registry_updates(json!({"foo": ["1.0.0", "2.0.0"]}))
249+
.build_with_registry_and_visit()
250+
.await;
251+
252+
// Should show registry update available
253+
assert!(ctx.instances.iter().any(|i| i.state.borrow().is_outdated()));
254+
}
255+
256+
#[tokio::test]
257+
async fn test_builder_with_update_target() {
258+
use crate::cli::UpdateTarget;
259+
260+
let ctx = TestBuilder::new()
261+
.with_package(json!({
262+
"name": "package-a",
263+
"dependencies": {"foo": "1.0.0"}
264+
}))
265+
.with_update_target(UpdateTarget::Minor)
266+
.with_registry_updates(json!({"foo": ["1.0.0", "1.1.0", "2.0.0"]}))
267+
.build_with_registry_and_visit()
268+
.await;
269+
270+
// Should target minor updates (1.1.0) not latest (2.0.0)
271+
let foo_instance = ctx.instances.iter().find(|i| i.descriptor.internal_name == "foo").unwrap();
272+
assert_eq!(foo_instance.expected_specifier.borrow().as_ref().unwrap().get_raw(), "1.1.0");
273+
}
274+
}

src/test/test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod builder;
12
pub mod expect;
23
pub mod mock;
34
pub mod registry_client;

src/visit_packages/banned_test.rs

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,31 @@ use {
22
crate::{
33
instance_state::{FixableInstance::*, InstanceState, SuspectInstance::*},
44
test::{
5-
self,
5+
builder::TestBuilder,
66
expect::{expect, ExpectedInstance},
77
},
8-
visit_packages::visit_packages,
9-
Context,
108
},
119
serde_json::json,
1210
};
1311

1412
#[test]
1513
fn refuses_to_ban_local_version() {
16-
let config = test::mock::config_from_mock(json!({
17-
"versionGroups": [{
14+
let ctx = TestBuilder::new()
15+
.with_packages(vec![
16+
json!({
17+
"name": "package-a",
18+
"version": "1.0.0"
19+
}),
20+
json!({
21+
"name": "package-b",
22+
"dependencies": {"package-a": "1.1.0"}
23+
}),
24+
])
25+
.with_version_group(json!({
1826
"dependencies": ["package-a"],
1927
"isBanned": true
20-
}]
21-
}));
22-
let packages = test::mock::packages_from_mocks(vec![
23-
json!({
24-
"name": "package-a",
25-
"version": "1.0.0"
26-
}),
27-
json!({
28-
"name": "package-b",
29-
"dependencies": {
30-
"package-a": "1.1.0"
31-
}
32-
}),
33-
]);
34-
let registry_client = None;
35-
let ctx = Context::create(config, packages, registry_client);
36-
let ctx = visit_packages(ctx);
28+
}))
29+
.build_and_visit();
3730
expect(&ctx).to_have_instances(vec![
3831
ExpectedInstance {
3932
state: InstanceState::suspect(InvalidLocalVersion),

src/visit_packages/ignored_test.rs

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,32 @@ use {
22
crate::{
33
instance_state::{InstanceState, ValidInstance::*},
44
test::{
5-
self,
5+
builder::TestBuilder,
66
expect::{expect, ExpectedInstance},
77
},
8-
visit_packages::visit_packages,
9-
Context,
108
},
119
serde_json::json,
1210
};
1311

1412
#[test]
1513
fn all_instances_are_ignored() {
16-
let config = test::mock::config_from_mock(json!({
17-
"versionGroups": [{
18-
"isIgnored": true,
19-
}]
20-
}));
21-
let packages = test::mock::packages_from_mocks(vec![
22-
json!({
23-
"name": "package-a",
24-
"version": "1.0.0"
25-
}),
26-
json!({
27-
"name": "package-b",
28-
"dependencies": {
29-
"package-a": "1.1.0"
30-
}
31-
}),
32-
]);
33-
let registry_client = None;
34-
let ctx = Context::create(config, packages, registry_client);
35-
let ctx = visit_packages(ctx);
14+
let ctx = TestBuilder::new()
15+
.with_packages(vec![
16+
json!({
17+
"name": "package-a",
18+
"version": "1.0.0"
19+
}),
20+
json!({
21+
"name": "package-b",
22+
"dependencies": {
23+
"package-a": "1.1.0"
24+
}
25+
}),
26+
])
27+
.with_version_group(json!({
28+
"isIgnored": true
29+
}))
30+
.build_and_visit();
3631
expect(&ctx).to_have_instances(vec![
3732
ExpectedInstance {
3833
state: InstanceState::valid(IsIgnored),

0 commit comments

Comments
 (0)