port part of settings to database (#54)

This commit is contained in:
2022-03-31 01:48:06 +03:00
committed by GitHub
parent fb02e676af
commit 28cc38aaa5
117 changed files with 2768 additions and 1044 deletions

View File

@ -0,0 +1,13 @@
import pytest
from sqlite3 import Connection
from unittest.mock import MagicMock
@pytest.fixture
def connection() -> Connection:
"""
mock object for sqlite3 connection
:return: sqlite3 connection test instance
"""
return MagicMock()

View File

@ -0,0 +1,33 @@
from pytest_mock import MockerFixture
from sqlite3 import Connection
from ahriman.core.configuration import Configuration
from ahriman.core.database.data import migrate_data
from ahriman.models.migration_result import MigrationResult
from ahriman.models.repository_paths import RepositoryPaths
def test_migrate_data_initial(connection: Connection, configuration: Configuration,
repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must perform initial migration
"""
packages = mocker.patch("ahriman.core.database.data.migrate_package_statuses")
users = mocker.patch("ahriman.core.database.data.migrate_users_data")
migrate_data(MigrationResult(old_version=0, new_version=900), connection, configuration, repository_paths)
packages.assert_called_once_with(connection, repository_paths)
users.assert_called_once_with(connection, configuration)
def test_migrate_data_skip(connection: Connection, configuration: Configuration,
repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must not migrate data if version is up-to-date
"""
packages = mocker.patch("ahriman.core.database.data.migrate_package_statuses")
users = mocker.patch("ahriman.core.database.data.migrate_users_data")
migrate_data(MigrationResult(old_version=900, new_version=900), connection, configuration, repository_paths)
packages.assert_not_called()
users.assert_not_called()

View File

@ -0,0 +1,43 @@
import pytest
from pytest_mock import MockerFixture
from sqlite3 import Connection
from unittest import mock
from ahriman.core.database.data import migrate_package_statuses
from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths
def test_migrate_package_statuses(connection: Connection, package_ahriman: Package, repository_paths: RepositoryPaths,
mocker: MockerFixture) -> None:
"""
must migrate packages to database
"""
response = {"packages": [pytest.helpers.get_package_status_extended(package_ahriman)]}
mocker.patch("pathlib.Path.is_file", return_value=True)
mocker.patch("pathlib.Path.open")
mocker.patch("json.load", return_value=response)
unlink_mock = mocker.patch("pathlib.Path.unlink")
migrate_package_statuses(connection, repository_paths)
unlink_mock.assert_called_once_with()
connection.execute.assert_has_calls([
mock.call(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int)),
mock.call(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int)),
])
connection.executemany.assert_has_calls([
mock.call(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int)),
])
connection.commit.assert_called_once_with()
def test_migrate_package_statuses_skip(connection: Connection, repository_paths: RepositoryPaths,
mocker: MockerFixture) -> None:
"""
must skip packages migration if no cache file found
"""
mocker.patch("pathlib.Path.is_file", return_value=False)
migrate_package_statuses(connection, repository_paths)
connection.commit.assert_not_called()

View File

@ -0,0 +1,53 @@
import pytest
from pathlib import Path
from pytest_mock import MockerFixture
from sqlite3 import Connection
from ahriman.core.database.data import migrate_patches
from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths
def test_migrate_patches(connection: Connection, repository_paths: RepositoryPaths,
package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must perform migration for patches
"""
mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("pathlib.Path.is_file", return_value=True)
iterdir_mock = mocker.patch("pathlib.Path.iterdir", return_value=[Path(package_ahriman.base)])
read_mock = mocker.patch("pathlib.Path.read_text", return_value="patch")
migrate_patches(connection, repository_paths)
iterdir_mock.assert_called_once_with()
read_mock.assert_called_once_with(encoding="utf8")
connection.execute.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int))
connection.commit.assert_called_once_with()
def test_migrate_patches_skip(connection: Connection, repository_paths: RepositoryPaths,
mocker: MockerFixture) -> None:
"""
must skip patches migration in case if no root found
"""
mocker.patch("pathlib.Path.is_dir", return_value=False)
iterdir_mock = mocker.patch("pathlib.Path.iterdir")
migrate_patches(connection, repository_paths)
iterdir_mock.assert_not_called()
def test_migrate_patches_no_patch(connection: Connection, repository_paths: RepositoryPaths,
package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must skip package if no match found
"""
mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("pathlib.Path.is_file", return_value=False)
iterdir_mock = mocker.patch("pathlib.Path.iterdir", return_value=[Path(package_ahriman.base)])
read_mock = mocker.patch("pathlib.Path.read_text")
migrate_patches(connection, repository_paths)
iterdir_mock.assert_called_once_with()
read_mock.assert_not_called()

View File

@ -0,0 +1,22 @@
import pytest
from sqlite3 import Connection
from unittest import mock
from ahriman.core.configuration import Configuration
from ahriman.core.database.data import migrate_users_data
def test_migrate_users_data(connection: Connection, configuration: Configuration) -> None:
"""
must users to database
"""
configuration.set_option("auth:read", "user1", "password1")
configuration.set_option("auth:write", "user2", "password2")
migrate_users_data(connection, configuration)
connection.execute.assert_has_calls([
mock.call(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int)),
mock.call(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int)),
])
connection.commit.assert_called_once_with()

View File

@ -0,0 +1,15 @@
import pytest
from sqlite3 import Connection
from ahriman.core.database.migrations import Migrations
@pytest.fixture
def migrations(connection: Connection) -> Migrations:
"""
fixture for migrations object
:param connection: sqlite connection fixture
:return: migrations test instance
"""
return Migrations(connection)

View File

@ -0,0 +1,8 @@
from ahriman.core.database.migrations.m000_initial import steps
def test_migration_initial() -> None:
"""
migration must not be empty
"""
assert steps

View File

@ -0,0 +1,109 @@
import pytest
from pytest_mock import MockerFixture
from sqlite3 import Connection
from unittest import mock
from unittest.mock import MagicMock
from ahriman.core.database.migrations import Migrations
from ahriman.models.migration import Migration
from ahriman.models.migration_result import MigrationResult
def test_migrate(connection: Connection, mocker: MockerFixture) -> None:
"""
must perform migrations
"""
run_mock = mocker.patch("ahriman.core.database.migrations.Migrations.run")
Migrations.migrate(connection)
run_mock.assert_called_once_with()
def test_migrations(migrations: Migrations) -> None:
"""
must retrieve migrations
"""
assert migrations.migrations()
def test_run_skip(migrations: Migrations, mocker: MockerFixture) -> None:
"""
must skip migration if version is the same
"""
mocker.patch.object(MigrationResult, "is_outdated", False)
migrations.run()
migrations.connection.cursor.assert_not_called()
def test_run(migrations: Migrations, mocker: MockerFixture) -> None:
"""
must run migration
"""
cursor = MagicMock()
mocker.patch("ahriman.core.database.migrations.Migrations.user_version", return_value=0)
mocker.patch("ahriman.core.database.migrations.Migrations.migrations",
return_value=[Migration(0, "test", ["select 1"])])
migrations.connection.cursor.return_value = cursor
validate_mock = mocker.patch("ahriman.models.migration_result.MigrationResult.validate")
migrations.run()
validate_mock.assert_called_once_with()
cursor.execute.assert_has_calls([
mock.call("begin exclusive"),
mock.call("select 1"),
mock.call("pragma user_version = 1"),
mock.call("commit"),
])
cursor.close.assert_called_once_with()
def test_run_migration_exception(migrations: Migrations, mocker: MockerFixture) -> None:
"""
must rollback and close cursor on exception during migration
"""
cursor = MagicMock()
mocker.patch("logging.Logger.info", side_effect=Exception())
mocker.patch("ahriman.core.database.migrations.Migrations.user_version", return_value=0)
mocker.patch("ahriman.core.database.migrations.Migrations.migrations",
return_value=[Migration(0, "test", ["select 1"])])
mocker.patch("ahriman.models.migration_result.MigrationResult.validate")
migrations.connection.cursor.return_value = cursor
with pytest.raises(Exception):
migrations.run()
cursor.execute.assert_has_calls([
mock.call("begin exclusive"),
mock.call("rollback"),
])
cursor.close.assert_called_once_with()
def test_run_sql_exception(migrations: Migrations, mocker: MockerFixture) -> None:
"""
must close cursor on general migration error
"""
cursor = MagicMock()
cursor.execute.side_effect = Exception()
mocker.patch("ahriman.core.database.migrations.Migrations.user_version", return_value=0)
mocker.patch("ahriman.core.database.migrations.Migrations.migrations",
return_value=[Migration(0, "test", ["select 1"])])
mocker.patch("ahriman.models.migration_result.MigrationResult.validate")
migrations.connection.cursor.return_value = cursor
with pytest.raises(Exception):
migrations.run()
cursor.close.assert_called_once_with()
def test_user_version(migrations: Migrations) -> None:
"""
must correctly extract current migration version
"""
cursor = MagicMock()
cursor.fetchone.return_value = {"user_version": 42}
migrations.connection.execute.return_value = cursor
version = migrations.user_version()
migrations.connection.execute.assert_called_once_with("pragma user_version")
assert version == 42

View File

@ -0,0 +1,97 @@
from ahriman.core.database.sqlite import SQLite
from ahriman.models.user import User
from ahriman.models.user_access import UserAccess
def test_user_get_update(database: SQLite, user: User) -> None:
"""
must retrieve user from the database
"""
database.user_update(user)
assert database.user_get(user.username) == user
def test_user_list(database: SQLite, user: User) -> None:
"""
must return all users
"""
database.user_update(user)
database.user_update(User(user.password, user.username, user.access))
users = database.user_list(None, None)
assert len(users) == 2
assert user in users
assert User(user.password, user.username, user.access) in users
def test_user_list_filter_by_username(database: SQLite) -> None:
"""
must return users filtered by its id
"""
first = User("1", "", UserAccess.Read)
second = User("2", "", UserAccess.Write)
third = User("3", "", UserAccess.Read)
database.user_update(first)
database.user_update(second)
database.user_update(third)
assert database.user_list("1", None) == [first]
assert database.user_list("2", None) == [second]
assert database.user_list("3", None) == [third]
def test_user_list_filter_by_access(database: SQLite) -> None:
"""
must return users filtered by its access
"""
first = User("1", "", UserAccess.Read)
second = User("2", "", UserAccess.Write)
third = User("3", "", UserAccess.Read)
database.user_update(first)
database.user_update(second)
database.user_update(third)
users = database.user_list(None, UserAccess.Read)
assert len(users) == 2
assert first in users
assert third in users
def test_user_list_filter_by_username_access(database: SQLite) -> None:
"""
must return users filtered by its access and username
"""
first = User("1", "", UserAccess.Read)
second = User("2", "", UserAccess.Write)
third = User("3", "", UserAccess.Read)
database.user_update(first)
database.user_update(second)
database.user_update(third)
assert database.user_list("1", UserAccess.Read) == [first]
assert not database.user_list("1", UserAccess.Write)
def test_user_remove_update(database: SQLite, user: User) -> None:
"""
must remove user from the database
"""
database.user_update(user)
database.user_remove(user.username)
assert database.user_get(user.username) is None
def test_user_update(database: SQLite, user: User) -> None:
"""
must update user in the database
"""
database.user_update(user)
assert database.user_get(user.username) == user
new_user = user.hash_password("salt")
new_user.access = UserAccess.Write
database.user_update(new_user)
assert database.user_get(new_user.username) == new_user

View File

@ -0,0 +1,45 @@
from ahriman.core.database.sqlite import SQLite
from ahriman.models.package import Package
def test_build_queue_insert_clear(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must clear all packages from queue
"""
database.build_queue_insert(package_ahriman)
database.build_queue_insert(package_python_schedule)
database.build_queue_clear(None)
assert not database.build_queue_get()
def test_build_queue_insert_clear_specific(database: SQLite, package_ahriman: Package,
package_python_schedule: Package) -> None:
"""
must remove only specified package from the queue
"""
database.build_queue_insert(package_ahriman)
database.build_queue_insert(package_python_schedule)
database.build_queue_clear(package_python_schedule.base)
assert database.build_queue_get() == [package_ahriman]
def test_build_queue_insert_get(database: SQLite, package_ahriman: Package) -> None:
"""
must insert and get package from the database
"""
database.build_queue_insert(package_ahriman)
assert database.build_queue_get() == [package_ahriman]
def test_build_queue_insert(database: SQLite, package_ahriman: Package) -> None:
"""
must update user in the database
"""
database.build_queue_insert(package_ahriman)
assert database.build_queue_get() == [package_ahriman]
package_ahriman.version = "42"
database.build_queue_insert(package_ahriman)
assert database.build_queue_get() == [package_ahriman]

View File

@ -0,0 +1,39 @@
import sqlite3
from pytest_mock import MockerFixture
from unittest.mock import MagicMock
from ahriman.core.database.sqlite import SQLite
def test_factory(database: SQLite) -> None:
"""
must convert response to dictionary
"""
result = database.with_connection(lambda conn: conn.execute("select 1 as result").fetchone())
assert isinstance(result, dict)
assert result["result"] == 1
def test_with_connection(database: SQLite, mocker: MockerFixture) -> None:
"""
must run query inside connection
"""
connection_mock = MagicMock()
connect_mock = mocker.patch("sqlite3.connect", return_value=connection_mock)
database.with_connection(lambda conn: conn.execute("select 1"))
connect_mock.assert_called_once_with(database.path, detect_types=sqlite3.PARSE_DECLTYPES)
connection_mock.__enter__().commit.assert_not_called()
def test_with_connection_with_commit(database: SQLite, mocker: MockerFixture) -> None:
"""
must run query inside connection and commit after
"""
connection_mock = MagicMock()
connection_mock.commit.return_value = 42
mocker.patch("sqlite3.connect", return_value=connection_mock)
database.with_connection(lambda conn: conn.execute("select 1"), commit=True)
connection_mock.__enter__().commit.assert_called_once_with()

View File

@ -0,0 +1,168 @@
import pytest
from pytest_mock import MockerFixture
from sqlite3 import Connection
from unittest import mock
from ahriman.core.database.sqlite import SQLite
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package
def test_package_remove_package_base(database: SQLite, connection: Connection) -> None:
"""
must remove package base
"""
database._package_remove_package_base(connection, "package")
connection.execute.assert_has_calls([
mock.call(pytest.helpers.anyvar(str, strict=True), {"package_base": "package"}),
mock.call(pytest.helpers.anyvar(str, strict=True), {"package_base": "package"}),
])
def test_package_remove_packages(database: SQLite, connection: Connection, package_ahriman: Package) -> None:
"""
must remove packages belong to base
"""
database._package_remove_packages(connection, package_ahriman.base, package_ahriman.packages.keys())
connection.execute.assert_called_once_with(
pytest.helpers.anyvar(str, strict=True), {"package_base": package_ahriman.base})
connection.executemany.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), [])
def test_package_update_insert_base(database: SQLite, connection: Connection, package_ahriman: Package) -> None:
"""
must insert base package
"""
database._package_update_insert_base(connection, package_ahriman)
connection.execute.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int))
def test_package_update_insert_packages(database: SQLite, connection: Connection, package_ahriman: Package) -> None:
"""
must insert single packages
"""
database._package_update_insert_packages(connection, package_ahriman)
connection.executemany(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int))
def test_package_update_insert_status(database: SQLite, connection: Connection, package_ahriman: Package) -> None:
"""
must insert single package status
"""
database._package_update_insert_status(connection, package_ahriman.base, BuildStatus())
connection.execute(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int))
def test_packages_get_select_package_bases(database: SQLite, connection: Connection) -> None:
"""
must select all bases
"""
database._packages_get_select_package_bases(connection)
connection.execute(pytest.helpers.anyvar(str, strict=True))
def test_packages_get_select_packages(database: SQLite, connection: Connection, package_ahriman: Package) -> None:
"""
must select all packages
"""
database._packages_get_select_packages(connection, {package_ahriman.base: package_ahriman})
connection.execute(pytest.helpers.anyvar(str, strict=True))
def test_packages_get_select_packages_skip(database: SQLite, connection: Connection, package_ahriman: Package) -> None:
"""
must skip unknown packages
"""
view = {"package_base": package_ahriman.base}
for package, properties in package_ahriman.packages.items():
view.update({"package": package})
view.update(properties.view())
connection.execute.return_value = [{"package_base": "random name"}, view]
database._packages_get_select_packages(connection, {package_ahriman.base: package_ahriman})
def test_packages_get_select_statuses(database: SQLite, connection: Connection) -> None:
"""
must select all statuses
"""
database._packages_get_select_statuses(connection)
connection.execute(pytest.helpers.anyvar(str, strict=True))
def test_package_remove(database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must totally remove package from the database
"""
remove_package_mock = mocker.patch("ahriman.core.database.sqlite.SQLite._package_remove_package_base")
remove_packages_mock = mocker.patch("ahriman.core.database.sqlite.SQLite._package_remove_packages")
database.package_remove(package_ahriman.base)
remove_package_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.base)
remove_packages_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.base, [])
def test_package_update(database: SQLite, package_ahriman: Package, mocker: MockerFixture):
"""
must update package status
"""
status = BuildStatus()
insert_base_mock = mocker.patch("ahriman.core.database.sqlite.SQLite._package_update_insert_base")
insert_status_mock = mocker.patch("ahriman.core.database.sqlite.SQLite._package_update_insert_status")
insert_packages_mock = mocker.patch("ahriman.core.database.sqlite.SQLite._package_update_insert_packages")
remove_packages_mock = mocker.patch("ahriman.core.database.sqlite.SQLite._package_remove_packages")
database.package_update(package_ahriman, status)
insert_base_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman)
insert_status_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.base, status)
insert_packages_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman)
remove_packages_mock.assert_called_once_with(
pytest.helpers.anyvar(int), package_ahriman.base, package_ahriman.packages.keys())
def test_packages_get(database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must return all packages
"""
select_bases_mock = mocker.patch("ahriman.core.database.sqlite.SQLite._packages_get_select_package_bases",
return_value={package_ahriman.base: package_ahriman})
select_packages_mock = mocker.patch("ahriman.core.database.sqlite.SQLite._packages_get_select_packages")
select_statuses_mock = mocker.patch("ahriman.core.database.sqlite.SQLite._packages_get_select_statuses")
database.packages_get()
select_bases_mock.assert_called_once_with(pytest.helpers.anyvar(int))
select_statuses_mock.assert_called_once_with(pytest.helpers.anyvar(int))
select_packages_mock.assert_called_once_with(pytest.helpers.anyvar(int), {package_ahriman.base: package_ahriman})
def test_package_update_get(database: SQLite, package_ahriman: Package) -> None:
"""
must insert and retrieve package
"""
status = BuildStatus()
database.package_update(package_ahriman, status)
assert next((db_package, db_status)
for db_package, db_status in database.packages_get()
if db_package.base == package_ahriman.base) == (package_ahriman, status)
def test_package_update_remove_get(database: SQLite, package_ahriman: Package) -> None:
"""
must insert, remove and retrieve package
"""
status = BuildStatus()
database.package_update(package_ahriman, status)
database.package_remove(package_ahriman.base)
assert not database.packages_get()
def test_package_update_update(database: SQLite, package_ahriman: Package) -> None:
"""
must perform update for existing package
"""
database.package_update(package_ahriman, BuildStatus())
database.package_update(package_ahriman, BuildStatus(BuildStatusEnum.Failed))
assert next(db_status.status
for db_package, db_status in database.packages_get()
if db_package.base == package_ahriman.base) == BuildStatusEnum.Failed

View File

@ -0,0 +1,55 @@
from ahriman.core.database.sqlite import SQLite
from ahriman.models.package import Package
def test_patches_get_insert(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must insert patch to database
"""
database.patches_insert(package_ahriman.base, "patch_1")
database.patches_insert(package_python_schedule.base, "patch_2")
assert database.patches_get(package_ahriman.base) == "patch_1"
assert not database.build_queue_get()
def test_patches_list(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must list all patches
"""
database.patches_insert(package_ahriman.base, "patch1")
database.patches_insert(package_python_schedule.base, "patch2")
assert database.patches_list(None) == {package_ahriman.base: "patch1", package_python_schedule.base: "patch2"}
def test_patches_list_filter(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must list all patches filtered by package name (same as get)
"""
database.patches_insert(package_ahriman.base, "patch1")
database.patches_insert(package_python_schedule.base, "patch2")
assert database.patches_list(package_ahriman.base) == {package_ahriman.base: "patch1"}
assert database.patches_list(package_python_schedule.base) == {package_python_schedule.base: "patch2"}
def test_patches_insert_remove(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must remove patch from database
"""
database.patches_insert(package_ahriman.base, "patch_1")
database.patches_insert(package_python_schedule.base, "patch_2")
database.patches_remove(package_ahriman.base)
assert database.patches_get(package_ahriman.base) is None
database.patches_insert(package_python_schedule.base, "patch_2")
def test_patches_insert_insert(database: SQLite, package_ahriman: Package) -> None:
"""
must update patch in database
"""
database.patches_insert(package_ahriman.base, "patch_1")
assert database.patches_get(package_ahriman.base) == "patch_1"
database.patches_insert(package_ahriman.base, "patch_2")
assert database.patches_get(package_ahriman.base) == "patch_2"

View File

@ -0,0 +1,28 @@
import pytest
from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.core.database.sqlite import SQLite
def test_load(configuration: Configuration, mocker: MockerFixture) -> None:
"""
must correctly load instance
"""
init_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.init")
SQLite.load(configuration)
init_mock.assert_called_once_with(configuration)
def test_init(database: SQLite, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run migrations on init
"""
migrate_schema_mock = mocker.patch("ahriman.core.database.migrations.Migrations.migrate")
migrate_data_mock = mocker.patch("ahriman.core.database.sqlite.migrate_data")
database.init(configuration)
migrate_schema_mock.assert_called_once_with(pytest.helpers.anyvar(int))
migrate_data_mock.assert_called_once_with(
pytest.helpers.anyvar(int), pytest.helpers.anyvar(int), configuration, configuration.repository_paths)