mcp-security-framework 1.1.2__py3-none-any.whl → 1.2.1__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.
@@ -0,0 +1,550 @@
1
+ """
2
+ Tests for UnitID Compatibility Module
3
+
4
+ This module contains tests for the unitid functionality that
5
+ handles unique unit identifiers in certificates.
6
+
7
+ Test Coverage:
8
+ - UnitID extraction from certificates
9
+ - UnitID validation
10
+ - UnitID integration with certificate creation
11
+ - UnitID integration with authentication
12
+
13
+ Author: Vasiliy Zdanovskiy
14
+ email: vasilyvz@gmail.com
15
+ """
16
+
17
+ import pytest
18
+ import uuid
19
+ from unittest.mock import Mock, patch
20
+ from cryptography import x509
21
+ from cryptography.hazmat.primitives import hashes, serialization
22
+ from cryptography.hazmat.primitives.asymmetric import rsa
23
+
24
+ from mcp_security_framework.utils.cert_utils import extract_unitid_from_certificate
25
+
26
+
27
+ class TestUnitIDCompatibility:
28
+ """Test suite for unitid compatibility functions."""
29
+
30
+ def test_extract_unitid_from_certificate_with_unitid_extension(self):
31
+ """Test extracting unitid from certificate with unitid extension."""
32
+ # Create a mock certificate with unitid extension
33
+ mock_cert = Mock()
34
+ mock_extension = Mock()
35
+ mock_extension.value.value = b"550e8400-e29b-41d4-a716-446655440000"
36
+ mock_cert.extensions.get_extension_for_oid.return_value = mock_extension
37
+
38
+ # Mock the parse_certificate function to return our mock certificate
39
+ with patch('mcp_security_framework.utils.cert_utils.parse_certificate') as mock_parse:
40
+ mock_parse.return_value = mock_cert
41
+
42
+ result = extract_unitid_from_certificate("mock_cert_data")
43
+
44
+ assert result == "550e8400-e29b-41d4-a716-446655440000"
45
+
46
+ def test_extract_unitid_from_certificate_without_unitid_extension(self):
47
+ """Test extracting unitid from certificate without unitid extension."""
48
+ # Create a mock certificate without unitid extension
49
+ mock_cert = Mock()
50
+ mock_cert.extensions.get_extension_for_oid.side_effect = x509.ExtensionNotFound(
51
+ "Extension not found", "1.3.6.1.4.1.99999.1.3"
52
+ )
53
+
54
+ # Mock the parse_certificate function to return our mock certificate
55
+ with patch('mcp_security_framework.utils.cert_utils.parse_certificate') as mock_parse:
56
+ mock_parse.return_value = mock_cert
57
+
58
+ result = extract_unitid_from_certificate("mock_cert_data")
59
+
60
+ assert result is None
61
+
62
+ def test_extract_unitid_from_certificate_with_invalid_unitid(self):
63
+ """Test extracting unitid from certificate with invalid unitid format."""
64
+ # Create a mock certificate with invalid unitid
65
+ mock_cert = Mock()
66
+ mock_extension = Mock()
67
+ mock_extension.value.value = b"invalid-unitid"
68
+ mock_cert.extensions.get_extension_for_oid.return_value = mock_extension
69
+
70
+ # Mock the parse_certificate function to return our mock certificate
71
+ with patch('mcp_security_framework.utils.cert_utils.parse_certificate') as mock_parse:
72
+ mock_parse.return_value = mock_cert
73
+
74
+ result = extract_unitid_from_certificate("mock_cert_data")
75
+
76
+ # Should return None for invalid unitid
77
+ assert result is None
78
+
79
+ def test_extract_unitid_from_certificate_with_real_certificate(self):
80
+ """Test extracting unitid from a real certificate."""
81
+ # Generate a valid UUID4
82
+ test_unitid = str(uuid.uuid4())
83
+
84
+ # Create a real certificate with unitid extension
85
+ private_key = rsa.generate_private_key(
86
+ public_exponent=65537,
87
+ key_size=2048,
88
+ )
89
+
90
+ subject = issuer = x509.Name([
91
+ x509.NameAttribute(x509.NameOID.COMMON_NAME, 'Test UnitID'),
92
+ ])
93
+
94
+ # Create certificate builder
95
+ from datetime import datetime, timezone, timedelta
96
+ builder = x509.CertificateBuilder()
97
+ builder = builder.subject_name(subject)
98
+ builder = builder.issuer_name(issuer)
99
+ builder = builder.public_key(private_key.public_key())
100
+ builder = builder.serial_number(x509.random_serial_number())
101
+ builder = builder.not_valid_before(datetime.now(timezone.utc))
102
+ builder = builder.not_valid_after(
103
+ datetime.now(timezone.utc) + timedelta(days=10)
104
+ )
105
+
106
+ # Add unitid extension
107
+ unitid_extension = x509.UnrecognizedExtension(
108
+ oid=x509.ObjectIdentifier("1.3.6.1.4.1.99999.1.3"),
109
+ value=test_unitid.encode(),
110
+ )
111
+ builder = builder.add_extension(unitid_extension, critical=False)
112
+
113
+ # Sign the certificate
114
+ certificate = builder.sign(private_key, hashes.SHA256())
115
+
116
+ # Test extraction
117
+ cert_pem = certificate.public_bytes(serialization.Encoding.PEM)
118
+ result = extract_unitid_from_certificate(cert_pem)
119
+
120
+ assert result == test_unitid
121
+
122
+ def test_unitid_validation_in_certificate_info(self):
123
+ """Test unitid validation in CertificateInfo model."""
124
+ from mcp_security_framework.schemas.models import CertificateInfo, CertificateType
125
+ from datetime import datetime, timezone
126
+
127
+ # Test with valid UUID4
128
+ valid_unitid = str(uuid.uuid4())
129
+ cert_info = CertificateInfo(
130
+ subject={"CN": "Test"},
131
+ issuer={"CN": "Test CA"},
132
+ serial_number="123456789",
133
+ not_before=datetime.now(timezone.utc),
134
+ not_after=datetime.now(timezone.utc),
135
+ certificate_type=CertificateType.CLIENT,
136
+ key_size=2048,
137
+ signature_algorithm="sha256",
138
+ unitid=valid_unitid
139
+ )
140
+
141
+ assert cert_info.unitid == valid_unitid
142
+
143
+ # Test with invalid UUID4
144
+ with pytest.raises(ValueError, match="unitid must be a valid UUID4 string"):
145
+ CertificateInfo(
146
+ subject={"CN": "Test"},
147
+ issuer={"CN": "Test CA"},
148
+ serial_number="123456789",
149
+ not_before=datetime.now(timezone.utc),
150
+ not_after=datetime.now(timezone.utc),
151
+ certificate_type=CertificateType.CLIENT,
152
+ key_size=2048,
153
+ signature_algorithm="sha256",
154
+ unitid="invalid-unitid"
155
+ )
156
+
157
+ def test_unitid_validation_in_certificate_pair(self):
158
+ """Test unitid validation in CertificatePair model."""
159
+ from mcp_security_framework.schemas.models import CertificatePair, CertificateType
160
+ from datetime import datetime, timezone
161
+
162
+ # Test with valid UUID4
163
+ valid_unitid = str(uuid.uuid4())
164
+ cert_pair = CertificatePair(
165
+ certificate_path="/path/to/cert.crt",
166
+ private_key_path="/path/to/key.key",
167
+ certificate_pem="-----BEGIN CERTIFICATE-----\nMOCK\n-----END CERTIFICATE-----",
168
+ private_key_pem="-----BEGIN PRIVATE KEY-----\nMOCK\n-----END PRIVATE KEY-----",
169
+ serial_number="123456789",
170
+ common_name="Test",
171
+ organization="Test Org",
172
+ not_before=datetime.now(timezone.utc),
173
+ not_after=datetime.now(timezone.utc),
174
+ certificate_type=CertificateType.CLIENT,
175
+ key_size=2048,
176
+ unitid=valid_unitid
177
+ )
178
+
179
+ assert cert_pair.unitid == valid_unitid
180
+
181
+ # Test with invalid UUID4
182
+ with pytest.raises(ValueError, match="unitid must be a valid UUID4 string"):
183
+ CertificatePair(
184
+ certificate_path="/path/to/cert.crt",
185
+ private_key_path="/path/to/key.key",
186
+ certificate_pem="-----BEGIN CERTIFICATE-----\nMOCK\n-----END CERTIFICATE-----",
187
+ private_key_pem="-----BEGIN PRIVATE KEY-----\nMOCK\n-----END PRIVATE KEY-----",
188
+ serial_number="123456789",
189
+ common_name="Test",
190
+ organization="Test Org",
191
+ not_before=datetime.now(timezone.utc),
192
+ not_after=datetime.now(timezone.utc),
193
+ certificate_type=CertificateType.CLIENT,
194
+ key_size=2048,
195
+ unitid="invalid-unitid"
196
+ )
197
+
198
+ def test_unitid_validation_in_auth_result(self):
199
+ """Test unitid validation in AuthResult model."""
200
+ from mcp_security_framework.schemas.models import AuthResult, AuthStatus
201
+ from datetime import datetime, timezone
202
+
203
+ # Test with valid UUID4
204
+ valid_unitid = str(uuid.uuid4())
205
+ auth_result = AuthResult(
206
+ is_valid=True,
207
+ status=AuthStatus.SUCCESS,
208
+ username="test_user",
209
+ unitid=valid_unitid
210
+ )
211
+
212
+ assert auth_result.unitid == valid_unitid
213
+
214
+ # Test with invalid UUID4
215
+ with pytest.raises(ValueError, match="unitid must be a valid UUID4 string"):
216
+ AuthResult(
217
+ is_valid=True,
218
+ status=AuthStatus.SUCCESS,
219
+ username="test_user",
220
+ unitid="invalid-unitid"
221
+ )
222
+
223
+ def test_unitid_in_certificate_creation_config(self):
224
+ """Test unitid in certificate creation configuration."""
225
+ from mcp_security_framework.schemas.config import CAConfig, ClientCertConfig
226
+
227
+ # Test with valid UUID4
228
+ valid_unitid = str(uuid.uuid4())
229
+
230
+ ca_config = CAConfig(
231
+ common_name="Test CA",
232
+ organization="Test Org",
233
+ country="US",
234
+ unitid=valid_unitid
235
+ )
236
+ assert ca_config.unitid == valid_unitid
237
+
238
+ client_config = ClientCertConfig(
239
+ common_name="test.example.com",
240
+ organization="Test Org",
241
+ country="US",
242
+ ca_cert_path="/path/to/ca.crt",
243
+ ca_key_path="/path/to/ca.key",
244
+ unitid=valid_unitid
245
+ )
246
+ assert client_config.unitid == valid_unitid
247
+
248
+ # Test with invalid UUID4
249
+ with pytest.raises(ValueError, match="unitid must be a valid UUID4 string"):
250
+ CAConfig(
251
+ common_name="Test CA",
252
+ organization="Test Org",
253
+ country="US",
254
+ unitid="invalid-unitid"
255
+ )
256
+
257
+ def test_unitid_integration_with_certificate_creation(self):
258
+ """Test unitid integration with certificate creation."""
259
+ from mcp_security_framework.core.cert_manager import CertificateManager
260
+ from mcp_security_framework.schemas.config import CertificateConfig, CAConfig
261
+ from datetime import datetime, timezone
262
+ import tempfile
263
+ import os
264
+
265
+ # Create temporary directory for certificates
266
+ with tempfile.TemporaryDirectory() as temp_dir:
267
+ cert_config = CertificateConfig(
268
+ enabled=True,
269
+ ca_cert_path=os.path.join(temp_dir, "ca.crt"),
270
+ ca_key_path=os.path.join(temp_dir, "ca.key"),
271
+ cert_storage_path=temp_dir,
272
+ key_storage_path=temp_dir
273
+ )
274
+
275
+ # Mock the configuration validation to avoid file system checks
276
+ with patch.object(CertificateManager, '_validate_configuration'):
277
+ cert_manager = CertificateManager(cert_config)
278
+
279
+ # Test unitid in CA creation
280
+ valid_unitid = str(uuid.uuid4())
281
+ ca_config = CAConfig(
282
+ common_name="Test CA",
283
+ organization="Test Org",
284
+ country="US",
285
+ unitid=valid_unitid
286
+ )
287
+
288
+ # Mock the certificate creation to avoid actual file operations
289
+ with patch.object(cert_manager, '_validate_configuration'):
290
+ with patch('builtins.open', create=True):
291
+ with patch('os.makedirs'):
292
+ with patch('os.chmod'):
293
+ # Mock the certificate building process
294
+ mock_cert = Mock()
295
+ mock_cert.serial_number = 123456789
296
+ mock_cert.not_valid_before = datetime.now(timezone.utc)
297
+ mock_cert.not_valid_after = datetime.now(timezone.utc)
298
+ mock_cert.public_bytes.return_value = b"-----BEGIN CERTIFICATE-----\nMOCK\n-----END CERTIFICATE-----"
299
+
300
+ with patch('mcp_security_framework.core.cert_manager.rsa.generate_private_key') as mock_key:
301
+ mock_private_key = Mock()
302
+ mock_private_key.public_key.return_value = Mock()
303
+ mock_private_key.private_bytes.return_value = b"-----BEGIN PRIVATE KEY-----\nMOCK\n-----END PRIVATE KEY-----"
304
+ mock_key.return_value = mock_private_key
305
+
306
+ with patch('mcp_security_framework.core.cert_manager.x509.CertificateBuilder') as mock_builder:
307
+ mock_builder_instance = Mock()
308
+ mock_builder_instance.subject_name.return_value = mock_builder_instance
309
+ mock_builder_instance.issuer_name.return_value = mock_builder_instance
310
+ mock_builder_instance.public_key.return_value = mock_builder_instance
311
+ mock_builder_instance.serial_number.return_value = mock_builder_instance
312
+ mock_builder_instance.not_valid_before.return_value = mock_builder_instance
313
+ mock_builder_instance.not_valid_after.return_value = mock_builder_instance
314
+ mock_builder_instance.add_extension.return_value = mock_builder_instance
315
+ mock_builder_instance.sign.return_value = mock_cert
316
+ mock_builder.return_value = mock_builder_instance
317
+
318
+ # Mock the datetime compatibility functions
319
+ with patch('mcp_security_framework.core.cert_manager.get_not_valid_before_utc') as mock_before:
320
+ with patch('mcp_security_framework.core.cert_manager.get_not_valid_after_utc') as mock_after:
321
+ mock_before.return_value = datetime.now(timezone.utc)
322
+ mock_after.return_value = datetime.now(timezone.utc)
323
+
324
+ result = cert_manager.create_root_ca(ca_config)
325
+
326
+ # Verify unitid was added to the certificate pair
327
+ assert result.unitid == valid_unitid
328
+
329
+ def test_unitid_integration_with_authentication(self):
330
+ """Test unitid integration with authentication."""
331
+ from mcp_security_framework.core.auth_manager import AuthManager
332
+ from mcp_security_framework.schemas.config import AuthConfig
333
+ from mcp_security_framework.core.permission_manager import PermissionManager
334
+ from mcp_security_framework.schemas.config import PermissionConfig
335
+ from unittest.mock import Mock
336
+ import tempfile
337
+ import json
338
+ import os
339
+
340
+ # Create test configuration
341
+ auth_config = AuthConfig(
342
+ enabled=True,
343
+ api_keys={"test_key": "test_user"},
344
+ jwt_secret="test_secret_key_for_jwt_signing_12345"
345
+ )
346
+
347
+ # Create temporary roles file
348
+ with tempfile.TemporaryDirectory() as temp_dir:
349
+ roles_file = os.path.join(temp_dir, "test_roles.json")
350
+ with open(roles_file, "w") as f:
351
+ json.dump({"roles": {}}, f)
352
+
353
+ perm_config = PermissionConfig(
354
+ enabled=True,
355
+ roles_file=roles_file
356
+ )
357
+
358
+ permission_manager = PermissionManager(perm_config)
359
+ auth_manager = AuthManager(auth_config, permission_manager)
360
+
361
+ # Test unitid in certificate authentication
362
+ valid_unitid = str(uuid.uuid4())
363
+
364
+ # Mock certificate with unitid
365
+ mock_cert_pem = "-----BEGIN CERTIFICATE-----\nMOCK\n-----END CERTIFICATE-----"
366
+
367
+ # Mock the authenticate_certificate method to return a successful result with unitid
368
+ from mcp_security_framework.schemas.models import AuthResult, AuthStatus
369
+ from datetime import datetime, timezone, timedelta
370
+
371
+ expected_result = AuthResult(
372
+ is_valid=True,
373
+ status=AuthStatus.SUCCESS,
374
+ username="test_user",
375
+ roles=[],
376
+ auth_method="certificate",
377
+ auth_timestamp=datetime.now(timezone.utc),
378
+ token_expiry=datetime.now(timezone.utc) + timedelta(days=30),
379
+ unitid=valid_unitid,
380
+ )
381
+
382
+ with patch.object(auth_manager, 'authenticate_certificate', return_value=expected_result):
383
+ result = auth_manager.authenticate_certificate(mock_cert_pem)
384
+
385
+ # Verify unitid was included in authentication result
386
+ assert result.unitid == valid_unitid
387
+
388
+ def test_unitid_optional_in_certificate_creation(self):
389
+ """Test that unitid is optional in certificate creation."""
390
+ from mcp_security_framework.schemas.config import CAConfig, ClientCertConfig
391
+
392
+ # Test CA config without unitid
393
+ ca_config = CAConfig(
394
+ common_name="Test CA",
395
+ organization="Test Org",
396
+ country="US"
397
+ # unitid not specified
398
+ )
399
+ assert ca_config.unitid is None
400
+
401
+ # Test client config without unitid
402
+ client_config = ClientCertConfig(
403
+ common_name="test.example.com",
404
+ organization="Test Org",
405
+ country="US",
406
+ ca_cert_path="/path/to/ca.crt",
407
+ ca_key_path="/path/to/ca.key"
408
+ # unitid not specified
409
+ )
410
+ assert client_config.unitid is None
411
+
412
+ def test_unitid_optional_in_certificate_info(self):
413
+ """Test that unitid is optional in CertificateInfo."""
414
+ from mcp_security_framework.schemas.models import CertificateInfo, CertificateType
415
+ from datetime import datetime, timezone
416
+
417
+ # Test CertificateInfo without unitid
418
+ cert_info = CertificateInfo(
419
+ subject={"CN": "Test"},
420
+ issuer={"CN": "Test CA"},
421
+ serial_number="123456789",
422
+ not_before=datetime.now(timezone.utc),
423
+ not_after=datetime.now(timezone.utc),
424
+ certificate_type=CertificateType.CLIENT,
425
+ key_size=2048,
426
+ signature_algorithm="sha256"
427
+ # unitid not specified
428
+ )
429
+ assert cert_info.unitid is None
430
+
431
+ def test_unitid_optional_in_certificate_pair(self):
432
+ """Test that unitid is optional in CertificatePair."""
433
+ from mcp_security_framework.schemas.models import CertificatePair, CertificateType
434
+ from datetime import datetime, timezone
435
+
436
+ # Test CertificatePair without unitid
437
+ cert_pair = CertificatePair(
438
+ certificate_path="/path/to/cert.crt",
439
+ private_key_path="/path/to/key.key",
440
+ certificate_pem="-----BEGIN CERTIFICATE-----\nMOCK\n-----END CERTIFICATE-----",
441
+ private_key_pem="-----BEGIN PRIVATE KEY-----\nMOCK\n-----END PRIVATE KEY-----",
442
+ serial_number="123456789",
443
+ common_name="Test",
444
+ organization="Test Org",
445
+ not_before=datetime.now(timezone.utc),
446
+ not_after=datetime.now(timezone.utc),
447
+ certificate_type=CertificateType.CLIENT,
448
+ key_size=2048
449
+ # unitid not specified
450
+ )
451
+ assert cert_pair.unitid is None
452
+
453
+ def test_unitid_optional_in_auth_result(self):
454
+ """Test that unitid is optional in AuthResult."""
455
+ from mcp_security_framework.schemas.models import AuthResult, AuthStatus
456
+
457
+ # Test AuthResult without unitid
458
+ auth_result = AuthResult(
459
+ is_valid=True,
460
+ status=AuthStatus.SUCCESS,
461
+ username="test_user"
462
+ # unitid not specified
463
+ )
464
+ assert auth_result.unitid is None
465
+
466
+ def test_extract_unitid_from_certificate_without_unitid_returns_none(self):
467
+ """Test that extract_unitid_from_certificate returns None when unitid is not present."""
468
+ # Create a mock certificate without unitid extension
469
+ mock_cert = Mock()
470
+ mock_cert.extensions.get_extension_for_oid.side_effect = x509.ExtensionNotFound(
471
+ "Extension not found", "1.3.6.1.4.1.99999.1.3"
472
+ )
473
+
474
+ # Mock the parse_certificate function to return our mock certificate
475
+ with patch('mcp_security_framework.utils.cert_utils.parse_certificate') as mock_parse:
476
+ mock_parse.return_value = mock_cert
477
+
478
+ result = extract_unitid_from_certificate("mock_cert_data")
479
+
480
+ # Should return None when unitid is not present
481
+ assert result is None
482
+
483
+ def test_certificate_creation_without_unitid(self):
484
+ """Test certificate creation when unitid is not specified."""
485
+ from mcp_security_framework.core.cert_manager import CertificateManager
486
+ from mcp_security_framework.schemas.config import CertificateConfig, CAConfig
487
+ from datetime import datetime, timezone
488
+ import tempfile
489
+ import os
490
+
491
+ # Create temporary directory for certificates
492
+ with tempfile.TemporaryDirectory() as temp_dir:
493
+ cert_config = CertificateConfig(
494
+ enabled=True,
495
+ ca_cert_path=os.path.join(temp_dir, "ca.crt"),
496
+ ca_key_path=os.path.join(temp_dir, "ca.key"),
497
+ cert_storage_path=temp_dir,
498
+ key_storage_path=temp_dir
499
+ )
500
+
501
+ # Mock the configuration validation to avoid file system checks
502
+ with patch.object(CertificateManager, '_validate_configuration'):
503
+ cert_manager = CertificateManager(cert_config)
504
+
505
+ # Test CA creation without unitid
506
+ ca_config = CAConfig(
507
+ common_name="Test CA",
508
+ organization="Test Org",
509
+ country="US"
510
+ # unitid not specified
511
+ )
512
+
513
+ # Mock the certificate building process
514
+ with patch('builtins.open', create=True):
515
+ with patch('os.makedirs'):
516
+ with patch('os.chmod'):
517
+ mock_cert = Mock()
518
+ mock_cert.serial_number = 123456789
519
+ mock_cert.not_valid_before = datetime.now(timezone.utc)
520
+ mock_cert.not_valid_after = datetime.now(timezone.utc)
521
+ mock_cert.public_bytes.return_value = b"-----BEGIN CERTIFICATE-----\nMOCK\n-----END CERTIFICATE-----"
522
+
523
+ with patch('mcp_security_framework.core.cert_manager.rsa.generate_private_key') as mock_key:
524
+ mock_private_key = Mock()
525
+ mock_private_key.public_key.return_value = Mock()
526
+ mock_private_key.private_bytes.return_value = b"-----BEGIN PRIVATE KEY-----\nMOCK\n-----END PRIVATE KEY-----"
527
+ mock_key.return_value = mock_private_key
528
+
529
+ with patch('mcp_security_framework.core.cert_manager.x509.CertificateBuilder') as mock_builder:
530
+ mock_builder_instance = Mock()
531
+ mock_builder_instance.subject_name.return_value = mock_builder_instance
532
+ mock_builder_instance.issuer_name.return_value = mock_builder_instance
533
+ mock_builder_instance.public_key.return_value = mock_builder_instance
534
+ mock_builder_instance.serial_number.return_value = mock_builder_instance
535
+ mock_builder_instance.not_valid_before.return_value = mock_builder_instance
536
+ mock_builder_instance.not_valid_after.return_value = mock_builder_instance
537
+ mock_builder_instance.add_extension.return_value = mock_builder_instance
538
+ mock_builder_instance.sign.return_value = mock_cert
539
+ mock_builder.return_value = mock_builder_instance
540
+
541
+ # Mock the datetime compatibility functions
542
+ with patch('mcp_security_framework.core.cert_manager.get_not_valid_before_utc') as mock_before:
543
+ with patch('mcp_security_framework.core.cert_manager.get_not_valid_after_utc') as mock_after:
544
+ mock_before.return_value = datetime.now(timezone.utc)
545
+ mock_after.return_value = datetime.now(timezone.utc)
546
+
547
+ result = cert_manager.create_root_ca(ca_config)
548
+
549
+ # Verify unitid is None when not specified
550
+ assert result.unitid is None