Skip to content

feat!: Major library refactor#32

Merged
gustavovalverde merged 35 commits intomainfrom
refactor-pyazul
May 26, 2025
Merged

feat!: Major library refactor#32
gustavovalverde merged 35 commits intomainfrom
refactor-pyazul

Conversation

@gustavovalverde
Copy link
Member

@gustavovalverde gustavovalverde commented May 26, 2025

Refactor: PyAzul Library Overhaul for version 2.0.0

This pull request marks a significant refactor of the pyazul library, elevating it to version 2.0.0. The changes span across the entire codebase, focusing on improving architecture, enhancing 3D Secure (3DS) capabilities, standardizing error handling, providing comprehensive documentation, revamping the testing framework, and modernizing development tooling.

Key Highlights:

🚀 Core Architecture & Design Enhancements:

  • PyAzul Facade as Primary Entry Point: Simplifies library interaction and is now consistently used in examples and internal logic.
  • Centralized AzulAPI Client: Improved management of API endpoints, authentication (now directly taking AzulSettings), and SSL contexts. An ADR explores future support for custom httpx.AsyncClient instances.
  • Service Layer Refinements: TransactionService, DataVaultService, and PaymentPageService have been updated for better clarity, method signatures, and defined responsibilities.
  • Pydantic Model Overhaul: Extensive refactoring of Pydantic models for robust request/response validation across all services. This includes centralized definitions, improved field validation (e.g., PaymentSchema for dynamic model instantiation based on TrxType), and specific models for 3DS flows.
  • Revamped Configuration (AzulSettings): Improved validation, direct PEM content support for certificates, and clearer documentation. Configuration is primarily loaded from .env files.
  • Standardized Error Handling: A comprehensive custom exception hierarchy (AzulError, APIError, AzulResponseError, SSLError, etc.) is now in place with clear usage guidelines and emphasis on exception chaining.

📚 Documentation & Developer Experience:

  • New Comprehensive Guides:
    • 3D Secure (3DS) Implementation Details
    • Overall Library Architecture
    • Configuration Management (.env, AzulSettings)
    • Error Handling Conventions and Best Practices
    • Pydantic Model Usage and Structure
    • Coding Standards, Linting, and Formatting (pyproject.toml based)
    • Python Best Practices (Docstrings, F-strings)
    • Test Structure and Best Practices
  • Architectural Decision Records (ADRs): Introduced to document key design decisions (e.g., testing strategy, custom HTTP client, synchronous API wrapper considerations).
  • Updated Examples: All example scripts now use the PyAzul facade and demonstrate new features, including the complete DataVault lifecycle and advanced 3DS payment flows (with a FastAPI example application).
  • Enhanced Docstrings: Improved code documentation throughout the library.

🧪 Testing Framework Overhaul:

  • Clear Separation of Unit & Integration Tests: Distinct directory structures and guidelines for improved organization and maintainability.
  • Robust Fixtures (conftest.py): Significantly updated with fixtures for AzulSettings, mocked API clients, and service instances.
  • Increased Test Coverage: Added numerous new tests for SSL certificate loading, DataVault operations, all transaction types (sale, hold, post, void, refund, verify), Payment Page generation, and comprehensive 3DS flow simulations (frictionless and challenge scenarios).
  • Test Utilities: New helpers for generating test order numbers and managing test card data.

🛠️ Tooling, CI/CD & Dependencies:

  • Modernized Linting & Formatting Stack: Updated configurations for pre-commit hooks, black, isort, flake8 (with flake8-docstrings), ruff, mypy, interrogate, yamllint, bandit, svgo, and markdownlint. All configurations increasingly centralized in pyproject.toml.
  • Trunk Integration: Updated trunk.yaml for current CLI, plugin, runtime, and linter versions.
  • CI/CD Pipeline Enhancements: Refined GitHub Actions workflows for tests, linting, and publishing, with specific path triggers and environment configurations.
  • Dependency Management: Updated requirements.txt and requirements-dev.txt. Package configuration now primarily managed via pyproject.toml (replacing setup.py).
  • .gitignore Updates: Expanded to better exclude common temporary and environment-specific files.

📄 License Change:

  • The library license has been changed from GNU Lesser General Public License (LGPL) to the MIT License.

🔢 Version Update:

  • The library version is now 2.0.0.

🐍 Python Version:

  • Standardized on Python 3.12 for development and CI workflows.

❗ Breaking Changes:

  • The introduction of the PyAzul facade as the primary interaction point might require changes in how the library is imported and used.
  • Service method signatures and initialization have been updated.
  • The structure of Pydantic models and some field names/validations may have changed.
  • Removal of utils.py and its helper functions (clean_amount, update_itbis).
  • The license change from LGPL to MIT.

Refactors the library to provide a comprehensive 3D Secure (3DS)
transaction flow accessible via the main `PyAzul` facade. This includes
methods for secure sales, token sales, holds, and handling 3DS method
and challenge callbacks.

Key changes:
- Introduces `PyAzul.secure_sale()`, `PyAzul.secure_token_sale()`,
  `PyAzul.secure_hold()`, `PyAzul.secure_3ds_method()`,
  `PyAzul.secure_challenge()`, and `PyAzul.create_challenge_form()`.
- Centralizes `AzulAPI` client instantiation within `PyAzul`. The shared
  client is now accessible via `PyAzul.api`.
- Services (`TransactionService`, `DataVaultService`, `SecureService`)
  are now initialized with this shared API client.
- Pydantic models are centralized under `pyazul.models`.
- Corrects the return type of `PyAzul.create_payment_page` to `str`.
- Improves error chaining (`raise ... from e`) in services and API client.

BREAKING CHANGE:
- Service constructors now require an `api_client` argument. Direct
  instantiation of services by users (not via `PyAzul`) will need adjustment.
Introduces a set of Cursor rule files (.mdc) to document key aspects
of the pyazul library and guide future development. These rules capture
decisions, best practices, and architectural overviews discussed during
the recent refactoring efforts.

The new rules cover:
- 3ds_secure_flow.mdc: Detailed guide on the 3D Secure implementation.
- architecture_overview.mdc: High-level structure of the library.
- coding_standards_and_linting.mdc: Guidelines for code quality.
- configuration_management.mdc: How settings and credentials are handled.
- error_handling_conventions.mdc: Best practices for custom exceptions.
Introduces an Architectural Decision Record (ADR) log in the
`docs/decisions` directory. This log will capture significant design
decisions, their context, and rationale.

This commit includes the initial set of ADRs:
- ADR-0001: Pluggable 3D Secure Session Management.
- ADR-0002: Optional Synchronous API Wrapper.
- ADR-0003: Advanced HTTP Client Customization.

Also adds a README.md to the `docs/decisions` directory explaining
the purpose and usage of ADRs in this project.
This commit introduces several improvements across configuration management,
the API client, and the Payment Page service, including more robust
validation and support for Azul's dual URL production architecture.

Key changes:

- **Configuration (`AzulSettings` & `BaseService`):**
    - Made core credential and URL fields (`AUTH1`, `AUTH2`, `MERCHANT_ID`,
      `AZUL_MERCHANT_ID`, `AZUL_AUTH_KEY`, `MERCHANT_NAME`, `MERCHANT_TYPE`,
      `DEV_URL`, `PROD_URL`) `Optional` at definition to better align with
      Pydantic's environment loading.
    - Introduced `ALT_PROD_URL_PAYMENT: Optional[str]` for user-configurable
      alternate Payment Page URLs.
    - Implemented a Pydantic `model_validator` (`_ensure_required_fields_are_set`)
      to:
        - Robustly enforce the presence of critical settings at runtime,
          raising `ValueError` if missing.
        - Conditionally require `DEV_URL` or `PROD_URL` based on the
          `ENVIRONMENT`.
        - Treat alternate URLs (`ALT_PROD_URL`, `ALT_PROD_URL_PAYMENT`)
          as optional overrides.
    - Resolved `AzulAPI` instantiation in `BaseService` by correctly
      passing the settings instance.

- **API Client (`AzulAPI`):**
    - Enhanced `_init_configuration` to prioritize user-defined
      `settings.ALT_PROD_URL` for `self.ALT_URL` before falling back to the
      `AzulEndpoints.ALT_PROD_URL` constant.
    - Replaced `assert` statements for `AUTH1` and `AUTH2` with explicit
      `ValueError` checks for improved runtime safety, addressing linter
      concerns about `assert` in optimized bytecode.

- **Constants (`AzulEndpoints`):**
    - Added `ALT_PROD_URL_PAYMEMT` constant for the alternate production
      Payment Page URL (`https://contpagos.azul.com.do/PaymentPage/Default.aspx`)
      based on official documentation.
    - Ensured `PROD_URL_PAYMEMT` path consistency.

- **Payment Page Service (`PaymentPageService`):**
    - Refactored to directly use an `AzulSettings` instance (removing the
      internal `AzulPageConfig` dataclass).
    - Added `use_alternate_url: bool` parameter to `create_payment_form`
      and `_get_base_url` to allow generation of forms targeting either
      primary or alternate production Payment Page URLs.
    - Alternate Payment Page URL is sourced from `settings.ALT_PROD_URL_PAYMENT`
      if available, else from the `AzulEndpoints.ALT_PROD_URL_PAYMEMT` constant.
    - Replaced `assert` statements for required payment page settings with
      explicit `ValueError` checks in `_generate_auth_hash`.
Translated various Spanish comments, docstrings, and user-facing
text within Python files (core library, examples, tests) and HTML
templates to English.

This change improves code readability and maintainability by ensuring
a consistent language throughout the project.

Key areas of translation:
- Python source files in `pyazul/` (docstrings, comments, string literals).
- Test files in `tests/` (docstrings, comments).
- HTML template files in `templates/` (user-facing text, comments).
…r 3DS flow

Adds new Pydantic models to better support the 3D Secure (3DS)
authentication process:

- `SecureSessionID`: Provides a validated structure for operations
  requiring only the 3DS session identifier (`secure_id`). This
  enhances internal consistency for multi-step 3DS interactions.

- `SecureChallengeRequest`: Models the data received from the Access
  Control Server (ACS) after a cardholder challenge, specifically
  the `secure_id` and the Challenge Response (`CRes`). This is essential
  for reliably processing the outcome of 3DS challenges.

These models improve clarity of data handling within the 3DS secure flow,
particularly for managing session state and processing challenge responses.
Both models are now exported via
`pyazul.models`.
- Add dedicated configuration files for linters and formatters:
  * .bandit: Security linting with B101 (assert) excluded for tests
  * .flake8: Style checking with 88 char line length (Black compatible)
  * .isort.cfg: Import sorting with Black profile
  * .pre-commit-config.yaml: Git hooks for automated code quality checks
  * pyproject.toml: Centralized Python project configuration
  * .trunk/trunk.yaml: Trunk.io linting orchestration

- Configure tool integration:
  * Black formatter with 88 character line length
  * isort with Black-compatible profile for import sorting
  * Flake8 with flake8-docstrings for style and documentation checks
  * Bandit for security vulnerability scanning
  * Interrogate for docstring coverage (70% threshold)
  * Pre-commit hooks for trailing whitespace, EOF, YAML/TOML validation

- Fix codebase-wide documentation issues:
  * Add missing module docstrings across all Python files
  * Correct docstring formatting to Google style convention
  * Fix imperative mood in method docstrings
  * Add proper line breaks between docstring summary and description
  * Ensure all docstrings end with periods

- Establish single source of truth for code style:
  * Line length: 88 characters (Black default)
  * Python version target: 3.12
  * Import style: Black-compatible grouping and ordering
  * Docstring convention: Google style with D100,D104,D107 ignored

This configuration ensures consistent code style across the project,
improves code quality through automated checks, and provides a
foundation for maintaining high standards in future development.
- **Facade Refinements (`PyAzul` in `pyazul/index.py`):**
    - Renamed `payment_page` service attribute to `payment_page_service` for clarity.
    - Standardized transaction method names:
        - `transaction.post_auth` to `transaction.post_sale`.
        - `transaction.token_sale` to `transaction.sale` (likely to consolidate sale operations).
    - Updated payment page method: `payment_page.generate_payment_page` to `payment_page_service.create_payment_form`.
    - Adjusted secure session access: `secure.get_session_info` to `secure.secure_sessions.get`.

- **Schema Enhancements (`pyazul/models/schemas.py`, `pyazul/models/secure.py`):**
    - General refactoring of Pydantic models for improved structure, validation logic, and overall maintainability. This includes updates to field definitions, validators, and helper functions within these schema files to ensure adherence to Azul documentation.

These changes contribute to a more consistent and understandable codebase, making future development and maintenance easier.
Replaces DataVaultCreateModel and DataVaultDeleteModel with a single
DataVaultRequestModel. This new model uses a `TrxType` field
(Literal["CREATE", "DELETE"]) to differentiate between creating and
deleting a DataVault token.

A Pydantic model_validator has been added to DataVaultRequestModel
to ensure that:
- CardNumber and Expiration are provided when TrxType is "CREATE".
- DataVaultToken is provided when TrxType is "DELETE".
- Irrelevant fields are not provided for the given TrxType.

The `examples/datavault_flow_example.py` has been updated to use
the new DataVaultRequestModel, demonstrating how to set the TrxType
and provide the correct payload for both create and delete operations.

This change simplifies the model structure for DataVault operations
and centralizes the logic for handling these two transaction types.
The explicit `TrxType` is now managed by the code constructing the
request model, aligning with the Pydantic Literal type requirements.
Introduces ADR-0004 which records the decision to make AzulAPI
fully responsible for selecting API endpoints (dev/prod, standard/secure)
and other settings-derived parameters.

This aims to simplify service dependencies by reducing their need to
directly access AzulSettings for API call mechanics, and centralizes
the logic for API communication within AzulAPI.
…ctor services

Key changes include:
- `AzulAPI` is now solely responsible for interpreting `AzulSettings` to
  determine API base URLs, authentication keys (standard vs. 3DS),
  and common request parameters (`Store`, `Channel`).
- Service Dependencies:
    - `TransactionService`, `DataVaultService`, and `SecureService` have
      been refactored to accept only an `api_client: AzulAPI` instance
      in their constructors. They no longer directly depend on `AzulSettings`
      for API call mechanics, accessing settings via `self.api.settings`
      when necessary for their internal logic.
    - `PaymentPageService` continues to use `AzulSettings` directly,
      which is appropriate for its HTML generation and auth hash logic.
- Naming Consistency: The attribute for the shared `AzulAPI` instance
  has been standardized to `self.api` across `BaseService` and all
  relevant service classes.
- `BaseService` (`pyazul/core/base.py`) constructor updated to accept
  `api_client` and use `self.api`.
- Model Usage: Updated `DataVaultService` and `PyAzul` facade to use the
  unified `DataVaultRequestModel` instead of separate `DataVaultCreateModel`
  and `DataVaultDeleteModel`, reflecting prior model consolidation.
- Example Relocation: The `token_secure_webapp.py` example application
  has been moved from `pyazul/` to the `examples/` directory.

These changes simplify service dependencies, improve separation of
concerns, and enhance maintainability by localizing API communication
logic within `AzulAPI`. The external API of the `PyAzul` facade
remains unchanged for end-users.
Reduces log verbosity in `pyazul.services.secure` by:
- Removing the `logging.basicConfig()` call, deferring log configuration to the application.
- Changing most `logger.info()` calls for process lifecycle events (e.g., starting a sale, initiating 3DS method/challenge, transaction approved) to `logger.debug()`. This makes these messages only visible when debug logging is explicitly enabled by the application.
- Changing `logger.info()` calls that logged full JSON request/response payloads to `logger.debug()`.
- For unexpected API responses, the warning about the event is kept at `logger.warning()`, but the full JSON payload dump is moved to `logger.debug()`.

This significantly quiets the library by default in production environments, only showing warnings, errors, or more severe messages unless debug logging is turned on. It also prevents potential logging of sensitive data in full JSON payloads at higher log levels.
Updates the README.md to reflect recent logging changes, clarify 3DS session management, Pydantic model usage, and improve overall guidance for implementers.

Refactors example scripts in the `examples/` directory to:
- Primarily use the `PyAzul` facade.
- Ensure all required data fields are present for model instantiation.
- Use consistent field names and placeholder values.
- Correctly demonstrate 3DS flow logic, including application-side session management for `secure_id` and `azul_order_id`.
- Remove library-level `logging.basicConfig()` calls.
- Address various linter errors and improve type hint compatibility.
This commit refactors README.md to improve clarity, structure, and reduce redundancy.

Key changes include:
- Clarified terminology: Consistently used "SDK" for PyAzul, "Azul API" for the external service, and "Facade" for the PyAzul class.
- Restructured content: Reorganized sections for a more logical flow, consolidating configuration, streamlining initialization examples, and better distinguishing usage examples from API references.
- Reduced redundancy: Minimized repeated information, especially regarding .env setup and Pydantic model usage.
- Improved 3DS guide: Enhanced the 3D Secure section with a clearer overview and more explicit notes on application-side session management.
- Cleaner examples: Removed transient and unnecessary comments from code snippets, retaining only those essential for understanding.
- Linting: Fixed trailing whitespace.
Generated from toptal.com/developers/gitignore.
This commit updates the project license from GNU Lesser General Public
License v3 (LGPLv3) to the MIT License.

The MIT License is chosen to allow commercial use of the software
while not granting explicit patent rights, aligning with the project's
licensing goals.

Changes include:
- Replacing the content of the LICENSE file with the MIT License text.
- Updating the license reference in README.md.
- Modifying the license metadata (text and classifier) in pyproject.toml.
This commit addresses several aspects of the PyAzul library, primarily focusing on DataVault functionality, example script robustness, and configuration clarity.

Key changes include:

DataVault & Models:
- Aligns DataVault token creation CVC policy with Azul API documentation:
    - Modifies `DataVaultRequestModel` validator to make CVC optional for "CREATE" operations, requiring only CardNumber and Expiration.
    - Updates the CVC field description in `DataVaultRequestModel` to "Optional for CREATE".
- Includes `Expiration` field (sourced from token creation response) in `token_sale_data` within `datavault_flow_example.py` as part of ongoing efforts to resolve a `VALIDATION_ERROR:Expiration` during token sales.
- Updates `TokenSaleModel` to include an optional `Expiration` field. (Note: The issue of this `Expiration` field sometimes not appearing in the final API payload for token sales, despite being present in the input dictionary, requires further investigation if it persists).

Examples & Testing:
- Modifies `datavault_flow_example.py` to test token creation without CVC by default, while explicitly adding CVC back for the subsequent token sale attempt for testing flexibility.
- Standardizes `OrderNumber` to use numeric strings and adds `ForceNo3DS="1"` in various examples (`post_example.py`, `refund_example.py`) for consistency and to bypass 3D Secure during tests not focused on 3DS.
- Enhances error handling and logging in `refund_example.py` for clearer test outcomes.
- Corrects template pathing in `secure_example.py` to use absolute paths, improving reliability.
- Fixes "APROBADA" vs "Approved" string comparisons in example scripts.

Configuration & Documentation:
- Significantly improves configuration documentation:
    - Restructures and clarifies `.env.copy` with detailed comments and logical grouping of settings.
This commit refines test suites and examples to align with recent refactoring of PyAzul models and services, and incorporates workarounds for observed Azul API behaviors, particularly for DataVault token sales.

- **Adapt Test Data to Model Changes**:
  Updated test fixtures and data instantiation across `test_datavault.py`, `test_hold.py`, `test_post.py`, `test_payment.py`, and `test_verify.py` to correctly use refactored Pydantic models. This includes:
    - Ensuring `Store` and `OrderNumber` are provided where required.
    - Removing fixed `TrxType` values from input dicts for models where it's a Literal default.
    - Correcting field names and data types (e.g., `SaveToDataVault` value, `ITBIS` casing in `secure.py`).

- **Address Azul API Quirk for Non-3DS Token Sales**:
  Implemented a workaround in `TransactionService.sale()` to conditionally add empty `CardNumber` and `Expiration` string fields to the payload for `TokenSaleModel` instances when `ForceNo3DS="1"`. This addresses an undocumented Azul test API behavior that otherwise leads to `VALIDATION_ERROR:Expiration`, even though documentation states these fields should not be sent.

- **Standardize Non-3DS Testing**:
  Consistently applied `ForceNo3DS="1"` in relevant tests (`test_datavault.py`, `test_payment.py`) and the `datavault_flow_example.py` for sale operations intended to be processed directly without 3D Secure. This ensures predictable API responses.

- **Resolve Validation Errors**:
  - Corrected `OrderNumber` length in `test_datavault.py` to meet the X(15) constraint, fixing `VALIDATION_ERROR:OrderNumber`.
  - The above API quirk workaround and `ForceNo3DS` usage resolved previous `VALIDATION_ERROR:Expiration` and `3D2METHOD` responses in token sale tests.

- **Schema Adjustments for Testability**:
  - `AzulBaseModel`: `Channel` and `Store` made `Optional` to better reflect their injection by `AzulAPI._prepare_request` in many test scenarios.
  - `TokenSaleModel`: Kept aligned with documentation (no `CardNumber`/`Expiration`); the API quirk is handled at the service level.

These changes ensure the test suite accurately reflects the intended SDK usage and robustly handles the nuances of the Azul test API, leading to all DataVault tests passing.
Refactors the Pydantic models in `pyazul/models/schemas.py` to establish a clearer inheritance hierarchy and reduce code duplication.

Key changes to schemas:
- Modified `AzulBaseModel` to be a leaner base, now containing a mandatory `Store: str` and a `Channel: str` (defaulting to "EC").
- Introduced `BaseTransactionAttributes(AzulBaseModel)` to hold attributes common to most full transaction types (e.g., `PosInputMode`, `OrderNumber`, `CustomOrderId`, `ForceNo3DS`).
- Introduced `CardPaymentAttributes(BaseTransactionAttributes)` to group fields and validators specific to card-based payments (e.g., `Amount`, `Itbis`, `CardNumber`, `Expiration`, `CVC`).
- Specific transaction models (`SaleTransactionModel`, `HoldTransactionModel`, `TokenSaleModel`, `RefundTransactionModel`, etc.) now inherit from these more appropriate base classes, simplifying their definitions.
- Models like `VerifyTransactionModel`, `PostSaleTransactionModel`, `DataVaultRequestModel`, and `VoidTransactionModel` now directly inherit `Store` and `Channel` from `AzulBaseModel` as mandatory fields.
- Updated `PaymentSchema` discriminator logic to align with the new model hierarchy.
- Adjusted `RefundTransactionModel` to set `AcquirerRefData = None` by default, resolving an API validation error for refund operations.

This refactoring makes the schema definitions more DRY, improves maintainability, and aligns field requirements more closely with observed API behavior and documentation.

Supporting changes:
- Updated `pyazul/api/client.py`:
    - `_prepare_request` now authoritatively sets `Channel` and `Store` from SDK settings for all outgoing requests, overriding any model-level values or defaults.
- Test adaptations:
    - Updated various test fixtures across `tests/services/` (e.g., in `test_payment.py`, `test_hold.py`, `test_post.py`, `test_verify.py`) to explicitly provide the `Store` field (and `Channel` where necessary) during model instantiation, complying with the new mandatory `Store` in `AzulBaseModel`.
    - Addressed minor linter/type-checker issues in test files arising from these changes.
- Documentation:
    - Created ADR `docs/decisions/0005-standardized-test-structure.md` outlining a plan to restructure tests into `unit/` and `integration/` directories and standardize testing practices.
    - Generated Cursor Rule `.cursor/rules/test_structure_guidelines.mdc` to guide future test development and AI assistance.

These changes enhance the robustness of the SDK's data models and prepare the groundwork for a more structured and maintainable test suite.
This commit implements Phase 1 and Phase 2 of ADR 0005, focusing on restructuring the test suite and standardizing integration tests.

Key changes include:

- Restructured test directories:
    - Created `tests/unit/` and `tests/integration/`
    - Moved existing service tests to `tests/integration/services/`
    - Renamed integration test files to `*_integration.py`
    - Identified and moved mock-based tests (`test_secure_*.py`, `test_token_secure_*.py`) to `tests/unit/services/` and renamed them to `*_unit.py`

- Standardized test configuration:
    - Ensured global `settings` fixture in `tests/conftest.py` is session-scoped.
    - Created `tests/integration/conftest.py` for centralized integration service fixtures (`TransactionService`, `DataVaultService`, `PaymentPageService`).
    - Refactored integration tests to use these centralized service fixtures.

- Standardized integration test data fixtures:
    - Ensured `Store` and `Channel` values are consistently sourced from the `settings` fixture.
    - Reordered keys in data fixture dictionaries for better readability, generally following Pydantic model field order.
    - Removed explicit setting of fields like `PosInputMode`, `AcquirerRefData`, and `SaveToDataVault` (where appropriate) to rely on Pydantic model defaults, simplifying fixtures.
    - Maintained explicit setting for test-specific values (e.g., `ForceNo3DS: "1"`, non-default `SaveToDataVault: "1"`).
    - Standardized comments within data fixtures to clarify usage of defaults vs. test-specific values.
Introduces centralized fixtures for test card data and order number generation

- Creates `tests/fixtures/cards.py` with a `TEST_CARDS` dictionary and `get_card`, `get_random_card` utility functions. This consolidates various test card definitions, including specific cards for 3DS scenarios.
- Creates `tests/fixtures/order.py` with a `generate_order_number` function to produce consistent, unique order IDs for tests.
- Refactors all integration tests in `tests/integration/services/` and unit tests in `tests/unit/services/` to utilize these new fixtures for card details and order number generation, replacing hardcoded values.
- Adjusts `asyncio.sleep` in `test_void_transaction` from 2 to 5 seconds to potentially mitigate timeout issues.
Refactors 3DS integration tests for clarity, correctness, and broader coverage of 3DS flows
Adds a suite of unit tests for core PyAzul functionalities, aligning with ADR 0005 for standardized test structure.

Key areas covered:
- `AzulSettings`: Configuration loading, default values, and custom validation logic, with robust isolation from environment interference.
- Custom Exceptions: Verification of `AzulError` hierarchy, including `APIError`, `AzulResponseError`, and `SSLError`, ensuring correct behavior and data storage.
- Pydantic Models: Validation tests for `CardHolderInfo` and `ThreeDSAuth` from `pyazul.models.secure`, checking field requirements and optionality. (Initially also included `pyazul.models.transaction` models, which are now covered under service tests).
- `AzulAPI` Client: Tests for initialization, endpoint construction, internal request preparation, and error handling mechanisms.
- `TransactionService`: Unit tests for `sale`, `refund`, `void`, `hold`, and `post_sale` methods, mocking API client interactions and verifying correct data marshalling and operation calls.
- `SecureService`: Leveraged existing unit tests that correctly mock dependencies and cover 3DS flow logic.

The new tests significantly improve code coverage and provide a solid foundation for verifying the correctness and reliability of the library's core components. Fixtures for mocking `AzulSettings` and `AzulAPI` have been added to `tests/unit/conftest.py` to promote DRY principles.
Consolidates optional dependencies into a single 'dev' group in pyproject.toml, removing 'testing', 'linting', and 'examples' groups. All non-core dependencies are now under 'dev'.

Updates all dependencies in pyproject.toml (core and dev) to their latest available versions based on web searches.

Removes setup.py as it's no longer necessary with modern setuptools and pyproject.toml handling all packaging and dependency metadata.

Deletes the old requirements.txt and adds new lock files:
- requirements.txt (for core dependencies)
- requirements-dev.txt (for core + dev dependencies)
Both generated using 'uv pip compile' to ensure reproducible environments.

Removes tests/conftest.py as it only contained logic to modify PYTHONPATH, which is generally not a good practice and should be handled by the test runner or environment setup.
- Introduced `tests.yaml` for dedicated unit testing.
- Introduced `ci.yaml` for integration testing.
- Introduced `linting.yaml` for dedicated code linting using super-linter.
- Updated `cd.yaml` (deployment workflow) with standardized concurrency
  controls, permissions, and trigger paths aligned with other CI workflows,
  ensuring it runs on relevant code changes.
@gustavovalverde gustavovalverde changed the title refactor: Major 3DS Enhancements, Documentation, and Core Refinements feat!: Major library refactor May 26, 2025
Removes DEV_URL, PROD_URL, ALT_PROD_URL, and ALT_PROD_URL_PAYMENT from .env.copy as they are optional and the SDK now consistently falls back to defaults in constants.py if these are not explicitly set via CUSTOM_URL or specific service logic.

The AzulSettings validator in core/config.py has been updated to no longer require DEV_URL and PROD_URL to be set if ENVIRONMENT is 'dev' or 'prod' respectively. This change reflects that the AzulAPI client uses URLs from AzulEndpoints in constants.py when CUSTOM_URL is not provided, making these specific settings fields redundant for default behavior.

This resolves an issue where CI pipelines would fail if DEV_URL was not set, even though a functional default exists in the constants. The .env.copy file is now cleaner and more accurately represents the minimal required configuration, while still allowing for overrides via CUSTOM_URL.

The configuration_management.mdc rule has also been updated to reflect these changes, removing references to the now-removed .env variables as key configuration items unless CUSTOM_URL is being used.
…tion

This commit addresses issues in certificate handling and improves the
robustness of configuration settings validation.

Key changes in `pyazul/core/config.py`:
- Corrected base64 decoding for certificate and key content by
  removing an erroneous `rstrip('%')` operation during decode.
  This fix applies to both the `is_base64` check and the `write_cert`
  function, preventing potential failures when processing valid
  base64 strings for `AZUL_CERT` and `AZUL_KEY`.
- Ensured proper handling of base64 encoded certificate content in
  `_load_certificates`. It now explicitly checks if `AZUL_CERT`
  (and by extension, `AZUL_KEY` through similar logic) is base64
  encoded and, if so, writes it to a temporary file using the
  corrected decoding path by calling `write_cert` with
  `is_base64_encoded=True`.
- Refactored the `_ensure_required_fields_are_set` model validator:
  - Switched to a more direct method of collecting missing required
    fields (AUTH1, AUTH2, MERCHANT_ID).
  - Made environment-specific URL checks (DEV_URL, PROD_URL)
    conditional on `CUSTOM_URL` not being set, providing more
    accurate validation and clearer error messages.
  - Reinstated checks to ensure `AZUL_CERT` and `AZUL_KEY` attributes
    are populated after the loading logic, confirming their
    availability for the API client.

CI Workflow Update:
- The `.github/workflows/ci.yaml` file was updated to include
  `MERCHANT_NAME: indexa` in the environment variables for CI runs,
  likely to provide a default value for tests or specific CI processes.
This commit refines the configuration settings validation in `AzulSettings`
and updates the corresponding unit tests to reflect the new logic,
addressing previous CI failures. Additionally, a default `MERCHANT_TYPE`
is added to the CI workflow.
@gustavovalverde gustavovalverde merged commit 81692d9 into main May 26, 2025
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant