mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-11-04 07:43:42 +00:00 
			
		
		
		
	github upload support (#41)
This commit is contained in:
		@ -110,10 +110,10 @@ def test_add_archive(application: Application, package_ahriman: Package, mocker:
 | 
			
		||||
    must add package from archive
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
 | 
			
		||||
    move_mock = mocker.patch("shutil.move")
 | 
			
		||||
    copy_mock = mocker.patch("shutil.copy")
 | 
			
		||||
 | 
			
		||||
    application.add([package_ahriman.base], PackageSource.Archive, False)
 | 
			
		||||
    move_mock.assert_called_once()
 | 
			
		||||
    copy_mock.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_add_remote(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
 | 
			
		||||
@ -149,11 +149,11 @@ def test_add_directory(application: Application, package_ahriman: Package, mocke
 | 
			
		||||
    mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
 | 
			
		||||
    iterdir_mock = mocker.patch("pathlib.Path.iterdir",
 | 
			
		||||
                                return_value=[package.filepath for package in package_ahriman.packages.values()])
 | 
			
		||||
    move_mock = mocker.patch("shutil.move")
 | 
			
		||||
    copy_mock = mocker.patch("shutil.copy")
 | 
			
		||||
 | 
			
		||||
    application.add([package_ahriman.base], PackageSource.Directory, False)
 | 
			
		||||
    iterdir_mock.assert_called_once()
 | 
			
		||||
    move_mock.assert_called_once()
 | 
			
		||||
    copy_mock.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_add_local(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,11 @@ import logging
 | 
			
		||||
import pytest
 | 
			
		||||
import subprocess
 | 
			
		||||
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from pytest_mock import MockerFixture
 | 
			
		||||
 | 
			
		||||
from ahriman.core.exceptions import InvalidOption
 | 
			
		||||
from ahriman.core.util import check_output, package_like, pretty_datetime, pretty_size
 | 
			
		||||
from ahriman.core.util import check_output, package_like, pretty_datetime, pretty_size, walk
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -138,3 +139,29 @@ def test_pretty_size_empty() -> None:
 | 
			
		||||
    must generate empty string for None value
 | 
			
		||||
    """
 | 
			
		||||
    assert pretty_size(None) == ""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_walk(resource_path_root: Path) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must traverse directory recursively
 | 
			
		||||
    """
 | 
			
		||||
    expected = sorted([
 | 
			
		||||
        resource_path_root / "core/ahriman.ini",
 | 
			
		||||
        resource_path_root / "core/logging.ini",
 | 
			
		||||
        resource_path_root / "models/big_file_checksum",
 | 
			
		||||
        resource_path_root / "models/empty_file_checksum",
 | 
			
		||||
        resource_path_root / "models/package_ahriman_srcinfo",
 | 
			
		||||
        resource_path_root / "models/package_tpacpi-bat-git_srcinfo",
 | 
			
		||||
        resource_path_root / "models/package_yay_srcinfo",
 | 
			
		||||
        resource_path_root / "web/templates/build-status/login-modal.jinja2",
 | 
			
		||||
        resource_path_root / "web/templates/build-status/package-actions-modals.jinja2",
 | 
			
		||||
        resource_path_root / "web/templates/build-status/package-actions-script.jinja2",
 | 
			
		||||
        resource_path_root / "web/templates/static/favicon.ico",
 | 
			
		||||
        resource_path_root / "web/templates/utils/bootstrap-scripts.jinja2",
 | 
			
		||||
        resource_path_root / "web/templates/utils/style.jinja2",
 | 
			
		||||
        resource_path_root / "web/templates/build-status.jinja2",
 | 
			
		||||
        resource_path_root / "web/templates/email-index.jinja2",
 | 
			
		||||
        resource_path_root / "web/templates/repo-index.jinja2",
 | 
			
		||||
    ])
 | 
			
		||||
    local_files = list(sorted(walk(resource_path_root)))
 | 
			
		||||
    assert local_files == expected
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,58 @@
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
from collections import namedtuple
 | 
			
		||||
from typing import List
 | 
			
		||||
from typing import Any, Dict, List
 | 
			
		||||
from unittest.mock import MagicMock
 | 
			
		||||
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.upload.github import Github
 | 
			
		||||
from ahriman.core.upload.rsync import Rsync
 | 
			
		||||
from ahriman.core.upload.s3 import S3
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_s3_object = namedtuple("s3_object", ["key", "e_tag", "delete"])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def github(configuration: Configuration) -> Github:
 | 
			
		||||
    """
 | 
			
		||||
    fixture for github synchronization
 | 
			
		||||
    :param configuration: configuration fixture
 | 
			
		||||
    :return: github test instance
 | 
			
		||||
    """
 | 
			
		||||
    return Github("x86_64", configuration)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def github_release() -> Dict[str, Any]:
 | 
			
		||||
    """
 | 
			
		||||
    fixture for the github release object
 | 
			
		||||
    :return: github test release object
 | 
			
		||||
    """
 | 
			
		||||
    return {
 | 
			
		||||
        "url": "release_url",
 | 
			
		||||
        "assets_url": "assets_url",
 | 
			
		||||
        "upload_url": "upload_url{?name,label}",
 | 
			
		||||
        "tag_name": "x86_64",
 | 
			
		||||
        "name": "x86_64",
 | 
			
		||||
        "assets": [{
 | 
			
		||||
            "url": "asset_url",
 | 
			
		||||
            "name": "asset_name",
 | 
			
		||||
        }],
 | 
			
		||||
        "body": None,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def rsync(configuration: Configuration) -> Rsync:
 | 
			
		||||
    """
 | 
			
		||||
    fixture for rsync synchronization
 | 
			
		||||
    :param configuration: configuration fixture
 | 
			
		||||
    :return: rsync test instance
 | 
			
		||||
    """
 | 
			
		||||
    return Rsync("x86_64", configuration)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def s3(configuration: Configuration) -> S3:
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										266
									
								
								tests/ahriman/core/upload/test_github.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								tests/ahriman/core/upload/test_github.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,266 @@
 | 
			
		||||
import pytest
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from pytest_mock import MockerFixture
 | 
			
		||||
from typing import Any, Dict
 | 
			
		||||
from unittest import mock
 | 
			
		||||
from unittest.mock import MagicMock
 | 
			
		||||
 | 
			
		||||
from ahriman.core.upload.github import Github
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_calculate_hash_empty(resource_path_root: Path) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must calculate checksum for empty file correctly
 | 
			
		||||
    """
 | 
			
		||||
    path = resource_path_root / "models" / "empty_file_checksum"
 | 
			
		||||
    assert Github.calculate_hash(path) == "d41d8cd98f00b204e9800998ecf8427e"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_calculate_hash_small(resource_path_root: Path) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must calculate checksum for path which is single chunk
 | 
			
		||||
    """
 | 
			
		||||
    path = resource_path_root / "models" / "package_ahriman_srcinfo"
 | 
			
		||||
    assert Github.calculate_hash(path) == "a55f82198e56061295d405aeb58f4062"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_body_get_hashes() -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must generate readable body
 | 
			
		||||
    """
 | 
			
		||||
    source = {Path("c"): "c_md5", Path("a"): "a_md5", Path("b"): "b_md5"}
 | 
			
		||||
    body = Github.get_body(source)
 | 
			
		||||
    parsed = Github.get_hashes({"body": body})
 | 
			
		||||
    assert {fn.name: md5 for fn, md5 in source.items()} == parsed
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_hashes_empty() -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must read empty body
 | 
			
		||||
    """
 | 
			
		||||
    assert Github.get_hashes({"body": None}) == {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_request(github: Github, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must call request method
 | 
			
		||||
    """
 | 
			
		||||
    response_mock = MagicMock()
 | 
			
		||||
    request_mock = mocker.patch("requests.request", return_value=response_mock)
 | 
			
		||||
 | 
			
		||||
    github._request("GET", "url", arg="arg")
 | 
			
		||||
    request_mock.assert_called_once_with("GET", "url", auth=github.auth, arg="arg")
 | 
			
		||||
    response_mock.raise_for_status.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_request_exception(github: Github, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must call request method and log HTTPError exception
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("requests.request", side_effect=requests.HTTPError())
 | 
			
		||||
    with pytest.raises(requests.HTTPError):
 | 
			
		||||
        github._request("GET", "url", arg="arg")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_asset_remove(github: Github, github_release: Dict[str, Any], mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must remove asset from the release
 | 
			
		||||
    """
 | 
			
		||||
    request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
 | 
			
		||||
    github.asset_remove(github_release, "asset_name")
 | 
			
		||||
    request_mock.assert_called_with("DELETE", "asset_url")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_asset_remove_unknown(github: Github, github_release: Dict[str, Any], mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must not fail if no asset found
 | 
			
		||||
    """
 | 
			
		||||
    request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
 | 
			
		||||
    github.asset_remove(github_release, "unknown_asset_name")
 | 
			
		||||
    request_mock.assert_not_called()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_asset_upload(github: Github, github_release: Dict[str, Any], mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must upload asset to the repository
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("pathlib.Path.open", return_value=b"")
 | 
			
		||||
    request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
 | 
			
		||||
    remove_mock = mocker.patch("ahriman.core.upload.github.Github.asset_remove")
 | 
			
		||||
 | 
			
		||||
    github.asset_upload(github_release, Path("/root/new.tar.xz"))
 | 
			
		||||
    request_mock.assert_called_with("POST", "upload_url", params={"name": "new.tar.xz"},
 | 
			
		||||
                                    data=b"", headers={"Content-Type": "application/x-tar"})
 | 
			
		||||
    remove_mock.assert_not_called()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_asset_upload_with_removal(github: Github, github_release: Dict[str, Any], mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must remove existing file before upload
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("pathlib.Path.open", return_value=b"")
 | 
			
		||||
    mocker.patch("ahriman.core.upload.github.Github._request")
 | 
			
		||||
    remove_mock = mocker.patch("ahriman.core.upload.github.Github.asset_remove")
 | 
			
		||||
 | 
			
		||||
    github.asset_upload(github_release, Path("asset_name"))
 | 
			
		||||
    remove_mock.assert_called_with(github_release, "asset_name")
 | 
			
		||||
 | 
			
		||||
    github.asset_upload(github_release, Path("/root/asset_name"))
 | 
			
		||||
    remove_mock.assert_called_with(github_release, "asset_name")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_asset_upload_empty_mimetype(github: Github, github_release: Dict[str, Any], mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must upload asset to the repository with empty mime type if cannot guess it
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("pathlib.Path.open", return_value=b"")
 | 
			
		||||
    mocker.patch("ahriman.core.upload.github.Github.asset_remove")
 | 
			
		||||
    mocker.patch("mimetypes.guess_type", return_value=(None, None))
 | 
			
		||||
    request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
 | 
			
		||||
 | 
			
		||||
    github.asset_upload(github_release, Path("/root/new.tar.xz"))
 | 
			
		||||
    request_mock.assert_called_with("POST", "upload_url", params={"name": "new.tar.xz"},
 | 
			
		||||
                                    data=b"", headers={"Content-Type": "application/octet-stream"})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_local_files(github: Github, resource_path_root: Path, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must get all local files recursively
 | 
			
		||||
    """
 | 
			
		||||
    walk_mock = mocker.patch("ahriman.core.util.walk")
 | 
			
		||||
    github.get_local_files(resource_path_root)
 | 
			
		||||
    walk_mock.assert_called()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_files_remove(github: Github, github_release: Dict[str, Any], mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must remove files from the remote
 | 
			
		||||
    """
 | 
			
		||||
    remove_mock = mocker.patch("ahriman.core.upload.github.Github.asset_remove")
 | 
			
		||||
    github.files_remove(github_release, {Path("a"): "a"}, {"a": "a", "b": "b"})
 | 
			
		||||
    remove_mock.assert_called_once_with(github_release, "b")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_files_remove_empty(github: Github, github_release: Dict[str, Any], mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must remove nothing if nothing changed
 | 
			
		||||
    """
 | 
			
		||||
    remove_mock = mocker.patch("ahriman.core.upload.github.Github.asset_remove")
 | 
			
		||||
    github.files_remove(github_release, {Path("a"): "a"}, {"a": "a"})
 | 
			
		||||
    remove_mock.assert_not_called()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_files_upload(github: Github, github_release: Dict[str, Any], mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must upload files to the remote
 | 
			
		||||
    """
 | 
			
		||||
    upload_mock = mocker.patch("ahriman.core.upload.github.Github.asset_upload")
 | 
			
		||||
    github.files_upload(github_release, {Path("a"): "a", Path("b"): "c", Path("c"): "c"}, {"a": "a", "b": "b"})
 | 
			
		||||
    upload_mock.assert_has_calls([
 | 
			
		||||
        mock.call(github_release, Path("b")),
 | 
			
		||||
        mock.call(github_release, Path("c")),
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_files_upload_empty(github: Github, github_release: Dict[str, Any], mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must upload nothing if nothing changed
 | 
			
		||||
    """
 | 
			
		||||
    upload_mock = mocker.patch("ahriman.core.upload.github.Github.asset_upload")
 | 
			
		||||
    github.files_upload(github_release, {Path("a"): "a"}, {"a": "a"})
 | 
			
		||||
    upload_mock.assert_not_called()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_release_create(github: Github, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must create release
 | 
			
		||||
    """
 | 
			
		||||
    request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
 | 
			
		||||
    github.release_create()
 | 
			
		||||
    request_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
 | 
			
		||||
                                         json={"tag_name": github.architecture, "name": github.architecture})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_release_get(github: Github, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must get release
 | 
			
		||||
    """
 | 
			
		||||
    request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
 | 
			
		||||
    github.release_get()
 | 
			
		||||
    request_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_release_get_empty(github: Github, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return nothing in case of 404 status code
 | 
			
		||||
    """
 | 
			
		||||
    response = requests.Response()
 | 
			
		||||
    response.status_code = 404
 | 
			
		||||
    mocker.patch("ahriman.core.upload.github.Github._request", side_effect=requests.HTTPError(response=response))
 | 
			
		||||
    assert github.release_get() is None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_release_get_exception(github: Github, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must re-raise non HTTPError exception
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("ahriman.core.upload.github.Github._request", side_effect=Exception())
 | 
			
		||||
    with pytest.raises(Exception):
 | 
			
		||||
        github.release_get()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_release_get_exception_http_error(github: Github, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must re-raise HTTPError exception with code differs from 404
 | 
			
		||||
    """
 | 
			
		||||
    exception = requests.HTTPError(response=requests.Response())
 | 
			
		||||
    mocker.patch("ahriman.core.upload.github.Github._request", side_effect=exception)
 | 
			
		||||
    with pytest.raises(requests.HTTPError):
 | 
			
		||||
        github.release_get()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_release_update(github: Github, github_release: Dict[str, Any], mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must update release
 | 
			
		||||
    """
 | 
			
		||||
    request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
 | 
			
		||||
    github.release_update(github_release, "body")
 | 
			
		||||
    request_mock.assert_called_once_with("POST", "release_url", json={"body": "body"})
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_release_sync(github: Github, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must run sync command
 | 
			
		||||
    """
 | 
			
		||||
    release_get_mock = mocker.patch("ahriman.core.upload.github.Github.release_get")
 | 
			
		||||
    get_hashes_mock = mocker.patch("ahriman.core.upload.github.Github.get_hashes")
 | 
			
		||||
    get_local_files_mock = mocker.patch("ahriman.core.upload.github.Github.get_local_files")
 | 
			
		||||
    files_upload_mock = mocker.patch("ahriman.core.upload.github.Github.files_upload")
 | 
			
		||||
    files_remove_mock = mocker.patch("ahriman.core.upload.github.Github.files_remove")
 | 
			
		||||
    release_update_mock = mocker.patch("ahriman.core.upload.github.Github.release_update")
 | 
			
		||||
 | 
			
		||||
    github.sync(Path("local"), [])
 | 
			
		||||
    release_get_mock.assert_called_once()
 | 
			
		||||
    get_hashes_mock.assert_called_once()
 | 
			
		||||
    get_local_files_mock.assert_called_once()
 | 
			
		||||
    files_upload_mock.assert_called_once()
 | 
			
		||||
    files_remove_mock.assert_called_once()
 | 
			
		||||
    release_update_mock.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_release_sync_create_release(github: Github, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must create release in case if it does not exist
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("ahriman.core.upload.github.Github.release_get", return_value=None)
 | 
			
		||||
    mocker.patch("ahriman.core.upload.github.Github.get_hashes")
 | 
			
		||||
    mocker.patch("ahriman.core.upload.github.Github.get_local_files")
 | 
			
		||||
    mocker.patch("ahriman.core.upload.github.Github.files_upload")
 | 
			
		||||
    mocker.patch("ahriman.core.upload.github.Github.files_remove")
 | 
			
		||||
    mocker.patch("ahriman.core.upload.github.Github.release_update")
 | 
			
		||||
    release_create_mock = mocker.patch("ahriman.core.upload.github.Github.release_create")
 | 
			
		||||
 | 
			
		||||
    github.sync(Path("local"), [])
 | 
			
		||||
    release_create_mock.assert_called_once()
 | 
			
		||||
@ -1,16 +1,13 @@
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from pytest_mock import MockerFixture
 | 
			
		||||
 | 
			
		||||
from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.upload.rsync import Rsync
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_sync(configuration: Configuration, mocker: MockerFixture) -> None:
 | 
			
		||||
def test_sync(rsync: Rsync, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must run sync command
 | 
			
		||||
    """
 | 
			
		||||
    check_output_mock = mocker.patch("ahriman.core.upload.rsync.Rsync._check_output")
 | 
			
		||||
 | 
			
		||||
    upload = Rsync("x86_64", configuration)
 | 
			
		||||
    upload.sync(Path("path"), [])
 | 
			
		||||
    rsync.sync(Path("path"), [])
 | 
			
		||||
    check_output_mock.assert_called_once()
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ def test_calculate_etag_small(resource_path_root: Path) -> None:
 | 
			
		||||
    assert S3.calculate_etag(path, _chunk_size) == "a55f82198e56061295d405aeb58f4062"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_remove_files(s3_remote_objects: List[Any]) -> None:
 | 
			
		||||
def test_files_remove(s3_remote_objects: List[Any]) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must remove remote objects
 | 
			
		||||
    """
 | 
			
		||||
@ -43,35 +43,49 @@ def test_remove_files(s3_remote_objects: List[Any]) -> None:
 | 
			
		||||
    }
 | 
			
		||||
    remote_objects = {Path(item.key): item for item in s3_remote_objects}
 | 
			
		||||
 | 
			
		||||
    S3.remove_files(local_files, remote_objects)
 | 
			
		||||
    S3.files_remove(local_files, remote_objects)
 | 
			
		||||
    remote_objects[Path("x86_64/a")].delete.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_local_files(s3: S3, resource_path_root: Path) -> None:
 | 
			
		||||
def test_files_upload(s3: S3, s3_remote_objects: List[Any], mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must upload changed files
 | 
			
		||||
    """
 | 
			
		||||
    def mimetype(path: Path) -> Tuple[Optional[str], None]:
 | 
			
		||||
        return ("text/html", None) if path.name == "b" else (None, None)
 | 
			
		||||
 | 
			
		||||
    root = Path("path")
 | 
			
		||||
    local_files = {
 | 
			
		||||
        Path(item.key.replace("a", "d")): item.e_tag.replace("b", "d").replace("\"", "")
 | 
			
		||||
        for item in s3_remote_objects
 | 
			
		||||
    }
 | 
			
		||||
    remote_objects = {Path(item.key): item for item in s3_remote_objects}
 | 
			
		||||
 | 
			
		||||
    mocker.patch("mimetypes.guess_type", side_effect=mimetype)
 | 
			
		||||
    upload_mock = s3.bucket = MagicMock()
 | 
			
		||||
 | 
			
		||||
    s3.files_upload(root, local_files, remote_objects)
 | 
			
		||||
    upload_mock.upload_file.assert_has_calls(
 | 
			
		||||
        [
 | 
			
		||||
            mock.call(
 | 
			
		||||
                Filename=str(root / s3.architecture / "b"),
 | 
			
		||||
                Key=f"{s3.architecture}/{s3.architecture}/b",
 | 
			
		||||
                ExtraArgs={"ContentType": "text/html"}),
 | 
			
		||||
            mock.call(
 | 
			
		||||
                Filename=str(root / s3.architecture / "d"),
 | 
			
		||||
                Key=f"{s3.architecture}/{s3.architecture}/d",
 | 
			
		||||
                ExtraArgs=None),
 | 
			
		||||
        ],
 | 
			
		||||
        any_order=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_local_files(s3: S3, resource_path_root: Path, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must get all local files recursively
 | 
			
		||||
    """
 | 
			
		||||
    expected = sorted([
 | 
			
		||||
        Path("core/ahriman.ini"),
 | 
			
		||||
        Path("core/logging.ini"),
 | 
			
		||||
        Path("models/big_file_checksum"),
 | 
			
		||||
        Path("models/empty_file_checksum"),
 | 
			
		||||
        Path("models/package_ahriman_srcinfo"),
 | 
			
		||||
        Path("models/package_tpacpi-bat-git_srcinfo"),
 | 
			
		||||
        Path("models/package_yay_srcinfo"),
 | 
			
		||||
        Path("web/templates/build-status/login-modal.jinja2"),
 | 
			
		||||
        Path("web/templates/build-status/package-actions-modals.jinja2"),
 | 
			
		||||
        Path("web/templates/build-status/package-actions-script.jinja2"),
 | 
			
		||||
        Path("web/templates/static/favicon.ico"),
 | 
			
		||||
        Path("web/templates/utils/bootstrap-scripts.jinja2"),
 | 
			
		||||
        Path("web/templates/utils/style.jinja2"),
 | 
			
		||||
        Path("web/templates/build-status.jinja2"),
 | 
			
		||||
        Path("web/templates/email-index.jinja2"),
 | 
			
		||||
        Path("web/templates/repo-index.jinja2"),
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
    local_files = list(sorted(s3.get_local_files(resource_path_root).keys()))
 | 
			
		||||
    assert local_files == expected
 | 
			
		||||
    walk_mock = mocker.patch("ahriman.core.util.walk")
 | 
			
		||||
    s3.get_local_files(resource_path_root)
 | 
			
		||||
    walk_mock.assert_called()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_remote_objects(s3: S3, s3_remote_objects: List[Any]) -> None:
 | 
			
		||||
@ -92,43 +106,11 @@ def test_sync(s3: S3, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    local_files_mock = mocker.patch("ahriman.core.upload.s3.S3.get_local_files")
 | 
			
		||||
    remote_objects_mock = mocker.patch("ahriman.core.upload.s3.S3.get_remote_objects")
 | 
			
		||||
    remove_files_mock = mocker.patch("ahriman.core.upload.s3.S3.remove_files")
 | 
			
		||||
    upload_files_mock = mocker.patch("ahriman.core.upload.s3.S3.upload_files")
 | 
			
		||||
    remove_files_mock = mocker.patch("ahriman.core.upload.s3.S3.files_remove")
 | 
			
		||||
    upload_files_mock = mocker.patch("ahriman.core.upload.s3.S3.files_upload")
 | 
			
		||||
 | 
			
		||||
    s3.sync(Path("root"), [])
 | 
			
		||||
    local_files_mock.assert_called_once()
 | 
			
		||||
    remote_objects_mock.assert_called_once()
 | 
			
		||||
    remove_files_mock.assert_called_once()
 | 
			
		||||
    upload_files_mock.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_upload_files(s3: S3, s3_remote_objects: List[Any], mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must upload changed files
 | 
			
		||||
    """
 | 
			
		||||
    def mimetype(path: Path) -> Tuple[Optional[str], None]:
 | 
			
		||||
        return ("text/html", None) if path.name == "b" else (None, None)
 | 
			
		||||
 | 
			
		||||
    root = Path("path")
 | 
			
		||||
    local_files = {
 | 
			
		||||
        Path(item.key.replace("a", "d")): item.e_tag.replace("b", "d").replace("\"", "")
 | 
			
		||||
        for item in s3_remote_objects
 | 
			
		||||
    }
 | 
			
		||||
    remote_objects = {Path(item.key): item for item in s3_remote_objects}
 | 
			
		||||
 | 
			
		||||
    mocker.patch("mimetypes.guess_type", side_effect=mimetype)
 | 
			
		||||
    upload_mock = s3.bucket = MagicMock()
 | 
			
		||||
 | 
			
		||||
    s3.upload_files(root, local_files, remote_objects)
 | 
			
		||||
    upload_mock.upload_file.assert_has_calls(
 | 
			
		||||
        [
 | 
			
		||||
            mock.call(
 | 
			
		||||
                Filename=str(root / s3.architecture / "b"),
 | 
			
		||||
                Key=f"{s3.architecture}/{s3.architecture}/b",
 | 
			
		||||
                ExtraArgs={"ContentType": "text/html"}),
 | 
			
		||||
            mock.call(
 | 
			
		||||
                Filename=str(root / s3.architecture / "d"),
 | 
			
		||||
                Key=f"{s3.architecture}/{s3.architecture}/d",
 | 
			
		||||
                ExtraArgs=None),
 | 
			
		||||
        ],
 | 
			
		||||
        any_order=True)
 | 
			
		||||
 | 
			
		||||
@ -44,3 +44,12 @@ def test_upload_s3(configuration: Configuration, mocker: MockerFixture) -> None:
 | 
			
		||||
    upload_mock = mocker.patch("ahriman.core.upload.s3.S3.sync")
 | 
			
		||||
    Upload.load("x86_64", configuration, UploadSettings.S3.name).run(Path("path"), [])
 | 
			
		||||
    upload_mock.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_upload_github(configuration: Configuration, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must upload via github
 | 
			
		||||
    """
 | 
			
		||||
    upload_mock = mocker.patch("ahriman.core.upload.github.Github.sync")
 | 
			
		||||
    Upload.load("x86_64", configuration, UploadSettings.Github.name).run(Path("path"), [])
 | 
			
		||||
    upload_mock.assert_called_once()
 | 
			
		||||
 | 
			
		||||
@ -21,3 +21,6 @@ def test_from_option_valid() -> None:
 | 
			
		||||
 | 
			
		||||
    assert UploadSettings.from_option("s3") == UploadSettings.S3
 | 
			
		||||
    assert UploadSettings.from_option("S3") == UploadSettings.S3
 | 
			
		||||
 | 
			
		||||
    assert UploadSettings.from_option("github") == UploadSettings.Github
 | 
			
		||||
    assert UploadSettings.from_option("GitHub") == UploadSettings.Github
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user