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
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
236 lines
14 KiB
Gherkin
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 |