Files
cleveragents-core/features/resource_handler_content_hash.feature
hamza.khyari 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
feat(resource): implement ResourceHandler content_hash method
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
2026-04-01 13:02:24 +00:00

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