regscale-cli 6.23.0.1__py3-none-any.whl → 6.24.0.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.

Potentially problematic release.


This version of regscale-cli might be problematic. Click here for more details.

Files changed (45) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/application.py +2 -0
  3. regscale/integrations/commercial/__init__.py +1 -0
  4. regscale/integrations/commercial/jira.py +95 -22
  5. regscale/integrations/commercial/sarif/sarif_converter.py +1 -1
  6. regscale/integrations/commercial/wizv2/click.py +132 -2
  7. regscale/integrations/commercial/wizv2/compliance_report.py +1574 -0
  8. regscale/integrations/commercial/wizv2/constants.py +72 -2
  9. regscale/integrations/commercial/wizv2/data_fetcher.py +61 -0
  10. regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
  11. regscale/integrations/commercial/wizv2/issue.py +775 -27
  12. regscale/integrations/commercial/wizv2/policy_compliance.py +599 -181
  13. regscale/integrations/commercial/wizv2/reports.py +243 -0
  14. regscale/integrations/commercial/wizv2/scanner.py +668 -245
  15. regscale/integrations/compliance_integration.py +534 -56
  16. regscale/integrations/due_date_handler.py +210 -0
  17. regscale/integrations/public/cci_importer.py +444 -0
  18. regscale/integrations/scanner_integration.py +718 -153
  19. regscale/models/integration_models/CCI_List.xml +1 -0
  20. regscale/models/integration_models/cisa_kev_data.json +18 -3
  21. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  22. regscale/models/regscale_models/control_implementation.py +13 -3
  23. regscale/models/regscale_models/form_field_value.py +1 -1
  24. regscale/models/regscale_models/milestone.py +1 -0
  25. regscale/models/regscale_models/regscale_model.py +225 -60
  26. regscale/models/regscale_models/security_plan.py +3 -2
  27. regscale/regscale.py +7 -0
  28. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/METADATA +17 -17
  29. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/RECORD +45 -28
  30. tests/fixtures/test_fixture.py +13 -8
  31. tests/regscale/integrations/public/__init__.py +0 -0
  32. tests/regscale/integrations/public/test_alienvault.py +220 -0
  33. tests/regscale/integrations/public/test_cci.py +458 -0
  34. tests/regscale/integrations/public/test_cisa.py +1021 -0
  35. tests/regscale/integrations/public/test_emass.py +518 -0
  36. tests/regscale/integrations/public/test_fedramp.py +851 -0
  37. tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
  38. tests/regscale/integrations/public/test_file_uploads.py +506 -0
  39. tests/regscale/integrations/public/test_oscal.py +453 -0
  40. tests/regscale/models/test_form_field_value_integration.py +304 -0
  41. tests/regscale/models/test_module_integration.py +582 -0
  42. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/LICENSE +0 -0
  43. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/WHEEL +0 -0
  44. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/entry_points.txt +0 -0
  45. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,582 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Integration tests for Module model API endpoints"""
4
+
5
+ from typing import List
6
+ from unittest.mock import MagicMock, patch
7
+
8
+ import pytest
9
+ from requests import Response
10
+
11
+ from regscale.models.regscale_models.module import Module, FormTab, FormField, Choice
12
+
13
+
14
+ class TestModuleIntegration:
15
+ """Integration tests for Module model covering all API endpoints"""
16
+
17
+ @pytest.fixture
18
+ def mock_api_handler(self):
19
+ """Mock API handler for testing"""
20
+ with patch("regscale.models.regscale_models.module.Module._get_api_handler") as mock:
21
+ yield mock.return_value
22
+
23
+ @pytest.fixture
24
+ def sample_module_data(self):
25
+ """Sample module data for testing"""
26
+ return {
27
+ "id": 1,
28
+ "displayName": "Test Module",
29
+ "regScaleName": "test-module",
30
+ "regScaleInformalName": "testmodule",
31
+ "route": "/test-module",
32
+ }
33
+
34
+ @pytest.fixture
35
+ def sample_detailed_module_data(self):
36
+ """Sample detailed module data with form tabs and fields"""
37
+ return {
38
+ "id": 1,
39
+ "displayName": "Cases",
40
+ "regScaleName": "cases",
41
+ "regScaleInformalName": "cases",
42
+ "route": "/cases",
43
+ "formTabs": [
44
+ {
45
+ "id": 10,
46
+ "displayName": "Basic Info",
47
+ "regScaleName": "basic-info",
48
+ "isActive": True,
49
+ "formFields": [
50
+ {
51
+ "id": 100,
52
+ "displayName": "Title",
53
+ "regScaleName": "title",
54
+ "fieldType": "text",
55
+ "isRequired": True,
56
+ }
57
+ ],
58
+ },
59
+ {
60
+ "id": 20,
61
+ "displayName": "Custom Fields",
62
+ "regScaleName": "custom-fields",
63
+ "isActive": True,
64
+ "formFields": [
65
+ {
66
+ "id": 200,
67
+ "displayName": "Custom Field 1",
68
+ "regScaleName": "custom-field-1",
69
+ "fieldType": "text",
70
+ "isCustom": True,
71
+ }
72
+ ],
73
+ },
74
+ ],
75
+ }
76
+
77
+ @pytest.fixture
78
+ def sample_modules_list(self):
79
+ """Sample list of modules for testing"""
80
+ return [
81
+ {"id": 1, "displayName": "Cases", "regScaleName": "cases", "regScaleInformalName": "cases"},
82
+ {"id": 2, "displayName": "Assets", "regScaleName": "assets", "regScaleInformalName": "assets"},
83
+ {"id": 3, "displayName": "Issues", "regScaleName": "issues", "regScaleInformalName": "issues"},
84
+ ]
85
+
86
+ def test_create_module_success(self, mock_api_handler, sample_module_data):
87
+ """Test POST /api/modules - Create new module success"""
88
+ # Setup
89
+ mock_response = MagicMock(spec=Response)
90
+ mock_response.ok = True
91
+ mock_response.json.return_value = sample_module_data
92
+ mock_api_handler.post.return_value = mock_response
93
+
94
+ # Execute
95
+ module = Module(**sample_module_data)
96
+ result = module.create()
97
+
98
+ # Assert
99
+ assert result is not None
100
+ assert isinstance(result, Module)
101
+ assert result.id == 1
102
+ assert result.displayName == "Test Module"
103
+ mock_api_handler.post.assert_called_once()
104
+
105
+ def test_create_module_failure(self, mock_api_handler, sample_module_data):
106
+ """Test POST /api/modules - Create module failure"""
107
+ # Setup
108
+ mock_response = MagicMock(spec=Response)
109
+ mock_response.ok = False
110
+ mock_response.status_code = 400
111
+ mock_response.reason = "Bad Request"
112
+ mock_response.text = "Invalid module data"
113
+ mock_api_handler.post.return_value = mock_response
114
+
115
+ # Execute
116
+ module = Module(**sample_module_data)
117
+
118
+ # Assert that APIInsertionError is raised
119
+ from regscale.core.app.utils.api_handler import APIInsertionError
120
+
121
+ with pytest.raises(APIInsertionError) as exc_info:
122
+ module.create()
123
+
124
+ assert "Response Code: 400:Bad Request - Invalid module data" in str(exc_info.value)
125
+
126
+ def test_get_modules_success(self, mock_api_handler, sample_modules_list):
127
+ """Test GET /api/modules - Get all modules success"""
128
+ # Setup
129
+ mock_response = MagicMock(spec=Response)
130
+ mock_response.ok = True
131
+ mock_response.json.return_value = sample_modules_list
132
+ mock_api_handler.get.return_value = mock_response
133
+
134
+ # Execute
135
+ result = Module.get_modules()
136
+
137
+ # Assert
138
+ assert len(result) == 3
139
+ assert all(isinstance(module, Module) for module in result)
140
+ assert result[0].displayName == "Cases"
141
+ assert result[1].displayName == "Assets"
142
+ assert result[2].displayName == "Issues"
143
+
144
+ mock_api_handler.get.assert_called_once()
145
+ call_args = mock_api_handler.get.call_args
146
+ assert call_args[1]["endpoint"] == "/api/modules"
147
+
148
+ def test_get_modules_failure(self, mock_api_handler):
149
+ """Test GET /api/modules - Get all modules failure"""
150
+ # Setup
151
+ mock_response = MagicMock(spec=Response)
152
+ mock_response.ok = False
153
+ mock_response.status_code = 500
154
+ mock_response.reason = "Internal Server Error"
155
+ mock_response.text = "Database error"
156
+ mock_api_handler.get.return_value = mock_response
157
+
158
+ # Mock log_response_error
159
+ with patch.object(Module, "log_response_error") as mock_log:
160
+ # Execute
161
+ result = Module.get_modules()
162
+
163
+ # Assert
164
+ assert result == []
165
+ mock_log.assert_called_once_with(response=mock_response)
166
+
167
+ def test_get_module_by_id_success(self, mock_api_handler, sample_detailed_module_data):
168
+ """Test GET /api/modules/{moduleId} - Get module by ID success"""
169
+ # Setup
170
+ mock_response = MagicMock(spec=Response)
171
+ mock_response.ok = True
172
+ mock_response.json.return_value = sample_detailed_module_data
173
+ mock_api_handler.get.return_value = mock_response
174
+
175
+ # Execute
176
+ result = Module.get_module_by_id(1)
177
+
178
+ # Assert
179
+ assert result is not None
180
+ assert isinstance(result, Module)
181
+ assert result.id == 1
182
+ assert result.displayName == "Cases"
183
+ assert len(result.formTabs) == 2
184
+
185
+ mock_api_handler.get.assert_called_once()
186
+ call_args = mock_api_handler.get.call_args
187
+ assert call_args[1]["endpoint"] == "/api/modules/1"
188
+
189
+ def test_get_module_by_id_failure(self, mock_api_handler):
190
+ """Test GET /api/modules/{moduleId} - Get module by ID failure"""
191
+ # Setup
192
+ mock_response = MagicMock(spec=Response)
193
+ mock_response.ok = False
194
+ mock_response.status_code = 500
195
+ mock_response.reason = "Internal Server Error"
196
+ mock_response.text = "Database error"
197
+ mock_api_handler.get.return_value = mock_response
198
+
199
+ # Mock log_response_error
200
+ with patch.object(Module, "log_response_error") as mock_log:
201
+ # Execute
202
+ result = Module.get_module_by_id(999)
203
+
204
+ # Assert
205
+ assert result is None
206
+ mock_log.assert_called_once_with(response=mock_response)
207
+
208
+ def test_get_module_by_id_not_found(self, mock_api_handler):
209
+ """Test GET /api/modules/{moduleId} - Module not found"""
210
+ # Setup
211
+ mock_response = MagicMock(spec=Response)
212
+ mock_response.ok = False
213
+ mock_response.status_code = 404
214
+ mock_response.reason = "Not Found"
215
+ mock_response.text = "Module not found"
216
+ mock_api_handler.get.return_value = mock_response
217
+
218
+ # Mock log_response_error
219
+ with patch.object(Module, "log_response_error") as mock_log:
220
+ # Execute
221
+ result = Module.get_module_by_id(999)
222
+
223
+ # Assert
224
+ assert result is None
225
+ mock_log.assert_called_once_with(response=mock_response)
226
+
227
+ def test_post_multiple_modules_endpoint(self, mock_api_handler):
228
+ """Test POST /api/modules/multiple - Bulk create modules"""
229
+ # Setup
230
+ modules_data = [
231
+ {"displayName": "Module 1", "regScaleName": "module1"},
232
+ {"displayName": "Module 2", "regScaleName": "module2"},
233
+ ]
234
+
235
+ mock_response = MagicMock(spec=Response)
236
+ mock_response.ok = True
237
+ mock_response.json.return_value = modules_data
238
+ mock_api_handler.post.return_value = mock_response
239
+
240
+ # This would be the method if implemented
241
+ def create_multiple(cls, modules_data: List[dict]) -> List[Module]:
242
+ result = cls._get_api_handler().post(endpoint="/api/modules/multiple", data=modules_data)
243
+ if result and result.ok:
244
+ return [cls(**module_data) for module_data in result.json()]
245
+ return []
246
+
247
+ # Add method to class temporarily for testing
248
+ Module.create_multiple = classmethod(create_multiple)
249
+
250
+ try:
251
+ # Execute
252
+ result = Module.create_multiple(modules_data)
253
+
254
+ # Assert
255
+ assert len(result) == 2
256
+ assert all(isinstance(module, Module) for module in result)
257
+ mock_api_handler.post.assert_called_once()
258
+ call_args = mock_api_handler.post.call_args
259
+ assert call_args[1]["endpoint"] == "/api/modules/multiple"
260
+ assert call_args[1]["data"] == modules_data
261
+ finally:
262
+ # Clean up
263
+ delattr(Module, "create_multiple")
264
+
265
+ def test_get_all_modules_with_form_fields_endpoint(self, mock_api_handler, sample_detailed_module_data):
266
+ """Test GET /api/modules/getAllModulesWithFormFields"""
267
+ # Setup
268
+ mock_response = MagicMock(spec=Response)
269
+ mock_response.ok = True
270
+ mock_response.json.return_value = [sample_detailed_module_data]
271
+ mock_api_handler.get.return_value = mock_response
272
+
273
+ # This would be the method if implemented
274
+ def get_all_modules_with_form_fields(cls) -> List[Module]:
275
+ result = cls._get_api_handler().get(endpoint="/api/modules/getAllModulesWithFormFields")
276
+ if result and result.ok:
277
+ return [cls(**module_data) for module_data in result.json()]
278
+ return []
279
+
280
+ # Add method to class temporarily for testing
281
+ Module.get_all_modules_with_form_fields = classmethod(get_all_modules_with_form_fields)
282
+
283
+ try:
284
+ # Execute
285
+ result = Module.get_all_modules_with_form_fields()
286
+
287
+ # Assert
288
+ assert len(result) == 1
289
+ assert isinstance(result[0], Module)
290
+ assert result[0].id == 1
291
+ assert len(result[0].formTabs) == 2
292
+ mock_api_handler.get.assert_called_once()
293
+ call_args = mock_api_handler.get.call_args
294
+ assert call_args[1]["endpoint"] == "/api/modules/getAllModulesWithFormFields"
295
+ finally:
296
+ # Clean up
297
+ delattr(Module, "get_all_modules_with_form_fields")
298
+
299
+ def test_reset_module_endpoint(self, mock_api_handler):
300
+ """Test GET /api/modules/reset/{moduleId} - Reset module labeling"""
301
+ # Setup
302
+ mock_response = MagicMock(spec=Response)
303
+ mock_response.ok = True
304
+ mock_response.json.return_value = {"message": "Module reset successfully"}
305
+ mock_api_handler.get.return_value = mock_response
306
+
307
+ # This would be the method if implemented
308
+ def reset_module(cls, module_id: int) -> bool:
309
+ result = cls._get_api_handler().get(endpoint=f"/api/modules/reset/{module_id}")
310
+ return result and result.ok
311
+
312
+ # Add method to class temporarily for testing
313
+ Module.reset_module = classmethod(reset_module)
314
+
315
+ try:
316
+ # Execute
317
+ result = Module.reset_module(1)
318
+
319
+ # Assert
320
+ assert result is True
321
+ mock_api_handler.get.assert_called_once()
322
+ call_args = mock_api_handler.get.call_args
323
+ assert call_args[1]["endpoint"] == "/api/modules/reset/1"
324
+ finally:
325
+ # Clean up
326
+ delattr(Module, "reset_module")
327
+
328
+ def test_get_module_by_name_success(self, mock_api_handler, sample_modules_list):
329
+ """Test get_module_by_name with existing module"""
330
+ # Setup
331
+ mock_response = MagicMock(spec=Response)
332
+ mock_response.ok = True
333
+ mock_response.json.return_value = sample_modules_list
334
+ mock_api_handler.get.return_value = mock_response
335
+
336
+ # Execute
337
+ result = Module.get_module_by_name("cases")
338
+
339
+ # Assert
340
+ assert result is not None
341
+ assert isinstance(result, Module)
342
+ assert result.regScaleInformalName == "cases"
343
+
344
+ def test_get_module_by_name_not_found(self, mock_api_handler, sample_modules_list):
345
+ """Test get_module_by_name with non-existing module"""
346
+ # Setup
347
+ mock_response = MagicMock(spec=Response)
348
+ mock_response.ok = True
349
+ mock_response.json.return_value = sample_modules_list
350
+ mock_api_handler.get.return_value = mock_response
351
+
352
+ # Execute
353
+ result = Module.get_module_by_name("nonexistent")
354
+
355
+ # Assert
356
+ assert result is None
357
+
358
+ def test_get_tab_by_name_success(self, mock_api_handler, sample_modules_list, sample_detailed_module_data):
359
+ """Test get_tab_by_name with existing tab"""
360
+ # Setup - Mock both get_modules and get_module_by_id calls
361
+ mock_response_modules = MagicMock(spec=Response)
362
+ mock_response_modules.ok = True
363
+ mock_response_modules.json.return_value = sample_modules_list
364
+
365
+ mock_response_detailed = MagicMock(spec=Response)
366
+ mock_response_detailed.ok = True
367
+ mock_response_detailed.json.return_value = sample_detailed_module_data
368
+
369
+ # Set up side effects for multiple calls
370
+ mock_api_handler.get.side_effect = [mock_response_modules, mock_response_detailed]
371
+
372
+ # Execute
373
+ result = Module.get_tab_by_name("cases", "basic-info")
374
+
375
+ # Assert
376
+ assert result is not None
377
+ assert isinstance(result, FormTab)
378
+ assert result.regScaleName == "basic-info"
379
+ assert result.id == 10
380
+
381
+ def test_get_tab_by_name_module_not_found(self, mock_api_handler, sample_modules_list):
382
+ """Test get_tab_by_name with non-existing module"""
383
+ # Setup
384
+ mock_response = MagicMock(spec=Response)
385
+ mock_response.ok = True
386
+ mock_response.json.return_value = sample_modules_list
387
+ mock_api_handler.get.return_value = mock_response
388
+
389
+ # Execute
390
+ result = Module.get_tab_by_name("nonexistent", "basic-info")
391
+
392
+ # Assert
393
+ assert result is None
394
+
395
+ def test_get_tab_by_name_tab_not_found(self, mock_api_handler, sample_modules_list, sample_detailed_module_data):
396
+ """Test get_tab_by_name with non-existing tab"""
397
+ # Setup
398
+ mock_response_modules = MagicMock(spec=Response)
399
+ mock_response_modules.ok = True
400
+ mock_response_modules.json.return_value = sample_modules_list
401
+
402
+ mock_response_detailed = MagicMock(spec=Response)
403
+ mock_response_detailed.ok = True
404
+ mock_response_detailed.json.return_value = sample_detailed_module_data
405
+
406
+ mock_api_handler.get.side_effect = [mock_response_modules, mock_response_detailed]
407
+
408
+ # Execute
409
+ result = Module.get_tab_by_name("cases", "nonexistent-tab")
410
+
411
+ # Assert
412
+ assert result is None
413
+
414
+ def test_get_new_custom_form_tab_id_success(
415
+ self, mock_api_handler, sample_modules_list, sample_detailed_module_data
416
+ ):
417
+ """Test get_new_custom_form_tab_id with existing tab"""
418
+ # Setup
419
+ mock_response_modules = MagicMock(spec=Response)
420
+ mock_response_modules.ok = True
421
+ mock_response_modules.json.return_value = sample_modules_list
422
+
423
+ mock_response_detailed = MagicMock(spec=Response)
424
+ mock_response_detailed.ok = True
425
+ mock_response_detailed.json.return_value = sample_detailed_module_data
426
+
427
+ mock_api_handler.get.side_effect = [mock_response_modules, mock_response_detailed]
428
+
429
+ # Execute
430
+ result = Module.get_new_custom_form_tab_id("cases", "basic-info")
431
+
432
+ # Assert
433
+ assert result == 10
434
+
435
+ def test_get_new_custom_form_tab_id_not_found(self, mock_api_handler, sample_modules_list):
436
+ """Test get_new_custom_form_tab_id with non-existing module"""
437
+ # Setup
438
+ mock_response = MagicMock(spec=Response)
439
+ mock_response.ok = True
440
+ mock_response.json.return_value = sample_modules_list
441
+ mock_api_handler.get.return_value = mock_response
442
+
443
+ # Execute
444
+ result = Module.get_new_custom_form_tab_id("nonexistent", "basic-info")
445
+
446
+ # Assert
447
+ assert result is None
448
+
449
+ def test_get_form_fields_by_tab_id_success(
450
+ self, mock_api_handler, sample_modules_list, sample_detailed_module_data
451
+ ):
452
+ """Test get_form_fields_by_tab_id with existing tab"""
453
+ # Setup
454
+ mock_response_modules = MagicMock(spec=Response)
455
+ mock_response_modules.ok = True
456
+ mock_response_modules.json.return_value = sample_modules_list
457
+
458
+ mock_response_detailed = MagicMock(spec=Response)
459
+ mock_response_detailed.ok = True
460
+ mock_response_detailed.json.return_value = sample_detailed_module_data
461
+
462
+ mock_api_handler.get.side_effect = [mock_response_modules, mock_response_detailed]
463
+
464
+ # Execute
465
+ result = Module.get_form_fields_by_tab_id("cases", "basic-info")
466
+
467
+ # Assert
468
+ assert result is not None
469
+ assert len(result) == 1
470
+ assert isinstance(result[0], FormField)
471
+ assert result[0].regScaleName == "title"
472
+
473
+ def test_get_form_fields_by_tab_id_not_found(self, mock_api_handler, sample_modules_list):
474
+ """Test get_form_fields_by_tab_id with non-existing module"""
475
+ # Setup
476
+ mock_response = MagicMock(spec=Response)
477
+ mock_response.ok = True
478
+ mock_response.json.return_value = sample_modules_list
479
+ mock_api_handler.get.return_value = mock_response
480
+
481
+ # Execute
482
+ result = Module.get_form_fields_by_tab_id("nonexistent", "basic-info")
483
+
484
+ # Assert
485
+ assert result is None
486
+
487
+ def test_module_slug(self):
488
+ """Test that the module slug is correctly set"""
489
+ assert Module._module_slug == "modules"
490
+
491
+ def test_additional_endpoints(self):
492
+ """Test that additional endpoints are correctly defined"""
493
+ endpoints = Module._get_additional_endpoints()
494
+
495
+ assert "get_modules" in endpoints
496
+ assert "get_module_by_id" in endpoints
497
+
498
+ assert endpoints["get_modules"] == "/api/{model_slug}"
499
+ assert endpoints["get_module_by_id"] == "/api/{model_slug}/{id}"
500
+
501
+ @pytest.mark.parametrize(
502
+ "module_id,expected_endpoint",
503
+ [
504
+ (1, "/api/modules/1"),
505
+ (999, "/api/modules/999"),
506
+ (42, "/api/modules/42"),
507
+ ],
508
+ )
509
+ def test_get_module_by_id_endpoint_formation(self, mock_api_handler, module_id, expected_endpoint):
510
+ """Test endpoint formation for get_module_by_id with different IDs"""
511
+ # Setup
512
+ mock_response = MagicMock(spec=Response)
513
+ mock_response.ok = True
514
+ mock_response.json.return_value = {"id": module_id, "displayName": f"Module {module_id}"}
515
+ mock_api_handler.get.return_value = mock_response
516
+
517
+ # Execute
518
+ Module.get_module_by_id(module_id)
519
+
520
+ # Assert
521
+ call_args = mock_api_handler.get.call_args
522
+ assert call_args[1]["endpoint"] == expected_endpoint
523
+
524
+ def test_form_tab_model_structure(self):
525
+ """Test FormTab model structure and field access"""
526
+ form_tab_data = {
527
+ "id": 1,
528
+ "displayName": "Test Tab",
529
+ "regScaleName": "test-tab",
530
+ "isActive": True,
531
+ "formFields": [{"id": 10, "displayName": "Test Field", "regScaleName": "test-field", "fieldType": "text"}],
532
+ }
533
+
534
+ form_tab = FormTab(**form_tab_data)
535
+
536
+ assert form_tab.id == 1
537
+ assert form_tab.displayName == "Test Tab"
538
+ assert form_tab.regScaleName == "test-tab"
539
+ assert form_tab.isActive is True
540
+ assert len(form_tab.formFields) == 1
541
+ assert isinstance(form_tab.formFields[0], FormField)
542
+
543
+ def test_form_field_model_structure(self):
544
+ """Test FormField model structure and field access"""
545
+ form_field_data = {
546
+ "id": 1,
547
+ "displayName": "Test Field",
548
+ "regScaleName": "test-field",
549
+ "fieldType": "select",
550
+ "isRequired": True,
551
+ "choices": [{"id": 1, "value": "option1", "label": "Option 1", "isActive": True}],
552
+ }
553
+
554
+ form_field = FormField(**form_field_data)
555
+
556
+ assert form_field.id == 1
557
+ assert form_field.displayName == "Test Field"
558
+ assert form_field.regScaleName == "test-field"
559
+ assert form_field.fieldType == "select"
560
+ assert form_field.isRequired is True
561
+ assert len(form_field.choices) == 1
562
+ assert isinstance(form_field.choices[0], Choice)
563
+
564
+ def test_choice_model_structure(self):
565
+ """Test Choice model structure and field access"""
566
+ choice_data = {
567
+ "id": 1,
568
+ "value": "test_value",
569
+ "label": "Test Label",
570
+ "regScaleLabel": "Test RegScale Label",
571
+ "isActive": True,
572
+ "sequence": 1,
573
+ }
574
+
575
+ choice = Choice(**choice_data)
576
+
577
+ assert choice.id == 1
578
+ assert choice.value == "test_value"
579
+ assert choice.label == "Test Label"
580
+ assert choice.regScaleLabel == "Test RegScale Label"
581
+ assert choice.isActive is True
582
+ assert choice.sequence == 1