1087 lines
43 KiB
Python
1087 lines
43 KiB
Python
import json
|
|
import os
|
|
from pathlib import Path
|
|
import sqlite3
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
APP_DIR = ROOT / 'app'
|
|
sys.path.insert(0, str(APP_DIR))
|
|
|
|
import docker_depth_report
|
|
from docker_depth_report import build_docker_depth_report
|
|
from scanner_db import ScannerDB, utc_now_iso
|
|
|
|
|
|
SCHEMA = '''
|
|
CREATE TABLE docker_depth_experiments (
|
|
id INTEGER PRIMARY KEY,
|
|
experiment_key TEXT NOT NULL,
|
|
source TEXT NOT NULL,
|
|
state TEXT NOT NULL,
|
|
query_count INTEGER NOT NULL,
|
|
target_count INTEGER NOT NULL,
|
|
selection_count INTEGER NOT NULL
|
|
);
|
|
CREATE TABLE docker_depth_experiment_queries (
|
|
id INTEGER PRIMARY KEY,
|
|
experiment_id INTEGER NOT NULL,
|
|
source TEXT NOT NULL,
|
|
query_ordinal INTEGER NOT NULL,
|
|
query TEXT NOT NULL,
|
|
query_sha256 TEXT NOT NULL,
|
|
required_repository_count INTEGER NOT NULL,
|
|
selected_repository_count INTEGER NOT NULL
|
|
);
|
|
CREATE TABLE docker_depth_experiment_repositories (
|
|
id INTEGER PRIMARY KEY,
|
|
experiment_id INTEGER NOT NULL,
|
|
query_ordinal INTEGER NOT NULL,
|
|
source TEXT NOT NULL,
|
|
query TEXT NOT NULL,
|
|
repository_queue_id INTEGER NOT NULL,
|
|
replacement_repository_queue_id INTEGER,
|
|
repository_rank INTEGER NOT NULL,
|
|
work_state TEXT NOT NULL,
|
|
resolver_attempts INTEGER NOT NULL
|
|
);
|
|
CREATE TABLE docker_depth_experiment_targets (
|
|
id INTEGER PRIMARY KEY,
|
|
experiment_id INTEGER NOT NULL,
|
|
target_queue_id INTEGER NOT NULL,
|
|
manifest_id INTEGER NOT NULL,
|
|
state TEXT NOT NULL
|
|
);
|
|
CREATE TABLE docker_depth_experiment_selections (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
experiment_id INTEGER NOT NULL,
|
|
query_ordinal INTEGER NOT NULL,
|
|
experiment_repository_id INTEGER NOT NULL,
|
|
experiment_target_id INTEGER NOT NULL,
|
|
image_rank INTEGER NOT NULL
|
|
);
|
|
CREATE TABLE docker_depth_experiment_scan_bindings (
|
|
id INTEGER PRIMARY KEY,
|
|
experiment_target_id INTEGER NOT NULL,
|
|
reservation_id INTEGER NOT NULL,
|
|
target_scan_id INTEGER,
|
|
attempt INTEGER NOT NULL,
|
|
state TEXT NOT NULL
|
|
);
|
|
CREATE TABLE target_queue (
|
|
id INTEGER PRIMARY KEY,
|
|
source TEXT NOT NULL,
|
|
platform TEXT NOT NULL,
|
|
query TEXT,
|
|
target TEXT NOT NULL,
|
|
normalized_target TEXT NOT NULL
|
|
);
|
|
CREATE TABLE result_reservations (
|
|
id INTEGER PRIMARY KEY,
|
|
queue_id INTEGER NOT NULL,
|
|
source TEXT NOT NULL,
|
|
platform TEXT NOT NULL,
|
|
query TEXT,
|
|
target TEXT NOT NULL,
|
|
normalized_target TEXT NOT NULL,
|
|
claim_lease_token TEXT NOT NULL,
|
|
scan_event_id TEXT NOT NULL,
|
|
state TEXT NOT NULL
|
|
);
|
|
CREATE TABLE target_scans (
|
|
id INTEGER PRIMARY KEY,
|
|
scan_event_id TEXT,
|
|
queue_id INTEGER,
|
|
result_reservation_id INTEGER,
|
|
claim_lease_token TEXT,
|
|
source TEXT,
|
|
normalized_target TEXT,
|
|
scan_type TEXT,
|
|
status TEXT,
|
|
duration_sec REAL,
|
|
error_count INTEGER,
|
|
query TEXT,
|
|
target TEXT,
|
|
raw_result_json TEXT
|
|
);
|
|
CREATE TABLE docker_image_manifests (
|
|
id INTEGER PRIMARY KEY,
|
|
target_queue_id INTEGER NOT NULL,
|
|
repository TEXT,
|
|
layer_count INTEGER NOT NULL
|
|
);
|
|
CREATE TABLE docker_manifest_layers (
|
|
id INTEGER PRIMARY KEY,
|
|
manifest_id INTEGER NOT NULL,
|
|
position_from_base INTEGER NOT NULL,
|
|
position_from_top INTEGER NOT NULL,
|
|
layer_digest TEXT NOT NULL
|
|
);
|
|
CREATE TABLE findings (
|
|
id INTEGER PRIMARY KEY,
|
|
target_scan_id INTEGER NOT NULL,
|
|
finding_fingerprint TEXT,
|
|
detector_secret_hash TEXT,
|
|
raw_secret TEXT,
|
|
redacted_secret TEXT,
|
|
raw_finding_json TEXT
|
|
);
|
|
CREATE TABLE docker_finding_layer_attributions (
|
|
id INTEGER PRIMARY KEY,
|
|
scan_binding_id INTEGER NOT NULL,
|
|
finding_id INTEGER NOT NULL,
|
|
manifest_layer_id INTEGER,
|
|
attribution_state TEXT NOT NULL,
|
|
reported_layer_digest TEXT,
|
|
position_from_base INTEGER,
|
|
position_from_top INTEGER
|
|
);
|
|
CREATE TABLE keycheck_credentials (
|
|
id INTEGER PRIMARY KEY,
|
|
secret_text TEXT,
|
|
secret_json TEXT,
|
|
key_masked TEXT
|
|
);
|
|
CREATE TABLE keycheck_candidates (
|
|
id INTEGER PRIMARY KEY,
|
|
credential_id INTEGER NOT NULL,
|
|
finding_id INTEGER NOT NULL,
|
|
target_scan_id INTEGER NOT NULL,
|
|
state TEXT NOT NULL,
|
|
attempts INTEGER NOT NULL,
|
|
keycheck_result_id INTEGER,
|
|
metadata_json TEXT
|
|
);
|
|
CREATE TABLE keycheck_results (
|
|
id INTEGER PRIMARY KEY,
|
|
candidate_id INTEGER,
|
|
credential_id INTEGER,
|
|
status TEXT NOT NULL,
|
|
status_group TEXT NOT NULL,
|
|
message TEXT,
|
|
source_line TEXT,
|
|
metadata_json TEXT
|
|
);
|
|
CREATE TABLE keycheck_current_state (
|
|
credential_id INTEGER PRIMARY KEY,
|
|
status TEXT NOT NULL,
|
|
status_group TEXT NOT NULL,
|
|
last_result_id INTEGER NOT NULL,
|
|
metadata_json TEXT
|
|
);
|
|
CREATE TABLE errors (
|
|
id INTEGER PRIMARY KEY,
|
|
target_scan_id INTEGER NOT NULL,
|
|
category TEXT,
|
|
summary TEXT,
|
|
raw_error TEXT
|
|
);
|
|
'''
|
|
|
|
|
|
class DockerDepthReportTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.conn = sqlite3.connect(':memory:')
|
|
self.conn.row_factory = sqlite3.Row
|
|
self.conn.executescript(SCHEMA)
|
|
self.conn.execute(
|
|
'''INSERT INTO docker_depth_experiments(
|
|
id, experiment_key, source, state, query_count,
|
|
target_count, selection_count
|
|
) VALUES (1, 'depth-fixture', 'dockerhub', 'completed', 0, 0, 0)'''
|
|
)
|
|
|
|
def tearDown(self):
|
|
self.conn.close()
|
|
|
|
def test_rows_fail_closed_before_materialization_bound_is_exceeded(self):
|
|
with self.assertRaisesRegex(RuntimeError, 'row bound is exceeded'):
|
|
docker_depth_report._rows(
|
|
self.conn,
|
|
'SELECT 1 AS value UNION ALL SELECT 2 UNION ALL SELECT 3',
|
|
(),
|
|
max_rows=2,
|
|
)
|
|
|
|
def add_query(
|
|
self, query_id, ordinal, *, query=None, attempts=1, required=1,
|
|
selected=None, repository_queue_id=None, add_repository=True,
|
|
repository_state='resolved'):
|
|
query = query or f'query-{query_id}'
|
|
selected = required if selected is None else selected
|
|
repository_queue_id = repository_queue_id or 2000 + query_id
|
|
self.conn.execute(
|
|
'''INSERT INTO docker_depth_experiment_queries(
|
|
id, experiment_id, source, query_ordinal, query,
|
|
query_sha256, required_repository_count,
|
|
selected_repository_count
|
|
) VALUES (?, 1, 'dockerhub', ?, ?, ?, ?, ?)''',
|
|
(query_id, ordinal, query, f'{query_id:064x}', required, selected),
|
|
)
|
|
if add_repository:
|
|
self.conn.execute(
|
|
'''INSERT INTO docker_depth_experiment_repositories(
|
|
id, experiment_id, query_ordinal, source, query,
|
|
repository_queue_id, repository_rank, work_state,
|
|
resolver_attempts
|
|
) VALUES (?, 1, ?, 'dockerhub', ?, ?, 1, ?, ?)''',
|
|
(
|
|
query_id, ordinal, query, repository_queue_id,
|
|
repository_state, attempts,
|
|
),
|
|
)
|
|
self.conn.execute(
|
|
'UPDATE docker_depth_experiments SET query_count = query_count + 1 WHERE id = 1'
|
|
)
|
|
|
|
def add_target(self, target_id, query_ranks, *, layer_count=1, repository='repo'):
|
|
target_queue_id = 1000 + target_id
|
|
target = f'{repository}@sha256:{target_id:064x}'
|
|
self.conn.execute(
|
|
'''INSERT INTO target_queue(
|
|
id, source, platform, query, target, normalized_target
|
|
) VALUES (?, 'dockerhub', 'docker', 'fixture', ?, ?)''',
|
|
(target_queue_id, target, target),
|
|
)
|
|
self.conn.execute(
|
|
'''INSERT INTO docker_image_manifests(
|
|
id, target_queue_id, repository, layer_count
|
|
) VALUES (?, ?, ?, ?)''',
|
|
(target_id, target_queue_id, repository, layer_count),
|
|
)
|
|
self.conn.execute(
|
|
'''INSERT INTO docker_depth_experiment_targets(
|
|
id, experiment_id, target_queue_id, manifest_id, state
|
|
) VALUES (?, 1, ?, ?, 'done')''',
|
|
(target_id, target_queue_id, target_id),
|
|
)
|
|
for query_id, rank in query_ranks.items():
|
|
query = self.conn.execute(
|
|
'''SELECT query_ordinal FROM docker_depth_experiment_queries
|
|
WHERE id = ?''',
|
|
(query_id,),
|
|
).fetchone()
|
|
self.conn.execute(
|
|
'''INSERT INTO docker_depth_experiment_selections(
|
|
experiment_id, query_ordinal, experiment_repository_id,
|
|
experiment_target_id, image_rank
|
|
) VALUES (1, ?, ?, ?, ?)''',
|
|
(query['query_ordinal'], query_id, target_id, rank),
|
|
)
|
|
self.conn.execute(
|
|
'''UPDATE docker_depth_experiments
|
|
SET target_count = target_count + 1,
|
|
selection_count = selection_count + ?
|
|
WHERE id = 1''',
|
|
(len(query_ranks),),
|
|
)
|
|
|
|
def add_scan(
|
|
self, target_id, scan_id, *, binding_id=None, attempt=1,
|
|
duration=1.0, error_count=0, status='clean', target=None,
|
|
binding_state='completed', reservation_state='acknowledged'):
|
|
binding_id = scan_id if binding_id is None else binding_id
|
|
queue_id = 1000 + target_id
|
|
reservation_id = 5000 + binding_id
|
|
queue = self.conn.execute(
|
|
'SELECT * FROM target_queue WHERE id = ?', (queue_id,),
|
|
).fetchone()
|
|
target = str(target or queue['target'])
|
|
normalized_target = target.lower()
|
|
self.conn.execute(
|
|
'''UPDATE target_queue SET target = ?, normalized_target = ?
|
|
WHERE id = ?''',
|
|
(target, normalized_target, queue_id),
|
|
)
|
|
self.conn.execute(
|
|
'''INSERT INTO result_reservations(
|
|
id, queue_id, source, platform, query, target,
|
|
normalized_target, claim_lease_token, scan_event_id, state
|
|
) VALUES (?, ?, 'dockerhub', 'docker', 'fixture', ?, ?, ?, ?, ?)''',
|
|
(
|
|
reservation_id, queue_id, target, normalized_target,
|
|
f'lease-{binding_id}', f'event-{binding_id}', reservation_state,
|
|
),
|
|
)
|
|
self.conn.execute(
|
|
'''INSERT INTO target_scans(
|
|
id, scan_event_id, queue_id, result_reservation_id,
|
|
claim_lease_token, source, query, target, normalized_target,
|
|
scan_type, status, duration_sec, error_count, raw_result_json
|
|
) VALUES (?, ?, ?, ?, ?, 'dockerhub', 'fixture', ?, ?,
|
|
'docker', ?, ?, ?, 'private result')''',
|
|
(
|
|
scan_id, f'event-{binding_id}', queue_id, reservation_id,
|
|
f'lease-{binding_id}', target, normalized_target, status,
|
|
duration, error_count,
|
|
),
|
|
)
|
|
self.conn.execute(
|
|
'''INSERT INTO docker_depth_experiment_scan_bindings(
|
|
id, experiment_target_id, reservation_id, target_scan_id,
|
|
attempt, state
|
|
) VALUES (?, ?, ?, ?, ?, ?)''',
|
|
(
|
|
binding_id, target_id, reservation_id, scan_id, attempt,
|
|
binding_state,
|
|
),
|
|
)
|
|
return binding_id
|
|
|
|
def add_scanless_binding(
|
|
self, target_id, binding_id, *, attempt, binding_state,
|
|
reservation_state):
|
|
queue_id = 1000 + target_id
|
|
reservation_id = 5000 + binding_id
|
|
queue = self.conn.execute(
|
|
'SELECT * FROM target_queue WHERE id = ?', (queue_id,),
|
|
).fetchone()
|
|
self.conn.execute(
|
|
'''INSERT INTO result_reservations(
|
|
id, queue_id, source, platform, query, target,
|
|
normalized_target, claim_lease_token, scan_event_id, state
|
|
) VALUES (?, ?, 'dockerhub', 'docker', 'fixture', ?, ?, ?, ?, ?)''',
|
|
(
|
|
reservation_id, queue_id, queue['target'],
|
|
queue['normalized_target'], f'lease-{binding_id}',
|
|
f'event-{binding_id}', reservation_state,
|
|
),
|
|
)
|
|
self.conn.execute(
|
|
'''INSERT INTO docker_depth_experiment_scan_bindings(
|
|
id, experiment_target_id, reservation_id, target_scan_id,
|
|
attempt, state
|
|
) VALUES (?, ?, ?, NULL, ?, ?)''',
|
|
(binding_id, target_id, reservation_id, attempt, binding_state),
|
|
)
|
|
|
|
def add_finding(
|
|
self, finding_id, scan_id, fingerprint, detector_hash,
|
|
*, secret='private-secret'):
|
|
self.conn.execute(
|
|
'''INSERT INTO findings(
|
|
id, target_scan_id, finding_fingerprint,
|
|
detector_secret_hash, raw_secret, redacted_secret,
|
|
raw_finding_json
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?)''',
|
|
(
|
|
finding_id, scan_id, fingerprint, detector_hash, secret,
|
|
f'redacted-{secret}', json.dumps({'excerpt': secret}),
|
|
),
|
|
)
|
|
|
|
def add_layer(self, layer_id, target_id, base, top, digest='sha256:layer'):
|
|
self.conn.execute(
|
|
'''INSERT INTO docker_manifest_layers(
|
|
id, manifest_id, position_from_base, position_from_top,
|
|
layer_digest
|
|
) VALUES (?, ?, ?, ?, ?)''',
|
|
(layer_id, target_id, base, top, digest),
|
|
)
|
|
|
|
def add_attribution(
|
|
self, attribution_id, binding_id, finding_id, *, state,
|
|
layer_id=None, base=None, top=None, digest=None):
|
|
self.conn.execute(
|
|
'''INSERT INTO docker_finding_layer_attributions(
|
|
id, scan_binding_id, finding_id, manifest_layer_id,
|
|
attribution_state, reported_layer_digest,
|
|
position_from_base, position_from_top
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
|
|
(
|
|
attribution_id, binding_id, finding_id, layer_id, state,
|
|
digest, base, top,
|
|
),
|
|
)
|
|
|
|
def add_credential(self, credential_id, secret='private-credential'):
|
|
self.conn.execute(
|
|
'''INSERT INTO keycheck_credentials(
|
|
id, secret_text, secret_json, key_masked
|
|
) VALUES (?, ?, ?, ?)''',
|
|
(credential_id, secret, json.dumps({'secret': secret}), f'***{secret}'),
|
|
)
|
|
|
|
def add_result(
|
|
self, result_id, credential_id, status, status_group,
|
|
*, candidate_id=None, sensitive='private-result-evidence'):
|
|
self.conn.execute(
|
|
'''INSERT INTO keycheck_results(
|
|
id, candidate_id, credential_id, status, status_group,
|
|
message, source_line, metadata_json
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
|
|
(
|
|
result_id, candidate_id, credential_id, status, status_group,
|
|
sensitive, sensitive, json.dumps({'evidence': sensitive}),
|
|
),
|
|
)
|
|
|
|
def add_candidate(
|
|
self, candidate_id, credential_id, finding_id, scan_id, *,
|
|
state='completed', attempts=1, result_id=None):
|
|
self.conn.execute(
|
|
'''INSERT INTO keycheck_candidates(
|
|
id, credential_id, finding_id, target_scan_id, state,
|
|
attempts, keycheck_result_id, metadata_json
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, 'private candidate evidence')''',
|
|
(
|
|
candidate_id, credential_id, finding_id, scan_id,
|
|
state, attempts, result_id,
|
|
),
|
|
)
|
|
|
|
def report(self, **selector):
|
|
self.conn.commit()
|
|
if not selector:
|
|
selector = {'experiment_id': 1}
|
|
return build_docker_depth_report(self.conn, **selector)
|
|
|
|
def test_shared_attribution_is_separate_from_physical_totals(self):
|
|
self.add_query(1, 0)
|
|
self.add_query(2, 1)
|
|
self.add_target(10, {1: 3, 2: 4})
|
|
self.add_scan(10, 100)
|
|
|
|
report = self.report(experiment_key='depth-fixture')
|
|
|
|
self.assertEqual(report['physical']['totals']['target_count'], 1)
|
|
self.assertEqual(
|
|
report['physical']['rank_buckets']['ranks_1_3']['target_count'], 1,
|
|
)
|
|
self.assertEqual(
|
|
report['physical']['rank_buckets']['ranks_4_10']['target_count'], 0,
|
|
)
|
|
self.assertEqual(report['per_query']['1']['totals']['target_count'], 1)
|
|
self.assertEqual(report['per_query']['2']['totals']['target_count'], 1)
|
|
self.assertEqual(
|
|
report['per_query']['1']['rank_buckets']['ranks_1_3']['target_count'], 1,
|
|
)
|
|
self.assertEqual(
|
|
report['per_query']['2']['rank_buckets']['ranks_4_10']['target_count'], 1,
|
|
)
|
|
self.assertEqual(report['overlap']['targets']['shared_physical_count'], 1)
|
|
self.assertEqual(report['overlap']['targets']['attribution_credit_count'], 2)
|
|
self.assertEqual(report['overlap']['scans']['overlap_credit_count'], 1)
|
|
|
|
def test_zero_member_query_reports_scarcity_without_missing_planned_work(self):
|
|
self.add_query(
|
|
1, 0, required=10, selected=0, add_repository=False,
|
|
)
|
|
|
|
coverage = self.report()['per_query']['1']['coverage']
|
|
|
|
self.assertEqual(coverage['required_repository_count'], 10)
|
|
self.assertEqual(coverage['cohort_repository_count'], 0)
|
|
self.assertEqual(coverage['unavailable_repository_count'], 10)
|
|
self.assertEqual(coverage['repository_count'], 0)
|
|
self.assertEqual(coverage['missing_cohort_repository_count'], 0)
|
|
|
|
def test_image_unavailable_repository_is_reported_without_a_target(self):
|
|
self.add_query(
|
|
1, 0, required=10, selected=1, repository_state='skipped',
|
|
)
|
|
|
|
report = self.report()
|
|
physical = report['physical']['coverage']
|
|
query = report['per_query']['1']['coverage']
|
|
|
|
self.assertEqual(physical['image_unavailable_repository_count'], 1)
|
|
self.assertEqual(
|
|
physical['image_unavailable_repository_membership_count'], 1,
|
|
)
|
|
self.assertEqual(
|
|
physical['queries_with_image_unavailable_repositories'], 1,
|
|
)
|
|
self.assertEqual(query['image_unavailable_repository_count'], 1)
|
|
self.assertEqual(query['target_count'], 0)
|
|
|
|
def test_shared_repository_uses_physical_queue_identity_and_membership_credits(self):
|
|
self.add_query(1, 0, repository_queue_id=9000)
|
|
self.add_query(2, 1, repository_queue_id=9000)
|
|
self.add_target(10, {1: 2, 2: 3})
|
|
|
|
report = self.report()
|
|
coverage = report['physical']['coverage']
|
|
|
|
self.assertEqual(coverage['repository_count'], 1)
|
|
self.assertEqual(coverage['repository_query_membership_credit_count'], 2)
|
|
self.assertEqual(coverage['repository_overlap_credit_count'], 1)
|
|
self.assertEqual(coverage['shared_repository_count'], 1)
|
|
self.assertEqual(coverage['selected_repository_count'], 1)
|
|
self.assertEqual(
|
|
coverage['selected_repository_query_membership_credit_count'], 2,
|
|
)
|
|
self.assertEqual(coverage['selected_repository_overlap_credit_count'], 1)
|
|
self.assertEqual(report['per_query']['1']['coverage']['repository_count'], 1)
|
|
self.assertEqual(report['per_query']['2']['coverage']['repository_count'], 1)
|
|
self.assertEqual(
|
|
report['overlap']['repositories'],
|
|
{
|
|
'physical_count': 1,
|
|
'query_membership_credit_count': 2,
|
|
'overlap_credit_count': 1,
|
|
'shared_physical_count': 1,
|
|
},
|
|
)
|
|
|
|
def test_replacement_repository_is_used_for_physical_overlap(self):
|
|
self.add_query(1, 0, repository_queue_id=9001)
|
|
self.add_query(2, 1, repository_queue_id=9002)
|
|
self.conn.execute(
|
|
'''UPDATE docker_depth_experiment_repositories
|
|
SET replacement_repository_queue_id = 9999 WHERE id IN (1, 2)'''
|
|
)
|
|
self.add_target(10, {1: 1, 2: 1})
|
|
|
|
coverage = self.report()['physical']['coverage']
|
|
|
|
self.assertEqual(coverage['repository_count'], 1)
|
|
self.assertEqual(coverage['shared_repository_count'], 1)
|
|
self.assertEqual(coverage['selected_repository_count'], 1)
|
|
|
|
def test_queries_match_the_current_scanner_schema(self):
|
|
with tempfile.TemporaryDirectory() as directory, mock.patch.dict(
|
|
os.environ, {'SCANNER_DB_URL': '', 'DATABASE_URL': ''}):
|
|
db = ScannerDB(db_path=os.path.join(directory, 'scanner.db'), db_url='')
|
|
try:
|
|
now = utc_now_iso()
|
|
digest = 'a' * 64
|
|
experiment_id = db.conn.execute(
|
|
'''INSERT INTO docker_depth_experiments(
|
|
experiment_key, source, state, config_sha256,
|
|
ordered_queries_sha256, selector_version,
|
|
selector_sha256, provenance_policy_sha256,
|
|
query_count, repositories_per_query,
|
|
images_per_repository, target_limit, created_at,
|
|
updated_at
|
|
) VALUES (
|
|
'schema-smoke', 'dockerhub', 'held', ?, ?,
|
|
'selector-v1', ?, ?, 1, 1, 1, 1, ?, ?
|
|
)''',
|
|
(digest, digest, digest, digest, now, now),
|
|
).lastrowid
|
|
db.conn.commit()
|
|
|
|
report = build_docker_depth_report(
|
|
db.conn, experiment_id=experiment_id,
|
|
)
|
|
|
|
self.assertEqual(report['experiment']['id'], experiment_id)
|
|
self.assertEqual(report['physical']['totals']['target_count'], 0)
|
|
self.assertEqual(report['per_query'], {})
|
|
finally:
|
|
db.close()
|
|
|
|
def test_rank_three_and_four_are_in_different_buckets(self):
|
|
self.add_query(1, 0)
|
|
self.add_target(10, {1: 1})
|
|
self.add_target(20, {1: 3})
|
|
self.add_target(30, {1: 4})
|
|
self.add_target(40, {1: 10})
|
|
|
|
report = self.report()
|
|
|
|
buckets = report['physical']['rank_buckets']
|
|
self.assertEqual(buckets['ranks_1_3']['target_count'], 2)
|
|
self.assertEqual(buckets['ranks_4_10']['target_count'], 2)
|
|
|
|
def test_minimum_rank_yield_uses_rank_three_at_the_bucket_boundary(self):
|
|
self.add_query(1, 0)
|
|
self.add_target(10, {1: 4})
|
|
self.add_target(20, {1: 3})
|
|
self.add_scan(10, 100)
|
|
self.add_scan(20, 200)
|
|
fingerprint = 'f' * 64
|
|
detector_hash = 'd' * 64
|
|
self.add_finding(1000, 100, fingerprint, detector_hash)
|
|
self.add_finding(2000, 200, fingerprint, detector_hash)
|
|
self.add_credential(50)
|
|
self.add_candidate(1, 50, 1000, 100)
|
|
self.add_candidate(2, 50, 2000, 200)
|
|
|
|
marginal = self.report()['physical']['marginal_minimum_rank_yield']
|
|
|
|
for metric in ('findings', 'detector_secret_identities', 'credentials'):
|
|
self.assertEqual(marginal[metric]['deduplicated_total'], 1)
|
|
self.assertEqual(marginal[metric]['ranks_1_3'], 1)
|
|
self.assertEqual(marginal[metric]['ranks_4_10'], 0)
|
|
|
|
def test_duplicate_identities_have_one_minimum_rank_yield(self):
|
|
self.add_query(1, 0)
|
|
self.add_target(10, {1: 2})
|
|
self.add_target(20, {1: 7})
|
|
self.add_scan(10, 100)
|
|
self.add_scan(20, 200)
|
|
fingerprint = 'f' * 64
|
|
detector_hash = 'd' * 64
|
|
self.add_finding(1000, 100, fingerprint, detector_hash)
|
|
self.add_finding(2000, 200, fingerprint, detector_hash)
|
|
self.add_credential(50)
|
|
self.add_candidate(1, 50, 1000, 100, state='pending')
|
|
self.add_candidate(2, 50, 2000, 200, state='pending')
|
|
|
|
report = self.report()
|
|
totals = report['physical']['totals']
|
|
marginal = report['physical']['marginal_minimum_rank_yield']
|
|
|
|
self.assertEqual(totals['findings']['occurrence_count'], 2)
|
|
self.assertEqual(totals['findings']['deduplicated_count'], 1)
|
|
self.assertEqual(totals['credentials']['deduplicated_count'], 1)
|
|
self.assertEqual(marginal['findings']['ranks_1_3'], 1)
|
|
self.assertEqual(marginal['findings']['ranks_4_10'], 0)
|
|
self.assertEqual(marginal['detector_secret_identities']['ranks_1_3'], 1)
|
|
self.assertEqual(marginal['credentials']['ranks_1_3'], 1)
|
|
self.assertEqual(marginal['credentials']['ranks_4_10'], 0)
|
|
|
|
def test_pending_frozen_and_current_verification_are_separate(self):
|
|
self.add_query(1, 0)
|
|
self.add_target(10, {1: 1})
|
|
self.add_scan(10, 100)
|
|
self.add_finding(1000, 100, 'a' * 64, 'b' * 64)
|
|
self.add_finding(1001, 100, 'c' * 64, 'd' * 64)
|
|
self.add_credential(50)
|
|
self.add_credential(60)
|
|
self.add_result(500, 50, 'VALID', 'alive', candidate_id=5)
|
|
self.add_result(501, 50, 'DEAD', 'dead')
|
|
self.add_candidate(5, 50, 1000, 100, result_id=500)
|
|
self.add_candidate(6, 60, 1001, 100, state='pending', attempts=0)
|
|
self.conn.execute(
|
|
'''INSERT INTO keycheck_current_state(
|
|
credential_id, status, status_group, last_result_id,
|
|
metadata_json
|
|
) VALUES (50, 'DEAD', 'dead', 501, 'private current evidence')'''
|
|
)
|
|
|
|
keychecks = self.report()['physical']['totals']['keychecks']
|
|
|
|
self.assertEqual(keychecks['pending_candidate_count'], 1)
|
|
self.assertEqual(keychecks['missing_frozen_result_candidate_count'], 1)
|
|
self.assertEqual(keychecks['frozen']['credential_count'], 1)
|
|
self.assertEqual(keychecks['frozen']['missing_credential_count'], 1)
|
|
self.assertEqual(keychecks['frozen']['status_counts'], {'VALID': 1})
|
|
self.assertEqual(keychecks['current']['credential_count'], 1)
|
|
self.assertEqual(keychecks['current']['missing_credential_count'], 1)
|
|
self.assertEqual(keychecks['current']['missing_backing_result_count'], 0)
|
|
self.assertEqual(keychecks['current']['status_counts'], {'DEAD': 1})
|
|
|
|
def test_keycheck_outcomes_require_candidate_and_credential_identity(self):
|
|
self.add_query(1, 0)
|
|
self.add_target(10, {1: 1})
|
|
self.add_scan(10, 100)
|
|
self.add_finding(1000, 100, 'a' * 64, 'b' * 64)
|
|
self.add_credential(50)
|
|
self.add_credential(60)
|
|
self.add_result(500, 60, 'VALID', 'alive', candidate_id=5)
|
|
self.add_result(501, 60, 'DEAD', 'dead', candidate_id=6)
|
|
self.add_candidate(5, 50, 1000, 100, result_id=500)
|
|
self.conn.execute(
|
|
'''INSERT INTO keycheck_current_state(
|
|
credential_id, status, status_group, last_result_id,
|
|
metadata_json
|
|
) VALUES (50, 'DEAD', 'dead', 501, 'private current evidence')'''
|
|
)
|
|
|
|
keychecks = self.report()['physical']['totals']['keychecks']
|
|
|
|
self.assertEqual(keychecks['missing_frozen_result_candidate_count'], 1)
|
|
self.assertEqual(keychecks['frozen']['result_count'], 0)
|
|
self.assertEqual(keychecks['frozen']['credential_count'], 0)
|
|
self.assertEqual(keychecks['current']['credential_count'], 1)
|
|
self.assertEqual(keychecks['current']['missing_backing_result_count'], 1)
|
|
self.assertEqual(
|
|
keychecks['current']['status_counts'], {'invalid_or_unknown': 1},
|
|
)
|
|
|
|
def test_duplicate_layer_digest_positions_remain_exact_rows(self):
|
|
self.add_query(1, 0)
|
|
self.add_target(10, {1: 1}, layer_count=3)
|
|
binding_id = self.add_scan(10, 100)
|
|
self.add_finding(1000, 100, 'a' * 64, 'b' * 64)
|
|
digest = 'sha256:duplicate'
|
|
self.add_layer(1, 10, 1, 3, digest)
|
|
self.add_layer(2, 10, 3, 1, digest)
|
|
self.add_attribution(
|
|
1, binding_id, 1000, state='exact', layer_id=1,
|
|
base=1, top=3, digest=digest,
|
|
)
|
|
self.add_attribution(
|
|
2, binding_id, 1000, state='exact', layer_id=2,
|
|
base=3, top=1, digest=digest,
|
|
)
|
|
|
|
totals = self.report()['physical']['totals']
|
|
layers = totals['layers']
|
|
|
|
self.assertEqual(totals['declared_manifest_layer_count'], 3)
|
|
self.assertEqual(layers['exact_attribution_count'], 2)
|
|
self.assertEqual(layers['exact_manifest_layer_count'], 2)
|
|
self.assertEqual(layers['positions_from_base'], {'1': 1, '3': 1})
|
|
self.assertEqual(layers['positions_from_top'], {'1': 1, '3': 1})
|
|
self.assertEqual(layers['unattributed_finding_occurrence_count'], 0)
|
|
|
|
def test_stale_or_cross_queue_bindings_cannot_admit_evidence(self):
|
|
self.add_query(1, 0)
|
|
for target_id, rank in ((10, 1), (20, 2), (30, 3), (40, 4)):
|
|
self.add_target(target_id, {1: rank})
|
|
self.add_scan(target_id, target_id * 10)
|
|
self.add_finding(
|
|
target_id * 100, target_id * 10,
|
|
f'{target_id:064x}', f'{target_id + 1:064x}',
|
|
)
|
|
self.conn.execute(
|
|
'UPDATE result_reservations SET queue_id = 999999 WHERE id = 5200'
|
|
)
|
|
self.conn.execute('UPDATE target_scans SET queue_id = 999998 WHERE id = 300')
|
|
self.conn.execute(
|
|
'UPDATE target_scans SET result_reservation_id = 5100 WHERE id = 400'
|
|
)
|
|
self.add_credential(50)
|
|
self.add_candidate(1, 50, 2000, 200)
|
|
self.conn.execute(
|
|
'''INSERT INTO errors(id, target_scan_id, category, summary, raw_error)
|
|
VALUES (1, 100, 'timeout', 'private', 'private'),
|
|
(2, 200, 'network', 'private', 'private')'''
|
|
)
|
|
|
|
totals = self.report()['physical']['totals']
|
|
|
|
self.assertEqual(totals['target_count'], 4)
|
|
self.assertEqual(totals['targets_with_bindings'], 3)
|
|
self.assertEqual(totals['targets_with_scans'], 1)
|
|
self.assertEqual(totals['scan_bindings']['attempt_count'], 3)
|
|
self.assertEqual(totals['scans']['count'], 1)
|
|
self.assertEqual(totals['findings']['occurrence_count'], 1)
|
|
self.assertEqual(totals['keychecks']['candidate_count'], 0)
|
|
self.assertEqual(totals['scans']['errors']['row_count'], 1)
|
|
self.assertEqual(
|
|
totals['scans']['errors']['category_counts'], {'timeout': 1},
|
|
)
|
|
|
|
def test_retries_duration_errors_and_coverage_are_aggregated_once(self):
|
|
self.add_query(1, 0, attempts=3)
|
|
self.add_target(10, {1: 1})
|
|
self.add_scan(10, 100, attempt=1, duration=2.0, error_count=1)
|
|
self.add_scan(10, 200, attempt=2, duration=3.0)
|
|
self.conn.execute(
|
|
'''INSERT INTO errors(id, target_scan_id, summary, raw_error)
|
|
VALUES (1, 100, 'private summary', 'private raw error')'''
|
|
)
|
|
|
|
report = self.report()
|
|
totals = report['physical']['totals']
|
|
coverage = report['per_query']['1']['coverage']
|
|
|
|
self.assertEqual(totals['scan_bindings']['attempt_count'], 2)
|
|
self.assertEqual(totals['scan_bindings']['retry_count'], 1)
|
|
self.assertEqual(totals['scan_bindings']['maximum_attempt'], 2)
|
|
self.assertEqual(totals['scans']['count'], 2)
|
|
self.assertEqual(totals['scans']['duration']['total_seconds'], 5.0)
|
|
self.assertEqual(totals['scans']['errors']['row_count'], 1)
|
|
self.assertEqual(totals['scans']['errors']['reported_count'], 1)
|
|
self.assertEqual(totals['scans']['errors']['scan_count'], 1)
|
|
self.assertEqual(coverage['repository_count'], 1)
|
|
self.assertEqual(coverage['selected_repository_count'], 1)
|
|
self.assertEqual(coverage['resolver_attempt_count'], 3)
|
|
self.assertEqual(coverage['resolver_retry_count'], 2)
|
|
|
|
def test_scanless_refund_and_retry_attempts_remain_visible(self):
|
|
self.add_query(1, 0)
|
|
self.add_target(10, {1: 1})
|
|
self.add_scanless_binding(
|
|
10, 100, attempt=1, binding_state='released',
|
|
reservation_state='refunded',
|
|
)
|
|
self.add_scanless_binding(
|
|
10, 200, attempt=2, binding_state='scanning',
|
|
reservation_state='ready',
|
|
)
|
|
self.conn.execute(
|
|
"UPDATE docker_depth_experiment_targets SET state = 'scanning' WHERE id = 10"
|
|
)
|
|
|
|
totals = self.report()['physical']['totals']
|
|
|
|
self.assertEqual(totals['targets_with_bindings'], 1)
|
|
self.assertEqual(totals['targets_with_scans'], 0)
|
|
self.assertEqual(totals['scan_bindings']['attempt_count'], 2)
|
|
self.assertEqual(totals['scan_bindings']['retry_count'], 1)
|
|
self.assertEqual(totals['scan_bindings']['refunded_attempt_count'], 1)
|
|
self.assertEqual(totals['scan_bindings']['maximum_attempt'], 2)
|
|
self.assertEqual(
|
|
totals['scan_bindings']['state_counts'],
|
|
{'released': 1, 'scanning': 1},
|
|
)
|
|
self.assertEqual(
|
|
totals['scan_bindings']['reservation_state_counts'],
|
|
{'ready': 1, 'refunded': 1},
|
|
)
|
|
|
|
def test_canonical_unknown_and_foundry_statuses_are_not_collapsed(self):
|
|
self.add_query(1, 0)
|
|
self.add_target(10, {1: 1})
|
|
self.add_scan(10, 100)
|
|
self.add_finding(1000, 100, 'a' * 64, 'b' * 64)
|
|
self.add_finding(1001, 100, 'c' * 64, 'd' * 64)
|
|
self.add_credential(50)
|
|
self.add_credential(60)
|
|
self.add_result(500, 50, 'UNKNOWN', 'unknown', candidate_id=5)
|
|
self.add_result(
|
|
600, 60, 'FOUNDRY_BAD_ENDPOINT', 'unknown', candidate_id=6,
|
|
)
|
|
self.add_candidate(5, 50, 1000, 100, result_id=500)
|
|
self.add_candidate(6, 60, 1001, 100, result_id=600)
|
|
self.conn.execute(
|
|
'''INSERT INTO keycheck_current_state(
|
|
credential_id, status, status_group, last_result_id,
|
|
metadata_json
|
|
) VALUES (50, 'UNKNOWN', 'unknown', 500, '{}'),
|
|
(60, 'FOUNDRY_BAD_ENDPOINT', 'unknown', 600, '{}')'''
|
|
)
|
|
self.conn.execute(
|
|
"INSERT INTO errors(id, target_scan_id, category) VALUES (1, 100, 'unknown')"
|
|
)
|
|
|
|
totals = self.report()['physical']['totals']
|
|
|
|
self.assertEqual(
|
|
totals['keychecks']['frozen']['status_counts'],
|
|
{'FOUNDRY_BAD_ENDPOINT': 1, 'UNKNOWN': 1},
|
|
)
|
|
self.assertEqual(
|
|
totals['keychecks']['current']['status_group_counts'], {'unknown': 2},
|
|
)
|
|
self.assertEqual(
|
|
totals['scans']['errors']['category_counts'], {'unknown': 1},
|
|
)
|
|
|
|
def test_unattributed_findings_and_secret_sentinels_never_leak(self):
|
|
sentinel = 'SECRET_SENTINEL_DO_NOT_LEAK'
|
|
self.add_query(1, 0, query=f'query-{sentinel}')
|
|
self.add_target(10, {1: 1}, repository=f'repository-{sentinel}')
|
|
binding_id = self.add_scan(10, 100, target=f'target-{sentinel}')
|
|
self.add_finding(1000, 100, 'a' * 64, 'b' * 64, secret=sentinel)
|
|
self.add_attribution(
|
|
1, binding_id, 1000, state='unattributed',
|
|
)
|
|
self.add_credential(50, secret=sentinel)
|
|
self.add_result(
|
|
500, 50, 'VALID', 'alive', candidate_id=5, sensitive=sentinel,
|
|
)
|
|
self.add_candidate(5, 50, 1000, 100, result_id=500)
|
|
self.conn.execute(
|
|
'INSERT INTO errors(id, target_scan_id, summary, raw_error) VALUES (1, 100, ?, ?)',
|
|
(sentinel, sentinel),
|
|
)
|
|
|
|
report = self.report()
|
|
serialized = json.dumps(report, sort_keys=True)
|
|
|
|
self.assertNotIn(sentinel, serialized)
|
|
layers = report['physical']['totals']['layers']
|
|
self.assertEqual(layers['unattributed_attribution_count'], 1)
|
|
self.assertEqual(layers['unattributed_finding_occurrence_count'], 1)
|
|
self.assertEqual(layers['unattributed_deduplicated_finding_count'], 1)
|
|
|
|
def test_untrusted_enum_and_error_sentinel_is_collapsed(self):
|
|
sentinel = 'SECRET_SENTINEL_DO_NOT_LEAK'
|
|
self.add_query(1, 0)
|
|
self.add_target(10, {1: 1})
|
|
binding_id = self.add_scan(10, 100)
|
|
self.add_finding(1000, 100, 'a' * 64, 'b' * 64)
|
|
self.add_attribution(1, binding_id, 1000, state=sentinel)
|
|
self.add_credential(50)
|
|
self.add_result(500, 50, sentinel, sentinel, candidate_id=5)
|
|
self.add_candidate(5, 50, 1000, 100, state=sentinel, result_id=500)
|
|
self.conn.execute(
|
|
'''INSERT INTO keycheck_current_state(
|
|
credential_id, status, status_group, last_result_id,
|
|
metadata_json
|
|
) VALUES (50, ?, ?, 500, 'private current evidence')''',
|
|
(sentinel, sentinel),
|
|
)
|
|
self.conn.execute(
|
|
'''INSERT INTO errors(id, target_scan_id, category, summary, raw_error)
|
|
VALUES (1, 100, ?, 'private', 'private')''',
|
|
(sentinel,),
|
|
)
|
|
self.conn.execute(
|
|
'''UPDATE docker_depth_experiments
|
|
SET experiment_key = ?, source = ?, state = ? WHERE id = 1''',
|
|
(sentinel, sentinel, sentinel),
|
|
)
|
|
self.conn.execute(
|
|
'''UPDATE docker_depth_experiment_queries
|
|
SET source = ?, query_sha256 = ? WHERE id = 1''',
|
|
(sentinel, sentinel),
|
|
)
|
|
self.conn.execute(
|
|
'''UPDATE docker_depth_experiment_repositories
|
|
SET source = ?, work_state = ? WHERE id = 1''',
|
|
(sentinel, sentinel),
|
|
)
|
|
self.conn.execute(
|
|
'UPDATE docker_depth_experiment_targets SET state = ? WHERE id = 10',
|
|
(sentinel,),
|
|
)
|
|
self.conn.execute(
|
|
'''UPDATE docker_depth_experiment_scan_bindings
|
|
SET state = ? WHERE id = ?''',
|
|
(sentinel, binding_id),
|
|
)
|
|
self.conn.execute(
|
|
'UPDATE target_scans SET status = ? WHERE id = 100',
|
|
(sentinel,),
|
|
)
|
|
|
|
report = self.report()
|
|
totals = report['physical']['totals']
|
|
|
|
self.assertNotIn(sentinel, json.dumps(report, sort_keys=True))
|
|
self.assertEqual(report['experiment']['key'], 'invalid_or_unknown')
|
|
self.assertEqual(report['experiment']['source'], 'invalid_or_unknown')
|
|
self.assertEqual(report['experiment']['state'], 'invalid_or_unknown')
|
|
self.assertEqual(report['per_query']['1']['query']['source'], 'invalid_or_unknown')
|
|
self.assertEqual(report['per_query']['1']['query']['sha256'], 'invalid_or_unknown')
|
|
self.assertEqual(totals['target_state_counts'], {'invalid_or_unknown': 1})
|
|
self.assertEqual(
|
|
totals['scan_bindings']['state_counts'], {'invalid_or_unknown': 1},
|
|
)
|
|
self.assertEqual(totals['scans']['status_counts'], {'invalid_or_unknown': 1})
|
|
self.assertEqual(
|
|
totals['scans']['errors']['category_counts'],
|
|
{'invalid_or_unknown': 1},
|
|
)
|
|
self.assertEqual(
|
|
totals['keychecks']['candidate_state_counts'],
|
|
{'invalid_or_unknown': 1},
|
|
)
|
|
self.assertEqual(
|
|
totals['keychecks']['frozen']['status_counts'],
|
|
{'invalid_or_unknown': 1},
|
|
)
|
|
self.assertEqual(
|
|
totals['keychecks']['current']['status_group_counts'],
|
|
{'invalid_or_unknown': 1},
|
|
)
|
|
self.assertEqual(totals['layers']['invalid_or_unknown_attribution_count'], 1)
|
|
|
|
def test_sqlite_report_uses_one_snapshot_during_concurrent_commit(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
path = os.path.join(directory, 'report.db')
|
|
reader = sqlite3.connect(path)
|
|
reader.row_factory = sqlite3.Row
|
|
reader.execute('PRAGMA journal_mode=WAL')
|
|
reader.executescript(SCHEMA)
|
|
reader.execute(
|
|
'''INSERT INTO docker_depth_experiments(
|
|
id, experiment_key, source, state, query_count,
|
|
target_count, selection_count
|
|
) VALUES (1, 'snapshot', 'dockerhub', 'completed', 0, 0, 0)'''
|
|
)
|
|
reader.commit()
|
|
writer = sqlite3.connect(path)
|
|
writer.row_factory = sqlite3.Row
|
|
|
|
class ConcurrentConnection:
|
|
def __init__(self):
|
|
self.changed = False
|
|
|
|
@property
|
|
def in_transaction(self):
|
|
return reader.in_transaction
|
|
|
|
def execute(self, sql, params=None):
|
|
cursor = reader.execute(sql, tuple(params or ()))
|
|
if (
|
|
not self.changed
|
|
and 'FROM docker_depth_experiment_queries' in sql
|
|
):
|
|
writer.execute(
|
|
'UPDATE docker_depth_experiments SET target_count = 99 WHERE id = 1'
|
|
)
|
|
writer.commit()
|
|
self.changed = True
|
|
return cursor
|
|
|
|
def rollback(self):
|
|
return reader.rollback()
|
|
|
|
try:
|
|
report = build_docker_depth_report(
|
|
ConcurrentConnection(), experiment_id=1,
|
|
)
|
|
self.assertEqual(
|
|
report['experiment']['persisted_counts']['targets'], 0,
|
|
)
|
|
self.assertEqual(
|
|
writer.execute(
|
|
'SELECT target_count FROM docker_depth_experiments WHERE id = 1'
|
|
).fetchone()['target_count'],
|
|
99,
|
|
)
|
|
self.assertFalse(reader.in_transaction)
|
|
self.assertEqual(
|
|
report['snapshot']['consistency'], 'sqlite_transaction',
|
|
)
|
|
finally:
|
|
writer.close()
|
|
reader.close()
|
|
|
|
def test_postgres_report_transaction_is_read_only_and_rolled_back(self):
|
|
class Connection:
|
|
is_postgres = True
|
|
|
|
def __init__(self):
|
|
self.statements = []
|
|
self.rollbacks = 0
|
|
|
|
def execute(self, sql, params=None):
|
|
self.statements.append(str(sql))
|
|
return object()
|
|
|
|
def rollback(self):
|
|
self.rollbacks += 1
|
|
|
|
connection = Connection()
|
|
fixture = {
|
|
'experiment': {'state': 'completed'},
|
|
'physical': {}, 'per_query': {}, 'overlap': {},
|
|
}
|
|
with mock.patch.object(
|
|
docker_depth_report, '_build_docker_depth_report_snapshot',
|
|
return_value=fixture,
|
|
):
|
|
report = build_docker_depth_report(connection, experiment_id=1)
|
|
self.assertEqual(
|
|
connection.statements,
|
|
['BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY'],
|
|
)
|
|
self.assertEqual(connection.rollbacks, 1)
|
|
self.assertEqual(report['snapshot']['consistency'], 'repeatable_read')
|
|
|
|
def test_raw_postgres_connection_is_rejected(self):
|
|
class Connection:
|
|
def execute(self, _sql, _params=None):
|
|
raise AssertionError('raw connection must be rejected before SQL')
|
|
|
|
Connection.__module__ = 'psycopg'
|
|
with self.assertRaisesRegex(ValueError, 'DatabaseConnection'):
|
|
build_docker_depth_report(Connection(), experiment_id=1)
|
|
|
|
def test_finding_position_fanout_is_aggregated_before_materialization(self):
|
|
source = (APP_DIR / 'docker_depth_report.py').read_text(encoding='utf-8')
|
|
self.assertIn('cursor.fetchmany(512)', source)
|
|
self.assertIn('COUNT(a.id) AS attribution_count', source)
|
|
self.assertIn(
|
|
'GROUP BY rb.target_id, ml.id, ml.position_from_base,', source,
|
|
)
|
|
self.assertNotIn('ORDER BY rb.target_id, f.id, a.id', source)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|