diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..7c7c7905 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,9 @@ +# Copilot Instructions + +This repository is `mpt-api-python-client` — a Python API client for the SoftwareONE +Marketplace Platform API. + +Read `AGENTS.md` in the repository root for the full documentation index, reading order, +key commands, and project structure. + +Quick validation: `make check && make test` diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..e2c38161 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,63 @@ +# AGENTS.md + +This file is the AI assistant entry point for `mpt-api-python-client`. + +## Repository Purpose + +Python API client for the SoftwareONE Marketplace Platform (MPT) API. Provides synchronous +(`MPTClient`) and asynchronous (`AsyncMPTClient`) clients built on httpx, with typed +resource services, mixin-based HTTP operations, and an RQL query builder. + +## Documentation Reading Order + +1. `README.md` — project overview and quick start +2. `docs/architecture.md` — layered architecture, directory structure, key abstractions +3. `docs/testing.md` — test structure, tooling, conventions, how to run tests +4. `docs/contributing.md` — development workflow, coding conventions, linting setup +5. `docs/local-development.md` — Docker setup, Make targets, environment variables + +## Library Usage + +See `docs/PROJECT_DESCRIPTION.md` for installation and usage examples (sync and async). + +## API Reference + +The upstream MPT API is described by the OpenAPI spec: +https://api.s1.show/public/v1/openapi.json + +Use this to understand available endpoints, request/response schemas, and field names. + +## Key Commands + +| Command | Purpose | +|------------------|------------------------------------------| +| `make build` | Build the Docker development environment | +| `make test` | Run unit tests | +| `make check` | Run all linting and type checks | +| `make check-all` | Run checks + tests | +| `make format` | Auto-format code | + +## Project Structure + +```text +mpt_api_client/ +├── mpt_client.py # MPTClient / AsyncMPTClient entry points +├── http/ # HTTP transport, services, mixins +├── resources/ # API domain modules (catalog, commerce, billing, …) +├── models/ # Response model classes +├── rql/ # RQL query builder +└── exceptions.py # Error hierarchy +``` + +## Shared Standards + +This repository follows shared engineering standards from +[mpt-extension-skills](https://github.com/softwareone-platform/mpt-extension-skills): + +- `standards/python-style-guide.md` +- `standards/testing-standard.md` +- `standards/contributing-standard.md` +- `standards/pull-request-guidelines.md` + + + diff --git a/README.md b/README.md index 0fc1214f..f75890c7 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,79 @@ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=softwareone-platform_mpt-api-python-client&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=softwareone-platform_mpt-api-python-client) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=softwareone-platform_mpt-api-python-client&metric=coverage)](https://sonarcloud.io/summary/new_code?id=softwareone-platform_mpt-api-python-client) - [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) # mpt-api-python-client -A Python client for interacting with the MPT API. - -## Documentation - -📚 **[Complete Usage Guide](docs/PROJECT_DESCRIPTION.md)** - -## Getting started - -### Prerequisites - -- Docker and Docker Compose plugin (`docker compose` CLI) -- `make` -- [CodeRabbit CLI](https://www.coderabbit.ai/cli) (optional. Used for running review check locally) - -### Make targets overview - -Common development workflows are wrapped in the `Makefile`. Run `make help` to see the list of available commands. - -### How the Makefile works +Python API client for the SoftwareONE Marketplace Platform (MPT) API. -The project uses a modular Makefile structure that organizes commands into logical groups: -- **Main Makefile** (`Makefile`): Entry point that automatically includes all `.mk` files from the `make/` directory -- **Modular includes** (`make/*.mk`): Commands are organized by category: - - `common.mk` - Core development commands (build, test, format, etc.) - - `repo.mk` - Repository management and dependency commands - - `migrations.mk` - Database migration commands (Only available in extension repositories) - - `external_tools.mk` - Integration with external tools +Provides synchronous (`MPTClient`) and asynchronous (`AsyncMPTClient`) clients built on +[httpx](https://www.python-httpx.org/), with typed resource services, mixin-based HTTP +operations, and an RQL query builder. - -You can extend the Makefile with your own custom commands creating a `local.mk` file inside make folder. This file is -automatically ignored by git, so your personal commands won't affect other developers or appear in version control. - - -### Setup - -Follow these steps to set up the development environment: - -#### 1. Clone the repository +## Quick Start ```bash -git clone -``` -```bash -cd mpt-api-python-client +cp .env.sample .env # configure MPT_API_BASE_URL and MPT_API_TOKEN +make build +make test ``` -#### 2. Create environment configuration +## Usage -Copy the sample environment file and update it with your values: +**[Installation & Usage Guide](docs/PROJECT_DESCRIPTION.md)** -```bash -cp .env.sample .env -``` - -Edit the `.env` file with your actual configuration values. See the [Configuration](#configuration) section for details on available variables. - -#### 3. Build the Docker images +```python +from mpt_api_client import MPTClient -Build the development environment: +client = MPTClient() # reads MPT_API_TOKEN and MPT_API_BASE_URL from environment -```bash -make build +for product in client.catalog.products.iterate(): + print(product.name) ``` -This will create the Docker images with all required dependencies and the virtualenv. +### RQL Filtering Example -#### 4. Verify the setup - -Run the test suite to ensure everything is configured correctly: - -```bash -make test -``` +```python +from mpt_api_client import MPTClient, RQLQuery -You're now ready to start developing! See [Running the client](#running-the-client) for next steps. +client = MPTClient() +products = client.catalog.products +target_ids = RQLQuery("id").in_([ + "PRD-123-456", + "PRD-789-012", +]) +active = RQLQuery(status="active") +vendor = RQLQuery("vendor.name").eq("Microsoft") -## Running the client +query = target_ids & active & vendor -Before running, ensure your `.env` file is populated. - -```bash -make run +for product in products.filter(query).order_by("-audit.updated.at").select("id", "name").iterate(): + print(product.id, product.name) ``` -## Developer utilities +## Documentation -Useful helper targets during development: +| Document | Description | +|----------------------------------------------------------------|-------------------------------------------------------------| +| [Architecture](docs/architecture.md) | Layered architecture, directory structure, key abstractions | +| [RQL Guide](docs/rql.md) | Fluent builder for Resource Query Language filters | +| [Contributing](docs/contributing.md) | Development workflow, coding conventions, linting setup | +| [Testing](docs/testing.md) | Test structure, tooling, conventions | +| [Local Development](docs/local-development.md) | Docker setup, Make targets, environment variables | +| [Usage Guide](docs/PROJECT_DESCRIPTION.md) | Installation, sync and async usage examples | +| [MPT OpenAPI Spec](https://api.s1.show/public/v1/openapi.json) | Upstream API contract (endpoints, schemas) | + +## Key Commands ```bash -make bash # open a bash shell in the app container -make check # run ruff, flake8, and lockfile checks -make check-all # run checks and tests -make format # auto-format code and imports -make review # check the code in the cli by running CodeRabbit +make build # build Docker development environment +make test # run unit tests +make check # run all quality checks (ruff, flake8, mypy) +make check-all # run checks + tests +make format # auto-format code +make bash # open a shell in the container +make run # start an IPython session ``` -## Configuration - -The following environment variables are typically set in `.env`. Docker Compose reads them when using the Make targets described above. - -### Application - -| Environment Variable | Default | Example | Description | -|---------------------------------|---------|-------------------------------------------|-------------------------------------------------------------------------------------------| -| `MPT_API_BASE_URL` | - | `https://portal.softwareone.com/mpt` | SoftwareONE Marketplace API URL | -| `MPT_API_TOKEN` | - | eyJhbGciOiJSUzI1N... | SoftwareONE Marketplace API Token | - -### E2E - -| Environment Variable | Default | Example | Description | -|----------------------------|---------|--------------------------------------|----------------------------------------------| -| `MPT_API_TOKEN_CLIENT` | - | eyJhbGciOiJSUzI1N... | SoftwareONE Marketplace API Client Token | -| `MPT_API_TOKEN_OPERATIONS` | - | eyJhbGciOiJSUzI1N... | SoftwareONE Marketplace API Operations Token | -| `MPT_API_TOKEN_VENDOR` | - | eyJhbGciOiJSUzI1N... | SoftwareONE Marketplace API Vendor Token | -| `RP_API_KEY` | - | pytest_XXXXXXXXXXXXXX | ReportPortal API key | -| `RP_ENDPOINT` | - | `https://reportportal.example.com` | ReportPortal endpoint | -| `RP_LAUNCH` | - | `dev-env` | ReportPortal launch | +Run `make help` to see all available commands. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 00000000..ec5b2b91 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,188 @@ +# Architecture + +This document describes the internal architecture of `mpt-api-python-client`. + +## Overview + +`mpt-api-python-client` is a Python API client that provides a typed, fluent interface for the +SoftwareONE Marketplace Platform (MPT) REST API. It supports both synchronous and asynchronous +usage and is built on top of [httpx](https://www.python-httpx.org/). + +**API Reference:** The full upstream API contract is described by the +[MPT OpenAPI Spec](https://api.s1.show/public/v1/openapi.json). +The client mirrors this spec's resource structure. + +The client exposes every MPT API domain (catalog, commerce, billing, etc.) as a resource group, +where each resource is a service object composed from reusable HTTP operation mixins. + +## Directory Structure + +```text +mpt_api_client/ +├── __init__.py # Public API: MPTClient, AsyncMPTClient, RQLQuery +├── mpt_client.py # Client entry points +├── constants.py # Shared constants (content types) +├── exceptions.py # Error hierarchy (MPTError, MPTHttpError, MPTAPIError) +│ +├── http/ # HTTP transport layer +│ ├── client.py # Sync HTTPClient (httpx.Client) +│ ├── async_client.py # Async AsyncHTTPClient (httpx.AsyncClient) +│ ├── base_service.py # ServiceBase — shared service logic +│ ├── service.py # Service — sync service (extends ServiceBase) +│ ├── async_service.py # AsyncService — async service (extends ServiceBase) +│ ├── query_state.py # Query parameter accumulation +│ ├── client_utils.py # URL validation helpers +│ ├── types.py # Type aliases (Response, HeaderTypes, etc.) +│ └── mixins/ # Composable HTTP operation mixins +│ ├── collection_mixin.py +│ ├── create_mixin.py +│ ├── create_file_mixin.py +│ ├── update_mixin.py +│ ├── update_file_mixin.py +│ ├── delete_mixin.py +│ ├── get_mixin.py +│ ├── enable_mixin.py +│ ├── disable_mixin.py +│ ├── download_file_mixin.py +│ ├── file_operations_mixin.py +│ ├── queryable_mixin.py +│ └── resource_mixins.py +│ +├── models/ # Response models +│ ├── model.py # Model base class (camelCase ↔ snake_case mapping) +│ ├── collection.py # Collection[Model] — paginated result set +│ ├── meta.py # Meta / Pagination metadata +│ └── file_model.py # FileModel for binary responses +│ +├── resources/ # API domain modules +│ ├── accounts/ # Account, Users, Buyers, Sellers, API Tokens, … +│ ├── audit/ # Audit records, Event types +│ ├── billing/ # Invoices, Ledgers, Journals, Statements, Credit memos, … +│ ├── catalog/ # Products, Listings, Price lists, Authorizations, … +│ ├── commerce/ # Agreements, Orders, Subscriptions, Assets +│ ├── helpdesk/ # Cases, Chats, Queues, Forms, … +│ └── notifications/ # Messages, Batches, Subscribers, … +│ +└── rql/ # RQL query builder + ├── query_builder.py # RQLQuery, RQLProperty, RQLValue + └── constants.py # RQL operator constants +``` + +## Layered Architecture + +The client is organized into four layers: + +```text +┌─────────────────────────────────────────────┐ +│ MPTClient / AsyncMPTClient │ Entry point +├─────────────────────────────────────────────┤ +│ Resource Groups (domains) │ catalog, commerce, billing, … +├─────────────────────────────────────────────┤ +│ Service + Mixins (HTTP operations) │ get, create, update, delete, iterate, … +├─────────────────────────────────────────────┤ +│ HTTPClient / AsyncHTTPClient │ httpx transport +└─────────────────────────────────────────────┘ +``` + +### 1. Client Layer — `mpt_client.py` + +`MPTClient` (sync) and `AsyncMPTClient` (async) are the public entry points. + +Each client holds an HTTP client instance and exposes domain-specific resource groups as +properties: + +```python +client = MPTClient.from_config(api_token="...", base_url="...") +client.catalog # Catalog +client.commerce # Commerce +client.billing # Billing +client.accounts # Accounts +client.audit # Audit +client.helpdesk # Helpdesk +client.notifications # Notifications +``` + +### 2. Resource Groups — `resources/` + +Each resource group (e.g. `Catalog`, `Commerce`) is a plain class that groups related service +objects. For example, `Catalog` exposes `products`, `listings`, `price_lists`, +`authorizations`, `pricing_policies`, `items`, and `units_of_measure`. + +Resource groups pass the HTTP client down to each service. + +### 3. Service Layer — `http/service.py`, `http/async_service.py` + +`Service` and `AsyncService` extend `ServiceBase` and represent a single REST resource +endpoint (e.g. `/catalog/products`). + +Services are composed using **mixins** that add HTTP operations: + +| Mixin | Operation | +|---|---| +| `CollectionMixin` | `iterate()` — paginated listing | +| `GetMixin` | `get(id)` — retrieve single resource | +| `CreateMixin` | `create(data)` — create resource | +| `UpdateMixin` | `update(id, data)` — update resource | +| `DeleteMixin` | `delete(id)` — delete resource | +| `CreateFileMixin` | create with file upload | +| `UpdateFileMixin` | update with file upload | +| `DownloadFileMixin` | download binary content | +| `EnableMixin` / `DisableMixin` | enable/disable actions | +| `QueryableMixin` | `filter()`, `order_by()`, `select()` — RQL query chaining | + +Example service definition: + +```python +class ProductsService( + Service[Model], + CollectionMixin, + GetMixin, + CreateFileMixin, + UpdateFileMixin, + DeleteMixin, +): + _endpoint = "/catalog/products" + _model_class = Model +``` + +### 4. HTTP Transport — `http/client.py`, `http/async_client.py` + +`HTTPClient` and `AsyncHTTPClient` wrap `httpx.Client` / `httpx.AsyncClient` with: + +- automatic Bearer token authentication +- base URL resolution +- retry transport (configurable) +- error transformation into `MPTHttpError` / `MPTAPIError` +- multipart file upload support + +Configuration is read from constructor arguments or environment variables +(`MPT_API_TOKEN`, `MPT_API_BASE_URL`). + +## Cross-Cutting Concerns + +### RQL Query Builder — `rql/` + +See [the RQL guide](rql.md) for the fluent query builder, filter chaining, and usage examples. + +### Model Layer — `models/` + +`Model` is a lightweight base class that: + +- converts API responses from `camelCase` to `snake_case` attribute access +- supports nested model parsing +- provides `to_dict()` serialization back to `camelCase` + +`Collection[Model]` wraps paginated API responses with metadata (`Meta`, `Pagination`). + +### Error Handling — `exceptions.py` + +All API errors are wrapped in a hierarchy: + +```text +MPTError +├── MPTHttpError # generic HTTP error (status_code, message, body) +│ └── MPTAPIError # structured API error (payload, title, detail, trace_id) +``` + + + diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 00000000..469bbec6 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,15 @@ +# Contributing + +This document describes the development workflow and coding conventions for +`mpt-api-python-client`. + +- [Package architecture](./architecture.md) +- [Python coding standards](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/python-coding.md) +- [Extensions best practices](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/extensions-best-practices.md) +- [Packages and dependencies](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/packages-and-dependencies.md) +- [Testing](./testing.md) + - [Unit tests](./unit_tests.md) + - [E2E tests](./e2e_tests.md) +- [Pull requests](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/pull-requests.md) +- [Makefiles](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/makefiles.md) + - [Makefile targets](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/knowledge/make-targets.md) diff --git a/docs/e2e_tests.md b/docs/e2e_tests.md new file mode 100644 index 00000000..91608a3d --- /dev/null +++ b/docs/e2e_tests.md @@ -0,0 +1,56 @@ +# End-to-End Tests + +End-to-end tests exercise the running MPT API and cover the full request/response lifecycle. +They live under `tests/e2e/` and rely on live credentials and configurable endpoints. + +## Directory Layout + +```text +tests/ +└── e2e/ + ├── conftest.py # E2E fixtures (mpt_vendor, mpt_client, mpt_operations) + ├── accounts/ + ├── audit/ + ├── billing/ + ├── catalog/ + ├── commerce/ + ├── helpdesk/ + └── notifications/ +``` + +## Running Tests + +```bash +make test args="tests/e2e" # run the E2E suite +make test args="tests/e2e/catalog" # run a subset of E2E tests +``` + +E2E suites use `pytest` markers and live API credentials, so they run outside the default +`make test` invocation. + +## Environment Variables + +| Variable | Description | +|----------------------------|----------------------| +| `MPT_API_BASE_URL` | MPT API base URL | +| `MPT_API_TOKEN_VENDOR` | Vendor API token | +| `MPT_API_TOKEN_CLIENT` | Client API token | +| `MPT_API_TOKEN_OPERATIONS` | Operations API token | + +### Optional ReportPortal Integration + +| Variable | Description | +|---------------|---------------------------| +| `RP_API_KEY` | ReportPortal API key | +| `RP_ENDPOINT` | ReportPortal endpoint URL | +| `RP_LAUNCH` | ReportPortal launch name | + +## Configuration + +E2E test configuration lives in `e2e_config.test.json`. +Set the required environment variables before invoking the suite to avoid credential +validation failures. + +Test results are published to [Report Portal](https://report-portal.s1.team/). + + diff --git a/docs/local-development.md b/docs/local-development.md new file mode 100644 index 00000000..7f7ce449 --- /dev/null +++ b/docs/local-development.md @@ -0,0 +1,107 @@ +# Local Development + +This document describes how to set up and run `mpt-api-python-client` locally. + +Make sure you have read the [Contributing guidelines](./contributing.md) + +## Prerequisites + +- **Docker** and **Docker Compose** plugin (`docker compose` CLI) +- **Make** +- [CodeRabbit CLI](https://www.coderabbit.ai/cli) (optional — used for running review checks locally) + +## Setup + +### 1. Clone the repository + +```bash +git clone +cd mpt-api-python-client +``` + +### 2. Create environment configuration + +```bash +cp .env.sample .env +``` + +Edit `.env` with your actual values. See [Environment Variables](#environment-variables) below. + +### 3. Build the Docker images + +```bash +make build +``` + +This creates the Docker images with all required dependencies and the virtual environment. + +### 4. Verify the setup + +```bash +make test +``` + +## Running the Client + +Start an interactive IPython session with the client available: + +```bash +make run +``` + +Ensure your `.env` file is populated with valid `MPT_API_BASE_URL` and `MPT_API_TOKEN` values. + +## Environment Variables + +### Application + +| Variable | Default | Example | Description | +|--------------------|---------|--------------------------------------|-----------------------------------| +| `MPT_API_BASE_URL` | — | `https://portal.softwareone.com/mpt` | SoftwareONE Marketplace API URL | +| `MPT_API_TOKEN` | — | `eyJhbGciOiJSUzI1N...` | SoftwareONE Marketplace API Token | + +### End-to-End Testing + +| Variable | Default | Example | Description | +|----------------------------|---------|------------------------------------|--------------------------| +| `MPT_API_TOKEN_CLIENT` | — | `eyJhbGciOiJSUzI1N...` | Client API token | +| `MPT_API_TOKEN_OPERATIONS` | — | `eyJhbGciOiJSUzI1N...` | Operations API token | +| `MPT_API_TOKEN_VENDOR` | — | `eyJhbGciOiJSUzI1N...` | Vendor API token | +| `RP_API_KEY` | — | `pytest_XXXX` | ReportPortal API key | +| `RP_ENDPOINT` | — | `https://reportportal.example.com` | ReportPortal endpoint | +| `RP_LAUNCH` | — | `dev-env` | ReportPortal launch name | + +## Docker + +The development environment runs entirely inside Docker: + +- **Base image**: `ghcr.io/astral-sh/uv:python3.12-bookworm-slim` +- **Package manager**: [uv](https://docs.astral.sh/uv/) +- **Services**: defined in `compose.yaml` — a single `app` service that mounts the project directory. + +## Make Targets + +Common development workflows are wrapped in the Makefile. Run `make help` to see all available +commands. + +[Read make targets for additional information](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/knowledge/make-targets.md) + +### How the Makefile Works + +[Read the makefile architecture for python repositories](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/makefiles.md) + +# Additional Resources +- [Package architecture](./architecture.md) +- [Python coding standards](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/python-coding.md) +- [Extensions best practices](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/extensions-best-practices.md) +- [Packages and dependencies](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/packages-and-dependencies.md) +- [Testing](./testing.md) + - [Unit tests](./unit_tests.md) + - [E2E tests](./e2e_tests.md) +- [Pull requests](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/pull-requests.md) +- [Makefiles](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/makefiles.md) + - [Makefile targets](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/knowledge/make-targets.md) + + + + diff --git a/docs/rql.md b/docs/rql.md new file mode 100644 index 00000000..92881b56 --- /dev/null +++ b/docs/rql.md @@ -0,0 +1,77 @@ +# RQL Query Builder + +`RQLQuery` is a fluent, type-safe builder for [Resource Query Language](https://doc.mpt.softwareone.com) +(RQL) filter expressions. RQL is used across the MPT API to express filters, sorting, and +field selection in a single, composable query string that the service mixins understand. + +## Builder Usage + +```python +from mpt_api_client import RQLQuery + +query = RQLQuery(status="active", product__id="PRD-123") +``` + +Instantiate `RQLQuery` with keyword arguments that mimic the API field names (use `__` to +nest properties). The builder instance can then be passed to service mixins or chained +via `QueryableMixin` helpers. + +## QueryableMixin Integration + +Service mixins such as `QueryableMixin` expose methods like `filter()`, `order_by()`, and +`select()` which accept `RQLQuery` instances. Each call returns a new `QueryableMixin` that +appends filters immutably, allowing expression composition without shared mutation. + +## Composing Multiple Filters + +You can build complex predicates by joining queries with `&` (AND), `|` (OR), and `~` (NOT): + +```python +from mpt_api_client import MPTClient, RQLQuery + +client = MPTClient() +products = client.catalog.products + +target_ids = RQLQuery("id").in_([ + "PRD-123-456", + "PRD-789-012", +]) +active = RQLQuery(status="active") +vendor = RQLQuery("vendor.name").eq("Microsoft") + +query = target_ids & active & vendor + +result = products.filter(query).order_by("-audit.updated.at").select("id", "name") +for product in result.iterate(): + print(product.id, product.name) +``` + +You can mix AND and OR to widen the match set while keeping base filters applied: + +```python +base = RQLQuery(status="active") +cheap = RQLQuery("price.amount").lt(50) +featured = RQLQuery("tags").in_(["featured", "bundle"]) + +query = base & (cheap | featured) + +filtered = products.filter(query) +``` + +Filters stay immutable: repeated `filter()` calls stack with AND by default. + +```python +recent = RQLQuery("updated_at").ge("2024-01-01") +has_docs = RQLQuery("documents.id").in_(["DOC-123", "DOC-456"]) + +stacked = products.filter(recent).filter(has_docs) + +combined = recent & has_docs +assert str(stacked.query_state.filter) == str(combined) +``` + + + + + + diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 00000000..880b8ec4 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,30 @@ +# Testing + +This document provides a high-level overview of the testing strategy for +`mpt-api-python-client` and points to the detailed guides for each test type. + +## Guides + +- [Unit test guide](unit_tests.md) — structure, conventions, tooling, and examples. +- [E2E test guide](e2e_tests.md) — directory layout, execution instructions, and environment setup. + +For a quick validation run: `make check && make test`. + +## Coverage + +Coverage is collected automatically via `pytest-cov` with the following defaults: + +- Source: `mpt_api_client/` +- Reports: terminal (missing lines) + XML (`coverage.xml`) +- Branch coverage enabled +- `__init__.py` files excluded + +Results are reported to SonarCloud via `sonar-project.properties`. + +## Shared Standards + +This repository follows the shared testing standard from +[mpt-extension-skills](https://github.com/softwareone-platform/mpt-extension-skills): + +- `standards/testing-standard.md` + diff --git a/docs/unit_tests.md b/docs/unit_tests.md new file mode 100644 index 00000000..f191d2e3 --- /dev/null +++ b/docs/unit_tests.md @@ -0,0 +1,39 @@ +# Unit Tests + +This guide covers the structure, tooling, and conventions for the unit test suite in +`mpt-api-python-client`. + +## Directory Layout + +```text +tests/ +└── unit/ + ├── conftest.py # Shared fixtures (http_client, async_http_client, DummyModel) + ├── http/ # Transport, services, and mixins + ├── models/ # Model, Collection, and Meta behavior + ├── resources/ # Resource-domain services (accounts, catalog, …) + ├── rql/ # RQL query builder + ├── test_constants.py + ├── test_exceptions.py + └── test_mpt_client.py +``` + +## Running Tests + +All commands run inside the Docker-based Makefile drivers. + +```bash +make test # run the full unit suite +make test args="tests/unit/http" # run a specific directory +make test args="-k test_create" # run a specific test pattern +``` + +## Unit tests general rules + +[Python Unit test general rules with examples](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/unittests.md#general-rules) + +## Unit tests mocking + +[Unit tests mocks wiht examples](https://github.com/softwareone-platform/mpt-extension-skills/blob/main/standards/unittests.md#mocking-rules) + +