1010from mergify_cli .ci .scopes import cli
1111
1212
13+ def test_from_yaml_with_extras_ignored (tmp_path : pathlib .Path ) -> None :
14+ config_file = tmp_path / "config.yml"
15+ config_file .write_text (
16+ yaml .dump (
17+ {
18+ "defaults" : {},
19+ "queue_rules" : [],
20+ "pull_request_rules" : [],
21+ "partitions_rules" : [],
22+ "scopes" : {
23+ "source" : {
24+ "files" : {
25+ "backend" : {"include" : ["api/**/*.py" , "backend/**/*.py" ]},
26+ "frontend" : {"include" : ["ui/**/*.js" , "ui/**/*.tsx" ]},
27+ "docs" : {"include" : ["*.md" , "docs/**/*" ]},
28+ },
29+ },
30+ },
31+ },
32+ ),
33+ )
34+
35+ config = cli .Config .from_yaml (str (config_file ))
36+ assert config .model_dump () == {
37+ "scopes" : {
38+ "source" : {
39+ "files" : {
40+ "backend" : {
41+ "include" : ("api/**/*.py" , "backend/**/*.py" ),
42+ "exclude" : (),
43+ },
44+ "frontend" : {
45+ "include" : ("ui/**/*.js" , "ui/**/*.tsx" ),
46+ "exclude" : (),
47+ },
48+ "docs" : {"include" : ("*.md" , "docs/**/*" ), "exclude" : ()},
49+ },
50+ },
51+ "merge_queue_scope" : "merge-queue" ,
52+ },
53+ }
54+
55+
1356def test_from_yaml_valid (tmp_path : pathlib .Path ) -> None :
1457 config_file = tmp_path / "config.yml"
1558 config_file .write_text (
1659 yaml .dump (
1760 {
1861 "scopes" : {
19- "backend" : {"include" : ["api/**/*.py" , "backend/**/*.py" ]},
20- "frontend" : {"include" : ["ui/**/*.js" , "ui/**/*.tsx" ]},
21- "docs" : {"include" : ["*.md" , "docs/**/*" ]},
62+ "source" : {
63+ "files" : {
64+ "backend" : {"include" : ["api/**/*.py" , "backend/**/*.py" ]},
65+ "frontend" : {"include" : ["ui/**/*.js" , "ui/**/*.tsx" ]},
66+ "docs" : {"include" : ["*.md" , "docs/**/*" ]},
67+ },
68+ },
2269 },
2370 },
2471 ),
2572 )
2673
2774 config = cli .Config .from_yaml (str (config_file ))
2875 assert config .model_dump () == {
29- "merge_queue_scope" : "merge-queue" ,
3076 "scopes" : {
31- "backend" : {"include" : ("api/**/*.py" , "backend/**/*.py" ), "exclude" : ()},
32- "frontend" : {"include" : ("ui/**/*.js" , "ui/**/*.tsx" ), "exclude" : ()},
33- "docs" : {"include" : ("*.md" , "docs/**/*" ), "exclude" : ()},
77+ "merge_queue_scope" : "merge-queue" ,
78+ "source" : {
79+ "files" : {
80+ "backend" : {
81+ "include" : ("api/**/*.py" , "backend/**/*.py" ),
82+ "exclude" : (),
83+ },
84+ "frontend" : {
85+ "include" : ("ui/**/*.js" , "ui/**/*.tsx" ),
86+ "exclude" : (),
87+ },
88+ "docs" : {"include" : ("*.md" , "docs/**/*" ), "exclude" : ()},
89+ },
90+ },
3491 },
3592 }
3693
3794
3895def test_from_yaml_invalid_config (tmp_path : pathlib .Path ) -> None :
3996 config_file = tmp_path / "config.yml"
40- config_file .write_text (yaml .dump ({"scopes" : {"Back#end-API" : ["api/**/*.py" ]}}))
97+ config_file .write_text (
98+ yaml .dump ({"scopes" : {"source" : {"files" : {"Back#end-API" : ["api/**/*.py" ]}}}}),
99+ )
41100
42101 # Bad name and missing dict
43102 with pytest .raises (cli .ConfigInvalidError , match = "2 validation errors" ):
@@ -48,15 +107,20 @@ def test_match_scopes_basic() -> None:
48107 config = cli .Config .from_dict (
49108 {
50109 "scopes" : {
51- "backend" : {"include" : ("api/**/*.py" , "backend/**/*.py" )},
52- "frontend" : {"include" : ("ui/**/*.js" , "ui/**/*.tsx" )},
53- "docs" : {"include" : ("*.md" , "docs/**/*" )},
110+ "source" : {
111+ "files" : {
112+ "backend" : {"include" : ("api/**/*.py" , "backend/**/*.py" )},
113+ "frontend" : {"include" : ("ui/**/*.js" , "ui/**/*.tsx" )},
114+ "docs" : {"include" : ("*.md" , "docs/**/*" )},
115+ },
116+ },
54117 },
55118 },
56119 )
57120 files = ["api/models.py" , "ui/components/Button.tsx" , "README.md" , "other.txt" ]
58121
59- scopes_hit , per_scope = cli .match_scopes (config , files )
122+ assert config .scopes .source is not None
123+ scopes_hit , per_scope = cli .match_scopes (files , config .scopes .source .files )
60124
61125 assert scopes_hit == {"backend" , "frontend" , "docs" }
62126 assert per_scope == {
@@ -70,14 +134,19 @@ def test_match_scopes_no_matches() -> None:
70134 config = cli .Config .from_dict (
71135 {
72136 "scopes" : {
73- "backend" : {"include" : ("api/**/*.py" ,)},
74- "frontend" : {"include" : ("ui/**/*.js" ,)},
137+ "source" : {
138+ "files" : {
139+ "backend" : {"include" : ("api/**/*.py" ,)},
140+ "frontend" : {"include" : ("ui/**/*.js" ,)},
141+ },
142+ },
75143 },
76144 },
77145 )
78146 files = ["other.txt" , "unrelated.cpp" ]
79147
80- scopes_hit , per_scope = cli .match_scopes (config , files )
148+ assert config .scopes .source is not None
149+ scopes_hit , per_scope = cli .match_scopes (files , config .scopes .source .files )
81150
82151 assert scopes_hit == set ()
83152 assert per_scope == {}
@@ -87,13 +156,18 @@ def test_match_scopes_multiple_include_single_scope() -> None:
87156 config = cli .Config .from_dict (
88157 {
89158 "scopes" : {
90- "backend" : {"include" : ("api/**/*.py" , "backend/**/*.py" )},
159+ "source" : {
160+ "files" : {
161+ "backend" : {"include" : ("api/**/*.py" , "backend/**/*.py" )},
162+ },
163+ },
91164 },
92165 },
93166 )
94167 files = ["api/models.py" , "backend/services.py" ]
95168
96- scopes_hit , per_scope = cli .match_scopes (config , files )
169+ assert config .scopes .source is not None
170+ scopes_hit , per_scope = cli .match_scopes (files , config .scopes .source .files )
97171
98172 assert scopes_hit == {"backend" }
99173 assert per_scope == {
@@ -105,13 +179,17 @@ def test_match_scopes_with_negation_include() -> None:
105179 config = cli .Config .from_dict (
106180 {
107181 "scopes" : {
108- "backend" : {
109- "include" : ("api/**/*.py" ,),
110- "exclude" : ("api/**/test_*.py" ,),
111- },
112- "frontend" : {
113- "include" : ("ui/**/*.js" ,),
114- "exclude" : ("ui/**/*.spec.js" ,),
182+ "source" : {
183+ "files" : {
184+ "backend" : {
185+ "include" : ("api/**/*.py" ,),
186+ "exclude" : ("api/**/test_*.py" ,),
187+ },
188+ "frontend" : {
189+ "include" : ("ui/**/*.js" ,),
190+ "exclude" : ("ui/**/*.spec.js" ,),
191+ },
192+ },
115193 },
116194 },
117195 },
@@ -123,7 +201,8 @@ def test_match_scopes_with_negation_include() -> None:
123201 "ui/components.spec.js" ,
124202 ]
125203
126- scopes_hit , per_scope = cli .match_scopes (config , files )
204+ assert config .scopes .source is not None
205+ scopes_hit , per_scope = cli .match_scopes (files , config .scopes .source .files )
127206
128207 assert scopes_hit == {"backend" , "frontend" }
129208 assert per_scope == {
@@ -136,16 +215,21 @@ def test_match_scopes_negation_only() -> None:
136215 config = cli .Config .from_dict (
137216 {
138217 "scopes" : {
139- "exclude_images" : {
140- "include" : ("**/*" ,),
141- "exclude" : ("**/*.jpeg" , "**/*.png" ),
218+ "source" : {
219+ "files" : {
220+ "exclude_images" : {
221+ "include" : ("**/*" ,),
222+ "exclude" : ("**/*.jpeg" , "**/*.png" ),
223+ },
224+ },
142225 },
143226 },
144227 },
145228 )
146229 files = ["image.jpeg" , "document.txt" , "photo.png" , "readme.md" ]
147230
148- scopes_hit , per_scope = cli .match_scopes (config , files )
231+ assert config .scopes .source is not None
232+ scopes_hit , per_scope = cli .match_scopes (files , config .scopes .source .files )
149233
150234 assert scopes_hit == {"exclude_images" }
151235 assert per_scope == {
@@ -157,9 +241,13 @@ def test_match_scopes_mixed_with_complex_negation() -> None:
157241 config = cli .Config .from_dict (
158242 {
159243 "scopes" : {
160- "backend" : {
161- "include" : ("**/*.py" ,),
162- "exclude" : ("**/test_*.py" , "**/*_test.py" ),
244+ "source" : {
245+ "files" : {
246+ "backend" : {
247+ "include" : ("**/*.py" ,),
248+ "exclude" : ("**/test_*.py" , "**/*_test.py" ),
249+ },
250+ },
163251 },
164252 },
165253 },
@@ -172,7 +260,8 @@ def test_match_scopes_mixed_with_complex_negation() -> None:
172260 "main.py" ,
173261 ]
174262
175- scopes_hit , per_scope = cli .match_scopes (config , files )
263+ assert config .scopes .source is not None
264+ scopes_hit , per_scope = cli .match_scopes (files , config .scopes .source .files )
176265
177266 assert scopes_hit == {"backend" }
178267 assert per_scope == {
@@ -219,8 +308,12 @@ def test_detect_with_matches(
219308 # Setup config file
220309 config_data = {
221310 "scopes" : {
222- "backend" : {"include" : ["api/**/*.py" ]},
223- "frontend" : {"include" : ["api/**/*.js" ]},
311+ "source" : {
312+ "files" : {
313+ "backend" : {"include" : ["api/**/*.py" ]},
314+ "frontend" : {"include" : ["api/**/*.js" ]},
315+ },
316+ },
224317 },
225318 }
226319 config_file = tmp_path / "mergify-ci.yml"
@@ -260,7 +353,9 @@ def test_detect_no_matches(
260353 tmp_path : pathlib .Path ,
261354) -> None :
262355 # Setup config file
263- config_data = {"scopes" : {"backend" : {"include" : ["api/**/*.py" ]}}}
356+ config_data = {
357+ "scopes" : {"source" : {"files" : {"backend" : {"include" : ["api/**/*.py" ]}}}},
358+ }
264359 config_file = tmp_path / ".mergify-ci.yml"
265360 config_file .write_text (yaml .dump (config_data ))
266361
@@ -292,7 +387,9 @@ def test_detect_debug_output(
292387 monkeypatch .setenv ("ACTIONS_STEP_DEBUG" , "true" )
293388
294389 # Setup config file
295- config_data = {"scopes" : {"backend" : {"include" : ["api/**/*.py" ]}}}
390+ config_data = {
391+ "scopes" : {"source" : {"files" : {"backend" : {"include" : ["api/**/*.py" ]}}}},
392+ }
296393 config_file = tmp_path / ".mergify-ci.yml"
297394 config_file .write_text (yaml .dump (config_data ))
298395
0 commit comments