waldur-site-agent-mup 0.7.0__tar.gz

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.
@@ -0,0 +1,6 @@
1
+ venv/
2
+ .idea/
3
+ .vscode/
4
+ dist/
5
+ **__pycache__
6
+ .DS_Store
@@ -0,0 +1 @@
1
+ 3.9
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: waldur-site-agent-mup
3
+ Version: 0.7.0
4
+ Summary: MUP plugin for Waldur Site Agent
5
+ Author-email: OpenNode Team <info@opennodecloud.com>
6
+ Requires-Python: <4,>=3.9
7
+ Requires-Dist: waldur-site-agent==0.7.0
8
+ Description-Content-Type: text/markdown
9
+
10
+ # MUP plugin for Waldur Site Agent
@@ -0,0 +1 @@
1
+ # MUP plugin for Waldur Site Agent
@@ -0,0 +1,21 @@
1
+ [project]
2
+ name = "waldur-site-agent-mup"
3
+ version = "0.7.0"
4
+ description = "MUP plugin for Waldur Site Agent"
5
+ readme = "README.md"
6
+ authors = [{ name = "OpenNode Team", email = "info@opennodecloud.com" }]
7
+ requires-python = ">=3.9, <4"
8
+ dependencies = [
9
+ "waldur-site-agent==0.7.0"
10
+ ]
11
+
12
+ [build-system]
13
+ requires = ["hatchling"]
14
+ build-backend = "hatchling.build"
15
+
16
+ [tool.uv.sources]
17
+ waldur-site-agent = { workspace = true }
18
+
19
+ # Entry points for exporting backends
20
+ [project.entry-points."waldur_site_agent.backends"]
21
+ mup = "waldur_site_agent_mup.backend:MUPBackend"
@@ -0,0 +1,549 @@
1
+ import unittest
2
+ import uuid
3
+ from unittest.mock import patch
4
+
5
+ import pytest
6
+ from waldur_api_client.models.offering_user import OfferingUser
7
+ from waldur_api_client.models.project_user import ProjectUser
8
+ from waldur_api_client.models.resource import Resource as WaldurResource
9
+ from waldur_api_client.models.resource_limits import ResourceLimits
10
+ from waldur_api_client.types import Unset
11
+
12
+ from waldur_site_agent.backend.exceptions import BackendError
13
+ from waldur_site_agent_mup.backend import MUPBackend
14
+ from waldur_site_agent_mup.client import MUPError
15
+ from waldur_site_agent.backend.structures import BackendResourceInfo
16
+
17
+
18
+ class MUPBackendTest(unittest.TestCase):
19
+ """Test suite for MUP backend functionality."""
20
+
21
+ def setUp(self) -> None:
22
+ """Set up test fixtures."""
23
+ self.mup_settings = {
24
+ "api_url": "https://mup-api.example.com/api",
25
+ "username": "test_user",
26
+ "password": "test_password",
27
+ "default_research_field": 1,
28
+ "default_agency": "FCT",
29
+ "project_prefix": "waldur_",
30
+ "allocation_prefix": "alloc_",
31
+ "default_allocation_type": "compute",
32
+ "default_storage_limit": 1000,
33
+ }
34
+
35
+ self.mup_components = {
36
+ "cpu": {
37
+ "measured_unit": "core-hours",
38
+ "unit_factor": 1,
39
+ "accounting_type": "limit",
40
+ "label": "CPU Cores",
41
+ "mup_allocation_type": "Deucalion x86_64",
42
+ }
43
+ }
44
+
45
+ self.project_uuid = uuid.uuid4()
46
+ self.resource_uuid = uuid.uuid4()
47
+ self.customer_uuid = uuid.uuid4()
48
+
49
+ self.sample_waldur_resource = WaldurResource(
50
+ uuid=self.resource_uuid,
51
+ name="test-resource",
52
+ project_uuid=self.project_uuid,
53
+ project_name="Test Project",
54
+ offering_uuid=uuid.uuid4(),
55
+ offering_name="MUP Offering",
56
+ limits=ResourceLimits.from_dict({"cpu": 10}),
57
+ )
58
+
59
+ self.sample_mup_project = {
60
+ "id": 1,
61
+ "title": "Test Project",
62
+ "description": "A test project",
63
+ "pi": "pi@example.com",
64
+ "grant_number": f"waldur_{self.resource_uuid.hex}",
65
+ "active": True,
66
+ "agency": "FCT",
67
+ }
68
+
69
+ self.sample_mup_allocation = {
70
+ "id": 1,
71
+ "type": "compute",
72
+ "identifier": f"alloc_{self.resource_uuid.hex}",
73
+ "size": 10,
74
+ "used": 0,
75
+ "active": True,
76
+ "project": 1,
77
+ }
78
+
79
+ # Sample user context for testing
80
+ self.sample_user_context = {
81
+ "team": [
82
+ ProjectUser(
83
+ uuid="user-uuid-1",
84
+ email="pi@example.com",
85
+ username="pi_user",
86
+ full_name="Principal Investigator",
87
+ role="admin",
88
+ url="https://waldur.example.com/api/users/user-uuid-1/",
89
+ expiration_time=None,
90
+ offering_user_username="pi_user",
91
+ )
92
+ ],
93
+ "offering_users": [
94
+ OfferingUser(
95
+ username="pi_user",
96
+ user_uuid="user-uuid-1",
97
+ offering_uuid="offering-uuid-1",
98
+ created="2024-01-01T00:00:00Z",
99
+ modified="2024-01-01T00:00:00Z",
100
+ )
101
+ ],
102
+ "user_mappings": {
103
+ "user-uuid-1": ProjectUser(
104
+ uuid="user-uuid-1",
105
+ email="pi@example.com",
106
+ username="pi_user",
107
+ full_name="Principal Investigator",
108
+ role="admin",
109
+ url="https://waldur.example.com/api/users/user-uuid-1/",
110
+ expiration_time=None,
111
+ offering_user_username="pi_user",
112
+ )
113
+ },
114
+ "offering_user_mappings": {
115
+ "user-uuid-1": OfferingUser(
116
+ username="pi_user",
117
+ user_uuid="user-uuid-1",
118
+ offering_uuid="offering-uuid-1",
119
+ created="2025-01-01T00:00:00Z",
120
+ modified="2025-01-01T00:00:00Z",
121
+ )
122
+ },
123
+ }
124
+
125
+ @patch("waldur_site_agent_mup.backend.MUPClient")
126
+ def test_init_with_valid_settings(self, mock_client_class) -> None:
127
+ """Test backend initialization with valid settings."""
128
+ backend = MUPBackend(self.mup_settings, self.mup_components)
129
+
130
+ assert backend.backend_type == "mup"
131
+ assert backend.project_prefix == "waldur_"
132
+ assert backend.allocation_prefix == "alloc_"
133
+ assert backend.default_research_field == 1
134
+ assert backend.default_agency == "FCT"
135
+
136
+ mock_client_class.assert_called_once_with(
137
+ api_url="https://mup-api.example.com/api",
138
+ username="test_user",
139
+ password="test_password",
140
+ )
141
+
142
+ def test_init_missing_required_settings(self) -> None:
143
+ """Test backend initialization fails with missing required settings."""
144
+ incomplete_settings = {"api_url": "https://example.com"}
145
+
146
+ with pytest.raises(ValueError) as context:
147
+ MUPBackend(incomplete_settings, self.mup_components)
148
+
149
+ assert "Missing required setting: username" in str(context.value)
150
+
151
+ def test_init_invalid_accounting_type(self) -> None:
152
+ """Test backend initialization fails with invalid accounting_type."""
153
+ invalid_components = {
154
+ "cpu": {
155
+ "measured_unit": "core-hours",
156
+ "unit_factor": 1,
157
+ "accounting_type": "usage", # Invalid for MUP
158
+ "label": "CPU Cores",
159
+ "mup_allocation_type": "Deucalion x86_64",
160
+ }
161
+ }
162
+
163
+ with pytest.raises(ValueError) as context:
164
+ MUPBackend(self.mup_settings, invalid_components)
165
+
166
+ assert "MUP backend only supports components with accounting_type='limit'" in str(
167
+ context.value
168
+ )
169
+ assert "Component 'cpu' has accounting_type='usage'" in str(context.value)
170
+
171
+ @patch("waldur_site_agent_mup.backend.MUPClient")
172
+ def test_ping_success(self, mock_client_class) -> None:
173
+ """Test successful ping to MUP backend."""
174
+ mock_client = mock_client_class.return_value
175
+ mock_client.get_research_fields.return_value = [{"id": 1, "name": "Computer Science"}]
176
+
177
+ backend = MUPBackend(self.mup_settings, self.mup_components)
178
+ result = backend.ping()
179
+
180
+ assert result
181
+ mock_client.get_research_fields.assert_called_once()
182
+
183
+ @patch("waldur_site_agent_mup.backend.MUPClient")
184
+ def test_ping_failure(self, mock_client_class) -> None:
185
+ """Test ping failure with MUP backend."""
186
+ mock_client = mock_client_class.return_value
187
+ mock_client.get_research_fields.side_effect = MUPError("Connection failed")
188
+
189
+ backend = MUPBackend(self.mup_settings, self.mup_components)
190
+ result = backend.ping()
191
+
192
+ assert not result
193
+
194
+ @patch("waldur_site_agent_mup.backend.MUPClient")
195
+ def test_ping_failure_with_exception(self, mock_client_class) -> None:
196
+ """Test ping failure raises exception when requested."""
197
+ mock_client = mock_client_class.return_value
198
+ mock_client.get_research_fields.side_effect = MUPError("Connection failed")
199
+
200
+ backend = MUPBackend(self.mup_settings, self.mup_components)
201
+
202
+ with pytest.raises(BackendError):
203
+ backend.ping(raise_exception=True)
204
+
205
+ @patch("waldur_site_agent_mup.backend.MUPClient")
206
+ def test_list_components(self, mock_client_class) -> None:
207
+ """Test listing backend components."""
208
+ backend = MUPBackend(self.mup_settings, self.mup_components)
209
+ components = backend.list_components()
210
+
211
+ assert components == ["cpu"]
212
+
213
+ @patch("waldur_site_agent_mup.backend.MUPClient")
214
+ def test_get_research_fields_caching(self, mock_client_class) -> None:
215
+ """Test research fields caching."""
216
+ mock_client = mock_client_class.return_value
217
+ research_fields = [{"id": 1, "name": "Computer Science"}]
218
+ mock_client.get_research_fields.return_value = research_fields
219
+
220
+ backend = MUPBackend(self.mup_settings, self.mup_components)
221
+
222
+ # First call
223
+ result1 = backend.get_research_fields()
224
+ # Second call should use cache
225
+ result2 = backend.get_research_fields()
226
+
227
+ assert result1 == research_fields
228
+ assert result2 == research_fields
229
+ mock_client.get_research_fields.assert_called_once()
230
+
231
+ @patch("waldur_site_agent_mup.backend.MUPClient")
232
+ def test_get_or_create_user_existing(self, mock_client_class) -> None:
233
+ """Test getting existing user."""
234
+ mock_client = mock_client_class.return_value
235
+ existing_users = [{"id": 1, "email": "pi@example.com", "username": "pi_user"}]
236
+ mock_client.get_users.return_value = existing_users
237
+
238
+ backend = MUPBackend(self.mup_settings, self.mup_components)
239
+ waldur_user = {
240
+ "email": "pi@example.com",
241
+ "username": "pi_user",
242
+ "first_name": "Principal",
243
+ "last_name": "Investigator",
244
+ }
245
+
246
+ user_id = backend._get_or_create_user(waldur_user)
247
+
248
+ assert user_id == 1
249
+ mock_client.get_users.assert_called_once()
250
+ mock_client.create_user_request.assert_not_called()
251
+
252
+ @patch("waldur_site_agent_mup.backend.MUPClient")
253
+ def test_get_or_create_user_create_new(self, mock_client_class) -> None:
254
+ """Test creating new user."""
255
+ mock_client = mock_client_class.return_value
256
+ mock_client.get_users.return_value = [] # No existing users
257
+ mock_client.create_user_request.return_value = {"id": 2}
258
+
259
+ backend = MUPBackend(self.mup_settings, self.mup_components)
260
+ waldur_user = {
261
+ "email": "new@example.com",
262
+ "username": "new_user",
263
+ "first_name": "New",
264
+ "last_name": "User",
265
+ }
266
+
267
+ user_id = backend._get_or_create_user(waldur_user)
268
+
269
+ assert user_id == 2
270
+ mock_client.get_users.assert_called_once()
271
+ mock_client.create_user_request.assert_called_once()
272
+
273
+ @patch("waldur_site_agent_mup.backend.MUPClient")
274
+ def test_get_or_create_user_no_email(self, mock_client_class) -> None:
275
+ """Test handling user without email."""
276
+ backend = MUPBackend(self.mup_settings, self.mup_components)
277
+ waldur_user = {"username": "user_without_email"}
278
+
279
+ user_id = backend._get_or_create_user(waldur_user)
280
+
281
+ assert user_id is None
282
+
283
+ @patch("waldur_site_agent_mup.backend.MUPClient")
284
+ def test_get_project_by_waldur_id_found(self, mock_client_class) -> None:
285
+ """Test finding project by Waldur ID."""
286
+ mock_client = mock_client_class.return_value
287
+ mock_client.get_projects.return_value = [self.sample_mup_project]
288
+
289
+ backend = MUPBackend(self.mup_settings, self.mup_components)
290
+ project = backend._get_project_by_waldur_id(self.resource_uuid.hex)
291
+
292
+ assert project == self.sample_mup_project
293
+
294
+ @patch("waldur_site_agent_mup.backend.MUPClient")
295
+ def test_get_project_by_waldur_id_not_found(self, mock_client_class) -> None:
296
+ """Test project not found by Waldur ID."""
297
+ mock_client = mock_client_class.return_value
298
+ mock_client.get_projects.return_value = []
299
+
300
+ backend = MUPBackend(self.mup_settings, self.mup_components)
301
+ project = backend._get_project_by_waldur_id("nonexistent_uuid")
302
+
303
+ assert project is None
304
+
305
+ @patch("waldur_site_agent_mup.backend.MUPClient")
306
+ def test_create_mup_project_success(self, mock_client_class) -> None:
307
+ """Test successful MUP project creation."""
308
+ mock_client = mock_client_class.return_value
309
+ mock_client.create_project.return_value = self.sample_mup_project
310
+
311
+ backend = MUPBackend(self.mup_settings, self.mup_components)
312
+ waldur_project = {
313
+ "uuid": self.project_uuid.hex,
314
+ "name": "Test Project",
315
+ "description": "A test project",
316
+ }
317
+ pi_email = "pi@example.com"
318
+
319
+ result = backend._create_mup_project(waldur_project, pi_email)
320
+
321
+ assert result == self.sample_mup_project
322
+ mock_client.create_project.assert_called_once()
323
+
324
+ # Check project data structure
325
+ call_args = mock_client.create_project.call_args[0][0]
326
+ assert call_args["title"] == "Test Project"
327
+ assert call_args["pi"] == "pi@example.com"
328
+ assert call_args["grant_number"] == f"waldur_{self.project_uuid.hex}"
329
+
330
+ @patch("waldur_site_agent_mup.backend.MUPClient")
331
+ def test_create_mup_project_failure(self, mock_client_class) -> None:
332
+ """Test MUP project creation failure."""
333
+ mock_client = mock_client_class.return_value
334
+ mock_client.create_project.side_effect = MUPError("Project creation failed")
335
+
336
+ backend = MUPBackend(self.mup_settings, self.mup_components)
337
+ waldur_project = {"uuid": self.project_uuid.hex, "name": "Test Project"}
338
+ pi_email = "pi@example.com"
339
+
340
+ result = backend._create_mup_project(waldur_project, pi_email)
341
+
342
+ assert result is None
343
+
344
+ @patch("waldur_site_agent_mup.backend.MUPClient")
345
+ def test_create_resource_success(self, mock_client_class) -> None:
346
+ """Test successful resource creation."""
347
+ mock_client = mock_client_class.return_value
348
+ # Mock for user creation
349
+ mock_client.get_users.return_value = [] # No existing users
350
+ mock_client.create_user_request.return_value = {"id": 1}
351
+ mock_client.get_projects.return_value = [self.sample_mup_project]
352
+ mock_client.create_allocation.return_value = self.sample_mup_allocation
353
+ mock_client.add_project_member.return_value = {"status": "success"}
354
+
355
+ backend = MUPBackend(self.mup_settings, self.mup_components)
356
+ result = backend.create_resource(self.sample_waldur_resource, self.sample_user_context)
357
+
358
+ assert isinstance(result, BackendResourceInfo)
359
+ assert result.backend_id == "1"
360
+ assert result.limits["cpu"] == 10
361
+
362
+ @patch("waldur_site_agent_mup.backend.MUPClient")
363
+ def test_create_resource_no_project_uuid(self, mock_client_class) -> None:
364
+ """Test resource creation failure with no project UUID."""
365
+ backend = MUPBackend(self.mup_settings, self.mup_components)
366
+
367
+ waldur_resource = self.sample_waldur_resource
368
+ waldur_resource.project_uuid = Unset()
369
+
370
+ with pytest.raises(BackendError) as context:
371
+ backend.create_resource(waldur_resource)
372
+
373
+ assert "No project UUID found" in str(context.value)
374
+
375
+ @patch("waldur_site_agent_mup.backend.MUPClient")
376
+ def test_create_resource_project_activation(self, mock_client_class) -> None:
377
+ """Test project activation during resource creation."""
378
+ mock_client = mock_client_class.return_value
379
+ # Mock for user creation
380
+ mock_client.get_users.return_value = [] # No existing users
381
+ mock_client.create_user_request.return_value = {"id": 1}
382
+
383
+ # Project exists but is inactive
384
+ inactive_project = self.sample_mup_project.copy()
385
+ inactive_project["active"] = False
386
+ mock_client.get_projects.return_value = [inactive_project]
387
+ mock_client.activate_project.return_value = {"status": "activated"}
388
+ mock_client.create_allocation.return_value = self.sample_mup_allocation
389
+ mock_client.add_project_member.return_value = {"status": "success"}
390
+
391
+ backend = MUPBackend(self.mup_settings, self.mup_components)
392
+ result = backend.create_resource(self.sample_waldur_resource, self.sample_user_context)
393
+
394
+ mock_client.activate_project.assert_called_once_with(1)
395
+ assert isinstance(result, BackendResourceInfo)
396
+
397
+ @patch("waldur_site_agent_mup.backend.MUPClient")
398
+ def test_create_resource_with_real_pi_from_context(self, mock_client_class) -> None:
399
+ """Test resource creation uses real PI from user context."""
400
+ mock_client = mock_client_class.return_value
401
+ mock_client.get_users.return_value = [] # No existing users
402
+ mock_client.create_user_request.return_value = {"id": 1}
403
+ mock_client.get_projects.return_value = [] # No existing project
404
+ mock_client.create_project.return_value = self.sample_mup_project
405
+ mock_client.create_allocation.return_value = self.sample_mup_allocation
406
+ mock_client.add_project_member.return_value = {"status": "success"}
407
+
408
+ backend = MUPBackend(self.mup_settings, self.mup_components)
409
+ result = backend.create_resource(self.sample_waldur_resource, self.sample_user_context)
410
+
411
+ # Verify project was created with real PI email from user context
412
+ mock_client.create_project.assert_called_once()
413
+ project_data = mock_client.create_project.call_args[0][0]
414
+ assert project_data["pi"] == "pi@example.com" # Real email from user context
415
+
416
+ # Verify user was added to project during creation
417
+ mock_client.add_project_member.assert_called()
418
+
419
+ assert isinstance(result, BackendResourceInfo)
420
+
421
+ @patch("waldur_site_agent_mup.backend.MUPClient")
422
+ def test_create_resource_without_user_context(self, mock_client_class) -> None:
423
+ """Test resource creation falls back to default PI when no user context."""
424
+ mock_client = mock_client_class.return_value
425
+ mock_client.get_users.return_value = []
426
+ mock_client.create_user_request.return_value = {"id": 1}
427
+ mock_client.get_projects.return_value = [] # No existing project
428
+ mock_client.create_project.return_value = self.sample_mup_project
429
+ mock_client.create_allocation.return_value = self.sample_mup_allocation
430
+
431
+ backend = MUPBackend(self.mup_settings, self.mup_components)
432
+ result = backend.create_resource(self.sample_waldur_resource) # No user context
433
+
434
+ # Verify project was created with fallback PI email
435
+ mock_client.create_project.assert_called_once()
436
+ project_data = mock_client.create_project.call_args[0][0]
437
+ assert project_data["pi"].endswith(".example.com") # Fallback email
438
+
439
+ # Verify no users were added during creation (since no context)
440
+ mock_client.add_project_member.assert_not_called()
441
+
442
+ assert isinstance(result, BackendResourceInfo)
443
+
444
+ @patch("waldur_site_agent_mup.backend.MUPClient")
445
+ def test_collect_limits(self, mock_client_class) -> None:
446
+ """Test limits collection."""
447
+ backend = MUPBackend(self.mup_settings, self.mup_components)
448
+
449
+ waldur_resource = WaldurResource(limits=ResourceLimits.from_dict({"cpu": 10}))
450
+
451
+ allocation_limits, waldur_limits = backend._collect_resource_limits(waldur_resource)
452
+
453
+ assert allocation_limits["cpu"] == 10 # unit_factor = 1
454
+ assert waldur_limits["cpu"] == 10
455
+
456
+ @patch("waldur_site_agent_mup.backend.MUPClient")
457
+ def test_get_resource_metadata(self, mock_client_class) -> None:
458
+ """Test getting resource metadata."""
459
+ mock_client = mock_client_class.return_value
460
+ mock_client.get_projects.return_value = [self.sample_mup_project]
461
+ mock_client.get_project_allocations.return_value = [self.sample_mup_allocation]
462
+
463
+ backend = MUPBackend(self.mup_settings, self.mup_components)
464
+ metadata = backend.get_resource_metadata("1")
465
+
466
+ expected_metadata = {
467
+ "mup_project_id": 1,
468
+ "mup_allocation_id": 1,
469
+ "allocation_type": "compute",
470
+ "allocation_size": 10,
471
+ "allocation_used": 0,
472
+ }
473
+
474
+ assert metadata == expected_metadata
475
+
476
+ @patch("waldur_site_agent_mup.backend.MUPClient")
477
+ def test_get_usage_report(self, mock_client_class) -> None:
478
+ """Test usage report generation."""
479
+ mock_client = mock_client_class.return_value
480
+ mock_client.get_projects.return_value = [self.sample_mup_project]
481
+ mock_client.get_project_allocations.return_value = [
482
+ {"id": 1, "type": "compute", "used": 5, "size": 10}
483
+ ]
484
+ mock_client.get_project_members.return_value = [
485
+ {"id": 1, "active": True, "member": {"username": "user1", "email": "user1@example.com"}}
486
+ ]
487
+
488
+ backend = MUPBackend(self.mup_settings, self.mup_components)
489
+ accounts = ["1"]
490
+ report = backend._get_usage_report(accounts)
491
+
492
+ account_key = "1"
493
+ assert account_key in report
494
+ assert "TOTAL_ACCOUNT_USAGE" in report[account_key]
495
+ assert report[account_key]["TOTAL_ACCOUNT_USAGE"]["cpu"] == 5
496
+ assert "user1" in report[account_key]
497
+
498
+ @patch("waldur_site_agent_mup.backend.MUPClient")
499
+ def test_add_users_to_resource(self, mock_client_class) -> None:
500
+ """Test adding users to resource."""
501
+ mock_client = mock_client_class.return_value
502
+ mock_client.get_projects.return_value = [self.sample_mup_project]
503
+ mock_client.get_project_allocations.return_value = [self.sample_mup_allocation]
504
+ mock_client.get_users.return_value = [] # User doesn't exist
505
+ mock_client.create_user_request.return_value = {"id": 2}
506
+ mock_client.add_project_member.return_value = {"status": "success"}
507
+
508
+ backend = MUPBackend(self.mup_settings, self.mup_components)
509
+ user_ids = {"newuser@example.com"}
510
+ resource_backend_id = "1"
511
+
512
+ added_users = backend.add_users_to_resource(resource_backend_id, user_ids)
513
+
514
+ assert added_users == user_ids
515
+ mock_client.add_project_member.assert_called_once()
516
+
517
+ @patch("waldur_site_agent_mup.backend.MUPClient")
518
+ def test_remove_users_from_account(self, mock_client_class) -> None:
519
+ """Test removing users from account."""
520
+ mock_client = mock_client_class.return_value
521
+ mock_client.get_projects.return_value = [self.sample_mup_project]
522
+ mock_client.get_project_allocations.return_value = [self.sample_mup_allocation]
523
+ mock_client.get_project_members.return_value = [
524
+ {"id": 1, "active": True, "member": {"username": "user1", "email": "user1@example.com"}}
525
+ ]
526
+ mock_client.toggle_member_status.return_value = {"status": "deactivated"}
527
+
528
+ backend = MUPBackend(self.mup_settings, self.mup_components)
529
+ usernames = {"user1"}
530
+ resource_backend_id = "1"
531
+
532
+ removed_users = backend.remove_users_from_resource(resource_backend_id, usernames)
533
+
534
+ assert removed_users == ["user1"]
535
+ mock_client.toggle_member_status.assert_called_once_with(1, 1, {"active": False})
536
+
537
+ @patch("waldur_site_agent_mup.backend.MUPClient")
538
+ def test_unsupported_operations_warning_only(self, mock_client_class) -> None:
539
+ """Test that unsupported operations return False and log warnings."""
540
+ backend = MUPBackend(self.mup_settings, self.mup_components)
541
+
542
+ # These operations are not supported by MUP but should not raise exceptions
543
+ assert not backend.downscale_resource("test_account")
544
+ assert not backend.pause_resource("test_account")
545
+ assert not backend.restore_resource("test_account")
546
+
547
+
548
+ if __name__ == "__main__":
549
+ unittest.main()