90 lines
3.3 KiB
Python
90 lines
3.3 KiB
Python
from datetime import UTC, datetime
|
|
|
|
import pytest
|
|
|
|
from tht.ports.evidence import EvidenceSourceError
|
|
|
|
|
|
class Body:
|
|
def __init__(self, data): self.data, self.closed = data, False
|
|
def read(self, amount): return self.data[:amount]
|
|
def close(self): self.closed = True
|
|
|
|
|
|
class Client:
|
|
def __init__(self): self.body = Body(b"hello")
|
|
def get_paginator(self, name): return self
|
|
def paginate(self, **kwargs):
|
|
yield {"Contents": [{"Key": "clinical/a.md", "ETag": '"abc"', "VersionId": "v1",
|
|
"Size": 5, "LastModified": datetime(2026, 1, 1, tzinfo=UTC)}]}
|
|
def get_object(self, **kwargs):
|
|
assert kwargs == {"Bucket": "evidence", "Key": "clinical/a.md", "VersionId": "v1"}
|
|
return {"Body": self.body, "ContentLength": 5, "ContentType": "text/markdown",
|
|
"ETag": '"abc"', "VersionId": "v1"}
|
|
|
|
|
|
def test_s3_canonical_uri_version_fingerprint_and_closed_body():
|
|
from tht.adapters.evidence.s3 import S3EvidenceSource
|
|
client = Client()
|
|
source = S3EvidenceSource(bucket="evidence", prefix="clinical/", client=client)
|
|
item = next(iter(source.discover()))
|
|
assert item.uri == "s3://evidence/clinical/a.md"
|
|
assert item.fingerprint == "s3-version:v1"
|
|
assert source.acquire(item).content == b"hello"
|
|
assert client.body.closed
|
|
|
|
|
|
def test_s3_etag_fallback_and_bounds():
|
|
from tht.adapters.evidence.s3 import S3EvidenceSource
|
|
client = Client()
|
|
with pytest.raises(ValueError):
|
|
S3EvidenceSource(bucket="evidence", client=client, max_objects=0)
|
|
|
|
|
|
def test_s3_rejects_private_or_insecure_endpoint_without_explicit_opt_in():
|
|
from tht.adapters.evidence.s3 import S3EvidenceSource
|
|
with pytest.raises(ValueError, match="private"):
|
|
S3EvidenceSource(bucket="evidence", endpoint_url="https://127.0.0.1:9000", client=Client())
|
|
with pytest.raises(ValueError, match="HTTPS"):
|
|
S3EvidenceSource(bucket="evidence", endpoint_url="http://s3.example.test", client=Client())
|
|
|
|
|
|
def test_s3_size_limit_closes_body():
|
|
from tht.adapters.evidence.s3 import S3EvidenceSource
|
|
client = Client()
|
|
source = S3EvidenceSource(bucket="evidence", client=client, max_bytes=4)
|
|
item = next(iter(source.discover()))
|
|
with pytest.raises(EvidenceSourceError):
|
|
source.acquire(item)
|
|
assert client.body.closed
|
|
|
|
|
|
def test_s3_config_serialization_masks_credentials():
|
|
from tht.config import S3EvidenceSourceConfig
|
|
config = S3EvidenceSourceConfig(type="s3", bucket="evidence",
|
|
access_key="access-secret", secret_key="write-secret")
|
|
assert "access-secret" not in repr(config)
|
|
assert "write-secret" not in repr(config)
|
|
|
|
|
|
def test_s3_config_loads_credentials_from_secret_files(tmp_path):
|
|
from tht.config import load_config
|
|
access, secret = tmp_path / "access", tmp_path / "secret"
|
|
access.write_text("access-value")
|
|
secret.write_text("secret-value")
|
|
workspace = tmp_path / "workspace.yaml"
|
|
workspace.write_text(f"""
|
|
dwh:
|
|
type: postgres_direct
|
|
connection: {{database: d, schema: public, user: u, password: p}}
|
|
evidence:
|
|
sources:
|
|
- type: s3
|
|
bucket: evidence
|
|
access_key_file: {access}
|
|
secret_key_file: {secret}
|
|
""")
|
|
source = load_config(workspace).evidence.sources[0]
|
|
assert source.access_key.get_secret_value() == "access-value"
|
|
assert source.secret_key.get_secret_value() == "secret-value"
|