Feature: Registry Client-Side Cache As a developer using the Package Registry Standard v1.0.0, I want a client-side LRU cache that stores retrieved packages locally, validates cached content via SHA-1, and expires entries based on TTL, so that I can reduce network roundtrips and ensure content integrity. Background: Given a clean test environment for registry cache # ── Cache Initialisation ────────────────────────────────────────────── Scenario: Create cache with default max size and TTL When I create a RegistryCache with default settings Then the cache max_size should be 256 Then the cache ttl should be 300 Scenario: Create cache with custom max size and TTL When I create a RegistryCache with max_size 64 and ttl 60 Then the cache max_size should be 64 Then the cache ttl should be 60 Scenario: Create cache with invalid client raises TypeError When I try to create a RegistryCache with a non-client argument Then a TypeError should be raised by the registry cache Scenario: Create cache with invalid max_size raises ValueError When I try to create a RegistryCache with max_size 0 Then a ValueError should be raised by the registry cache Scenario: Create cache with invalid ttl raises ValueError When I try to create a RegistryCache with ttl -1 Then a ValueError should be raised by the registry cache Scenario: Create cache with boolean max_size raises ValueError When I try to create a RegistryCache with max_size boolean True Then a ValueError should be raised by the registry cache # ── Cache Miss — Fresh Fetch ─────────────────────────────────────────── Scenario: First access triggers upstream fetch When I create a RegistryCache with validation disabled and label "dummy" When the mock upstream records a standard package When I call cache get_package for the standard package Then the cache stats hits should be 0 Then the cache stats misses should be 1 Scenario: First access returns upstream content When I create a RegistryCache with validation disabled and label "dummy" When the mock upstream records a standard package When I call cache get_package for the standard package Then the cache content should match the standard package # ── Cache Hit ────────────────────────────────────────────────────────── Scenario: Second access hits cache and does not call upstream When I create a RegistryCache with validation disabled and label "dummy" When the mock upstream records a standard package When I call cache get_package for the standard package When I call cache get_package for the standard package Then the cache stats hits should be 1 # ── Content Validation ───────────────────────────────────────────────── Scenario: Valid content passes SHA-1 validation and serves from cache When I create a RegistryCache with validation enabled and label "validated" When the mock upstream records a standard package When I call cache get_package for the standard package When I call cache get_package for the standard package Then the cache stats hits should be 1 Then the cache stats misses should be 1 Scenario: Tampered cache entry fails validation and re-fetches When I create a RegistryCache with validation enabled and label "validated" When the mock upstream records a standard package When I call cache get_package for the standard package When I tamper with the cached standard package When I call cache get_package for the standard package Then the cache stats misses should be 2 # ── TTL Expiration ────────────────────────────────────────────────────── Scenario: Expired TTL triggers re-fetch When I create a RegistryCache with max_size 256 and ttl 0.01 and no validation and label "dummy" When the mock upstream records a standard package When I call cache get_package for the standard package Then I wait 0.05 seconds for TTL to expire When I call cache get_package for the standard package Then the cache stats misses should be 2 # ── LRU Eviction ──────────────────────────────────────────────────────── Scenario: Exceeding max_size evicts least-recently-used entry When I create a RegistryCache with max_size 2 and ttl 300 and no validation and label "dummy" When the mock upstream records package A When the mock upstream records package B When the mock upstream records package C When I call cache get_package for package A When I call cache get_package for package B When I call cache get_package for package C Then the cache stats evictions should be 1 Scenario: LRU eviction removes the least-recently-used entry not the MRU When I create a RegistryCache with max_size 2 and ttl 300 and no validation and label "dummy" When the mock upstream records package A When the mock upstream records package B When I call cache get_package for package A When I call cache get_package for package B When I call cache get_package for package A When the mock upstream records package C When I call cache get_package for package C Then the cache stats evictions should be 1 Then package B should NOT be in cache Then package A should be in cache Then package C should be in cache # ── Cache Statistics ──────────────────────────────────────────────────── Scenario: Statistics track hits and misses across multiple accesses When I create a RegistryCache with validation disabled and label "dummy" When the mock upstream records a standard package When I call cache get_package for the standard package When I call cache get_package for the standard package Then the cache stats hits should be 1 Then the cache stats misses should be 1 # ── Invalidation ──────────────────────────────────────────────────────── Scenario: Invalidating an entry causes a cache miss on next access When I create a RegistryCache with validation disabled and label "dummy" When the mock upstream records a standard package When I call cache get_package for the standard package When I invalidate the standard package from cache When the mock upstream records a standard package When I call cache get_package for the standard package Then the cache stats misses should be 2 Scenario: Invalidating a missing entry returns False When I create a RegistryCache with default settings When I invalidate a missing cache entry Then the cache should report the entry was NOT invalidated # ── Clear ─────────────────────────────────────────────────────────────── Scenario: Clearing the cache resets statistics and removes all entries When I create a RegistryCache with validation disabled and label "dummy" When the mock upstream records a standard package When I call cache get_package for the standard package When I clear the cache Then the cache stats hits should be 0 Then the cache stats misses should be 0 Then the standard package should NOT be in cache # ── Wrapping RegistryClient ───────────────────────────────────────────── Scenario: Cache wraps RegistryClient transparently When I create a RegistryCache wrapping a real RegistryClient Then the cache should have the same base URL "https://registry.example.com" # ── Concurrent Access ─────────────────────────────────────────────────── Scenario: Concurrent access does not corrupt cache state When I create a RegistryCache with validation disabled and label "dummy" When the mock upstream records a standard package When I call cache get_package for the standard package 8 times concurrently Then the cache stats hits should be 0 Then the cache stats misses should be 1 Then the total stats hits plus misses should be 1 Then the cache content should match the standard package Then the standard package should be in cache # ── get_package_content Alias ─────────────────────────────────────────── Scenario: get_package_content alias delegates to get_package When I create a RegistryCache with validation disabled and label "dummy" When the mock upstream records a standard package When I call cache get_package_content for the standard package Then the cache stats misses should be 1 Then the cache content should match the standard package # ── Error Propagation ─────────────────────────────────────────────────── Scenario: Invalid PackageId raises InvalidPackageIdError before upstream call When I create a RegistryCache with validation disabled and label "dummy" When I try to call cache get_package with an invalid package ID Then an InvalidPackageIdError should be raised by the registry cache Scenario: Upstream PackageNotFoundError propagates to caller When I create a RegistryCache with validation disabled and label "dummy" When the mock upstream records a standard package When the mock upstream raises PackageNotFoundError for the standard package When I call cache get_package for the standard package and catch the error Then a PackageNotFoundError should be raised by the registry cache Scenario: Upstream RegistryNetworkError propagates to caller When I create a RegistryCache with validation disabled and label "dummy" When the mock upstream records a standard package When the mock upstream raises RegistryNetworkError for the standard package When I call cache get_package for the standard package and catch the error Then a RegistryNetworkError should be raised by the registry cache # ── Async Context Manager ─────────────────────────────────────────────── Scenario: Async context manager works correctly and closes client When I create a RegistryCache with validation disabled and label "dummy" When the mock upstream records a standard package When I use the RegistryCache as an async context manager Then the underlying client should be closed after context exit