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)