mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-28 09:52:37 +02:00
238 lines
7.9 KiB
Python
238 lines
7.9 KiB
Python
import hashlib
|
|
|
|
import pytest
|
|
import requests
|
|
from pytest_mock import MockerFixture
|
|
|
|
from dangerzone.updater.registry import (
|
|
Image,
|
|
_get_auth_header,
|
|
_url,
|
|
get_manifest,
|
|
get_manifest_digest,
|
|
list_tags,
|
|
parse_image_location,
|
|
)
|
|
|
|
|
|
def test_parse_image_location_no_tag():
|
|
"""Test that parse_image_location correctly handles an image location without a tag."""
|
|
image_str = "ghcr.io/freedomofpress/dangerzone"
|
|
image = parse_image_location(image_str)
|
|
|
|
assert isinstance(image, Image)
|
|
assert image.registry == "ghcr.io"
|
|
assert image.namespace == "freedomofpress"
|
|
assert image.image_name == "dangerzone"
|
|
assert image.tag == "latest" # Default tag should be "latest"
|
|
assert image.digest is None
|
|
|
|
|
|
def test_parse_image_location_with_tag():
|
|
"""Test that parse_image_location correctly handles an image location with a tag."""
|
|
image_str = "ghcr.io/freedomofpress/dangerzone:v0.4.2"
|
|
image = parse_image_location(image_str)
|
|
|
|
assert isinstance(image, Image)
|
|
assert image.registry == "ghcr.io"
|
|
assert image.namespace == "freedomofpress"
|
|
assert image.image_name == "dangerzone"
|
|
assert image.tag == "v0.4.2"
|
|
|
|
|
|
def test_parse_image_location_tag_plus_digest():
|
|
"""Test that parse_image_location handles an image location with a tag that includes a digest."""
|
|
image_str = (
|
|
"ghcr.io/freedomofpress/dangerzone"
|
|
":20250205-0.8.0-148-ge67fbc1"
|
|
"@sha256:19e8eacd75879d05f6621c2ea8dd955e68ee3e07b41b9d53f4c8cc9929a68a67"
|
|
)
|
|
|
|
image = parse_image_location(image_str)
|
|
|
|
assert isinstance(image, Image)
|
|
assert image.registry == "ghcr.io"
|
|
assert image.namespace == "freedomofpress"
|
|
assert image.image_name == "dangerzone"
|
|
assert image.tag == "20250205-0.8.0-148-ge67fbc1"
|
|
assert (
|
|
image.digest
|
|
== "sha256:19e8eacd75879d05f6621c2ea8dd955e68ee3e07b41b9d53f4c8cc9929a68a67"
|
|
)
|
|
|
|
|
|
def test_parse_invalid_image_location():
|
|
"""Test that parse_image_location raises an error for invalid image locations."""
|
|
invalid_image_locations = [
|
|
"ghcr.io/dangerzone", # Missing namespace
|
|
"ghcr.io/freedomofpress/dangerzone:", # Empty tag
|
|
"freedomofpress/dangerzone", # Missing registry
|
|
"ghcr.io:freedomofpress/dangerzone", # Invalid format
|
|
"", # Empty string
|
|
]
|
|
|
|
for invalid_image in invalid_image_locations:
|
|
with pytest.raises(ValueError, match="Malformed image location"):
|
|
parse_image_location(invalid_image)
|
|
|
|
|
|
def test_list_tags(mocker: MockerFixture):
|
|
"""Test that list_tags correctly retrieves tags from the registry."""
|
|
# Mock the authentication response
|
|
image_str = "ghcr.io/freedomofpress/dangerzone"
|
|
|
|
# Mock requests.get to return appropriate values for both calls
|
|
mock_response_auth = mocker.Mock()
|
|
mock_response_auth.json.return_value = {"token": "dummy_token"}
|
|
mock_response_auth.raise_for_status.return_value = None
|
|
|
|
mock_response_tags = mocker.Mock()
|
|
mock_response_tags.json.return_value = {
|
|
"tags": ["v0.4.0", "v0.4.1", "v0.4.2", "latest"]
|
|
}
|
|
mock_response_tags.raise_for_status.return_value = None
|
|
|
|
# Setup the mock to return different responses for each URL
|
|
def mock_get(url, **kwargs):
|
|
if "token" in url:
|
|
return mock_response_auth
|
|
else:
|
|
return mock_response_tags
|
|
|
|
mocker.patch("requests.get", side_effect=mock_get)
|
|
|
|
# Call the function
|
|
tags = list_tags(image_str)
|
|
|
|
# Verify the result
|
|
assert tags == ["v0.4.0", "v0.4.1", "v0.4.2", "latest"]
|
|
|
|
|
|
def test_list_tags_auth_error(mocker: MockerFixture):
|
|
"""Test that list_tags handles authentication errors correctly."""
|
|
image_str = "ghcr.io/freedomofpress/dangerzone"
|
|
|
|
# Mock requests.get to raise an HTTPError
|
|
mock_response = mocker.Mock()
|
|
mock_response.raise_for_status.side_effect = requests.exceptions.HTTPError(
|
|
"401 Client Error: Unauthorized"
|
|
)
|
|
|
|
mocker.patch("requests.get", return_value=mock_response)
|
|
|
|
# Call the function and expect an error
|
|
with pytest.raises(requests.exceptions.HTTPError):
|
|
list_tags(image_str)
|
|
|
|
|
|
def test_list_tags_registry_error(mocker: MockerFixture):
|
|
"""Test that list_tags handles registry errors correctly."""
|
|
image_str = "ghcr.io/freedomofpress/dangerzone"
|
|
|
|
# Mock requests.get to return success for auth but error for tags
|
|
mock_response_auth = mocker.Mock()
|
|
mock_response_auth.json.return_value = {"token": "dummy_token"}
|
|
mock_response_auth.raise_for_status.return_value = None
|
|
|
|
mock_response_tags = mocker.Mock()
|
|
mock_response_tags.raise_for_status.side_effect = requests.exceptions.HTTPError(
|
|
"404 Client Error: Not Found"
|
|
)
|
|
|
|
# Setup the mock to return different responses for each URL
|
|
def mock_get(url, **kwargs):
|
|
if "token" in url:
|
|
return mock_response_auth
|
|
else:
|
|
return mock_response_tags
|
|
|
|
mocker.patch("requests.get", side_effect=mock_get)
|
|
|
|
# Call the function and expect an error
|
|
with pytest.raises(requests.exceptions.HTTPError):
|
|
list_tags(image_str)
|
|
|
|
|
|
def test_get_manifest(mocker: MockerFixture):
|
|
"""Test that get_manifest correctly retrieves manifests from the registry."""
|
|
image_str = "ghcr.io/freedomofpress/dangerzone:v0.4.2"
|
|
|
|
# Mock the responses
|
|
manifest_content = {
|
|
"schemaVersion": 2,
|
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
|
"config": {
|
|
"mediaType": "application/vnd.docker.container.image.v1+json",
|
|
"size": 1234,
|
|
"digest": "sha256:abc123def456",
|
|
},
|
|
"layers": [
|
|
{
|
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
|
"size": 12345,
|
|
"digest": "sha256:layer1",
|
|
}
|
|
],
|
|
}
|
|
|
|
mock_response_auth = mocker.Mock()
|
|
mock_response_auth.json.return_value = {"token": "dummy_token"}
|
|
mock_response_auth.raise_for_status.return_value = None
|
|
|
|
mock_response_manifest = mocker.Mock()
|
|
mock_response_manifest.json.return_value = manifest_content
|
|
mock_response_manifest.status_code = 200
|
|
mock_response_manifest.raise_for_status.return_value = None
|
|
|
|
# Setup the mock to return different responses for each URL
|
|
def mock_get(url, **kwargs):
|
|
if "token" in url:
|
|
return mock_response_auth
|
|
else:
|
|
return mock_response_manifest
|
|
|
|
mocker.patch("requests.get", side_effect=mock_get)
|
|
|
|
# Call the function
|
|
response = get_manifest(image_str)
|
|
|
|
# Verify the result
|
|
assert response.status_code == 200
|
|
assert response.json() == manifest_content
|
|
|
|
|
|
def test_get_manifest_digest():
|
|
"""Test that get_manifest_digest correctly calculates the manifest digest."""
|
|
# Create a sample manifest content
|
|
manifest_content = b'{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json"}'
|
|
|
|
# Calculate the expected digest manually
|
|
import hashlib
|
|
|
|
expected_digest = hashlib.sha256(manifest_content).hexdigest()
|
|
|
|
# Call the function with the content directly
|
|
digest = get_manifest_digest("unused_image_str", manifest_content)
|
|
|
|
# Verify the result
|
|
assert digest == expected_digest
|
|
|
|
|
|
def test_get_manifest_digest_from_registry(mocker: MockerFixture):
|
|
"""Test that get_manifest_digest correctly retrieves and calculates digests from the registry."""
|
|
image_str = "ghcr.io/freedomofpress/dangerzone:v0.4.2"
|
|
|
|
# Sample manifest content
|
|
manifest_content = b'{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json"}'
|
|
expected_digest = hashlib.sha256(manifest_content).hexdigest()
|
|
|
|
# Mock get_manifest
|
|
mock_response = mocker.Mock()
|
|
mock_response.content = manifest_content
|
|
mocker.patch("dangerzone.updater.registry.get_manifest", return_value=mock_response)
|
|
|
|
# Call the function
|
|
digest = get_manifest_digest(image_str)
|
|
|
|
# Verify the result
|
|
assert digest == expected_digest
|