|
2 | 2 | Unit tests for apimtypes.py |
3 | 3 | """ |
4 | 4 |
|
| 5 | +import importlib |
5 | 6 | from pathlib import Path |
6 | 7 | import pytest |
| 8 | +import apimtypes |
7 | 9 |
|
8 | 10 | # APIM Samples imports |
9 | 11 | from apimtypes import API, APIMNetworkMode, APIM_SKU, APIOperation, BACKEND_XML_POLICY_PATH, DEFAULT_XML_POLICY_PATH, GET_APIOperation, \ |
10 | | - get_project_root, HELLO_WORLD_XML_POLICY_PATH, HTTP_VERB, INFRASTRUCTURE, NamedValue, Output, PolicyFragment, POST_APIOperation, \ |
11 | | - Product, REQUEST_HEADERS_XML_POLICY_PATH, Role, SUBSCRIPTION_KEY_PARAMETER_NAME, SLEEP_TIME_BETWEEN_REQUESTS_MS |
| 12 | + GET_APIOperation2, get_project_root, HELLO_WORLD_XML_POLICY_PATH, HTTP_VERB, INFRASTRUCTURE, NamedValue, Output, PolicyFragment, \ |
| 13 | + POST_APIOperation, Product, REQUEST_HEADERS_XML_POLICY_PATH, Role, SUBSCRIPTION_KEY_PARAMETER_NAME, SLEEP_TIME_BETWEEN_REQUESTS_MS |
12 | 14 | from test_helpers import assert_policy_fragment_structure |
13 | 15 |
|
14 | 16 |
|
@@ -213,6 +215,27 @@ def test_apim_sku(self, enum_value, expected): |
213 | 215 | """Test APIM_SKU enum values.""" |
214 | 216 | assert enum_value == expected |
215 | 217 |
|
| 218 | + @pytest.mark.parametrize('sku', [ |
| 219 | + APIM_SKU.DEVELOPER, |
| 220 | + APIM_SKU.BASIC, |
| 221 | + APIM_SKU.STANDARD, |
| 222 | + APIM_SKU.PREMIUM |
| 223 | + ]) |
| 224 | + def test_apim_sku_is_v1(self, sku): |
| 225 | + """Test APIM_SKU.is_v1() method for v1 SKUs.""" |
| 226 | + assert sku.is_v1() is True |
| 227 | + assert sku.is_v2() is False |
| 228 | + |
| 229 | + @pytest.mark.parametrize('sku', [ |
| 230 | + APIM_SKU.BASICV2, |
| 231 | + APIM_SKU.STANDARDV2, |
| 232 | + APIM_SKU.PREMIUMV2 |
| 233 | + ]) |
| 234 | + def test_apim_sku_is_v2(self, sku): |
| 235 | + """Test APIM_SKU.is_v2() method for v2 SKUs.""" |
| 236 | + assert sku.is_v2() is True |
| 237 | + assert sku.is_v1() is False |
| 238 | + |
216 | 239 | @pytest.mark.parametrize('enum_value,expected', [ |
217 | 240 | (HTTP_VERB.GET, 'GET'), |
218 | 241 | (HTTP_VERB.POST, 'POST'), |
@@ -284,6 +307,24 @@ def test_get_operation(self): |
284 | 307 | assert op.policyXml == '<xml/>' |
285 | 308 | assert op.to_dict()['method'] == HTTP_VERB.GET |
286 | 309 |
|
| 310 | + def test_get_operation2(self): |
| 311 | + """Test GET_APIOperation2 class with custom parameters.""" |
| 312 | + op = GET_APIOperation2( |
| 313 | + name='get-users', |
| 314 | + displayName='Get Users', |
| 315 | + urlTemplate='/users', |
| 316 | + description='Get all users', |
| 317 | + policyXml='<custom/>' |
| 318 | + ) |
| 319 | + |
| 320 | + assert op.name == 'get-users' |
| 321 | + assert op.displayName == 'Get Users' |
| 322 | + assert op.urlTemplate == '/users' |
| 323 | + assert op.method == HTTP_VERB.GET |
| 324 | + assert op.description == 'Get all users' |
| 325 | + assert op.policyXml == '<custom/>' |
| 326 | + assert op.to_dict()['method'] == HTTP_VERB.GET |
| 327 | + |
287 | 328 | def test_post_operation(self): |
288 | 329 | """Test POST_APIOperation convenience class.""" |
289 | 330 | op = POST_APIOperation(description='desc', policyXml='<xml/>') |
@@ -361,6 +402,18 @@ def test_approval_required_default(self, base_product_params): |
361 | 402 | product = Product(**base_product_params) |
362 | 403 | assert product.approvalRequired is False |
363 | 404 |
|
| 405 | + def test_product_fallback_policy_when_file_not_found(self, monkeypatch, base_product_params): |
| 406 | + """Test Product uses fallback policy when default policy file is not found.""" |
| 407 | + def mock_read_policy_xml_raise(path): |
| 408 | + raise FileNotFoundError(f'Policy file not found: {path}') |
| 409 | + |
| 410 | + monkeypatch.setattr(apimtypes, '_read_policy_xml', mock_read_policy_xml_raise) |
| 411 | + |
| 412 | + product = Product(**base_product_params) |
| 413 | + assert product.policyXml is not None |
| 414 | + assert '<policies>' in product.policyXml |
| 415 | + assert '<inbound>' in product.policyXml |
| 416 | + |
364 | 417 |
|
365 | 418 | class TestProductSerialization: |
366 | 419 | """Test suite for Product.to_dict() method.""" |
@@ -451,6 +504,164 @@ def test_json_parsing_invalid(self): |
451 | 504 | output = Output(success=True, text='not json') |
452 | 505 | assert output.json_data is None |
453 | 506 |
|
| 507 | + def test_get_method_with_properties_structure(self): |
| 508 | + """Test Output.get() with standard deployment output structure.""" |
| 509 | + json_text = '''{"properties": {"outputs": {"endpoint": {"value": "https://test.com"}}}}''' |
| 510 | + output = Output(success=True, text=json_text) |
| 511 | + |
| 512 | + result = output.get('endpoint', suppress_logging=True) |
| 513 | + assert result == 'https://test.com' |
| 514 | + |
| 515 | + def test_get_method_with_simple_structure(self): |
| 516 | + """Test Output.get() with simple output structure.""" |
| 517 | + json_text = '''{"endpoint": {"value": "https://simple.com"}}''' |
| 518 | + output = Output(success=True, text=json_text) |
| 519 | + |
| 520 | + result = output.get('endpoint', suppress_logging=True) |
| 521 | + assert result == 'https://simple.com' |
| 522 | + |
| 523 | + def test_get_method_key_not_found(self): |
| 524 | + """Test Output.get() when key is not found.""" |
| 525 | + json_text = '''{"properties": {"outputs": {"other": {"value": "val"}}}}''' |
| 526 | + output = Output(success=True, text=json_text) |
| 527 | + |
| 528 | + result = output.get('missing', suppress_logging=True) |
| 529 | + assert result is None |
| 530 | + |
| 531 | + def test_get_method_key_not_found_with_label_raises(self): |
| 532 | + """Test Output.get() raises when key not found and label provided.""" |
| 533 | + json_text = '''{"properties": {"outputs": {"other": {"value": "val"}}}}''' |
| 534 | + output = Output(success=True, text=json_text) |
| 535 | + |
| 536 | + with pytest.raises(Exception): |
| 537 | + output.get('missing', label='Test Label', suppress_logging=True) |
| 538 | + |
| 539 | + def test_get_method_with_label_and_secure_masking(self): |
| 540 | + """Test Output.get() with label and secure masking.""" |
| 541 | + json_text = '''{"properties": {"outputs": {"secret": {"value": "supersecretvalue"}}}}''' |
| 542 | + output = Output(success=True, text=json_text) |
| 543 | + |
| 544 | + result = output.get('secret', label='Secret', secure=True) |
| 545 | + assert result == 'supersecretvalue' |
| 546 | + |
| 547 | + def test_get_method_json_data_not_dict(self): |
| 548 | + """Test Output.get() when json_data is not a dict.""" |
| 549 | + output = Output(success=True, text='["array", "data"]') |
| 550 | + |
| 551 | + result = output.get('key', suppress_logging=True) |
| 552 | + assert result is None |
| 553 | + |
| 554 | + def test_get_method_properties_not_dict(self): |
| 555 | + """Test Output.get() when properties is not a dict.""" |
| 556 | + json_text = '''{"properties": "not a dict"}''' |
| 557 | + output = Output(success=True, text=json_text) |
| 558 | + |
| 559 | + result = output.get('key', suppress_logging=True) |
| 560 | + assert result is None |
| 561 | + |
| 562 | + def test_get_method_outputs_not_dict(self): |
| 563 | + """Test Output.get() when outputs is not a dict.""" |
| 564 | + json_text = '''{"properties": {"outputs": "not a dict"}}''' |
| 565 | + output = Output(success=True, text=json_text) |
| 566 | + |
| 567 | + result = output.get('key', suppress_logging=True) |
| 568 | + assert result is None |
| 569 | + |
| 570 | + def test_get_method_output_entry_invalid(self): |
| 571 | + """Test Output.get() when output entry is invalid.""" |
| 572 | + json_text = '''{"properties": {"outputs": {"key": "no value field"}}}''' |
| 573 | + output = Output(success=True, text=json_text) |
| 574 | + |
| 575 | + result = output.get('key', suppress_logging=True) |
| 576 | + assert result is None |
| 577 | + |
| 578 | + def test_getjson_method_with_dict_value(self): |
| 579 | + """Test Output.getJson() with dictionary value.""" |
| 580 | + json_text = '''{"properties": {"outputs": {"config": {"value": {"key": "val"}}}}}''' |
| 581 | + output = Output(success=True, text=json_text) |
| 582 | + |
| 583 | + result = output.getJson('config', suppress_logging=True) |
| 584 | + assert result == {'key': 'val'} |
| 585 | + |
| 586 | + def test_getjson_method_with_string_json(self): |
| 587 | + """Test Output.getJson() parsing string as JSON.""" |
| 588 | + json_text = '''{"properties": {"outputs": {"data": {"value": "{\\"nested\\": \\"value\\"}"}}}}''' |
| 589 | + output = Output(success=True, text=json_text) |
| 590 | + |
| 591 | + result = output.getJson('data', suppress_logging=True) |
| 592 | + assert result == {'nested': 'value'} |
| 593 | + |
| 594 | + def test_getjson_method_with_python_literal(self): |
| 595 | + """Test Output.getJson() parsing Python literal.""" |
| 596 | + json_text = '''{"properties": {"outputs": {"data": {"value": "{'key': 'value'}"}}}}''' |
| 597 | + output = Output(success=True, text=json_text) |
| 598 | + |
| 599 | + result = output.getJson('data', suppress_logging=True) |
| 600 | + assert result == {'key': 'value'} |
| 601 | + |
| 602 | + def test_getjson_method_unparseable_string(self): |
| 603 | + """Test Output.getJson() with unparseable string returns original value.""" |
| 604 | + json_text = '''{"properties": {"outputs": {"data": {"value": "not valid json or literal"}}}}''' |
| 605 | + output = Output(success=True, text=json_text) |
| 606 | + |
| 607 | + result = output.getJson('data') |
| 608 | + assert result == 'not valid json or literal' |
| 609 | + |
| 610 | + def test_getjson_method_key_not_found(self): |
| 611 | + """Test Output.getJson() when key not found.""" |
| 612 | + json_text = '''{"properties": {"outputs": {"other": {"value": "val"}}}}''' |
| 613 | + output = Output(success=True, text=json_text) |
| 614 | + |
| 615 | + result = output.getJson('missing', suppress_logging=True) |
| 616 | + assert result is None |
| 617 | + |
| 618 | + def test_getjson_method_raises_with_label(self): |
| 619 | + """Test Output.getJson() raises when key not found and label provided.""" |
| 620 | + json_text = '''{"properties": {"outputs": {}}}''' |
| 621 | + output = Output(success=True, text=json_text) |
| 622 | + |
| 623 | + with pytest.raises(Exception): |
| 624 | + output.getJson('missing', label='Test') |
| 625 | + |
| 626 | + def test_getjson_method_json_data_not_dict(self): |
| 627 | + """Test Output.getJson() when json_data is not a dict.""" |
| 628 | + output = Output(success=True, text='[1, 2, 3]') |
| 629 | + |
| 630 | + result = output.getJson('key', suppress_logging=True) |
| 631 | + assert result is None |
| 632 | + |
| 633 | + def test_getjson_method_properties_not_dict(self): |
| 634 | + """Test Output.getJson() when properties is not a dict.""" |
| 635 | + json_text = '''{"properties": ["not", "a", "dict"]}''' |
| 636 | + output = Output(success=True, text=json_text) |
| 637 | + |
| 638 | + result = output.getJson('key', suppress_logging=True) |
| 639 | + assert result is None |
| 640 | + |
| 641 | + def test_getjson_method_outputs_not_dict(self): |
| 642 | + """Test Output.getJson() when outputs is not a dict.""" |
| 643 | + json_text = '''{"properties": {"outputs": ["not", "dict"]}}''' |
| 644 | + output = Output(success=True, text=json_text) |
| 645 | + |
| 646 | + result = output.getJson('key', suppress_logging=True) |
| 647 | + assert result is None |
| 648 | + |
| 649 | + def test_getjson_method_output_entry_invalid(self): |
| 650 | + """Test Output.getJson() when output entry is missing value field.""" |
| 651 | + json_text = '''{"properties": {"outputs": {"key": {"no_value": "here"}}}}''' |
| 652 | + output = Output(success=True, text=json_text) |
| 653 | + |
| 654 | + result = output.getJson('key', suppress_logging=True) |
| 655 | + assert result is None |
| 656 | + |
| 657 | + def test_output_with_simple_structure_getjson(self): |
| 658 | + """Test Output.getJson() with simple structure (no properties wrapper).""" |
| 659 | + json_text = '''{"data": {"value": {"nested": "obj"}}}''' |
| 660 | + output = Output(success=True, text=json_text) |
| 661 | + |
| 662 | + result = output.getJson('data', suppress_logging=True) |
| 663 | + assert result == {'nested': 'obj'} |
| 664 | + |
454 | 665 |
|
455 | 666 | # ------------------------------ |
456 | 667 | # NAMED VALUE TESTS |
@@ -536,3 +747,14 @@ def test_get_project_root(self): |
536 | 747 | assert isinstance(root, Path) |
537 | 748 | assert root.exists() |
538 | 749 | assert root.is_dir() |
| 750 | + |
| 751 | + def test_get_project_root_from_env_var(self, monkeypatch): |
| 752 | + """Test get_project_root uses PROJECT_ROOT environment variable.""" |
| 753 | + test_path = Path('c:/test/project') |
| 754 | + monkeypatch.setenv('PROJECT_ROOT', str(test_path)) |
| 755 | + |
| 756 | + # Need to reimport to pick up new env var |
| 757 | + importlib.reload(apimtypes) |
| 758 | + |
| 759 | + root = apimtypes.get_project_root() |
| 760 | + assert root == test_path |
0 commit comments