18b9d61e35
CI / build (pull_request) Successful in 20s
CI / lint (pull_request) Successful in 30s
CI / quality (pull_request) Successful in 42s
CI / helm (pull_request) Successful in 26s
CI / security (pull_request) Successful in 1m9s
CI / typecheck (pull_request) Successful in 3m59s
CI / benchmark-publish (pull_request) Has been skipped
CI / coverage (pull_request) Successful in 9m25s
CI / e2e_tests (pull_request) Successful in 17m19s
CI / integration_tests (pull_request) Successful in 21m10s
CI / unit_tests (pull_request) Successful in 6m0s
CI / docker (pull_request) Successful in 2m22s
CI / status-check (pull_request) Successful in 1s
CI / benchmark-regression (pull_request) Successful in 54m57s
Add content_hash(resource, *, algorithm='sha256') -> str to the ResourceHandler protocol and all handler implementations: - Protocol: new content_hash method on ResourceHandler (protocol.py) - BaseResourceHandler: default impl hashes file content or directory entry names; returns EMPTY_CONTENT_HASH sentinel for missing resources - GitCheckoutHandler: hashes git rev-parse HEAD through the requested algorithm for consistent digest format - FsDirectoryHandler: recursive walk hashing sorted relative paths and file contents (content-only, ignores metadata) - DevcontainerHandler: hashes devcontainer.json config file - DatabaseResourceHandler: hashes connection string; for SQLite file-based DBs, hashes the database file content - _DefaultHandler: delegates to BaseResourceHandler EMPTY_CONTENT_HASH sentinel is the SHA-256 of empty input (e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855). Hash algorithm is configurable via the algorithm parameter (default sha256, accepts any hashlib.new()-compatible name). Behave tests (10 scenarios): sentinel for missing/nonexistent, determinism, different content produces different hash, fs-directory recursive hash with change detection, git-checkout hash, configurable algorithm (sha256 vs sha512), protocol compliance for all 4 handlers. ISSUES CLOSED: #837
189 lines
9.7 KiB
Gherkin
189 lines
9.7 KiB
Gherkin
@resource @handler @content_hash
|
|
Feature: ResourceHandler content_hash method
|
|
As a developer using change detection and deduplication
|
|
I want resource handlers to compute deterministic content hashes
|
|
So that the system can detect changes and deduplicate resources
|
|
|
|
# ── Sentinel hash for empty/missing resources ──────────────
|
|
|
|
Scenario: Missing resource returns sentinel hash
|
|
Given a resource with no location for content_hash
|
|
When I compute the content_hash via base handler
|
|
Then the content_hash result should be the EMPTY sentinel
|
|
|
|
Scenario: Non-existent path returns sentinel hash
|
|
Given a resource with location "/nonexistent/path" for content_hash
|
|
When I compute the content_hash via base handler
|
|
Then the content_hash result should be the EMPTY sentinel
|
|
|
|
# ── Determinism ────────────────────────────────────────────
|
|
|
|
Scenario: Same file produces same hash on repeated calls
|
|
Given a temporary file with content "hello world" for content_hash
|
|
When I compute the content_hash twice via base handler
|
|
Then the two content_hash results should be identical
|
|
|
|
Scenario: Different content produces different hash
|
|
Given a temporary file with content "hello" for content_hash
|
|
And a second temporary file with content "world" for content_hash
|
|
When I compute content_hash for both files
|
|
Then the two content_hash results should differ
|
|
|
|
# ── fs-directory recursive hash ────────────────────────────
|
|
|
|
Scenario: FsDirectoryHandler hashes directory recursively
|
|
Given a temporary directory with files for content_hash
|
|
When I compute the content_hash via FsDirectoryHandler
|
|
Then the content_hash result should not be the EMPTY sentinel
|
|
|
|
Scenario: FsDirectoryHandler hash changes when file content changes
|
|
Given a temporary directory with files for content_hash
|
|
When I compute the content_hash via FsDirectoryHandler
|
|
And I modify a file in the directory
|
|
And I compute the content_hash via FsDirectoryHandler again
|
|
Then the two directory hashes should differ
|
|
|
|
# ── git-checkout hash ──────────────────────────────────────
|
|
|
|
Scenario: GitCheckoutHandler hashes a git repo
|
|
Given a temporary git repository for content_hash
|
|
When I compute the content_hash via GitCheckoutHandler
|
|
Then the content_hash result should not be the EMPTY sentinel
|
|
|
|
# ── Configurable algorithm ─────────────────────────────────
|
|
|
|
Scenario: SHA-512 algorithm produces different length hash
|
|
Given a temporary file with content "test" for content_hash
|
|
When I compute the content_hash with algorithm "sha512"
|
|
Then the content_hash length should be 128
|
|
|
|
Scenario: Default SHA-256 produces 64-char hash
|
|
Given a temporary file with content "test" for content_hash
|
|
When I compute the content_hash with algorithm "sha256"
|
|
Then the content_hash length should be 64
|
|
|
|
# ── Protocol compliance ────────────────────────────────────
|
|
|
|
Scenario: All handlers have content_hash method
|
|
Then GitCheckoutHandler should have a content_hash method
|
|
And FsDirectoryHandler should have a content_hash method
|
|
And DevcontainerHandler should have a content_hash method
|
|
And DatabaseResourceHandler should have a content_hash method
|
|
|
|
# ── devcontainer content_hash ──────────────────────────────
|
|
|
|
Scenario: DevcontainerHandler config file hash for devcontainer-file
|
|
Given a temporary devcontainer project for content_hash
|
|
When I compute the content_hash via DevcontainerHandler
|
|
Then the content_hash result should not be the EMPTY sentinel
|
|
|
|
Scenario: DevcontainerHandler returns sentinel for missing config
|
|
Given a resource with location "/tmp/no-devcontainer" for content_hash
|
|
When I compute the content_hash via DevcontainerHandler for missing
|
|
Then the content_hash result should be the EMPTY sentinel
|
|
|
|
Scenario: DevcontainerHandler exec hash falls back to config when no container
|
|
Given a temporary devcontainer project for content_hash
|
|
When I compute the content_hash via DevcontainerHandler
|
|
Then the devcontainer hash should be from config file fallback
|
|
|
|
# ── database content_hash ──────────────────────────────────
|
|
|
|
Scenario: DatabaseResourceHandler hashes SQLite schema
|
|
Given a temporary SQLite database for content_hash
|
|
When I compute the content_hash via DatabaseResourceHandler
|
|
Then the content_hash result should not be the EMPTY sentinel
|
|
|
|
Scenario: DatabaseResourceHandler schema hash changes when table added
|
|
Given a temporary SQLite database for content_hash
|
|
When I compute the content_hash via DatabaseResourceHandler
|
|
And I add a table to the database
|
|
And I compute the content_hash via DatabaseResourceHandler again
|
|
Then the two database hashes should differ
|
|
|
|
Scenario: DatabaseResourceHandler returns identity hash for remote DB
|
|
Given a remote database resource for content_hash
|
|
When I compute the content_hash via DatabaseResourceHandler for remote
|
|
Then the content_hash result should not be the EMPTY sentinel
|
|
|
|
# ── cloud content_hash ─────────────────────────────────────
|
|
|
|
Scenario: CloudResourceHandler returns identity hash with location
|
|
Given a cloud resource with ARN location for content_hash
|
|
When I compute the content_hash via CloudResourceHandler
|
|
Then the content_hash result should not be the EMPTY sentinel
|
|
|
|
Scenario: CloudResourceHandler returns sentinel without location
|
|
Given a cloud resource without location for content_hash
|
|
When I compute the content_hash via CloudResourceHandler for empty
|
|
Then the content_hash result should be the EMPTY sentinel
|
|
|
|
# ── Edge case and error path coverage ──────────────────────
|
|
|
|
Scenario: Base handler hashes directory entries including subdirectory names
|
|
Given a temporary directory with only subdirectories for content_hash
|
|
When I compute the content_hash via base handler for dir
|
|
Then the content_hash result should not be the EMPTY sentinel
|
|
|
|
Scenario: FsDirectoryHandler returns sentinel for non-directory location
|
|
Given a temporary file pretending to be a directory for content_hash
|
|
When I compute the content_hash via FsDirectoryHandler for non-dir
|
|
Then the content_hash result should be the EMPTY sentinel
|
|
|
|
Scenario: FsDirectoryHandler skips unreadable files gracefully
|
|
Given a temporary directory with an unreadable file for content_hash
|
|
When I compute the content_hash via FsDirectoryHandler for unreadable
|
|
Then the content_hash result should not be the EMPTY sentinel
|
|
|
|
Scenario: GitCheckoutHandler returns sentinel for non-git directory
|
|
Given a temporary non-git directory for content_hash
|
|
When I compute the content_hash via GitCheckoutHandler for non-git
|
|
Then the content_hash result should be the EMPTY sentinel
|
|
|
|
Scenario: GitCheckoutHandler returns sentinel for file path
|
|
Given a temporary file as git location for content_hash
|
|
When I compute the content_hash via GitCheckoutHandler for file
|
|
Then the content_hash result should be the EMPTY sentinel
|
|
|
|
Scenario: DatabaseResourceHandler returns sentinel for no location
|
|
Given a database resource with no location for content_hash
|
|
When I compute the content_hash via DatabaseResourceHandler for noloc
|
|
Then the content_hash result should be the EMPTY sentinel
|
|
|
|
Scenario: DatabaseResourceHandler hashes non-db-extension SQLite file directly
|
|
Given a SQLite database with non-standard extension for content_hash
|
|
When I compute the content_hash via DatabaseResourceHandler for nonext
|
|
Then the content_hash result should not be the EMPTY sentinel
|
|
|
|
Scenario: DevcontainerHandler _find_running_container returns None without docker
|
|
Given a temporary devcontainer project for content_hash
|
|
When I check _find_running_container
|
|
Then the container ID should be None
|
|
|
|
# ── Mock-based branch coverage ─────────────────────────────
|
|
|
|
Scenario: DevcontainerHandler exec hash success path via mock
|
|
Given a temporary devcontainer project for content_hash
|
|
When I compute content_hash with mocked docker exec returning file hashes
|
|
Then the content_hash result should not be the EMPTY sentinel
|
|
|
|
Scenario: DevcontainerHandler _find_running_container OSError path
|
|
Given a temporary devcontainer project for content_hash
|
|
When I compute content_hash with docker ps raising OSError
|
|
Then the devcontainer falls back to config hash
|
|
|
|
Scenario: GitCheckoutHandler returns sentinel on subprocess timeout
|
|
Given a temporary git-checkout resource for content_hash timeout
|
|
When I compute content_hash with git rev-parse timing out
|
|
Then the content_hash result should be the EMPTY sentinel
|
|
|
|
Scenario: GitCheckoutHandler returns sentinel on OSError
|
|
Given a temporary git-checkout resource for content_hash timeout
|
|
When I compute content_hash with git rev-parse raising OSError
|
|
Then the content_hash result should be the EMPTY sentinel
|
|
|
|
Scenario: Base handler returns sentinel for special file paths
|
|
Given a resource pointing to a non-file non-dir path for content_hash
|
|
When I compute the content_hash via base handler for special
|
|
Then the content_hash result should be the EMPTY sentinel
|