From 7aceef25ec8443a6f13b5b69798ead09e23d90d8 Mon Sep 17 00:00:00 2001 From: b-long Date: Sat, 6 Sep 2025 09:20:55 -0400 Subject: [PATCH 01/12] chore: update pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index de489ad..dba9fe7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,7 +32,7 @@ repos: rev: v0.12.12 hooks: # Run the linter. - - id: ruff + - id: ruff-check # Run the formatter. - id: ruff-format - repo: https://github.com/compilerla/conventional-pre-commit From 5e2c965f02d1003acbae3d0fe6df0269a1651a5a Mon Sep 17 00:00:00 2001 From: b-long Date: Sat, 6 Sep 2025 09:21:24 -0400 Subject: [PATCH 02/12] fix: type annotations in tdf.py --- src/otdf_python/tdf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/otdf_python/tdf.py b/src/otdf_python/tdf.py index 5eec00e..4a0ef5e 100644 --- a/src/otdf_python/tdf.py +++ b/src/otdf_python/tdf.py @@ -277,7 +277,7 @@ def create_tdf( self, payload: bytes | BinaryIO, config: TDFConfig, - output_stream: BinaryIO | None = None, + output_stream: io.BytesIO | None = None, ): if output_stream is None: output_stream = io.BytesIO() @@ -376,13 +376,13 @@ def create_tdf( return manifest, size, output_stream def load_tdf( - self, tdf_data: bytes | BinaryIO, config: TDFReaderConfig + self, tdf_data: bytes | io.BytesIO, config: TDFReaderConfig ) -> TDFReader: # Extract manifest, unwrap payload key using KAS client # Handle both bytes and BinaryIO input tdf_bytes_io = io.BytesIO(tdf_data) if isinstance(tdf_data, bytes) else tdf_data - with zipfile.ZipFile(tdf_bytes_io, "r") as z: # type: ignore + with zipfile.ZipFile(tdf_bytes_io, "r") as z: manifest_json = z.read("0.manifest.json").decode() manifest = Manifest.from_json(manifest_json) From d149a24432f24252f16622be6ffe59c5974ce8a6 Mon Sep 17 00:00:00 2001 From: b-long Date: Sat, 6 Sep 2025 09:36:23 -0400 Subject: [PATCH 03/12] chore: expand inspect tests --- tests/integration/test_cli_inspect.py | 37 +++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_cli_inspect.py b/tests/integration/test_cli_inspect.py index 4791432..1e14465 100644 --- a/tests/integration/test_cli_inspect.py +++ b/tests/integration/test_cli_inspect.py @@ -84,7 +84,40 @@ def test_cli_inspect_v4_2_2_vs_v4_3_1(all_target_mode_tdf_files, temp_credential @pytest.mark.integration -def test_cli_inspect_different_file_types(tdf_v4_3_1_files, temp_credentials_file): +def test_cli_inspect_different_file_types_v4_2_2(tdf_v4_2_2_files, temp_credentials_file): + """ + Test CLI inspect with different file types. + """ + + file_types_to_test = [ + "text", + "binary", + "with_attributes", + ] # TODO: Consider adding "empty" file type as well + + for file_type in file_types_to_test: + tdf_path = tdf_v4_2_2_files[file_type] + + # Inspect the TDF + result = _run_cli_inspect(tdf_path, temp_credentials_file) + + assert result is not None, f"Failed to inspect {file_type} TDF" + assert "manifest" in result, f"{file_type} TDF inspection missing manifest" + + # Check file-type specific expectations + if file_type == "empty": + # Empty files should still have valid manifests + assert "encryptionInformation" in result["manifest"] + elif file_type == "with_attributes": + # Attributed files should have keyAccess information + assert ( + "keyAccess" in result["manifest"] + or "encryptionInformation" in result["manifest"] + ) + + +@pytest.mark.integration +def test_cli_inspect_different_file_types_v4_3_1(tdf_v4_3_1_files, temp_credentials_file): """ Test CLI inspect with different file types. """ @@ -118,7 +151,7 @@ def test_cli_inspect_different_file_types(tdf_v4_3_1_files, temp_credentials_fil def _run_cli_inspect(tdf_path: Path, creds_file: Path) -> dict | None: """ - Helper function to run CLI inspect command and return parsed JSON result. + Helper function to run Python CLI inspect command and return parsed JSON result. This demonstrates how the CLI inspect functionality could be tested with the new fixtures. From d6db5ee48ba74c3e1f71f0f2737fc9ab2659ef1b Mon Sep 17 00:00:00 2001 From: b-long Date: Sun, 7 Sep 2025 09:57:26 -0400 Subject: [PATCH 04/12] chore: cleanup tests --- conftest.py | 2 +- tests/config_pydantic.py | 2 +- tests/integration/conftest.py | 3 +- .../test_cli_comparison.py | 0 .../test_cli_inspect.py | 21 +++-- tests/integration/support_sdk.py | 5 +- tests/integration/test_cli_integration.py | 84 +++---------------- tests/integration/test_cli_tdf_validation.py | 2 +- .../test_kas_client_integration.py | 1 + tests/integration/test_pe_interaction.py | 3 +- tests/mock_crypto.py | 2 +- tests/server_logs.py | 2 +- tests/test_aesgcm.py | 3 +- tests/test_assertion_config.py | 11 +-- tests/test_asym_encryption.py | 2 +- tests/test_autoconfigure_utils.py | 5 +- tests/test_cli.py | 5 +- tests/test_collection_store.py | 3 +- tests/test_config.py | 2 +- tests/test_crypto_utils.py | 4 +- tests/test_eckeypair.py | 1 + tests/test_header.py | 7 +- tests/test_inner_classes.py | 1 + tests/test_kas_client.py | 6 +- tests/test_kas_key_cache.py | 3 +- tests/test_kas_key_management.py | 7 +- tests/test_key_type.py | 1 + tests/test_log_collection.py | 2 +- tests/test_manifest.py | 8 +- tests/test_manifest_format.py | 5 +- tests/test_nanotdf.py | 8 +- tests/test_nanotdf_ecdsa_struct.py | 2 +- tests/test_nanotdf_integration.py | 10 ++- tests/test_nanotdf_type.py | 5 +- tests/test_policy_object.py | 1 + tests/test_sdk_builder.py | 5 +- tests/test_sdk_exceptions.py | 3 +- tests/test_sdk_mock.py | 8 +- tests/test_tdf.py | 9 +- tests/test_tdf_key_management.py | 10 +-- tests/test_tdf_reader.py | 7 +- tests/test_tdf_writer.py | 3 +- tests/test_token_source.py | 3 +- tests/test_url_normalization.py | 2 +- tests/test_use_plaintext_flow.py | 2 +- tests/test_validate_otdf_python.py | 5 +- tests/test_version.py | 1 + tests/test_zip_reader.py | 5 +- tests/test_zip_writer.py | 5 +- 49 files changed, 137 insertions(+), 160 deletions(-) rename tests/integration/{ => otdfctl_to_python}/test_cli_comparison.py (100%) rename tests/integration/{ => otdfctl_to_python}/test_cli_inspect.py (93%) diff --git a/conftest.py b/conftest.py index a9bc2ab..c816ae6 100644 --- a/conftest.py +++ b/conftest.py @@ -6,6 +6,7 @@ """ import pytest + from tests.server_logs import log_server_logs_on_failure @@ -43,7 +44,6 @@ def pytest_runtest_makereport(item, call): log_server_logs_on_failure(test_name) -# Optional: Add a fixture to manually collect logs @pytest.fixture def collect_server_logs(): """ diff --git a/tests/config_pydantic.py b/tests/config_pydantic.py index 076a7f7..457f01c 100644 --- a/tests/config_pydantic.py +++ b/tests/config_pydantic.py @@ -14,8 +14,8 @@ """ -from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic import Field +from pydantic_settings import BaseSettings, SettingsConfigDict class ConfigureTdf(BaseSettings): diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 2b17fd8..997a3f0 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -3,13 +3,14 @@ """ import json +import logging import os import subprocess import tempfile from pathlib import Path import pytest -import logging + from tests.support_cli_args import get_otdfctl_flags, get_platform_url logger = logging.getLogger(__name__) diff --git a/tests/integration/test_cli_comparison.py b/tests/integration/otdfctl_to_python/test_cli_comparison.py similarity index 100% rename from tests/integration/test_cli_comparison.py rename to tests/integration/otdfctl_to_python/test_cli_comparison.py diff --git a/tests/integration/test_cli_inspect.py b/tests/integration/otdfctl_to_python/test_cli_inspect.py similarity index 93% rename from tests/integration/test_cli_inspect.py rename to tests/integration/otdfctl_to_python/test_cli_inspect.py index 1e14465..1a8029d 100644 --- a/tests/integration/test_cli_inspect.py +++ b/tests/integration/otdfctl_to_python/test_cli_inspect.py @@ -3,12 +3,11 @@ """ import json +import logging import subprocess import sys from pathlib import Path -import logging - import pytest from tests.config_pydantic import CONFIG_TDF @@ -22,7 +21,7 @@ @pytest.mark.integration def test_cli_inspect_v4_2_2_vs_v4_3_1(all_target_mode_tdf_files, temp_credentials_file): """ - Test CLI inspect with various TDF versions. + Test Python CLI inspect with various TDF versions created by otdfctl. """ v4_2_2_files = all_target_mode_tdf_files["v4.2.2"] @@ -74,17 +73,21 @@ def test_cli_inspect_v4_2_2_vs_v4_3_1(all_target_mode_tdf_files, temp_credential f"v4.3.1 {file_type} inspection missing size" ) - print(f"\n=== {file_type.upper()} TDF Comparison (Limited Inspection) ===") - print( + logger.info( + f"\n=== {file_type.upper()} TDF Comparison (Limited Inspection) ===" + ) + logger.info( f"v4.2.2 type: {v4_2_2_result['type']}, size: {v4_2_2_result['size']}" ) - print( + logger.info( f"v4.3.1 type: {v4_3_1_result['type']}, size: {v4_3_1_result['size']}" ) @pytest.mark.integration -def test_cli_inspect_different_file_types_v4_2_2(tdf_v4_2_2_files, temp_credentials_file): +def test_cli_inspect_different_file_types_v4_2_2( + tdf_v4_2_2_files, temp_credentials_file +): """ Test CLI inspect with different file types. """ @@ -117,7 +120,9 @@ def test_cli_inspect_different_file_types_v4_2_2(tdf_v4_2_2_files, temp_credenti @pytest.mark.integration -def test_cli_inspect_different_file_types_v4_3_1(tdf_v4_3_1_files, temp_credentials_file): +def test_cli_inspect_different_file_types_v4_3_1( + tdf_v4_3_1_files, temp_credentials_file +): """ Test CLI inspect with different file types. """ diff --git a/tests/integration/support_sdk.py b/tests/integration/support_sdk.py index 84dd1e1..0d93ba3 100644 --- a/tests/integration/support_sdk.py +++ b/tests/integration/support_sdk.py @@ -1,7 +1,8 @@ -from otdf_python.sdk_builder import SDKBuilder +import httpx + from otdf_python.sdk import SDK +from otdf_python.sdk_builder import SDKBuilder from tests.config_pydantic import CONFIG_TDF -import httpx def _get_sdk_builder() -> SDKBuilder: diff --git a/tests/integration/test_cli_integration.py b/tests/integration/test_cli_integration.py index f2b3acb..3a3765e 100644 --- a/tests/integration/test_cli_integration.py +++ b/tests/integration/test_cli_integration.py @@ -10,7 +10,6 @@ import pytest -from tests.config_pydantic import CONFIG_TDF from tests.support_cli_args import get_platform_url original_env = os.environ.copy() @@ -65,7 +64,7 @@ def test_cli_decrypt_otdfctl_tdf(temp_credentials_file): env=original_env, ) - # If otdfctl fails, skip the test (might be server issues) + # If otdfctl fails to encrypt, fail fast if otdfctl_result.returncode != 0: raise Exception(f"otdfctl encrypt failed: {otdfctl_result.stderr}") @@ -165,7 +164,7 @@ def test_otdfctl_decrypt_comparison(collect_server_logs, temp_credentials_file): env=original_env, ) - # If otdfctl encrypt fails, skip the test (might be server issues) + # If otdfctl fails to encrypt, fail fast if otdfctl_encrypt_result.returncode != 0: raise Exception(f"otdfctl encrypt failed: {otdfctl_encrypt_result.stderr}") @@ -226,23 +225,9 @@ def test_otdfctl_decrypt_comparison(collect_server_logs, temp_credentials_file): # Check that our CLI succeeded if cli_decrypt_result.returncode != 0: - # Collect server logs for debugging logs = collect_server_logs() print(f"Server logs when Python CLI decrypt failed:\n{logs}") - - # Check if this is a server connectivity issue - if ( - "401 Unauthorized" in cli_decrypt_result.stderr - or "token endpoint discovery" in cli_decrypt_result.stderr - or "Issuer endpoint must be configured" in cli_decrypt_result.stderr - ): - pytest.skip( - f"Server connectivity or authentication issue: {cli_decrypt_result.stderr}" - ) - else: - assert cli_decrypt_result.returncode == 0, ( - f"Python CLI decrypt failed: {cli_decrypt_result.stderr}" - ) + raise Exception(f"Python CLI decrypt failed: {cli_decrypt_result.stderr}") # Verify both decrypted files were created assert otdfctl_decrypt_output.exists(), "otdfctl did not create decrypted file" @@ -327,25 +312,9 @@ def test_otdfctl_encrypt_decrypt_roundtrip(collect_server_logs, temp_credentials env=original_env, ) - # If otdfctl encrypt fails, skip the test (might be server issues) + # If otdfctl fails to encrypt, fail fast if otdfctl_encrypt_result.returncode != 0: - # Collect server logs for debugging - logs = collect_server_logs() - print(f"Server logs when otdfctl encrypt failed:\n{logs}") - - # Check if this is a server connectivity issue - if ( - "401 Unauthorized" in otdfctl_encrypt_result.stderr - or "token endpoint discovery" in otdfctl_encrypt_result.stderr - or "Issuer endpoint must be configured" in otdfctl_encrypt_result.stderr - ): - pytest.skip( - f"Server connectivity or authentication issue: {otdfctl_encrypt_result.stderr}" - ) - else: - assert otdfctl_encrypt_result.returncode == 0, ( - f"otdfctl encrypt failed: {otdfctl_encrypt_result.stderr}" - ) + raise Exception(f"otdfctl encrypt failed: {otdfctl_encrypt_result.stderr}") # Verify the TDF file was created assert otdfctl_tdf_output.exists(), "otdfctl did not create TDF file" @@ -378,25 +347,11 @@ def test_otdfctl_encrypt_decrypt_roundtrip(collect_server_logs, temp_credentials env=original_env, ) - # Check that otdfctl decrypt succeeded + # If otdfctl fails to decrypt, fail fast if otdfctl_decrypt_result.returncode != 0: - # Collect server logs for debugging logs = collect_server_logs() print(f"Server logs when otdfctl decrypt failed:\n{logs}") - - # Check if this is a server connectivity issue - if ( - "401 Unauthorized" in otdfctl_decrypt_result.stderr - or "token endpoint discovery" in otdfctl_decrypt_result.stderr - or "Issuer endpoint must be configured" in otdfctl_decrypt_result.stderr - ): - pytest.skip( - f"Server connectivity or authentication issue: {otdfctl_decrypt_result.stderr}" - ) - else: - assert otdfctl_decrypt_result.returncode == 0, ( - f"otdfctl decrypt failed: {otdfctl_decrypt_result.stderr}" - ) + raise Exception(f"otdfctl decrypt failed: {otdfctl_decrypt_result.stderr}") # Verify the decrypted file was created assert otdfctl_decrypt_output.exists(), "otdfctl did not create decrypted file" @@ -429,14 +384,8 @@ def test_otdfctl_encrypt_decrypt_roundtrip(collect_server_logs, temp_credentials @pytest.mark.integration -def test_cli_encrypt_integration(temp_credentials_file): +def test_cli_encrypt_integration(collect_server_logs, temp_credentials_file): """Integration test comparing our CLI with otdfctl""" - # Skip if OPENTDF_PLATFORM_URL is not set - platform_url = CONFIG_TDF.OPENTDF_PLATFORM_URL - if not platform_url: - raise Exception( - "OPENTDF_PLATFORM_URL must be set in config for integration tests" - ) # Create temporary directory for work with tempfile.TemporaryDirectory() as temp_dir: @@ -502,20 +451,9 @@ def test_cli_encrypt_integration(temp_credentials_file): # Check that our CLI succeeded if cli_result.returncode != 0: - # Check if this is a server connectivity issue - if ( - "401 Unauthorized" in cli_result.stderr - or "token endpoint discovery" in cli_result.stderr - or "Issuer endpoint must be configured" in cli_result.stderr - ): - pytest.skip( - f"Server connectivity or authentication issue: {cli_result.stderr}" - ) - - else: - assert cli_result.returncode == 0, ( - f"Python CLI failed: {cli_result.stderr}" - ) + logs = collect_server_logs() + print(f"Server logs when Python CLI encrypt failed:\n{logs}") + raise Exception(f"Python CLI failed: {cli_result.stderr}") # Both output files should exist assert otdfctl_output.exists(), "otdfctl output file does not exist" diff --git a/tests/integration/test_cli_tdf_validation.py b/tests/integration/test_cli_tdf_validation.py index 51421cd..9e9f971 100644 --- a/tests/integration/test_cli_tdf_validation.py +++ b/tests/integration/test_cli_tdf_validation.py @@ -13,9 +13,9 @@ from otdf_python.tdf_reader import TDF_MANIFEST_FILE_NAME, TDF_PAYLOAD_FILE_NAME from tests.support_cli_args import ( + get_cli_flags, get_otdfctl_flags, get_platform_url, - get_cli_flags, ) original_env = os.environ.copy() diff --git a/tests/integration/test_kas_client_integration.py b/tests/integration/test_kas_client_integration.py index c904437..97fc723 100644 --- a/tests/integration/test_kas_client_integration.py +++ b/tests/integration/test_kas_client_integration.py @@ -3,6 +3,7 @@ """ import pytest + from otdf_python.kas_client import KASClient, KeyAccess from otdf_python.kas_key_cache import KASKeyCache from otdf_python.sdk_exceptions import SDKException diff --git a/tests/integration/test_pe_interaction.py b/tests/integration/test_pe_interaction.py index 02418a3..00e4447 100644 --- a/tests/integration/test_pe_interaction.py +++ b/tests/integration/test_pe_interaction.py @@ -5,11 +5,12 @@ import logging import tempfile from pathlib import Path + import pytest from otdf_python.sdk import SDK -from tests.config_pydantic import CONFIG_TDF from otdf_python.sdk_exceptions import SDKException +from tests.config_pydantic import CONFIG_TDF from tests.integration.support_sdk import get_sdk_for_pe # Test files (adjust paths as needed) diff --git a/tests/mock_crypto.py b/tests/mock_crypto.py index 0209f67..006963b 100644 --- a/tests/mock_crypto.py +++ b/tests/mock_crypto.py @@ -1,5 +1,5 @@ -from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa def generate_rsa_keypair(): diff --git a/tests/server_logs.py b/tests/server_logs.py index 34d424d..0bc846d 100644 --- a/tests/server_logs.py +++ b/tests/server_logs.py @@ -2,8 +2,8 @@ Server log collection utility for debugging test failures. """ -import subprocess import logging +import subprocess from tests.config_pydantic import CONFIG_TESTING diff --git a/tests/test_aesgcm.py b/tests/test_aesgcm.py index b7a7e73..965f62d 100644 --- a/tests/test_aesgcm.py +++ b/tests/test_aesgcm.py @@ -1,6 +1,7 @@ +import os import unittest + from otdf_python.aesgcm import AesGcm -import os class TestAesGcm(unittest.TestCase): diff --git a/tests/test_assertion_config.py b/tests/test_assertion_config.py index 15b085c..f7c1a30 100644 --- a/tests/test_assertion_config.py +++ b/tests/test_assertion_config.py @@ -1,13 +1,14 @@ import unittest + from otdf_python.assertion_config import ( - Type, - Scope, - AssertionKeyAlg, AppliesToState, - BindingMethod, + AssertionConfig, AssertionKey, + AssertionKeyAlg, + BindingMethod, + Scope, Statement, - AssertionConfig, + Type, ) diff --git a/tests/test_asym_encryption.py b/tests/test_asym_encryption.py index e54eac9..617eb35 100644 --- a/tests/test_asym_encryption.py +++ b/tests/test_asym_encryption.py @@ -1,5 +1,5 @@ -from otdf_python.asym_encryption import AsymEncryption from otdf_python.asym_decryption import AsymDecryption +from otdf_python.asym_encryption import AsymEncryption from tests.mock_crypto import generate_rsa_keypair diff --git a/tests/test_autoconfigure_utils.py b/tests/test_autoconfigure_utils.py index 4916712..39fa412 100644 --- a/tests/test_autoconfigure_utils.py +++ b/tests/test_autoconfigure_utils.py @@ -1,10 +1,11 @@ import unittest + from otdf_python.autoconfigure_utils import ( - RuleType, - KeySplitStep, AttributeNameFQN, AttributeValueFQN, AutoConfigureException, + KeySplitStep, + RuleType, ) diff --git a/tests/test_cli.py b/tests/test_cli.py index e056ad6..999bdd7 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,13 +2,14 @@ Test CLI functionality """ -import pytest +import os import subprocess import sys import tempfile -import os from pathlib import Path +import pytest + def test_cli_help(): """Test that CLI help command works""" diff --git a/tests/test_collection_store.py b/tests/test_collection_store.py index 3131d87..5d18c24 100644 --- a/tests/test_collection_store.py +++ b/tests/test_collection_store.py @@ -1,8 +1,9 @@ import unittest + from otdf_python.collection_store import ( CollectionKey, - NoOpCollectionStore, CollectionStoreImpl, + NoOpCollectionStore, ) diff --git a/tests/test_config.py b/tests/test_config.py index 442f0d1..421691b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,4 +1,4 @@ -from otdf_python.config import TDFConfig, KASInfo, get_kas_address +from otdf_python.config import KASInfo, TDFConfig, get_kas_address def test_tdf_config_defaults(): diff --git a/tests/test_crypto_utils.py b/tests/test_crypto_utils.py index 5f5fefa..2a802f5 100644 --- a/tests/test_crypto_utils.py +++ b/tests/test_crypto_utils.py @@ -1,7 +1,9 @@ import unittest -from otdf_python.crypto_utils import CryptoUtils + from cryptography.hazmat.primitives.asymmetric import ec +from otdf_python.crypto_utils import CryptoUtils + class TestCryptoUtils(unittest.TestCase): def test_hmac(self): diff --git a/tests/test_eckeypair.py b/tests/test_eckeypair.py index 81dddf5..b193dce 100644 --- a/tests/test_eckeypair.py +++ b/tests/test_eckeypair.py @@ -1,4 +1,5 @@ import unittest + from otdf_python.eckeypair import ECKeyPair diff --git a/tests/test_header.py b/tests/test_header.py index 62fcb79..678f931 100644 --- a/tests/test_header.py +++ b/tests/test_header.py @@ -1,9 +1,10 @@ +import unittest + +from otdf_python.ecc_mode import ECCMode from otdf_python.header import Header +from otdf_python.policy_info import PolicyInfo from otdf_python.resource_locator import ResourceLocator -from otdf_python.ecc_mode import ECCMode from otdf_python.symmetric_and_payload_config import SymmetricAndPayloadConfig -from otdf_python.policy_info import PolicyInfo -import unittest class TestHeader(unittest.TestCase): diff --git a/tests/test_inner_classes.py b/tests/test_inner_classes.py index 378abc7..3b5ea89 100644 --- a/tests/test_inner_classes.py +++ b/tests/test_inner_classes.py @@ -1,4 +1,5 @@ import unittest + from otdf_python.auth_headers import AuthHeaders from otdf_python.kas_info import KASInfo from otdf_python.policy_binding_serializer import PolicyBinding, PolicyBindingSerializer diff --git a/tests/test_kas_client.py b/tests/test_kas_client.py index 9620d45..b4c3b17 100644 --- a/tests/test_kas_client.py +++ b/tests/test_kas_client.py @@ -2,9 +2,11 @@ Unit tests for KASClient. """ -import pytest -from unittest.mock import patch, MagicMock from base64 import b64decode +from unittest.mock import MagicMock, patch + +import pytest + from otdf_python.kas_client import KASClient, KeyAccess from otdf_python.kas_key_cache import KASKeyCache from otdf_python.sdk_exceptions import SDKException diff --git a/tests/test_kas_key_cache.py b/tests/test_kas_key_cache.py index e665beb..b4e6cc7 100644 --- a/tests/test_kas_key_cache.py +++ b/tests/test_kas_key_cache.py @@ -2,9 +2,10 @@ Unit tests for KASKeyCache. """ -from otdf_python.kas_key_cache import KASKeyCache from dataclasses import dataclass +from otdf_python.kas_key_cache import KASKeyCache + @dataclass class MockKasInfo: diff --git a/tests/test_kas_key_management.py b/tests/test_kas_key_management.py index d1f1527..cb964df 100644 --- a/tests/test_kas_key_management.py +++ b/tests/test_kas_key_management.py @@ -1,11 +1,12 @@ -import unittest -from unittest.mock import Mock, patch import base64 import os +import unittest +from unittest.mock import Mock, patch + import pytest from otdf_python.kas_client import KASClient, KeyAccess -from otdf_python.key_type_constants import RSA_KEY_TYPE, EC_KEY_TYPE +from otdf_python.key_type_constants import EC_KEY_TYPE, RSA_KEY_TYPE class TestKASKeyManagement(unittest.TestCase): diff --git a/tests/test_key_type.py b/tests/test_key_type.py index 872b2c8..e899b90 100644 --- a/tests/test_key_type.py +++ b/tests/test_key_type.py @@ -1,4 +1,5 @@ import unittest + from otdf_python.key_type import KeyType diff --git a/tests/test_log_collection.py b/tests/test_log_collection.py index 2de4b75..1845290 100644 --- a/tests/test_log_collection.py +++ b/tests/test_log_collection.py @@ -5,8 +5,8 @@ This script tests the server log collection without running full pytest. """ -from tests.server_logs import collect_server_logs, log_server_logs_on_failure from tests.config_pydantic import CONFIG_TESTING +from tests.server_logs import collect_server_logs, log_server_logs_on_failure def test_log_collection(): diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 49fd5fa..f9e36d1 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -1,11 +1,11 @@ from otdf_python.manifest import ( Manifest, - ManifestEncryptionInformation, - ManifestPayload, ManifestAssertion, - ManifestMethod, - ManifestKeyAccess, + ManifestEncryptionInformation, ManifestIntegrityInformation, + ManifestKeyAccess, + ManifestMethod, + ManifestPayload, ManifestRootSignature, ManifestSegment, ) diff --git a/tests/test_manifest_format.py b/tests/test_manifest_format.py index 38353e7..674aa26 100644 --- a/tests/test_manifest_format.py +++ b/tests/test_manifest_format.py @@ -3,10 +3,9 @@ """ import json -from otdf_python.tdf import TDF -from otdf_python.config import TDFConfig, KASInfo - +from otdf_python.config import KASInfo, TDFConfig +from otdf_python.tdf import TDF from tests.mock_crypto import generate_rsa_keypair diff --git a/tests/test_nanotdf.py b/tests/test_nanotdf.py index dd36e09..1517d1e 100644 --- a/tests/test_nanotdf.py +++ b/tests/test_nanotdf.py @@ -1,7 +1,9 @@ -import pytest import secrets -from otdf_python.nanotdf import NanoTDF, NanoTDFMaxSizeLimit, InvalidNanoTDFConfig + +import pytest + from otdf_python.config import NanoTDFConfig +from otdf_python.nanotdf import InvalidNanoTDFConfig, NanoTDF, NanoTDFMaxSizeLimit def test_nanotdf_roundtrip(): @@ -39,8 +41,8 @@ def test_nanotdf_invalid_magic(): @pytest.mark.integration def test_nanotdf_integration_encrypt_decrypt(): # Load environment variables for integration - from tests.config_pydantic import CONFIG_TDF from otdf_python.config import KASInfo + from tests.config_pydantic import CONFIG_TDF # Create KAS info from configuration kas_info = KASInfo(url=CONFIG_TDF.KAS_ENDPOINT) diff --git a/tests/test_nanotdf_ecdsa_struct.py b/tests/test_nanotdf_ecdsa_struct.py index c8b71eb..d83eb16 100644 --- a/tests/test_nanotdf_ecdsa_struct.py +++ b/tests/test_nanotdf_ecdsa_struct.py @@ -5,8 +5,8 @@ import pytest from otdf_python.nanotdf_ecdsa_struct import ( - NanoTDFECDSAStruct, IncorrectNanoTDFECDSASignatureSize, + NanoTDFECDSAStruct, ) diff --git a/tests/test_nanotdf_integration.py b/tests/test_nanotdf_integration.py index cb1d47e..943cfaf 100644 --- a/tests/test_nanotdf_integration.py +++ b/tests/test_nanotdf_integration.py @@ -1,9 +1,11 @@ +import io + import pytest -from otdf_python.nanotdf import NanoTDF -from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization -import io -from otdf_python.config import NanoTDFConfig, KASInfo +from cryptography.hazmat.primitives.asymmetric import rsa + +from otdf_python.config import KASInfo, NanoTDFConfig +from otdf_python.nanotdf import NanoTDF @pytest.mark.integration diff --git a/tests/test_nanotdf_type.py b/tests/test_nanotdf_type.py index f3b614d..c93c8b8 100644 --- a/tests/test_nanotdf_type.py +++ b/tests/test_nanotdf_type.py @@ -1,10 +1,11 @@ import unittest + from otdf_python.nanotdf_type import ( + Cipher, ECCurve, - Protocol, IdentifierType, PolicyType, - Cipher, + Protocol, ) diff --git a/tests/test_policy_object.py b/tests/test_policy_object.py index f75e13d..a0ceb01 100644 --- a/tests/test_policy_object.py +++ b/tests/test_policy_object.py @@ -1,4 +1,5 @@ import unittest + from otdf_python.policy_object import AttributeObject, PolicyBody, PolicyObject diff --git a/tests/test_sdk_builder.py b/tests/test_sdk_builder.py index d3b039f..0e9f835 100644 --- a/tests/test_sdk_builder.py +++ b/tests/test_sdk_builder.py @@ -3,10 +3,11 @@ """ import os +import tempfile +from unittest.mock import MagicMock, patch + import pytest import respx -import tempfile -from unittest.mock import patch, MagicMock from otdf_python.sdk import SDK from otdf_python.sdk_builder import SDKBuilder diff --git a/tests/test_sdk_exceptions.py b/tests/test_sdk_exceptions.py index 9a5307e..92ddf4b 100644 --- a/tests/test_sdk_exceptions.py +++ b/tests/test_sdk_exceptions.py @@ -1,5 +1,6 @@ import unittest -from otdf_python.sdk_exceptions import SDKException, AutoConfigureException + +from otdf_python.sdk_exceptions import AutoConfigureException, SDKException class TestSDKExceptions(unittest.TestCase): diff --git a/tests/test_sdk_mock.py b/tests/test_sdk_mock.py index 531e309..088a452 100644 --- a/tests/test_sdk_mock.py +++ b/tests/test_sdk_mock.py @@ -1,12 +1,12 @@ from otdf_python.sdk import ( - SDK, KAS, + SDK, AttributesServiceClientInterface, - NamespaceServiceClientInterface, - SubjectMappingServiceClientInterface, - ResourceMappingServiceClientInterface, AuthorizationServiceClientInterface, KeyAccessServerRegistryServiceClientInterface, + NamespaceServiceClientInterface, + ResourceMappingServiceClientInterface, + SubjectMappingServiceClientInterface, ) diff --git a/tests/test_tdf.py b/tests/test_tdf.py index 664f0ac..699ba32 100644 --- a/tests/test_tdf.py +++ b/tests/test_tdf.py @@ -1,11 +1,12 @@ -from otdf_python.tdf import TDF, TDFReaderConfig -from otdf_python.config import TDFConfig, KASInfo -from otdf_python.manifest import Manifest import io -import zipfile import json +import zipfile + import pytest +from otdf_python.config import KASInfo, TDFConfig +from otdf_python.manifest import Manifest +from otdf_python.tdf import TDF, TDFReaderConfig from tests.mock_crypto import generate_rsa_keypair diff --git a/tests/test_tdf_key_management.py b/tests/test_tdf_key_management.py index 25cfc0c..03d1fff 100644 --- a/tests/test_tdf_key_management.py +++ b/tests/test_tdf_key_management.py @@ -1,20 +1,20 @@ -import unittest -from unittest.mock import Mock, patch import base64 import io +import unittest import zipfile +from unittest.mock import Mock, patch -from otdf_python.tdf import TDF, TDFReaderConfig from otdf_python.manifest import ( Manifest, ManifestEncryptionInformation, + ManifestIntegrityInformation, + ManifestKeyAccess, ManifestMethod, ManifestPayload, - ManifestKeyAccess, - ManifestIntegrityInformation, ManifestRootSignature, ManifestSegment, ) +from otdf_python.tdf import TDF, TDFReaderConfig class TestTDFKeyManagement(unittest.TestCase): diff --git a/tests/test_tdf_reader.py b/tests/test_tdf_reader.py index 12e09f0..5e7c634 100644 --- a/tests/test_tdf_reader.py +++ b/tests/test_tdf_reader.py @@ -4,15 +4,16 @@ import io import json -import pytest from unittest.mock import MagicMock, patch +import pytest + +from otdf_python.policy_object import PolicyObject from otdf_python.tdf_reader import ( - TDFReader, TDF_MANIFEST_FILE_NAME, TDF_PAYLOAD_FILE_NAME, + TDFReader, ) -from otdf_python.policy_object import PolicyObject class TestTDFReader: diff --git a/tests/test_tdf_writer.py b/tests/test_tdf_writer.py index e586446..4d6ff79 100644 --- a/tests/test_tdf_writer.py +++ b/tests/test_tdf_writer.py @@ -1,6 +1,7 @@ -import unittest import io +import unittest import zipfile + from otdf_python.tdf_writer import TDFWriter diff --git a/tests/test_token_source.py b/tests/test_token_source.py index 972254d..5bc9e28 100644 --- a/tests/test_token_source.py +++ b/tests/test_token_source.py @@ -3,7 +3,8 @@ """ import time -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch + from otdf_python.token_source import TokenSource diff --git a/tests/test_url_normalization.py b/tests/test_url_normalization.py index 9c71351..6c6eab9 100644 --- a/tests/test_url_normalization.py +++ b/tests/test_url_normalization.py @@ -7,8 +7,8 @@ """ # Allow importing from src directory -import sys import os +import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) diff --git a/tests/test_use_plaintext_flow.py b/tests/test_use_plaintext_flow.py index dfc09e6..3019bed 100644 --- a/tests/test_use_plaintext_flow.py +++ b/tests/test_use_plaintext_flow.py @@ -2,7 +2,7 @@ Test to verify that the use_plaintext parameter flows correctly from SDKBuilder to KASClient. """ -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch from otdf_python.sdk_builder import SDKBuilder diff --git a/tests/test_validate_otdf_python.py b/tests/test_validate_otdf_python.py index b2bfc3c..10b3860 100644 --- a/tests/test_validate_otdf_python.py +++ b/tests/test_validate_otdf_python.py @@ -6,13 +6,14 @@ uv run pytest tests/test_validate_otdf_python.py """ +import logging import sys import tempfile -import logging from pathlib import Path -from otdf_python.tdf import TDFReaderConfig + import pytest +from otdf_python.tdf import TDFReaderConfig from tests.integration.support_sdk import get_sdk # Set up detailed logging diff --git a/tests/test_version.py b/tests/test_version.py index 4232608..c4740cf 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,4 +1,5 @@ import unittest + from otdf_python.version import Version diff --git a/tests/test_zip_reader.py b/tests/test_zip_reader.py index 1d98120..11af01c 100644 --- a/tests/test_zip_reader.py +++ b/tests/test_zip_reader.py @@ -1,8 +1,9 @@ -import unittest import io import random -from otdf_python.zip_writer import ZipWriter +import unittest + from otdf_python.zip_reader import ZipReader +from otdf_python.zip_writer import ZipWriter class TestZipReader(unittest.TestCase): diff --git a/tests/test_zip_writer.py b/tests/test_zip_writer.py index cb1112d..bf7dccd 100644 --- a/tests/test_zip_writer.py +++ b/tests/test_zip_writer.py @@ -1,8 +1,9 @@ -import unittest import io -from otdf_python.zip_writer import ZipWriter +import unittest import zipfile +from otdf_python.zip_writer import ZipWriter + class TestZipWriter(unittest.TestCase): def test_data_and_stream(self): From 10fedad83e07c47de33e1806408d3fdba9b45247 Mon Sep 17 00:00:00 2001 From: b-long Date: Sun, 7 Sep 2025 10:17:17 -0400 Subject: [PATCH 05/12] chore: organize imports --- src/otdf_python/__init__.py | 4 +-- src/otdf_python/aesgcm.py | 3 +- src/otdf_python/asym_crypto.py | 5 ++- src/otdf_python/asym_decryption.py | 8 ++--- src/otdf_python/asym_encryption.py | 10 +++--- src/otdf_python/cli.py | 2 +- src/otdf_python/config.py | 2 +- src/otdf_python/crypto_utils.py | 7 +++-- src/otdf_python/dpop.py | 5 +-- src/otdf_python/eckeypair.py | 10 +++--- src/otdf_python/header.py | 6 ++-- src/otdf_python/kas_client.py | 15 ++++----- src/otdf_python/kas_connect_rpc_client.py | 5 +-- src/otdf_python/manifest.py | 4 +-- src/otdf_python/nanotdf.py | 31 ++++++++++--------- src/otdf_python/sdk.py | 8 ++--- src/otdf_python/sdk_builder.py | 9 +++--- src/otdf_python/tdf.py | 37 ++++++++++++----------- src/otdf_python/tdf_reader.py | 10 +++--- src/otdf_python/tdf_writer.py | 1 + src/otdf_python/token_source.py | 1 + src/otdf_python/zip_reader.py | 3 +- src/otdf_python/zip_writer.py | 2 +- 23 files changed, 100 insertions(+), 88 deletions(-) diff --git a/src/otdf_python/__init__.py b/src/otdf_python/__init__.py index df07890..f8dcd49 100644 --- a/src/otdf_python/__init__.py +++ b/src/otdf_python/__init__.py @@ -5,10 +5,10 @@ Provides both programmatic APIs and command-line interface for encryption and decryption. """ +from .cli import main as cli_main +from .config import KASInfo, NanoTDFConfig, TDFConfig from .sdk import SDK from .sdk_builder import SDKBuilder -from .config import TDFConfig, NanoTDFConfig, KASInfo -from .cli import main as cli_main __all__ = [ "SDK", diff --git a/src/otdf_python/aesgcm.py b/src/otdf_python/aesgcm.py index a7d7446..ced6427 100644 --- a/src/otdf_python/aesgcm.py +++ b/src/otdf_python/aesgcm.py @@ -1,6 +1,7 @@ -from cryptography.hazmat.primitives.ciphers.aead import AESGCM import os +from cryptography.hazmat.primitives.ciphers.aead import AESGCM + class AesGcm: GCM_NONCE_LENGTH = 12 diff --git a/src/otdf_python/asym_crypto.py b/src/otdf_python/asym_crypto.py index 78e3f8d..932bfa1 100644 --- a/src/otdf_python/asym_crypto.py +++ b/src/otdf_python/asym_crypto.py @@ -2,10 +2,9 @@ Asymmetric encryption and decryption utilities for RSA keys in PEM format. """ -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa, padding -from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding, rsa from cryptography.x509 import load_pem_x509_certificate from .sdk_exceptions import SDKException diff --git a/src/otdf_python/asym_decryption.py b/src/otdf_python/asym_decryption.py index 2ea7611..af11414 100644 --- a/src/otdf_python/asym_decryption.py +++ b/src/otdf_python/asym_decryption.py @@ -1,9 +1,9 @@ -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.backends import default_backend import base64 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding + from .sdk_exceptions import SDKException diff --git a/src/otdf_python/asym_encryption.py b/src/otdf_python/asym_encryption.py index e385817..7e5e27a 100644 --- a/src/otdf_python/asym_encryption.py +++ b/src/otdf_python/asym_encryption.py @@ -1,11 +1,11 @@ -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.hazmat.primitives import hashes -from cryptography.x509 import load_pem_x509_certificate -from cryptography.hazmat.backends import default_backend import base64 import re +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.x509 import load_pem_x509_certificate + from .sdk_exceptions import SDKException diff --git a/src/otdf_python/cli.py b/src/otdf_python/cli.py index 0eb0dd1..18b7b9c 100644 --- a/src/otdf_python/cli.py +++ b/src/otdf_python/cli.py @@ -11,9 +11,9 @@ import json import logging import sys +from dataclasses import asdict from io import BytesIO from pathlib import Path -from dataclasses import asdict from otdf_python.config import KASInfo, NanoTDFConfig, TDFConfig from otdf_python.sdk import SDK diff --git a/src/otdf_python/config.py b/src/otdf_python/config.py index 0531458..646acec 100644 --- a/src/otdf_python/config.py +++ b/src/otdf_python/config.py @@ -1,7 +1,7 @@ from dataclasses import dataclass, field from enum import Enum -from urllib.parse import urlparse, urlunparse from typing import Any +from urllib.parse import urlparse, urlunparse class TDFFormat(Enum): diff --git a/src/otdf_python/crypto_utils.py b/src/otdf_python/crypto_utils.py index 2b80e79..b32a5e9 100644 --- a/src/otdf_python/crypto_utils.py +++ b/src/otdf_python/crypto_utils.py @@ -1,8 +1,9 @@ -import hmac import hashlib -from cryptography.hazmat.primitives.asymmetric import rsa, ec -from cryptography.hazmat.primitives import serialization +import hmac + from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import ec, rsa class CryptoUtils: diff --git a/src/otdf_python/dpop.py b/src/otdf_python/dpop.py index d27bc0f..c442a5e 100644 --- a/src/otdf_python/dpop.py +++ b/src/otdf_python/dpop.py @@ -2,9 +2,10 @@ DPoP (Demonstration of Proof-of-Possession) token generation utilities. """ -import time -import hashlib import base64 +import hashlib +import time + import jwt from .crypto_utils import CryptoUtils diff --git a/src/otdf_python/eckeypair.py b/src/otdf_python/eckeypair.py index f463abc..3dee0aa 100644 --- a/src/otdf_python/eckeypair.py +++ b/src/otdf_python/eckeypair.py @@ -1,14 +1,14 @@ +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives.serialization import ( Encoding, - PublicFormat, - PrivateFormat, NoEncryption, + PrivateFormat, + PublicFormat, ) -from cryptography.hazmat.backends import default_backend -from cryptography.exceptions import InvalidSignature class ECKeyPair: diff --git a/src/otdf_python/header.py b/src/otdf_python/header.py index ceae3f6..df7186d 100644 --- a/src/otdf_python/header.py +++ b/src/otdf_python/header.py @@ -1,8 +1,8 @@ -from otdf_python.resource_locator import ResourceLocator +from otdf_python.constants import MAGIC_NUMBER_AND_VERSION from otdf_python.ecc_mode import ECCMode -from otdf_python.symmetric_and_payload_config import SymmetricAndPayloadConfig from otdf_python.policy_info import PolicyInfo -from otdf_python.constants import MAGIC_NUMBER_AND_VERSION +from otdf_python.resource_locator import ResourceLocator +from otdf_python.symmetric_and_payload_config import SymmetricAndPayloadConfig class Header: diff --git a/src/otdf_python/kas_client.py b/src/otdf_python/kas_client.py index 39bba64..43b3c6a 100644 --- a/src/otdf_python/kas_client.py +++ b/src/otdf_python/kas_client.py @@ -2,21 +2,22 @@ KASClient: Handles communication with the Key Access Service (KAS). """ -import time -import logging +import base64 import hashlib +import logging import secrets -import base64 +import time from base64 import b64decode from dataclasses import dataclass + import jwt -from .kas_key_cache import KASKeyCache -from .sdk_exceptions import SDKException -from .crypto_utils import CryptoUtils from .asym_decryption import AsymDecryption -from .key_type_constants import RSA_KEY_TYPE, EC_KEY_TYPE +from .crypto_utils import CryptoUtils from .kas_connect_rpc_client import KASConnectRPCClient +from .kas_key_cache import KASKeyCache +from .key_type_constants import EC_KEY_TYPE, RSA_KEY_TYPE +from .sdk_exceptions import SDKException @dataclass diff --git a/src/otdf_python/kas_connect_rpc_client.py b/src/otdf_python/kas_connect_rpc_client.py index c8b7319..3b39021 100644 --- a/src/otdf_python/kas_connect_rpc_client.py +++ b/src/otdf_python/kas_connect_rpc_client.py @@ -4,12 +4,13 @@ """ import logging -import urllib3 -from .sdk_exceptions import SDKException +import urllib3 from otdf_python_proto.kas import kas_pb2 from otdf_python_proto.kas.kas_pb2_connect import AccessServiceClient +from .sdk_exceptions import SDKException + class KASConnectRPCClient: """ diff --git a/src/otdf_python/manifest.py b/src/otdf_python/manifest.py index 1d771da..1ebbae3 100644 --- a/src/otdf_python/manifest.py +++ b/src/otdf_python/manifest.py @@ -1,6 +1,6 @@ -from dataclasses import dataclass, field, asdict -from typing import Any import json +from dataclasses import asdict, dataclass, field +from typing import Any @dataclass diff --git a/src/otdf_python/nanotdf.py b/src/otdf_python/nanotdf.py index 9e896fa..d8a063e 100644 --- a/src/otdf_python/nanotdf.py +++ b/src/otdf_python/nanotdf.py @@ -1,20 +1,22 @@ -from cryptography.hazmat.primitives.ciphers.aead import AESGCM -from otdf_python.asym_crypto import AsymDecryption +import hashlib +import json import secrets -from typing import BinaryIO from io import BytesIO +from typing import BinaryIO + +from cryptography.hazmat.primitives.ciphers.aead import AESGCM + +from otdf_python.asym_crypto import AsymDecryption from otdf_python.collection_store import CollectionStore, NoOpCollectionStore -from otdf_python.policy_stub import NULL_POLICY_UUID -from otdf_python.sdk_exceptions import SDKException +from otdf_python.config import KASInfo, NanoTDFConfig from otdf_python.constants import MAGIC_NUMBER_AND_VERSION -from otdf_python.resource_locator import ResourceLocator -from otdf_python.policy_object import PolicyObject, PolicyBody, AttributeObject -from otdf_python.symmetric_and_payload_config import SymmetricAndPayloadConfig from otdf_python.ecc_mode import ECCMode -import json -import hashlib from otdf_python.policy_info import PolicyInfo -from otdf_python.config import NanoTDFConfig, KASInfo +from otdf_python.policy_object import AttributeObject, PolicyBody, PolicyObject +from otdf_python.policy_stub import NULL_POLICY_UUID +from otdf_python.resource_locator import ResourceLocator +from otdf_python.sdk_exceptions import SDKException +from otdf_python.symmetric_and_payload_config import SymmetricAndPayloadConfig class NanoTDFException(SDKException): @@ -54,7 +56,7 @@ def _create_policy_object(self, attributes: list[str]) -> PolicyObject: def _serialize_policy_object(self, obj): """Custom NanoTDF serializer to convert to compatible JSON format.""" - from otdf_python.policy_object import PolicyBody, AttributeObject + from otdf_python.policy_object import AttributeObject, PolicyBody if isinstance(obj, PolicyBody): # Convert data_attributes to dataAttributes and use null instead of empty array @@ -224,10 +226,9 @@ def _wrap_key_if_needed( break if kas_public_key: - from cryptography.hazmat.primitives import serialization - from cryptography.hazmat.primitives.asymmetric import padding - from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import padding public_key = serialization.load_pem_public_key( kas_public_key.encode(), backend=default_backend() diff --git a/src/otdf_python/sdk.py b/src/otdf_python/sdk.py index 7734519..407db14 100644 --- a/src/otdf_python/sdk.py +++ b/src/otdf_python/sdk.py @@ -2,14 +2,14 @@ Python port of the main SDK class for OpenTDF platform interaction. """ -from typing import Any, BinaryIO -from io import BytesIO from contextlib import AbstractContextManager +from io import BytesIO +from typing import Any, BinaryIO -from otdf_python.tdf import TDF, TDFReaderConfig, TDFReader +from otdf_python.config import NanoTDFConfig, TDFConfig from otdf_python.nanotdf import NanoTDF from otdf_python.sdk_exceptions import SDKException -from otdf_python.config import NanoTDFConfig, TDFConfig +from otdf_python.tdf import TDF, TDFReader, TDFReaderConfig # Stubs for service client interfaces (to be implemented) diff --git a/src/otdf_python/sdk_builder.py b/src/otdf_python/sdk_builder.py index 87e34e0..ead42f5 100644 --- a/src/otdf_python/sdk_builder.py +++ b/src/otdf_python/sdk_builder.py @@ -3,14 +3,15 @@ Provides methods to configure and build SDK instances. """ -from typing import Any -import os import logging +import os import ssl -import httpx from dataclasses import dataclass +from typing import Any + +import httpx -from otdf_python.sdk import SDK, KAS +from otdf_python.sdk import KAS, SDK from otdf_python.sdk_exceptions import AutoConfigureException # Configure logging diff --git a/src/otdf_python/tdf.py b/src/otdf_python/tdf.py index 4a0ef5e..3dd35af 100644 --- a/src/otdf_python/tdf.py +++ b/src/otdf_python/tdf.py @@ -1,31 +1,32 @@ -from typing import BinaryIO, TYPE_CHECKING -import io -import os +import base64 import hashlib import hmac -import base64 -import zipfile +import io import logging +import os +import zipfile +from typing import TYPE_CHECKING, BinaryIO if TYPE_CHECKING: from otdf_python.kas_client import KASClient +from dataclasses import dataclass + +from otdf_python.aesgcm import AesGcm +from otdf_python.config import TDFConfig +from otdf_python.key_type_constants import RSA_KEY_TYPE from otdf_python.manifest import ( Manifest, - ManifestSegment, - ManifestIntegrityInformation, - ManifestRootSignature, ManifestEncryptionInformation, - ManifestPayload, - ManifestMethod, + ManifestIntegrityInformation, ManifestKeyAccess, + ManifestMethod, + ManifestPayload, + ManifestRootSignature, + ManifestSegment, ) from otdf_python.policy_stub import NULL_POLICY_UUID from otdf_python.tdf_writer import TDFWriter -from otdf_python.aesgcm import AesGcm -from dataclasses import dataclass -from otdf_python.key_type_constants import RSA_KEY_TYPE -from otdf_python.config import TDFConfig @dataclass @@ -83,10 +84,11 @@ def _validate_kas_infos(self, kas_infos): return validated_kas_infos def _wrap_key_for_kas(self, key, kas_infos, policy_json=None): - from otdf_python.asym_crypto import AsymEncryption import hashlib import hmac + from otdf_python.asym_crypto import AsymEncryption + key_access_objs = [] for kas in kas_infos: asym = AsymEncryption(kas.public_key) @@ -161,7 +163,7 @@ def _build_policy_json(self, config: TDFConfig) -> str: def _serialize_policy_object(self, obj): """Custom TDF serializer to convert to compatible JSON format.""" - from otdf_python.policy_object import PolicyBody, AttributeObject + from otdf_python.policy_object import AttributeObject, PolicyBody if isinstance(obj, PolicyBody): # Convert data_attributes to dataAttributes and use null instead of empty array @@ -424,10 +426,11 @@ def read_payload( """ Reads and verifies TDF segments, decrypts if needed, and writes the payload to output_stream. """ + import base64 import zipfile + from otdf_python.aesgcm import AesGcm from otdf_python.asym_crypto import AsymDecryption - import base64 with zipfile.ZipFile(io.BytesIO(tdf_bytes), "r") as z: manifest_json = z.read("0.manifest.json").decode() diff --git a/src/otdf_python/tdf_reader.py b/src/otdf_python/tdf_reader.py index 8e797b7..a414f16 100644 --- a/src/otdf_python/tdf_reader.py +++ b/src/otdf_python/tdf_reader.py @@ -2,10 +2,10 @@ TDFReader is responsible for reading and processing Trusted Data Format (TDF) files. """ -from .zip_reader import ZipReader -from .sdk_exceptions import SDKException -from .policy_object import PolicyObject from .manifest import Manifest +from .policy_object import PolicyObject +from .sdk_exceptions import SDKException +from .zip_reader import ZipReader # Constants from TDFWriter TDF_MANIFEST_FILE_NAME = "0.manifest.json" @@ -119,9 +119,9 @@ def read_policy_object(self) -> PolicyObject: # Convert to PolicyObject from otdf_python.policy_object import ( - PolicyObject, - PolicyBody, AttributeObject, + PolicyBody, + PolicyObject, ) # Parse data attributes - handle case where body might be None or have None values diff --git a/src/otdf_python/tdf_writer.py b/src/otdf_python/tdf_writer.py index 93ad9a9..6dcd7d5 100644 --- a/src/otdf_python/tdf_writer.py +++ b/src/otdf_python/tdf_writer.py @@ -1,4 +1,5 @@ import io + from otdf_python.zip_writer import ZipWriter diff --git a/src/otdf_python/token_source.py b/src/otdf_python/token_source.py index f3610bc..0c60c3a 100644 --- a/src/otdf_python/token_source.py +++ b/src/otdf_python/token_source.py @@ -3,6 +3,7 @@ """ import time + import httpx diff --git a/src/otdf_python/zip_reader.py b/src/otdf_python/zip_reader.py index ddc8e82..78d7ecb 100644 --- a/src/otdf_python/zip_reader.py +++ b/src/otdf_python/zip_reader.py @@ -1,5 +1,6 @@ -import zipfile import io +import zipfile + from otdf_python.invalid_zip_exception import InvalidZipException diff --git a/src/otdf_python/zip_writer.py b/src/otdf_python/zip_writer.py index a96b551..e548d97 100644 --- a/src/otdf_python/zip_writer.py +++ b/src/otdf_python/zip_writer.py @@ -1,5 +1,5 @@ -import zipfile import io +import zipfile import zlib From f5484650077ac2e601c62bc72a6db819f97ebdf3 Mon Sep 17 00:00:00 2001 From: b-long Date: Sun, 7 Sep 2025 10:20:09 -0400 Subject: [PATCH 06/12] chore: require sorted imports --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index aa396f2..810b110 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,6 +68,8 @@ lint.select = [ "C4", # McCabe complexity "C90", + # isort + "I", # Performance-related rules "PERF", # Ruff's performance rules # Additional useful rules From 5590acf58f5be61d2961ea73e5d66815e5f76164 Mon Sep 17 00:00:00 2001 From: b-long Date: Sun, 7 Sep 2025 10:57:52 -0400 Subject: [PATCH 07/12] chore: add test_cli_decrypt.py --- .../otdfctl_to_python/test_cli_decrypt.py | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 tests/integration/otdfctl_to_python/test_cli_decrypt.py diff --git a/tests/integration/otdfctl_to_python/test_cli_decrypt.py b/tests/integration/otdfctl_to_python/test_cli_decrypt.py new file mode 100644 index 0000000..b5cc6f3 --- /dev/null +++ b/tests/integration/otdfctl_to_python/test_cli_decrypt.py @@ -0,0 +1,241 @@ +""" +Tests using target mode fixtures, for CLI integration testing. +""" + +import logging +import subprocess +import sys +import tempfile +from pathlib import Path + +import pytest + +from tests.config_pydantic import CONFIG_TDF +from tests.support_cli_args import ( + get_cli_flags, +) + +logger = logging.getLogger(__name__) + + +@pytest.mark.integration +def test_cli_decrypt_v4_2_2_vs_v4_3_1(all_target_mode_tdf_files, temp_credentials_file): + """ + Test Python CLI decrypt with various TDF versions created by otdfctl. + """ + + v4_2_2_files = all_target_mode_tdf_files["v4.2.2"] + v4_3_1_files = all_target_mode_tdf_files["v4.3.1"] + + # Test decrypt on both versions of the same file type + for file_type in ["text", "binary"]: + v4_2_2_tdf = v4_2_2_files[file_type] + v4_3_1_tdf = v4_3_1_files[file_type] + + # Decrypt v4.2.2 TDF + v4_2_2_output = _run_cli_decrypt(v4_2_2_tdf, temp_credentials_file) + + # Decrypt v4.3.1 TDF + v4_3_1_output = _run_cli_decrypt(v4_3_1_tdf, temp_credentials_file) + + # Both should succeed and produce output files + assert v4_2_2_output is not None, f"Failed to decrypt v4.2.2 {file_type} TDF" + assert v4_3_1_output is not None, f"Failed to decrypt v4.3.1 {file_type} TDF" + + assert v4_2_2_output.exists(), ( + f"v4.2.2 {file_type} decrypt output file not created" + ) + assert v4_3_1_output.exists(), ( + f"v4.3.1 {file_type} decrypt output file not created" + ) + + # Both output files should have content (not empty) + assert v4_2_2_output.stat().st_size > 0, ( + f"v4.2.2 {file_type} decrypt produced empty file" + ) + assert v4_3_1_output.stat().st_size > 0, ( + f"v4.3.1 {file_type} decrypt produced empty file" + ) + + # Log the decryption results for comparison + logger.info(f"\n=== {file_type.upper()} TDF Decryption Comparison ===") + logger.info(f"v4.2.2 output size: {v4_2_2_output.stat().st_size} bytes") + logger.info(f"v4.3.1 output size: {v4_3_1_output.stat().st_size} bytes") + + # For text files, we can compare the content directly + if file_type == "text": + v4_2_2_content = v4_2_2_output.read_text() + v4_3_1_content = v4_3_1_output.read_text() + + logger.info(f"v4.2.2 content preview: {v4_2_2_content[:50]}...") + logger.info(f"v4.3.1 content preview: {v4_3_1_content[:50]}...") + + # Clean up output files + v4_2_2_output.unlink() + v4_3_1_output.unlink() + + +@pytest.mark.integration +def test_cli_decrypt_different_file_types_v4_3_1( + tdf_v4_3_1_files, temp_credentials_file +): + """ + Test CLI decrypt with different file types. + """ + + file_types_to_test = [ + "text", + "binary", + "with_attributes", + ] # TODO: Consider adding "empty" file type as well + + for file_type in file_types_to_test: + tdf_path = tdf_v4_3_1_files[file_type] + + # Decrypt the TDF + output_file = _run_cli_decrypt(tdf_path, temp_credentials_file) + + assert output_file is not None, f"Failed to decrypt {file_type} TDF" + assert output_file.exists(), f"{file_type} TDF decrypt output file not created" + + # Check file-type specific expectations + if file_type == "empty": + # Empty files should produce empty output files + assert output_file.stat().st_size == 0, ( + f"{file_type} TDF should produce empty output" + ) + else: + # Non-empty files should produce non-empty output + assert output_file.stat().st_size > 0, ( + f"{file_type} TDF produced empty decrypt output" + ) + + # For attributed files, just ensure they decrypt successfully + if file_type == "with_attributes": + logger.info( + f"Successfully decrypted attributed TDF, output size: {output_file.stat().st_size}" + ) + + # For text files, verify the content is readable + if file_type == "text": + try: + content = output_file.read_text() + assert len(content) > 0, "Text file should have readable content" + logger.info(f"Text content preview: {content[:100]}...") + except UnicodeDecodeError: + pytest.fail(f"Decrypted {file_type} file should be valid text") + + # Clean up output file + output_file.unlink() + + +def _run_cli_decrypt(tdf_path: Path, creds_file: Path) -> Path | None: + """ + Helper function to run Python CLI decrypt command and return the output file path. + + Returns the Path to the decrypted output file if successful, None if failed. + """ + # Determine platform flags + platform_url = CONFIG_TDF.OPENTDF_PLATFORM_URL + cli_flags = get_cli_flags() + + # Create a temporary output file + with tempfile.NamedTemporaryFile(delete=False, suffix=".decrypted") as temp_file: + output_path = Path(temp_file.name) + + try: + # Build CLI command + cmd = [ + sys.executable, + "-m", + "otdf_python.cli", + "--platform-url", + platform_url, + "--with-client-creds-file", + str(creds_file), + *cli_flags, + "decrypt", + str(tdf_path), + "-o", + str(output_path), + ] + + # Run the CLI command + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True, + cwd=Path(__file__).parent.parent.parent, # Project root + ) + + logger.debug(f"CLI decrypt succeeded for {tdf_path}") + if result.stdout: + logger.debug(f"CLI stdout: {result.stdout}") + + return output_path + + except subprocess.CalledProcessError as e: + logger.error(f"CLI decrypt failed for {tdf_path}: {e}") + logger.error(f"CLI stderr: {e.stderr}") + logger.error(f"CLI stdout: {e.stdout}") + + # Clean up the output file if it was created but command failed + if output_path.exists(): + output_path.unlink() + + raise Exception(f"Failed to decrypt TDF {tdf_path}: {e}") from e + + +@pytest.mark.integration +def test_cli_decrypt_different_file_types_v4_2_2( + tdf_v4_2_2_files, temp_credentials_file +): + """ + Test CLI decrypt with different file types. + """ + + file_types_to_test = [ + "text", + "binary", + "with_attributes", + ] # TODO: Consider adding "empty" file type as well + + for file_type in file_types_to_test: + tdf_path = tdf_v4_2_2_files[file_type] + + # Decrypt the TDF + output_file = _run_cli_decrypt(tdf_path, temp_credentials_file) + + assert output_file is not None, f"Failed to decrypt {file_type} TDF" + assert output_file.exists(), f"{file_type} TDF decrypt output file not created" + + # Check file-type specific expectations + if file_type == "empty": + # Empty files should produce empty output files + assert output_file.stat().st_size == 0, ( + f"{file_type} TDF should produce empty output" + ) + else: + # Non-empty files should produce non-empty output + assert output_file.stat().st_size > 0, ( + f"{file_type} TDF produced empty decrypt output" + ) + + # For attributed files, just ensure they decrypt successfully + if file_type == "with_attributes": + logger.info( + f"Successfully decrypted attributed TDF, output size: {output_file.stat().st_size}" + ) + + # For text files, verify the content is readable + if file_type == "text": + try: + content = output_file.read_text() + assert len(content) > 0, "Text file should have readable content" + logger.info(f"Text content preview: {content[:100]}...") + except UnicodeDecodeError: + pytest.fail(f"Decrypted {file_type} file should be valid text") + + # Clean up output file + output_file.unlink() From 282d642ad9e6643fe3915b097ac0ecd75f38f20e Mon Sep 17 00:00:00 2001 From: b-long Date: Sun, 7 Sep 2025 11:05:16 -0400 Subject: [PATCH 08/12] chore: organize integration tests --- tests/integration/{ => otdfctl_only}/test_fixture_structure.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/integration/{ => otdfctl_only}/test_fixture_structure.py (100%) diff --git a/tests/integration/test_fixture_structure.py b/tests/integration/otdfctl_only/test_fixture_structure.py similarity index 100% rename from tests/integration/test_fixture_structure.py rename to tests/integration/otdfctl_only/test_fixture_structure.py From 3f83d5b4bb65b3a3b48073274aeaad6c2e746b31 Mon Sep 17 00:00:00 2001 From: b-long Date: Sun, 7 Sep 2025 11:12:12 -0400 Subject: [PATCH 09/12] chore: organize integration tests --- .../test_tdf_reader_integration.py | 14 ++++++-------- .../test_kas_client_integration.py | 0 2 files changed, 6 insertions(+), 8 deletions(-) rename tests/integration/{ => otdfctl_to_python}/test_tdf_reader_integration.py (96%) rename tests/integration/{ => python_only}/test_kas_client_integration.py (100%) diff --git a/tests/integration/test_tdf_reader_integration.py b/tests/integration/otdfctl_to_python/test_tdf_reader_integration.py similarity index 96% rename from tests/integration/test_tdf_reader_integration.py rename to tests/integration/otdfctl_to_python/test_tdf_reader_integration.py index 341aa08..6c58c32 100644 --- a/tests/integration/test_tdf_reader_integration.py +++ b/tests/integration/otdfctl_to_python/test_tdf_reader_integration.py @@ -55,13 +55,15 @@ def test_read_otdfctl_created_tdf_structure(self, temp_credentials_file): str(otdfctl_output), ] - otdfctl_result = subprocess.run( + otdfctl_encrypt_result = subprocess.run( otdfctl_cmd, capture_output=True, text=True, cwd=temp_path ) # If otdfctl fails, skip the test (might be server issues) - if otdfctl_result.returncode != 0: - pytest.skip(f"otdfctl encrypt failed: {otdfctl_result.stderr}") + if otdfctl_encrypt_result.returncode != 0: + raise Exception( + f"otdfctl encrypt failed: {otdfctl_encrypt_result.stderr}" + ) # Verify the TDF file was created assert otdfctl_output.exists(), "otdfctl did not create TDF file" @@ -153,13 +155,9 @@ def test_read_otdfctl_tdf_with_attributes(self, temp_credentials_file): # If otdfctl fails, skip the test # assert otdfctl_result.returncode == 0, "otdfctl encrypt failed" if otdfctl_result.returncode != 0: - print(f"otdfctl encrypt failed: {otdfctl_result.stderr}") - # Skip the test - pytest.skip( + raise Exception( f"otdfctl encrypt with attributes failed: {otdfctl_result.stderr}" ) - else: - print("otdfctl encrypt with attributes succeeded") # Verify the TDF file was created assert otdfctl_output.exists(), "otdfctl did not create TDF file" diff --git a/tests/integration/test_kas_client_integration.py b/tests/integration/python_only/test_kas_client_integration.py similarity index 100% rename from tests/integration/test_kas_client_integration.py rename to tests/integration/python_only/test_kas_client_integration.py From 43f2619408b2144c60a9e19e1f8987f0fb34240b Mon Sep 17 00:00:00 2001 From: b-long Date: Mon, 8 Sep 2025 13:41:42 -0400 Subject: [PATCH 10/12] Tweak attributes --- .github/workflows/platform-integration-test.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/platform-integration-test.yaml b/.github/workflows/platform-integration-test.yaml index 23c2f25..f4d8420 100644 --- a/.github/workflows/platform-integration-test.yaml +++ b/.github/workflows/platform-integration-test.yaml @@ -163,8 +163,8 @@ jobs: OIDC_TOKEN_ENDPOINT: "http://localhost:8888/auth/realms/opentdf/protocol/openid-connect/token" OPENTDF_KAS_URL: "http://localhost:8080/kas" INSECURE_SKIP_VERIFY: "TRUE" - TEST_OPENTDF_ATTRIBUTE_1: "https://example.com/attr/attr1/value/value1" - TEST_OPENTDF_ATTRIBUTE_2: "https://example.com/attr/attr1/value/value2" + TEST_OPENTDF_ATTRIBUTE_1: "https://example.net/attr/attr1/value/value1" + TEST_OPENTDF_ATTRIBUTE_2: "https://example.com/attr/attr1/value/value1" run: | uv sync # Skip the tests marked "integration" @@ -180,8 +180,8 @@ jobs: OIDC_OP_TOKEN_ENDPOINT: "http://localhost:8888/auth/realms/opentdf/protocol/openid-connect/token" OPENTDF_KAS_URL: "http://localhost:8080/kas" INSECURE_SKIP_VERIFY: "TRUE" - TEST_OPENTDF_ATTRIBUTE_1: "https://example.com/attr/attr1/value/value1" - TEST_OPENTDF_ATTRIBUTE_2: "https://example.com/attr/attr1/value/value2" + TEST_OPENTDF_ATTRIBUTE_1: "https://example.net/attr/attr1/value/value1" + TEST_OPENTDF_ATTRIBUTE_2: "https://example.com/attr/attr1/value/value1" run: | # Run check_entitlements.sh ./.github/check_entitlements.sh From d3d1eb4f3be7b717273ddd52dee53cf73c663955 Mon Sep 17 00:00:00 2001 From: b-long Date: Mon, 8 Sep 2025 14:10:23 -0400 Subject: [PATCH 11/12] chore: cleanup tests --- .../otdfctl_to_python/test_cli_inspect.py | 91 +++++++------------ 1 file changed, 33 insertions(+), 58 deletions(-) diff --git a/tests/integration/otdfctl_to_python/test_cli_inspect.py b/tests/integration/otdfctl_to_python/test_cli_inspect.py index 1a8029d..369d264 100644 --- a/tests/integration/otdfctl_to_python/test_cli_inspect.py +++ b/tests/integration/otdfctl_to_python/test_cli_inspect.py @@ -85,73 +85,48 @@ def test_cli_inspect_v4_2_2_vs_v4_3_1(all_target_mode_tdf_files, temp_credential @pytest.mark.integration -def test_cli_inspect_different_file_types_v4_2_2( - tdf_v4_2_2_files, temp_credentials_file +def test_cli_inspect_different_file_types( + all_target_mode_tdf_files, + temp_credentials_file, # , request ): """ Test CLI inspect with different file types. """ + # tdf_files = request.getfixturevalue(tdf_files_fixture) + assert "v4.2.2" in all_target_mode_tdf_files + assert "v4.3.1" in all_target_mode_tdf_files - file_types_to_test = [ - "text", - "binary", - "with_attributes", - ] # TODO: Consider adding "empty" file type as well - - for file_type in file_types_to_test: - tdf_path = tdf_v4_2_2_files[file_type] - - # Inspect the TDF - result = _run_cli_inspect(tdf_path, temp_credentials_file) - - assert result is not None, f"Failed to inspect {file_type} TDF" - assert "manifest" in result, f"{file_type} TDF inspection missing manifest" - - # Check file-type specific expectations - if file_type == "empty": - # Empty files should still have valid manifests - assert "encryptionInformation" in result["manifest"] - elif file_type == "with_attributes": - # Attributed files should have keyAccess information - assert ( - "keyAccess" in result["manifest"] - or "encryptionInformation" in result["manifest"] - ) + # Check each version has the expected file types + for version in ["v4.2.2", "v4.3.1"]: + tdf_files = all_target_mode_tdf_files[version] + file_types_to_test = [ + "text", + "binary", + "with_attributes", + ] # TODO: Consider adding "empty" file type as well -@pytest.mark.integration -def test_cli_inspect_different_file_types_v4_3_1( - tdf_v4_3_1_files, temp_credentials_file -): - """ - Test CLI inspect with different file types. - """ + for file_type in file_types_to_test: + tdf_path = tdf_files[file_type] + + # Inspect the TDF + result = _run_cli_inspect(tdf_path, temp_credentials_file) - file_types_to_test = [ - "text", - "binary", - "with_attributes", - ] # TODO: Consider adding "empty" file type as well - - for file_type in file_types_to_test: - tdf_path = tdf_v4_3_1_files[file_type] - - # Inspect the TDF - result = _run_cli_inspect(tdf_path, temp_credentials_file) - - assert result is not None, f"Failed to inspect {file_type} TDF" - assert "manifest" in result, f"{file_type} TDF inspection missing manifest" - - # Check file-type specific expectations - if file_type == "empty": - # Empty files should still have valid manifests - assert "encryptionInformation" in result["manifest"] - elif file_type == "with_attributes": - # Attributed files should have keyAccess information - assert ( - "keyAccess" in result["manifest"] - or "encryptionInformation" in result["manifest"] + assert result is not None, ( + f"Failed to inspect {file_type} TDF, TDF version {version}" ) + assert "manifest" in result, f"{file_type} TDF inspection missing manifest" + + # Check file-type specific expectations + if file_type == "empty": + # Empty files should still have valid manifests + assert "encryptionInformation" in result["manifest"] + elif file_type == "with_attributes": + # Attributed files should have keyAccess information + assert ( + "keyAccess" in result["manifest"] + or "encryptionInformation" in result["manifest"] + ) def _run_cli_inspect(tdf_path: Path, creds_file: Path) -> dict | None: From 4527c0276cbafa6c2c682628e2ef750be74eb44a Mon Sep 17 00:00:00 2001 From: b-long Date: Mon, 8 Sep 2025 16:17:36 -0400 Subject: [PATCH 12/12] chore: cleanup tests --- .../otdfctl_to_python/test_cli_decrypt.py | 141 ++++++------------ .../otdfctl_to_python/test_cli_inspect.py | 3 +- 2 files changed, 49 insertions(+), 95 deletions(-) diff --git a/tests/integration/otdfctl_to_python/test_cli_decrypt.py b/tests/integration/otdfctl_to_python/test_cli_decrypt.py index b5cc6f3..b1eedcc 100644 --- a/tests/integration/otdfctl_to_python/test_cli_decrypt.py +++ b/tests/integration/otdfctl_to_python/test_cli_decrypt.py @@ -76,57 +76,66 @@ def test_cli_decrypt_v4_2_2_vs_v4_3_1(all_target_mode_tdf_files, temp_credential @pytest.mark.integration -def test_cli_decrypt_different_file_types_v4_3_1( - tdf_v4_3_1_files, temp_credentials_file +def test_cli_decrypt_different_file_types( + all_target_mode_tdf_files, temp_credentials_file ): """ Test CLI decrypt with different file types. """ - file_types_to_test = [ - "text", - "binary", - "with_attributes", - ] # TODO: Consider adding "empty" file type as well + assert "v4.2.2" in all_target_mode_tdf_files + assert "v4.3.1" in all_target_mode_tdf_files - for file_type in file_types_to_test: - tdf_path = tdf_v4_3_1_files[file_type] + # Check each version has the expected file types + for version in ["v4.2.2", "v4.3.1"]: + tdf_files = all_target_mode_tdf_files[version] - # Decrypt the TDF - output_file = _run_cli_decrypt(tdf_path, temp_credentials_file) + file_types_to_test = [ + "text", + "binary", + "with_attributes", + ] # TODO: Consider adding "empty" file type as well - assert output_file is not None, f"Failed to decrypt {file_type} TDF" - assert output_file.exists(), f"{file_type} TDF decrypt output file not created" + for file_type in file_types_to_test: + tdf_path = tdf_files[file_type] - # Check file-type specific expectations - if file_type == "empty": - # Empty files should produce empty output files - assert output_file.stat().st_size == 0, ( - f"{file_type} TDF should produce empty output" - ) - else: - # Non-empty files should produce non-empty output - assert output_file.stat().st_size > 0, ( - f"{file_type} TDF produced empty decrypt output" - ) + # Decrypt the TDF + output_file = _run_cli_decrypt(tdf_path, temp_credentials_file) - # For attributed files, just ensure they decrypt successfully - if file_type == "with_attributes": - logger.info( - f"Successfully decrypted attributed TDF, output size: {output_file.stat().st_size}" + assert output_file is not None, f"Failed to decrypt {file_type} TDF" + assert output_file.exists(), ( + f"{file_type} TDF decrypt output file not created" ) - # For text files, verify the content is readable - if file_type == "text": - try: - content = output_file.read_text() - assert len(content) > 0, "Text file should have readable content" - logger.info(f"Text content preview: {content[:100]}...") - except UnicodeDecodeError: - pytest.fail(f"Decrypted {file_type} file should be valid text") - - # Clean up output file - output_file.unlink() + # Check file-type specific expectations + if file_type == "empty": + # Empty files should produce empty output files + assert output_file.stat().st_size == 0, ( + f"{file_type} TDF should produce empty output" + ) + else: + # Non-empty files should produce non-empty output + assert output_file.stat().st_size > 0, ( + f"{file_type} TDF produced empty decrypt output" + ) + + # For attributed files, just ensure they decrypt successfully + if file_type == "with_attributes": + logger.info( + f"Successfully decrypted attributed TDF, output size: {output_file.stat().st_size}" + ) + + # For text files, verify the content is readable + if file_type == "text": + try: + content = output_file.read_text() + assert len(content) > 0, "Text file should have readable content" + logger.info(f"Text content preview: {content[:100]}...") + except UnicodeDecodeError: + pytest.fail(f"Decrypted {file_type} file should be valid text") + + # Clean up output file + output_file.unlink() def _run_cli_decrypt(tdf_path: Path, creds_file: Path) -> Path | None: @@ -185,57 +194,3 @@ def _run_cli_decrypt(tdf_path: Path, creds_file: Path) -> Path | None: output_path.unlink() raise Exception(f"Failed to decrypt TDF {tdf_path}: {e}") from e - - -@pytest.mark.integration -def test_cli_decrypt_different_file_types_v4_2_2( - tdf_v4_2_2_files, temp_credentials_file -): - """ - Test CLI decrypt with different file types. - """ - - file_types_to_test = [ - "text", - "binary", - "with_attributes", - ] # TODO: Consider adding "empty" file type as well - - for file_type in file_types_to_test: - tdf_path = tdf_v4_2_2_files[file_type] - - # Decrypt the TDF - output_file = _run_cli_decrypt(tdf_path, temp_credentials_file) - - assert output_file is not None, f"Failed to decrypt {file_type} TDF" - assert output_file.exists(), f"{file_type} TDF decrypt output file not created" - - # Check file-type specific expectations - if file_type == "empty": - # Empty files should produce empty output files - assert output_file.stat().st_size == 0, ( - f"{file_type} TDF should produce empty output" - ) - else: - # Non-empty files should produce non-empty output - assert output_file.stat().st_size > 0, ( - f"{file_type} TDF produced empty decrypt output" - ) - - # For attributed files, just ensure they decrypt successfully - if file_type == "with_attributes": - logger.info( - f"Successfully decrypted attributed TDF, output size: {output_file.stat().st_size}" - ) - - # For text files, verify the content is readable - if file_type == "text": - try: - content = output_file.read_text() - assert len(content) > 0, "Text file should have readable content" - logger.info(f"Text content preview: {content[:100]}...") - except UnicodeDecodeError: - pytest.fail(f"Decrypted {file_type} file should be valid text") - - # Clean up output file - output_file.unlink() diff --git a/tests/integration/otdfctl_to_python/test_cli_inspect.py b/tests/integration/otdfctl_to_python/test_cli_inspect.py index 369d264..3acd0fb 100644 --- a/tests/integration/otdfctl_to_python/test_cli_inspect.py +++ b/tests/integration/otdfctl_to_python/test_cli_inspect.py @@ -87,12 +87,11 @@ def test_cli_inspect_v4_2_2_vs_v4_3_1(all_target_mode_tdf_files, temp_credential @pytest.mark.integration def test_cli_inspect_different_file_types( all_target_mode_tdf_files, - temp_credentials_file, # , request + temp_credentials_file, ): """ Test CLI inspect with different file types. """ - # tdf_files = request.getfixturevalue(tdf_files_fixture) assert "v4.2.2" in all_target_mode_tdf_files assert "v4.3.1" in all_target_mode_tdf_files