qontract-reconcile 0.10.1rc513__py3-none-any.whl → 0.10.1rc515__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (20) hide show
  1. {qontract_reconcile-0.10.1rc513.dist-info → qontract_reconcile-0.10.1rc515.dist-info}/METADATA +1 -1
  2. {qontract_reconcile-0.10.1rc513.dist-info → qontract_reconcile-0.10.1rc515.dist-info}/RECORD +19 -17
  3. reconcile/cli.py +3 -3
  4. reconcile/{template_tester.py → resource_template_tester.py} +3 -3
  5. reconcile/saas_auto_promotions_manager/integration.py +28 -3
  6. reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager.py +5 -5
  7. reconcile/saas_auto_promotions_manager/merge_request_manager/merge_request_manager_v2.py +144 -0
  8. reconcile/saas_auto_promotions_manager/merge_request_manager/mr_parser.py +13 -9
  9. reconcile/saas_auto_promotions_manager/merge_request_manager/reconciler.py +207 -0
  10. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +9 -6
  11. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/conftest.py +25 -12
  12. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/data_keys.py +3 -0
  13. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_merge_request_manager.py +51 -153
  14. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_mr_parser.py +11 -9
  15. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_reconciler.py +408 -0
  16. reconcile/test/saas_auto_promotions_manager/test_integration_test.py +23 -88
  17. reconcile/test/saas_auto_promotions_manager/merge_request_manager/merge_request_manager/test_unbatching.py +0 -96
  18. {qontract_reconcile-0.10.1rc513.dist-info → qontract_reconcile-0.10.1rc515.dist-info}/WHEEL +0 -0
  19. {qontract_reconcile-0.10.1rc513.dist-info → qontract_reconcile-0.10.1rc515.dist-info}/entry_points.txt +0 -0
  20. {qontract_reconcile-0.10.1rc513.dist-info → qontract_reconcile-0.10.1rc515.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,408 @@
1
+ from collections.abc import Sequence
2
+ from unittest.mock import create_autospec
3
+
4
+ import pytest
5
+ from gitlab.v4.objects import ProjectMergeRequest
6
+
7
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
8
+ OpenMergeRequest,
9
+ )
10
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
11
+ MSG_MISSING_UNBATCHING,
12
+ MSG_NEW_BATCH,
13
+ MSG_OUTDATED_CONTENT,
14
+ Addition,
15
+ Deletion,
16
+ Diff,
17
+ Promotion,
18
+ Reconciler,
19
+ )
20
+
21
+
22
+ def _aggregate_hashes(items: Sequence[Addition | Deletion]) -> set[str]:
23
+ hashes: set[str] = set()
24
+ for item in items:
25
+ if isinstance(item, Addition):
26
+ hashes.update(item.content_hashes)
27
+ else:
28
+ hashes.update(item.mr.content_hashes)
29
+ return hashes
30
+
31
+
32
+ def _aggregate_channels(items: Sequence[Addition | Deletion]) -> set[str]:
33
+ channels: set[str] = set()
34
+ for item in items:
35
+ if isinstance(item, Addition):
36
+ channels.update(item.channels)
37
+ else:
38
+ channels.update(item.mr.channels)
39
+ return channels
40
+
41
+
42
+ @pytest.mark.parametrize(
43
+ "desired_promotions, open_mrs, expected_diff",
44
+ [
45
+ # No open MRs. Aggregate desired promotions into single batched Promotion
46
+ (
47
+ [
48
+ Promotion(
49
+ channels={"chan1"},
50
+ content_hashes={"hash1"},
51
+ ),
52
+ Promotion(
53
+ channels={"chan2", "chan3"},
54
+ content_hashes={"hash2", "hash3"},
55
+ ),
56
+ ],
57
+ [],
58
+ Diff(
59
+ deletions=[],
60
+ additions=[
61
+ Addition(
62
+ channels={"chan1", "chan2", "chan3"},
63
+ content_hashes={"hash1", "hash2", "hash3"},
64
+ batchable=True,
65
+ ),
66
+ ],
67
+ ),
68
+ ),
69
+ # No desired promotions. We expect that every MR gets closed,
70
+ # no matter its current state.
71
+ (
72
+ [],
73
+ [
74
+ OpenMergeRequest(
75
+ raw=create_autospec(spec=ProjectMergeRequest),
76
+ channels={"chan1", "chan2"},
77
+ content_hashes={"hash1", "hash2"},
78
+ failed_mr_check=True,
79
+ is_batchable=True,
80
+ ),
81
+ OpenMergeRequest(
82
+ raw=create_autospec(spec=ProjectMergeRequest),
83
+ channels={"chan3"},
84
+ content_hashes={"hash3"},
85
+ failed_mr_check=False,
86
+ is_batchable=True,
87
+ ),
88
+ OpenMergeRequest(
89
+ raw=create_autospec(spec=ProjectMergeRequest),
90
+ channels={"chan4"},
91
+ content_hashes={"hash4"},
92
+ failed_mr_check=False,
93
+ is_batchable=False,
94
+ ),
95
+ OpenMergeRequest(
96
+ raw=create_autospec(spec=ProjectMergeRequest),
97
+ channels={"chan5"},
98
+ content_hashes={"hash5"},
99
+ failed_mr_check=True,
100
+ is_batchable=False,
101
+ ),
102
+ ],
103
+ Diff(
104
+ deletions=[
105
+ Deletion(
106
+ mr=OpenMergeRequest(
107
+ raw=create_autospec(spec=ProjectMergeRequest),
108
+ channels={"chan1", "chan2"},
109
+ content_hashes={"hash1", "hash2"},
110
+ failed_mr_check=True,
111
+ is_batchable=True,
112
+ ),
113
+ reason=MSG_MISSING_UNBATCHING,
114
+ ),
115
+ Deletion(
116
+ mr=OpenMergeRequest(
117
+ raw=create_autospec(spec=ProjectMergeRequest),
118
+ channels={"chan3"},
119
+ content_hashes={"hash3"},
120
+ failed_mr_check=False,
121
+ is_batchable=True,
122
+ ),
123
+ reason=MSG_OUTDATED_CONTENT,
124
+ ),
125
+ Deletion(
126
+ mr=OpenMergeRequest(
127
+ raw=create_autospec(spec=ProjectMergeRequest),
128
+ channels={"chan4"},
129
+ content_hashes={"hash4"},
130
+ failed_mr_check=False,
131
+ is_batchable=False,
132
+ ),
133
+ reason=MSG_OUTDATED_CONTENT,
134
+ ),
135
+ Deletion(
136
+ mr=OpenMergeRequest(
137
+ raw=create_autospec(spec=ProjectMergeRequest),
138
+ channels={"chan5"},
139
+ content_hashes={"hash5"},
140
+ failed_mr_check=True,
141
+ is_batchable=False,
142
+ ),
143
+ reason=MSG_OUTDATED_CONTENT,
144
+ ),
145
+ ],
146
+ additions=[],
147
+ ),
148
+ ),
149
+ # We have a single failed, but still marked as batchable MR.
150
+ # The hashes in the failed MR are still desired state.
151
+ # We expect this MR to be closed. Further, a new MR should be
152
+ # opened that is marked as unbatchable. Each hash should have
153
+ # its own separated MR.
154
+ (
155
+ [
156
+ Promotion(
157
+ channels={"chan1"},
158
+ content_hashes={"hash1"},
159
+ ),
160
+ Promotion(
161
+ channels={"chan2"},
162
+ content_hashes={"hash2"},
163
+ ),
164
+ ],
165
+ [
166
+ OpenMergeRequest(
167
+ raw=create_autospec(spec=ProjectMergeRequest),
168
+ channels={"chan1", "chan2"},
169
+ content_hashes={"hash1", "hash2"},
170
+ failed_mr_check=True,
171
+ is_batchable=True,
172
+ ),
173
+ ],
174
+ Diff(
175
+ deletions=[
176
+ Deletion(
177
+ mr=OpenMergeRequest(
178
+ raw=create_autospec(spec=ProjectMergeRequest),
179
+ channels={"chan1", "chan2"},
180
+ content_hashes={"hash1", "hash2"},
181
+ failed_mr_check=True,
182
+ is_batchable=True,
183
+ ),
184
+ reason=MSG_MISSING_UNBATCHING,
185
+ )
186
+ ],
187
+ additions=[
188
+ Addition(
189
+ channels={"chan1"},
190
+ content_hashes={"hash1"},
191
+ batchable=False,
192
+ ),
193
+ Addition(
194
+ channels={"chan2"},
195
+ content_hashes={"hash2"},
196
+ batchable=False,
197
+ ),
198
+ ],
199
+ ),
200
+ ),
201
+ # We have an open valid batched MR. All desired promotions
202
+ # are already addressed by that MR -> there is nothing to do.
203
+ (
204
+ [
205
+ Promotion(
206
+ channels={"chan1"},
207
+ content_hashes={"hash1"},
208
+ ),
209
+ Promotion(
210
+ channels={"chan2"},
211
+ content_hashes={"hash2"},
212
+ ),
213
+ ],
214
+ [
215
+ OpenMergeRequest(
216
+ raw=create_autospec(spec=ProjectMergeRequest),
217
+ channels={"chan1", "chan2"},
218
+ content_hashes={"hash1", "hash2"},
219
+ failed_mr_check=False,
220
+ is_batchable=True,
221
+ ),
222
+ ],
223
+ Diff(
224
+ deletions=[],
225
+ additions=[],
226
+ ),
227
+ ),
228
+ # We have an open valid batched MR. However, there is a promotion
229
+ # that is not addressed by the open MR.
230
+ # We expect that the MR gets closed and a new
231
+ # batched MR gets created which addresses all desired promotions.
232
+ (
233
+ [
234
+ Promotion(
235
+ channels={"chan1"},
236
+ content_hashes={"hash1"},
237
+ ),
238
+ Promotion(
239
+ channels={"chan2"},
240
+ content_hashes={"hash2"},
241
+ ),
242
+ Promotion(
243
+ channels={"chan3", "chan4"},
244
+ content_hashes={"hash3", "hash4"},
245
+ ),
246
+ ],
247
+ [
248
+ OpenMergeRequest(
249
+ raw=create_autospec(spec=ProjectMergeRequest),
250
+ channels={"chan1", "chan2"},
251
+ content_hashes={"hash1", "hash2"},
252
+ failed_mr_check=False,
253
+ is_batchable=True,
254
+ ),
255
+ ],
256
+ Diff(
257
+ deletions=[
258
+ Deletion(
259
+ mr=OpenMergeRequest(
260
+ raw=create_autospec(spec=ProjectMergeRequest),
261
+ channels={"chan1", "chan2"},
262
+ content_hashes={"hash1", "hash2"},
263
+ failed_mr_check=False,
264
+ is_batchable=True,
265
+ ),
266
+ reason=MSG_NEW_BATCH,
267
+ )
268
+ ],
269
+ additions=[
270
+ Addition(
271
+ content_hashes={"hash1", "hash2", "hash3", "hash4"},
272
+ channels={"chan1", "chan2", "chan3", "chan4"},
273
+ batchable=True,
274
+ )
275
+ ],
276
+ ),
277
+ ),
278
+ # We want to promote 12 content hashes.
279
+ # We surpass the current batch limit of 5.
280
+ # We expect a total of 3 batch MRs to be created.
281
+ # The existing open batch MR should be closed.
282
+ (
283
+ [
284
+ Promotion(
285
+ channels={"chan1", "chan2", "chan3"},
286
+ content_hashes={"hash1", "hash2", "hash3"},
287
+ ),
288
+ Promotion(
289
+ channels={"chan4", "chan5", "chan6"},
290
+ content_hashes={"hash4", "hash5", "hash6"},
291
+ ),
292
+ Promotion(
293
+ channels={"chan7", "chan8", "chan9"},
294
+ content_hashes={"hash7", "hash8", "hash9"},
295
+ ),
296
+ Promotion(
297
+ channels={"chan10", "chan11", "chan12"},
298
+ content_hashes={"hash10", "hash11", "hash12"},
299
+ ),
300
+ Promotion(
301
+ channels={"chan13", "chan14"},
302
+ content_hashes={"hash13", "hash14"},
303
+ ),
304
+ ],
305
+ [
306
+ OpenMergeRequest(
307
+ raw=create_autospec(spec=ProjectMergeRequest),
308
+ channels={"chan1", "chan2"},
309
+ content_hashes={"hash1", "hash2"},
310
+ failed_mr_check=False,
311
+ is_batchable=True,
312
+ ),
313
+ ],
314
+ Diff(
315
+ deletions=[
316
+ Deletion(
317
+ mr=OpenMergeRequest(
318
+ raw=create_autospec(spec=ProjectMergeRequest),
319
+ channels={"chan1", "chan2"},
320
+ content_hashes={"hash1", "hash2"},
321
+ failed_mr_check=False,
322
+ is_batchable=True,
323
+ ),
324
+ reason=MSG_NEW_BATCH,
325
+ )
326
+ ],
327
+ additions=[
328
+ Addition(
329
+ # Note, that we expect more than 5 hashes here. The reason is that the
330
+ # aggregated promotions consist of 3 hashes, i.e., 3+3 = 6
331
+ # This is expected and fine.
332
+ content_hashes={
333
+ "hash1",
334
+ "hash2",
335
+ "hash3",
336
+ "hash4",
337
+ "hash5",
338
+ "hash6",
339
+ },
340
+ channels={"chan1", "chan2", "chan3", "chan4", "chan5", "chan6"},
341
+ batchable=True,
342
+ ),
343
+ Addition(
344
+ content_hashes={
345
+ "hash7",
346
+ "hash8",
347
+ "hash9",
348
+ "hash10",
349
+ "hash11",
350
+ "hash12",
351
+ },
352
+ channels={
353
+ "chan7",
354
+ "chan8",
355
+ "chan9",
356
+ "chan10",
357
+ "chan11",
358
+ "chan12",
359
+ },
360
+ batchable=True,
361
+ ),
362
+ Addition(
363
+ content_hashes={"hash13", "hash14"},
364
+ channels={"chan13", "chan14"},
365
+ batchable=True,
366
+ ),
367
+ ],
368
+ ),
369
+ ),
370
+ ],
371
+ )
372
+ def test_reconcile(
373
+ desired_promotions: list[Promotion],
374
+ open_mrs: list[OpenMergeRequest],
375
+ expected_diff: Diff,
376
+ ) -> None:
377
+ reconciler = Reconciler()
378
+ diff = reconciler.reconcile(
379
+ desired_promotions=desired_promotions, open_mrs=open_mrs, batch_limit=5
380
+ )
381
+
382
+ # We do not care about the order. As the batcher is working on sets, this
383
+ # is not deterministic
384
+ assert len(diff.additions) == len(expected_diff.additions)
385
+ assert _aggregate_hashes(diff.additions) == _aggregate_hashes(
386
+ expected_diff.additions
387
+ )
388
+ assert _aggregate_channels(diff.additions) == _aggregate_channels(
389
+ expected_diff.additions
390
+ )
391
+
392
+ # Deletions are actually processed in a deterministic way.
393
+ assert len(diff.deletions) == len(expected_diff.deletions)
394
+ for i in range(len(diff.deletions)):
395
+ assert diff.deletions[i].mr.channels == expected_diff.deletions[i].mr.channels
396
+ assert (
397
+ diff.deletions[i].mr.content_hashes
398
+ == expected_diff.deletions[i].mr.content_hashes
399
+ )
400
+ assert (
401
+ diff.deletions[i].mr.failed_mr_check
402
+ == expected_diff.deletions[i].mr.failed_mr_check
403
+ )
404
+ assert (
405
+ diff.deletions[i].mr.is_batchable
406
+ == expected_diff.deletions[i].mr.is_batchable
407
+ )
408
+ assert diff.deletions[i].reason == expected_diff.deletions[i].reason
@@ -1,124 +1,59 @@
1
- from collections.abc import (
2
- Callable,
3
- Iterable,
4
- Mapping,
5
- )
6
1
  from unittest.mock import create_autospec
7
2
 
8
3
  from reconcile.saas_auto_promotions_manager.integration import SaasAutoPromotionsManager
9
4
  from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager import (
10
5
  MergeRequestManager,
11
6
  )
7
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager_v2 import (
8
+ MergeRequestManagerV2,
9
+ )
12
10
  from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
13
11
  MRParser,
14
12
  )
13
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.reconciler import (
14
+ Reconciler,
15
+ )
15
16
  from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
16
17
  Renderer,
17
18
  )
18
19
  from reconcile.saas_auto_promotions_manager.utils.saas_files_inventory import (
19
20
  SaasFilesInventory,
20
21
  )
21
- from reconcile.typed_queries.saas_files import SaasFile
22
22
  from reconcile.utils.promotion_state import (
23
- PromotionData,
24
23
  PromotionState,
25
24
  )
26
25
  from reconcile.utils.vcs import VCS
27
26
 
28
27
 
29
- def test_integration_test(
30
- saas_files_builder: Callable[[Iterable[Mapping]], list[SaasFile]],
31
- vcs_builder: Callable[..., VCS],
32
- promotion_state_builder: Callable[..., PromotionState],
33
- ):
28
+ def test_integration_test():
34
29
  """
35
30
  Have all the parts glued together and have one full run.
36
- This is too complex to setup and maintain for multiple
37
- test cases. However, it is a good single test to see if
31
+ This is too complex to setup and properly maintain.
32
+ However, it is a good single test to see if
38
33
  all components are wired properly.
39
-
40
- These saas files and states should result in a single
41
- merge request being opened.
42
34
  """
43
- saas_files = saas_files_builder([
44
- {
45
- "path": "/saas1.yml",
46
- "name": "saas_1",
47
- "resourceTemplates": [
48
- {
49
- "name": "template_1",
50
- "url": "repo1/url",
51
- "targets": [
52
- {
53
- "ref": "main",
54
- "namespace": {"path": "/namespace1.yml"},
55
- "promotion": {
56
- "publish": ["channel-1"],
57
- },
58
- },
59
- {
60
- "ref": "main",
61
- "namespace": {"path": "/namespace2.yml"},
62
- "promotion": {
63
- "publish": ["channel-2"],
64
- },
65
- },
66
- ],
67
- }
68
- ],
69
- },
70
- {
71
- "path": "/saas2.yml",
72
- "name": "saas_2",
73
- "resourceTemplates": [
74
- {
75
- "name": "template_2",
76
- "url": "repo1/url",
77
- "targets": [
78
- {
79
- "ref": "current_sha",
80
- "namespace": {"path": "/namespace3.yml"},
81
- "promotion": {
82
- "subscribe": ["channel-1", "channel-2"],
83
- "auto": True,
84
- },
85
- }
86
- ],
87
- }
88
- ],
89
- },
90
- ])
91
- vcs = vcs_builder()
92
- deployment_state = promotion_state_builder([
93
- PromotionData(
94
- success=True,
95
- target_config_hash="new_hash",
96
- saas_file="saas_1",
97
- ),
98
- PromotionData(
99
- success=True,
100
- target_config_hash="new_hash",
101
- saas_file="saas_1",
102
- ),
103
- ])
104
- renderer = create_autospec(spec=Renderer)
105
- mr_parser = create_autospec(spec=MRParser)
35
+ vcs = create_autospec(spec=VCS)
36
+ merge_request_manager_v2 = MergeRequestManagerV2(
37
+ vcs=vcs,
38
+ reconciler=create_autospec(spec=Reconciler),
39
+ mr_parser=create_autospec(spec=MRParser),
40
+ renderer=create_autospec(spec=Renderer),
41
+ )
106
42
  merge_request_manager = MergeRequestManager(
107
43
  vcs=vcs,
108
- mr_parser=mr_parser,
109
- renderer=renderer,
44
+ mr_parser=create_autospec(spec=MRParser),
45
+ renderer=create_autospec(spec=Renderer),
110
46
  )
111
47
  manager = SaasAutoPromotionsManager(
112
- deployment_state=deployment_state,
113
- saas_file_inventory=SaasFilesInventory(saas_files=saas_files),
48
+ deployment_state=create_autospec(spec=PromotionState),
49
+ saas_file_inventory=SaasFilesInventory(saas_files=[]),
114
50
  vcs=vcs,
51
+ merge_request_manager_v2=merge_request_manager_v2,
115
52
  merge_request_manager=merge_request_manager,
116
53
  thread_pool_size=1,
117
54
  dry_run=False,
118
55
  )
119
56
  manager.reconcile()
120
57
 
121
- # Only one MR should be opened
122
- # TODO: assert MR data
123
- vcs.close_app_interface_mr.assert_not_called() # type: ignore[attr-defined]
124
- vcs.open_app_interface_merge_request.assert_called_once() # type: ignore[attr-defined]
58
+ vcs.close_app_interface_mr.assert_not_called()
59
+ vcs.open_app_interface_merge_request.assert_not_called()
@@ -1,96 +0,0 @@
1
- from collections.abc import (
2
- Callable,
3
- Iterable,
4
- )
5
- from unittest.mock import call, create_autospec
6
-
7
- from gitlab.v4.objects import ProjectMergeRequest
8
-
9
- from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager import (
10
- MergeRequestManager,
11
- )
12
- from reconcile.saas_auto_promotions_manager.merge_request_manager.mr_parser import (
13
- MRParser,
14
- OpenMergeRequest,
15
- )
16
- from reconcile.saas_auto_promotions_manager.merge_request_manager.renderer import (
17
- Renderer,
18
- )
19
- from reconcile.utils.vcs import VCS
20
-
21
-
22
- def test_unbatch_multiple_valid(
23
- mr_parser_builder: Callable[[Iterable], MRParser],
24
- renderer: Renderer,
25
- ) -> None:
26
- # We have 2 MRs that failed MR check, but are marked as batchable
27
- open_mrs = [
28
- OpenMergeRequest(
29
- raw=create_autospec(spec=ProjectMergeRequest),
30
- channels="multiple,aggregated,channels",
31
- content_hashes="a,b,c",
32
- is_batchable=True,
33
- failed_mr_check=True,
34
- ),
35
- OpenMergeRequest(
36
- raw=create_autospec(spec=ProjectMergeRequest),
37
- channels="chan",
38
- content_hashes="d",
39
- is_batchable=True,
40
- failed_mr_check=True,
41
- ),
42
- ]
43
- mr_parser = mr_parser_builder(open_mrs)
44
-
45
- vcs = create_autospec(spec=VCS)
46
- merge_request_manager = MergeRequestManager(
47
- vcs=vcs,
48
- mr_parser=mr_parser,
49
- renderer=renderer,
50
- )
51
- expected_close_mr_calls = [
52
- call(
53
- mr.raw,
54
- "Closing this MR because it failed MR check and isn't marked un-batchable yet.",
55
- )
56
- for mr in open_mrs
57
- ]
58
- merge_request_manager.housekeeping()
59
- vcs.close_app_interface_mr.assert_has_calls(expected_close_mr_calls, any_order=True)
60
- assert vcs.close_app_interface_mr.call_count == 2
61
- assert merge_request_manager._unbatchable_hashes == set(["a", "b", "c", "d"])
62
- assert len(merge_request_manager._open_mrs) == 0
63
-
64
-
65
- def test_unbatch_multiple_invalid(
66
- mr_parser_builder: Callable[[Iterable], MRParser],
67
- renderer: Renderer,
68
- ) -> None:
69
- open_mrs = [
70
- OpenMergeRequest(
71
- raw=create_autospec(spec=ProjectMergeRequest),
72
- channels="multiple,aggregated,channels",
73
- content_hashes="a,b,c",
74
- is_batchable=False,
75
- failed_mr_check=False,
76
- ),
77
- OpenMergeRequest(
78
- raw=create_autospec(spec=ProjectMergeRequest),
79
- channels="chan",
80
- content_hashes="d",
81
- is_batchable=True,
82
- failed_mr_check=False,
83
- ),
84
- ]
85
- mr_parser = mr_parser_builder(open_mrs)
86
-
87
- vcs = create_autospec(spec=VCS)
88
- merge_request_manager = MergeRequestManager(
89
- vcs=vcs,
90
- mr_parser=mr_parser,
91
- renderer=renderer,
92
- )
93
- merge_request_manager.housekeeping()
94
- vcs.close_app_interface_mr.assert_not_called()
95
- assert merge_request_manager._unbatchable_hashes == set()
96
- assert len(merge_request_manager._open_mrs) == 2