Files
cleveractors-core/features/registry_http_client.feature
CoreRasurae e8bd348c77
CI / lint (pull_request) Successful in 41s
CI / typecheck (pull_request) Successful in 56s
CI / security (pull_request) Successful in 51s
CI / quality (pull_request) Successful in 53s
CI / integration_tests (pull_request) Successful in 1m10s
CI / build (pull_request) Successful in 43s
CI / unit_tests (pull_request) Successful in 3m30s
CI / coverage (pull_request) Successful in 3m22s
CI / status-check (pull_request) Successful in 4s
CI / quality (push) Successful in 45s
CI / security (push) Successful in 48s
CI / lint (push) Successful in 49s
CI / typecheck (push) Successful in 50s
CI / build (push) Successful in 49s
CI / integration_tests (push) Successful in 1m9s
CI / unit_tests (push) Successful in 3m13s
CI / coverage (push) Successful in 3m7s
CI / status-check (push) Successful in 3s
feat(registry): implement RegistryError hierarchy with typed exceptions
Add RegistryError base with message, details, and original_reference fields.
Implement all 9 typed exception types per Package Registry Standard §13.2:
PackageNotFoundError, InvalidPackageIdError, InvalidPackageReferenceError,
VersionNotFoundError, ValidationError, AuthenticationRequiredError,
AccessDeniedError, ConflictError, and RegistryNetworkError.

RegistryNetworkError carries status_code and url for transport failures.
__str__ includes original_reference when available.
exception_for_status() maps HTTP codes to typed exceptions.
_ERROR_TYPE_MAP enables error-type parsing from JSON error bodies.

23 Behave BDD scenarios in features/registry_exceptions.feature.
12 Robot Framework integration tests in robot/exceptions.robot.
Existing registry_http_client tests updated for InternalServerError removal.

ISSUES CLOSED: #29
2026-06-11 20:44:54 +01:00

236 lines
14 KiB
Gherkin

Feature: Registry HTTP Client
As a developer using the Package Registry Standard v1.0.0,
I want an async HTTP client that can retrieve packages, resolve references,
browse published packages, and discover registry metadata,
so that I can interact with any compliant registry server.
Background:
Given a clean test environment for registry HTTP client
# ── Client Initialisation ────────────────────────────────────────────
Scenario: Create client with base URL
When I create a RegistryClient pointed at "https://registry.example.com"
Then the client base URL should be "https://registry.example.com"
Scenario: Create client with optional API key
When I create a RegistryClient with base URL "https://registry.example.com" and API key "secret-token"
Then the client should have the API key "secret-token"
Scenario: Create client with empty base URL raises ValueError
When I try to create a RegistryClient with empty base URL
Then a ValueError should be raised by the registry client
# ── GET /packages/{package_id} (§8.2.1) ──────────────────────────────
Scenario: Fetch package by valid PackageId returns YAML content
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 200 with body '{"content": "name: test-pkg\ntype: actor"}'
When I call get_package with package_id "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef"
Then the registry result body equals '{"content": "name: test-pkg\ntype: actor"}'
Scenario: Fetch package returns 404 raises PackageNotFoundError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 404 with body '{"message": "Package not found"}'
When I try to call get_package with package_id "pkg_act_deadbeef000000000000000000000000000000"
Then a PackageNotFoundError should be raised
Scenario: Fetch package returns 400 raises InvalidPackageIdError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 400 with body '{"message": "Invalid Package ID"}'
When I try to call get_package with package_id "invalid_id"
Then an InvalidPackageIdError should be raised
# ── GET /{type}/{ns}/{name}?version= (§8.2.2) ────────────────────────
Scenario: Resolve package with concrete version
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 200 with body '{"package_id": "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef", "type": "actor"}'
When I call resolve_package with type "actor", namespace "example", name "my-actor", version "v1.0.0"
Then the resolved package_id should be "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef"
Scenario: Resolve package with latest alias
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 200 with body '{"package_id": "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef", "type": "actor"}'
When I call resolve_package with type "actor", namespace "example", name "my-actor" and version "latest"
Then the resolved package_id should be "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef"
Scenario: Resolve package without version defaults to latest
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 200 with body '{"package_id": "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef", "type": "actor"}'
When I call resolve_package with type "actor", namespace "example", name "my-actor" without version
Then the resolved package_id should be "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef"
Scenario: Resolve package returns 404 raises PackageNotFoundError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 404 with body '{"message": "Not found"}'
When I try to call resolve_package with type "actor", namespace "example", name "missing", version "v1.0.0"
Then a PackageNotFoundError should be raised
# ── GET /browse (§8.4.1) ─────────────────────────────────────────────
Scenario: Browse all packages without filters
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 200 with body '{"packages": [], "count": 0}'
When I call browse without filters
Then the result should have "packages" key and "count" key
Scenario: Browse packages filtered by type
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 200 with body '{"packages": [{"id": "pkg_act_abc", "type": "actor", "name": "test"}], "count": 1}'
When I call browse with type "actor"
Then the result count should be 1
Scenario: Browse packages filtered by namespace
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 200 with body '{"packages": [], "count": 0}'
When I call browse with namespace "example"
Then the result count should be 0
# ── GET /.well-known/cleverthis-packages (§8.4.2) ────────────────────
Scenario: Discover registry metadata
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 200 with body '{"name": "Test Registry", "version": "1.0.0", "supported_types": ["actor"], "authentication": ["anonymous"], "features": ["semantic_versioning"]}'
When I call discover
Then the result should have key "name" with value "Test Registry"
Then the result should have key "supported_types"
# ── Network errors ───────────────────────────────────────────────────
Scenario: Connection timeout raises RegistryNetworkError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server raises a timeout error
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then a RegistryNetworkError should be raised
And the raised error should have url None
# ── Error mapping (§13.2) ─────────────────────────────────────────────
Scenario: Response with null message preserves default HTTP status message
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 500 with body '{"message": null}'
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then a RegistryNetworkError should be raised
And the error message should be "HTTP 500"
Scenario: 401 response raises AuthenticationRequiredError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 401 with body '{"message": "Authentication required"}'
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then an AuthenticationRequiredError should be raised
Scenario: 403 response raises AccessDeniedError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 403 with body '{"message": "Access denied"}'
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then an AccessDeniedError should be raised
Scenario: 409 response raises ConflictError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 409 with body '{"message": "Package ID conflict"}'
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then a ConflictError should be raised
Scenario: 500 response raises RegistryNetworkError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 500 with body '{"message": "Internal error"}'
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then a RegistryNetworkError should be raised
And the raised error should have status_code 500
And the raised error should have url set
Scenario: 503 response raises RegistryNetworkError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 503 with body '{"message": "Service unavailable"}'
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then a RegistryNetworkError should be raised
And the raised error should have status_code 503
And the raised error should have url set
Scenario: Unknown error status falls back to RegistryError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 418 with non-JSON body "plain text error"
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then a RegistryError should be raised
Scenario: Error response with non-JSON body maps by status
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 400 with non-JSON body "plain text error"
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then an InvalidPackageIdError should be raised
# ── Error type parsing from body (§13.1) ───────────────────────────────
Scenario: 404 with VersionNotFound error type in body raises VersionNotFoundError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 404 with body '{"error": {"type": "VersionNotFound", "message": "Version v99.0.0 not found"}}'
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then a VersionNotFoundError should be raised
Scenario: Error response with details propagates details to exception
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 404 with body '{"error": {"type": "PackageNotFound", "message": "not found", "details": {"package_id": "pkg_abc"}}}'
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then a PackageNotFoundError should be raised
And the error details should equal {"package_id": "pkg_abc"}
Scenario: 400 with ValidationError error type in body raises ValidationError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 400 with body '{"error": {"type": "ValidationError", "message": "Package validation failed"}}'
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then a ValidationError should be raised
Scenario: 404 without error type in body falls back to PackageNotFoundError
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 404 with body '{"message": "Package not found"}'
When I try to call get_package with package_id "pkg_act_test00000000000000000000000000000000000"
Then a PackageNotFoundError should be raised
Scenario: Client with API key includes authorization header
When I create an authenticated RegistryClient pointed at "https://registry.example.com" with API key "my-api-key"
When the mock server returns status 200 with body '{"content": "ok"}'
When I call get_package with API key client and package_id "pkg_act_a1b2c3d4e5f67890abcdef1234567890abcdef" capturing headers
Then the registry result body equals '{"content": "ok"}'
And the Authorization header should be "Bearer my-api-key"
# ── All package type prefixes ────────────────────────────────────────
Scenario Outline: Resolve supports all package types
When I create a RegistryClient pointed at "https://registry.example.com"
When the mock server returns status 200 with body '{"package_id": "pkg_act_test00000000000000000000000000000000000", "type": "actor"}'
When I call resolve_package with type "<type>", namespace "example", name "test", version "v1.0.0"
Then the resolved package_id should be "pkg_act_test00000000000000000000000000000000000"
Examples:
| type |
| actor |
| graph |
| stream |
| agent |
| template |
| skill |
| mcp |
| lsp |
# ── Context manager ──────────────────────────────────────────────────
Scenario: Client can be used as async context manager
When I create a RegistryClient pointed at "https://registry.example.com"
When I use the client as an async context manager
Then the client should be closed after the context block
# ── HTTPS enforcement (spec §12.2) ────────────────────────────────────
Scenario: Client warns when HTTP is used with an API key
When I create a RegistryClient with base URL "http://registry.example.com" and API key "token123" and capture logs
Then a cleartext credentials warning was logged
Scenario: Client logs info when HTTP is used without an API key
When I create a RegistryClient pointed at "http://registry.example.com" and capture logs
Then an HTTPS requirement info message was logged
Scenario: Client with allow_insecure suppresses HTTP warnings
When I create an insecure RegistryClient pointed at "http://registry.example.com" and capture logs
Then no HTTPS warnings were logged