517dfb4cce
CI / lint (pull_request) Successful in 35s
CI / quality (pull_request) Successful in 45s
CI / integration_tests (pull_request) Successful in 1m2s
CI / typecheck (pull_request) Successful in 1m10s
CI / security (pull_request) Successful in 1m10s
CI / build (pull_request) Successful in 42s
CI / unit_tests (pull_request) Successful in 3m21s
CI / coverage (pull_request) Successful in 3m7s
CI / status-check (pull_request) Successful in 3s
CI / quality (push) Successful in 50s
CI / security (push) Successful in 51s
CI / lint (push) Successful in 54s
CI / typecheck (push) Successful in 58s
CI / integration_tests (push) Successful in 1m0s
CI / build (push) Successful in 43s
CI / unit_tests (push) Successful in 3m23s
CI / coverage (push) Successful in 3m14s
CI / status-check (push) Successful in 3s
Implements the local:<path> reference scheme per Package Registry Standard v1.0.0 §5.3. Closes #46
275 lines
13 KiB
Gherkin
275 lines
13 KiB
Gherkin
Feature: Package Registry Reference Resolver
|
|
As a developer
|
|
I want to parse and resolve package references
|
|
So that I can use the Package Registry Standard v1.0.0 reference formats
|
|
|
|
Background:
|
|
Given I have imported the registry resolver module
|
|
|
|
# ── Reference parsing through ReferenceResolver.parse ─────────────────────
|
|
|
|
Scenario: Registry reference with version parses all fields
|
|
Given a package reference string "registry.example.com:acme/search@v1.2.3"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then the reference_type should be REGISTRY
|
|
And the server should be "registry.example.com"
|
|
And the namespace should be "acme"
|
|
And the name should be "search"
|
|
And the version should be "v1.2.3"
|
|
And the original_reference should be "registry.example.com:acme/search@v1.2.3"
|
|
|
|
Scenario: Registry reference with latest alias
|
|
Given a package reference string "registry.cleverthis.com:acme/web_search@latest"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then the reference_type should be REGISTRY
|
|
And the server should be "registry.cleverthis.com"
|
|
And the namespace should be "acme"
|
|
And the name should be "web_search"
|
|
And the version should be "latest"
|
|
|
|
Scenario: Registry reference without version defaults to latest
|
|
Given a package reference string "registry.cleverthis.com:simple/agent"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then the version should be "latest"
|
|
|
|
Scenario: ID reference parsing
|
|
Given a package reference string "ID:pkg_act_0123456789abcdef0123456789abcdef01234567"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then the reference_type should be ID
|
|
And the id_string should be "pkg_act_0123456789abcdef0123456789abcdef01234567"
|
|
And the parsed server should be null
|
|
And the parsed namespace should be null
|
|
|
|
Scenario: ID reference with graph type
|
|
Given a package reference string "ID:pkg_grh_deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then the reference_type should be ID
|
|
And the id_string should be "pkg_grh_deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
|
|
|
Scenario: Local reference parsing
|
|
Given a package reference string "local:./my-actor"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then the reference_type should be LOCAL
|
|
And the name should be "./my-actor"
|
|
|
|
Scenario: Local reference with path
|
|
Given a package reference string "local:path/to/component"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then the reference_type should be LOCAL
|
|
And the name should be "path/to/component"
|
|
|
|
Scenario: original_reference survives round-trip through parsing
|
|
Given a package reference string "registry.cleverthis.com:acme/web_search@v1.0.0"
|
|
When I parse it with ReferenceResolver.parse
|
|
And I convert the PackageReference to str
|
|
Then the str result is "registry.cleverthis.com:acme/web_search@v1.0.0"
|
|
|
|
Scenario: Empty reference string raises InvalidPackageReferenceError
|
|
Given an empty package reference string
|
|
When I parse it with ReferenceResolver.parse
|
|
Then an InvalidPackageReferenceError should be raised with message containing "must not be empty"
|
|
|
|
Scenario: Reference without colon separator raises error
|
|
Given a package reference string "nocolonserver"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then an InvalidPackageReferenceError should be raised with message containing "must be in format"
|
|
|
|
Scenario: Registry reference with empty namespace raises error
|
|
Given a package reference string "srv:/name"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then an InvalidPackageReferenceError should be raised with message containing "must include a namespace"
|
|
|
|
Scenario: Registry reference with empty name raises error
|
|
Given a package reference string "srv:ns/"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then an InvalidPackageReferenceError should be raised with message containing "must include a name"
|
|
|
|
Scenario: Registry reference with empty server raises error
|
|
Given a package reference string ":ns/name"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then an InvalidPackageReferenceError should be raised with message containing "must include a server name"
|
|
|
|
Scenario: Registry reference with empty version after at defaults to latest
|
|
Given a package reference string "srv:ns/name@"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then the version should be "latest"
|
|
|
|
Scenario: Registry reference with @ in namespace raises error
|
|
Given a package reference string "registry.com:ns/n@me@v1.0.0"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then an InvalidPackageReferenceError should be raised with message containing "@"
|
|
|
|
Scenario: Registry reference with path traversal in name raises error
|
|
Given a package reference string "registry.com:foo/../bar@v1.0.0"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then an InvalidPackageReferenceError should be raised with message containing "must contain only"
|
|
|
|
Scenario: Local reference with directory traversal raises error
|
|
Given a package reference string "local:../../../etc/passwd"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then an InvalidPackageReferenceError should be raised with message containing ".."
|
|
|
|
Scenario: Local reference with absolute path raises error
|
|
Given a package reference string "local:/etc/shadow"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then an InvalidPackageReferenceError should be raised with message containing "absolute"
|
|
|
|
# ── Reference resolution ──────────────────────────────────────────────────
|
|
|
|
Scenario: ID reference resolves directly to PackageId
|
|
Given a package reference string "ID:pkg_act_0123456789abcdef0123456789abcdef01234567"
|
|
And I create a ReferenceResolver without a client
|
|
When I resolve the reference
|
|
Then the resolved PackageId id_string should be "pkg_act_0123456789abcdef0123456789abcdef01234567"
|
|
|
|
Scenario: Registry reference resolution requires a client
|
|
Given a package reference string "registry.example.com:acme/search@v1.0.0"
|
|
And I create a ReferenceResolver without a client
|
|
When I resolve the reference
|
|
Then an InvalidPackageReferenceError should be raised with message containing "RegistryClient is required"
|
|
|
|
Scenario: Local reference resolution requires a LocalPackageStore
|
|
Given a package reference string "local:./my-actor"
|
|
And I create a ReferenceResolver without a client
|
|
When I resolve the reference
|
|
Then an InvalidPackageReferenceError should be raised with message containing "no LocalPackageStore configured"
|
|
|
|
Scenario: ReferenceResolver can resolve registry ref with mock client
|
|
Given a package reference string "registry.example.com:acme/search@v1.0.0"
|
|
And I create a ReferenceResolver with a mock client that returns "pkg_act_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
When I resolve the reference
|
|
Then the resolved PackageId id_string should be "pkg_act_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
|
|
Scenario: ReferenceResolver wraps client RegistryError in InvalidPackageReferenceError
|
|
Given a package reference string "registry.example.com:acme/search@v1.0.0"
|
|
And I create a ReferenceResolver with a mock client that raises PackageNotFoundError
|
|
When I resolve the reference
|
|
Then an InvalidPackageReferenceError should be raised with message containing "not found"
|
|
|
|
Scenario: ReferenceResolver close closes client when client is present
|
|
When I create a ReferenceResolver with a mock close tracking client
|
|
And I call close on the ReferenceResolver
|
|
Then the mock client close method should have been called
|
|
|
|
Scenario: ReferenceResolver supports async context manager
|
|
When I use ReferenceResolver as an async context manager with a mock client
|
|
Then clean-up should have been performed
|
|
|
|
# ── Version resolution ────────────────────────────────────────────────────
|
|
|
|
Scenario Outline: Concrete version resolves directly
|
|
Given I have available versions "v1.0.0, v1.1.0, v2.0.0"
|
|
When I call resolve_version with "<version>"
|
|
Then the resolved version is "<expected>"
|
|
|
|
Examples:
|
|
| version | expected |
|
|
| v1.0.0 | v1.0.0 |
|
|
| v1.1.0 | v1.1.0 |
|
|
| v2.0.0 | v2.0.0 |
|
|
|
|
Scenario: latest alias resolves to newest version
|
|
Given I have available versions "v1.0.0, v1.1.0, v2.0.0, v1.0.1"
|
|
When I call resolve_version with "latest"
|
|
Then the resolved version is "v2.0.0"
|
|
|
|
Scenario: vx alias resolves same as latest
|
|
Given I have available versions "v1.0.0, v1.1.0, v2.0.0"
|
|
When I call resolve_version with "vx"
|
|
Then the resolved version is "v2.0.0"
|
|
|
|
Scenario: x alias resolves same as latest
|
|
Given I have available versions "v1.0.0, v3.0.0, v2.0.0"
|
|
When I call resolve_version with "x"
|
|
Then the resolved version is "v3.0.0"
|
|
|
|
Scenario: Major alias vX.x resolves to latest in major series
|
|
Given I have available versions "v1.0.0, v1.1.0, v1.5.0, v2.0.0"
|
|
When I call resolve_version with "v1.x"
|
|
Then the resolved version is "v1.5.0"
|
|
|
|
Scenario: Minor alias vX.Y.x resolves to latest patch in series
|
|
Given I have available versions "v1.2.0, v1.2.1, v1.2.9, v1.3.0"
|
|
When I call resolve_version with "v1.2.x"
|
|
Then the resolved version is "v1.2.9"
|
|
|
|
Scenario: Concrete version not in list raises error
|
|
Given I have available versions "v1.0.0, v2.0.0"
|
|
When I call resolve_version with "v3.0.0"
|
|
Then an InvalidPackageReferenceError should be raised with message containing "not found in available versions"
|
|
|
|
Scenario: Alias with no matching versions raises error
|
|
Given I have available versions "v2.0.0, v2.1.0"
|
|
When I call resolve_version with "v1.x"
|
|
Then an InvalidPackageReferenceError should be raised with message containing "No versions match alias"
|
|
|
|
Scenario: Resolving against empty version list raises error
|
|
Given I have no available versions
|
|
When I call resolve_version with "latest"
|
|
Then an InvalidPackageReferenceError should be raised with message containing "No available versions"
|
|
|
|
Scenario: Unrecognized version format raises error
|
|
Given I have available versions "v1.0.0"
|
|
When I call resolve_version with "garbage"
|
|
Then an InvalidPackageReferenceError should be raised with message containing "Unrecognized version format"
|
|
|
|
# ── Version classification helpers ─────────────────────────────────────────
|
|
|
|
Scenario: is_concrete_version identifies concrete versions
|
|
Given I check if "v1.2.3" is a concrete version
|
|
Then the is_concrete_version result should be true
|
|
|
|
Scenario: is_concrete_version rejects aliases
|
|
Given I check if "latest" is a concrete version
|
|
Then the is_concrete_version result should be false
|
|
|
|
Scenario: is_concrete_version rejects major alias
|
|
Given I check if "v2.x" is a concrete version
|
|
Then the is_concrete_version result should be false
|
|
|
|
Scenario: is_concrete_version rejects minor alias
|
|
Given I check if "v2.1.x" is a concrete version
|
|
Then the is_concrete_version result should be false
|
|
|
|
Scenario: is_version_alias identifies latest as alias
|
|
Given I check if "latest" is a version alias
|
|
Then the is_version_alias result should be true
|
|
|
|
Scenario: is_version_alias identifies concrete as not alias
|
|
Given I check if "v1.2.3" is a version alias
|
|
Then the is_version_alias result should be false
|
|
|
|
Scenario Outline: is_version_alias rejects invalid strings
|
|
Given I check if "<version>" is a version alias
|
|
Then the is_version_alias result should be false
|
|
|
|
Examples:
|
|
| version |
|
|
| garbage |
|
|
| 1.2.3 |
|
|
| v1.2 |
|
|
| hello |
|
|
|
|
Scenario Outline: is_version_alias recognizes valid aliases
|
|
Given I check if "<version>" is a version alias
|
|
Then the is_version_alias result should be true
|
|
|
|
Examples:
|
|
| version |
|
|
| latest |
|
|
| vx |
|
|
| x |
|
|
| v2.x |
|
|
| v2.1.x |
|
|
|
|
# ── ReferenceResolver constructor and properties ──────────────────────────
|
|
|
|
Scenario: ReferenceResolver can be constructed without a client
|
|
When I create a ReferenceResolver without a client
|
|
Then the resolver client is null
|
|
|
|
Scenario: ReferenceResolver preserves original_reference through parse
|
|
Given a package reference string "registry.cleverthis.com:acme/search@v1.2.3"
|
|
When I parse it with ReferenceResolver.parse
|
|
Then the original_reference should be "registry.cleverthis.com:acme/search@v1.2.3"
|