65 lines
2.7 KiB
Python
65 lines
2.7 KiB
Python
import json
|
|||
|
|
from unittest.mock import Mock
|
||
|
|
import pytest
|
||
|
|
from scripts.ci import release
|
||
|
|
|
||
|
|
SHA = 'a' * 40
|
||
|
|
BUILD = 'b' * 32
|
||
|
|
DIGESTS = ['sha256:' + c * 64 for c in '123']
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def docker(monkeypatch):
|
||
|
|
monkeypatch.setenv('REGISTRY', 'registry.example')
|
||
|
|
refs = {}
|
||
|
|
for component, digest in zip(release.COMPONENTS, DIGESTS):
|
||
|
|
monkeypatch.setenv(component + '_IMAGE_NAME', component.lower())
|
||
|
|
monkeypatch.setenv(component + '_DIGEST', digest)
|
||
|
|
refs[f'registry.example/{component.lower()}'] = digest
|
||
|
|
def run(*args):
|
||
|
|
if args[0] == 'create': return ''
|
||
|
|
if args[-1] == '{{json .Manifest}}':
|
||
|
|
return json.dumps({'digest': refs[args[1].split(':')[0]]})
|
||
|
|
return json.dumps({'config': {'Labels': {'io.schoolcompare.commit': SHA,
|
||
|
|
'io.schoolcompare.build-id': BUILD}}})
|
||
|
|
mock = Mock(side_effect=run)
|
||
|
|
monkeypatch.setattr(release, 'docker', mock)
|
||
|
|
return mock
|
||
|
|
|
||
|
|
|
||
|
|
def test_wrong_deployed_build_is_rejected_even_at_same_commit():
|
||
|
|
assert not release.matches({'frontend': {'sha': SHA, 'build_id': BUILD},
|
||
|
|
'backend': {'sha': SHA, 'build_id': 'c' * 32}}, SHA, BUILD)
|
||
|
|
assert release.matches({c: {'sha': SHA, 'build_id': BUILD} for c in ('frontend', 'backend')}, SHA, BUILD)
|
||
|
|
|
||
|
|
|
||
|
|
def test_verification_tags_the_captured_digests(docker):
|
||
|
|
release.verify(SHA, BUILD)
|
||
|
|
creates = [c.args for c in docker.call_args_list if c.args[0] == 'create']
|
||
|
|
assert len(creates) == 3
|
||
|
|
for call, digest in zip(creates, DIGESTS):
|
||
|
|
assert call[-1].endswith('@' + digest)
|
||
|
|
assert call[2].endswith(':verified-' + SHA)
|
||
|
|
|
||
|
|
|
||
|
|
def test_promotion_resolves_all_verified_images_before_mutation(docker):
|
||
|
|
result = release.promote(SHA)
|
||
|
|
assert result['build_id'] == BUILD
|
||
|
|
calls = [c.args for c in docker.call_args_list]
|
||
|
|
first_write = next(i for i, c in enumerate(calls) if c[0] == 'create')
|
||
|
|
assert first_write == 6 # each of three candidates needs manifest + config
|
||
|
|
assert all(c[-1].endswith('@' + d) for c, d in zip(calls[-3:], DIGESTS))
|
||
|
|
|
||
|
|
|
||
|
|
def test_mixed_builds_fail_before_any_tag_is_changed(docker, monkeypatch):
|
||
|
|
identities = iter([(SHA, BUILD), (SHA, 'c' * 32), (SHA, BUILD)])
|
||
|
|
monkeypatch.setattr(release, 'image_identity', lambda _: next(identities))
|
||
|
|
with pytest.raises(ValueError, match='mixed'):
|
||
|
|
release.promote(SHA)
|
||
|
|
assert not any(c.args[0] == 'create' for c in docker.call_args_list)
|
||
|
|
|
||
|
|
|
||
|
|
def test_missing_candidate_fails_before_any_tag_is_changed(docker):
|
||
|
|
docker.side_effect = RuntimeError('missing verified tag')
|
||
|
|
with pytest.raises(RuntimeError): release.promote(SHA)
|
||
|
|
assert not any(c.args[0] == 'create' for c in docker.call_args_list)
|