diff --git a/backend/.dockerignore b/backend/.dockerignore index 667375e7..5b8d1eac 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -39,6 +39,7 @@ var/ .installed.cfg *.egg .mypy_cache +.pytest_cache # PyInstaller # Usually these files are written by a python script from a template diff --git a/backend/pyproject.toml b/backend/pyproject.toml index b32d2412..b41d3f68 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -34,7 +34,7 @@ metadata = "true" tagged-metadata = "true" [tool.ruff] -select = ["E", "F", "B", "N", "UP", "ANN", "C4", "T20", "SIM", "ARG", "ERA", "PLE"] +select = ["E", "F", "B", "N", "UP", "ANN", "C4", "T20", "SIM", "ARG", "ERA", "PLE", "TID252"] ignore = ["ANN201", "ANN003", "N999", "ANN101", "ANN204"] [tool.ruff.flake8-bugbear] diff --git a/backend/test_observer/controllers/artefacts/artefacts.py b/backend/test_observer/controllers/artefacts/artefacts.py index 4b4c4494..ccb75c91 100644 --- a/backend/test_observer/controllers/artefacts/artefacts.py +++ b/backend/test_observer/controllers/artefacts/artefacts.py @@ -15,191 +15,26 @@ # along with this program. If not, see . # # Written by: -# Nadzeya Hutsko # Omar Selo - - -import logging +# Nadzeya Hutsko from fastapi import APIRouter, Depends -from fastapi.responses import JSONResponse from sqlalchemy.orm import Session - -from test_observer.data_access.repository import ( - get_stage_by_name, - get_artefacts_by_family_name, -) -from test_observer.data_access.models import Artefact -from test_observer.data_access.models_enums import FamilyName +from test_observer.data_access.models import ArtefactBuild from test_observer.data_access.setup import get_db -from test_observer.external_apis.snapcraft import ( - get_channel_map_from_snapcraft, -) -from test_observer.external_apis.archive import ArchiveManager - -router = APIRouter() - -logger = logging.getLogger("test-observer-backend") - -CHANNEL_PROMOTION_MAP = { - # channel -> next-channel - "edge": "beta", - "beta": "candidate", - "candidate": "stable", - "stable": "stable", -} - -REPOSITORY_PROMOTION_MAP = { - # repository -> next-repository - "proposed": "updates", - "updates": "updates", -} +from .models import ArtefactBuildDTO -@router.put("/promote") -def promote_artefacts(db: Session = Depends(get_db)): - """ - Promote all the artefacts in all the families if it has been updated on the - external source - """ - try: - processed_artefacts = promoter_controller(db) - logger.info("INFO: Processed artefacts %s", processed_artefacts) - if False in processed_artefacts.values(): - return JSONResponse( - status_code=500, - content={ - "detail": ( - "Got some errors while processing the next artefacts: " - ", ".join( - [k for k, v in processed_artefacts.items() if v is False] - ) - ) - }, - ) - return JSONResponse( - status_code=200, - content={"detail": "All the artefacts have been processed successfully"}, - ) - except Exception as exc: - return JSONResponse(status_code=500, content={"detail": str(exc)}) - - -def promoter_controller(session: Session) -> dict: - """ - Orchestrate the snap promoter job - - :session: DB connection session - :return: dict with the processed cards and the status of execution - """ - family_mapping = { - FamilyName.SNAP: run_snap_promoter, - FamilyName.DEB: run_deb_promoter, - } - for family_name, promoter_function in family_mapping.items(): - artefacts = get_artefacts_by_family_name(session, family_name) - processed_artefacts = {} - for artefact in artefacts: - try: - processed_artefacts[ - f"{family_name} - {artefact.name} - {artefact.version}" - ] = True - promoter_function(session, artefact) - except Exception as exc: - processed_artefacts[ - f"{family_name} - {artefact.name} - {artefact.version}" - ] = False - logger.warning("WARNING: %s", str(exc), exc_info=True) - return processed_artefacts - - -def run_snap_promoter(session: Session, artefact: Artefact) -> None: - """ - Check snap artefacts state and move/archive them if necessary - - :session: DB connection session - :artefact_build: an ArtefactBuild object - """ - for build in artefact.builds: - arch = build.architecture - channel_map = get_channel_map_from_snapcraft( - arch=arch, - snapstore=artefact.source["store"], - snap_name=artefact.name, - ) - track = artefact.source.get("track", "latest") - - for channel_info in channel_map: - if not ( - channel_info.channel.track == track - and channel_info.channel.architecture == arch - ): - continue - - risk = channel_info.channel.risk - try: - version = channel_info.version - revision = channel_info.revision - except KeyError as exc: - logger.warning( - "No key '%s' is found. Continue processing...", - str(exc), - ) - continue - - next_risk = CHANNEL_PROMOTION_MAP[artefact.stage.name] - if ( - risk == next_risk != artefact.stage.name.lower() - and version == artefact.version - and revision == build.revision - ): - logger.info("Move artefact '%s' to the '%s' stage", artefact, next_risk) - stage = get_stage_by_name( - session, stage_name=next_risk, family=artefact.stage.family - ) - if stage: - artefact.stage = stage - session.commit() - # The artefact was promoted, so we're done - return +router = APIRouter() -def run_deb_promoter(session: Session, artefact: Artefact) -> None: - """ - Check deb artefacts state and move/archive them if necessary - :session: DB connection session - :artefact: an Artefact object - """ - for build in artefact.builds: - arch = build.architecture - for repo in REPOSITORY_PROMOTION_MAP: - with ArchiveManager( - arch=arch, - series=artefact.source["series"], - pocket=repo, - apt_repo=artefact.source["repo"], - ) as archivemanager: - deb_version = archivemanager.get_deb_version(artefact.name) - if deb_version is None: - logger.error( - "Cannot find deb_version with deb %s in package data", - artefact.name, - ) - continue - next_repo = REPOSITORY_PROMOTION_MAP.get(artefact.stage.name) - logger.debug( - "Artefact version: %s, deb version: %s", artefact.version, deb_version - ) - if ( - repo == next_repo != artefact.stage.name - and deb_version == artefact.version - ): - logger.info("Move artefact '%s' to the '%s' stage", artefact, next_repo) - stage = get_stage_by_name( - session, stage_name=next_repo, family=artefact.stage.family - ) - if stage: - artefact.stage = stage - session.commit() - # The artefact was promoted, so we're done - return +@router.get("/{artefact_id}/builds", response_model=list[ArtefactBuildDTO]) +def get_artefact_builds(artefact_id: int, db: Session = Depends(get_db)): + """Get latest artefact builds of an artefact together with their test executions""" + return ( + db.query(ArtefactBuild) + .filter(ArtefactBuild.artefact_id == artefact_id) + .distinct(ArtefactBuild.architecture) + .order_by(ArtefactBuild.architecture, ArtefactBuild.revision.desc()) + .all() + ) diff --git a/backend/test_observer/controllers/artefacts/models.py b/backend/test_observer/controllers/artefacts/models.py new file mode 100644 index 00000000..2a89263d --- /dev/null +++ b/backend/test_observer/controllers/artefacts/models.py @@ -0,0 +1,48 @@ +# Copyright 2023 Canonical Ltd. +# All rights reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Written by: +# Omar Selo +# Nadzeya Hutsko +from pydantic import BaseModel + + +class EnvironmentDTO(BaseModel): + id: int + name: str + architecture: str + + class Config: + orm_mode = True + + +class TestExecutionDTO(BaseModel): + id: int + jenkins_link: str | None + c3_link: str | None + environment: EnvironmentDTO + + class Config: + orm_mode = True + + +class ArtefactBuildDTO(BaseModel): + id: int + revision: int | None + test_executions: list[TestExecutionDTO] + + class Config: + orm_mode = True diff --git a/backend/tests/controllers/artefacts/__init__.py b/backend/test_observer/controllers/promoter/__init__.py similarity index 100% rename from backend/tests/controllers/artefacts/__init__.py rename to backend/test_observer/controllers/promoter/__init__.py diff --git a/backend/test_observer/controllers/promoter/promoter.py b/backend/test_observer/controllers/promoter/promoter.py new file mode 100644 index 00000000..695eaf1e --- /dev/null +++ b/backend/test_observer/controllers/promoter/promoter.py @@ -0,0 +1,205 @@ +# Copyright 2023 Canonical Ltd. +# All rights reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Written by: +# Nadzeya Hutsko +# Omar Selo + + +import logging +from fastapi import APIRouter, Depends +from fastapi.responses import JSONResponse +from sqlalchemy.orm import Session + +from test_observer.data_access.repository import ( + get_stage_by_name, + get_artefacts_by_family_name, +) +from test_observer.data_access.models import Artefact +from test_observer.data_access.models_enums import FamilyName +from test_observer.data_access.setup import get_db +from test_observer.external_apis.snapcraft import ( + get_channel_map_from_snapcraft, +) +from test_observer.external_apis.archive import ArchiveManager + +router = APIRouter() + +logger = logging.getLogger("test-observer-backend") + +CHANNEL_PROMOTION_MAP = { + # channel -> next-channel + "edge": "beta", + "beta": "candidate", + "candidate": "stable", + "stable": "stable", +} + +REPOSITORY_PROMOTION_MAP = { + # repository -> next-repository + "proposed": "updates", + "updates": "updates", +} + + +@router.put("/v0/artefacts/promote") +def promote_artefacts(db: Session = Depends(get_db)): + """ + Promote all the artefacts in all the families if it has been updated on the + external source + """ + try: + processed_artefacts = promoter_controller(db) + logger.info("INFO: Processed artefacts %s", processed_artefacts) + if False in processed_artefacts.values(): + return JSONResponse( + status_code=500, + content={ + "detail": ( + "Got some errors while processing the next artefacts: " + ", ".join( + [k for k, v in processed_artefacts.items() if v is False] + ) + ) + }, + ) + return JSONResponse( + status_code=200, + content={"detail": "All the artefacts have been processed successfully"}, + ) + except Exception as exc: + return JSONResponse(status_code=500, content={"detail": str(exc)}) + + +def promoter_controller(session: Session) -> dict: + """ + Orchestrate the snap promoter job + + :session: DB connection session + :return: dict with the processed cards and the status of execution + """ + family_mapping = { + FamilyName.SNAP: run_snap_promoter, + FamilyName.DEB: run_deb_promoter, + } + for family_name, promoter_function in family_mapping.items(): + artefacts = get_artefacts_by_family_name(session, family_name) + processed_artefacts = {} + for artefact in artefacts: + try: + processed_artefacts[ + f"{family_name} - {artefact.name} - {artefact.version}" + ] = True + promoter_function(session, artefact) + except Exception as exc: + processed_artefacts[ + f"{family_name} - {artefact.name} - {artefact.version}" + ] = False + logger.warning("WARNING: %s", str(exc), exc_info=True) + return processed_artefacts + + +def run_snap_promoter(session: Session, artefact: Artefact) -> None: + """ + Check snap artefacts state and move/archive them if necessary + + :session: DB connection session + :artefact_build: an ArtefactBuild object + """ + for build in artefact.builds: + arch = build.architecture + channel_map = get_channel_map_from_snapcraft( + arch=arch, + snapstore=artefact.source["store"], + snap_name=artefact.name, + ) + track = artefact.source.get("track", "latest") + + for channel_info in channel_map: + if not ( + channel_info.channel.track == track + and channel_info.channel.architecture == arch + ): + continue + + risk = channel_info.channel.risk + try: + version = channel_info.version + revision = channel_info.revision + except KeyError as exc: + logger.warning( + "No key '%s' is found. Continue processing...", + str(exc), + ) + continue + + next_risk = CHANNEL_PROMOTION_MAP[artefact.stage.name] + if ( + risk == next_risk != artefact.stage.name.lower() + and version == artefact.version + and revision == build.revision + ): + logger.info("Move artefact '%s' to the '%s' stage", artefact, next_risk) + stage = get_stage_by_name( + session, stage_name=next_risk, family=artefact.stage.family + ) + if stage: + artefact.stage = stage + session.commit() + # The artefact was promoted, so we're done + return + + +def run_deb_promoter(session: Session, artefact: Artefact) -> None: + """ + Check deb artefacts state and move/archive them if necessary + + :session: DB connection session + :artefact: an Artefact object + """ + for build in artefact.builds: + arch = build.architecture + for repo in REPOSITORY_PROMOTION_MAP: + with ArchiveManager( + arch=arch, + series=artefact.source["series"], + pocket=repo, + apt_repo=artefact.source["repo"], + ) as archivemanager: + deb_version = archivemanager.get_deb_version(artefact.name) + if deb_version is None: + logger.error( + "Cannot find deb_version with deb %s in package data", + artefact.name, + ) + continue + next_repo = REPOSITORY_PROMOTION_MAP.get(artefact.stage.name) + logger.debug( + "Artefact version: %s, deb version: %s", artefact.version, deb_version + ) + if ( + repo == next_repo != artefact.stage.name + and deb_version == artefact.version + ): + logger.info("Move artefact '%s' to the '%s' stage", artefact, next_repo) + stage = get_stage_by_name( + session, stage_name=next_repo, family=artefact.stage.family + ) + if stage: + artefact.stage = stage + session.commit() + # The artefact was promoted, so we're done + return diff --git a/backend/test_observer/controllers/router.py b/backend/test_observer/controllers/router.py index 60848046..474279fd 100644 --- a/backend/test_observer/controllers/router.py +++ b/backend/test_observer/controllers/router.py @@ -1,20 +1,42 @@ +# Copyright 2023 Canonical Ltd. +# All rights reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Written by: +# Omar Selo +# Nadzeya Hutsko from fastapi import APIRouter, Depends from sqlalchemy import text from sqlalchemy.orm import Session + from test_observer.data_access.setup import get_db from .application import version from .artefacts import artefacts from .families import families +from .promoter import promoter from .test_execution import test_execution from .test_executions import test_executions router = APIRouter() -router.include_router(artefacts.router, prefix="/v0/artefacts") +router.include_router(promoter.router) router.include_router(test_execution.router, prefix="/v1/test-execution") router.include_router(families.router, prefix="/v1/families") router.include_router(version.router, prefix="/v1/version") router.include_router(test_executions.router, prefix="/v1/test-executions") +router.include_router(artefacts.router, prefix="/v1/artefacts") @router.get("/") diff --git a/backend/test_observer/data_access/repository.py b/backend/test_observer/data_access/repository.py index b0eb75f6..d0f0824c 100644 --- a/backend/test_observer/data_access/repository.py +++ b/backend/test_observer/data_access/repository.py @@ -20,9 +20,9 @@ """Services for working with objects from DB""" -from sqlalchemy import func, and_ +from sqlalchemy import and_, func from sqlalchemy.dialects.postgresql import insert -from sqlalchemy.orm import joinedload, Session +from sqlalchemy.orm import Session, joinedload from .models_enums import FamilyName diff --git a/backend/tests/controllers/artefacts/test_artefacts.py b/backend/tests/controllers/artefacts/test_artefacts.py index 7667d160..d67b5018 100644 --- a/backend/tests/controllers/artefacts/test_artefacts.py +++ b/backend/tests/controllers/artefacts/test_artefacts.py @@ -15,128 +15,68 @@ # along with this program. If not, see . # # Written by: -# Nadzeya Hutsko # Omar Selo -"""Test snap manager API""" - - -from datetime import datetime, timedelta - +# Nadzeya Hutsko from fastapi.testclient import TestClient -from requests_mock import Mocker from sqlalchemy.orm import Session +from test_observer.data_access.models import ArtefactBuild, Environment, TestExecution -from ...helpers import create_artefact, create_artefact_builds +from tests.helpers import create_artefact, create_artefact_builds -def test_run_to_move_artefact_snap( - db_session: Session, test_client: TestClient, requests_mock: Mocker -): - """ - If artefact's current stage name is different to its stage name on - snapcraft, the artefact is moved to the next stage - """ - # Arrange - artefact = create_artefact( - db_session, - "edge", - name="core20", - version="1.1.1", - source={"store": "ubuntu"}, - created_at=datetime.utcnow(), +def test_get_artefact_builds(db_session: Session, test_client: TestClient): + artefact = create_artefact(db_session, "beta") + artefact_build = create_artefact_builds(db_session, artefact, 1)[0] + environment = Environment( + name="some-environment", architecture=artefact_build.architecture ) - create_artefact_builds(db_session, artefact) - create_artefact( - db_session, - "edge", - name="core20", - version="1.1.0", - source={"store": "ubuntu"}, - created_at=datetime.utcnow() - timedelta(days=1), - ) - requests_mock.get( - "https://api.snapcraft.io/v2/snaps/info/core20", - json={ - "channel-map": [ - { - "channel": { - "architecture": artefact.builds[0].architecture, - "name": "beta", - "released-at": "2023-05-17T12:39:07.471800+00:00", - "risk": "beta", - "track": "latest", - }, - "created-at": "2023-04-10T09:59:22.309277+00:00", - "download": { - "deltas": [], - "sha3-384": "70f0", - "size": 130830336, - "url": "https://api.snapcraft.io/api/v1/snaps/download/...", - }, - "revision": artefact.builds[0].revision, - "type": "app", - "version": "1.1.1", - }, - ] - }, + test_execution = TestExecution( + artefact_build=artefact_build, environment=environment ) + db_session.add_all([environment, test_execution]) + db_session.commit() - # Act - test_client.put("/v0/artefacts/promote") + response = test_client.get(f"/v1/artefacts/{artefact.id}/builds") - db_session.refresh(artefact) - - # Assert - assert artefact.stage.name == "beta" + assert response.status_code == 200 + assert response.json() == [ + { + "id": artefact_build.id, + "revision": artefact_build.revision, + "test_executions": [ + { + "id": test_execution.id, + "jenkins_link": test_execution.jenkins_link, + "c3_link": test_execution.c3_link, + "environment": { + "id": environment.id, + "name": environment.name, + "architecture": environment.architecture, + }, + } + ], + } + ] -def test_run_to_move_artefact_deb( - db_session: Session, test_client: TestClient, requests_mock: Mocker -): - """ - If artefact's current stage name is different to its stage name on - deb archive, the artefact is moved to the next stage - """ - # Arrange - artefact = create_artefact( - db_session, - "proposed", - name="linux-generic", - version="5.19.0.43.39", - source={"series": "kinetic", "repo": "main"}, - created_at=datetime.utcnow(), +def test_get_artefact_builds_only_latest(db_session: Session, test_client: TestClient): + artefact = create_artefact(db_session, "beta") + artefact_build1 = ArtefactBuild( + architecture="amd64", revision="1", artefact=artefact ) - create_artefact_builds(db_session, artefact) - create_artefact( - db_session, - "proposed", - name="linux-generic", - version="5.19.0.43.38", - source={"series": "kinetic", "repo": "main"}, - created_at=datetime.utcnow() - timedelta(days=1), + artefact_build2 = ArtefactBuild( + architecture="amd64", revision="2", artefact=artefact ) - - with open("tests/test_data/Packages-proposed.gz", "rb") as f: - proposed_content = f.read() - with open("tests/test_data/Packages-updates.gz", "rb") as f: - updates_content = f.read() - - for build in artefact.builds: - requests_mock.get( - "http://us.archive.ubuntu.com/ubuntu/dists/kinetic-proposed/main/" - f"binary-{build.architecture}/Packages.gz", - content=proposed_content, - ) - requests_mock.get( - "http://us.archive.ubuntu.com/ubuntu/dists/kinetic-updates/main/" - f"binary-{build.architecture}/Packages.gz", - content=updates_content, - ) - - # Act - test_client.put("/v0/artefacts/promote") - - db_session.refresh(artefact) - - # Assert - assert artefact.stage.name == "updates" + db_session.add_all([artefact_build1, artefact_build2]) + db_session.commit() + + response = test_client.get(f"/v1/artefacts/{artefact.id}/builds") + + assert response.status_code == 200 + assert response.json() == [ + { + "id": artefact_build2.id, + "revision": artefact_build2.revision, + "test_executions": [], + } + ] diff --git a/backend/tests/controllers/promoter/__init__.py b/backend/tests/controllers/promoter/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/tests/controllers/promoter/test_promoter.py b/backend/tests/controllers/promoter/test_promoter.py new file mode 100644 index 00000000..bb90c175 --- /dev/null +++ b/backend/tests/controllers/promoter/test_promoter.py @@ -0,0 +1,142 @@ +# Copyright 2023 Canonical Ltd. +# All rights reserved. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Written by: +# Nadzeya Hutsko +# Omar Selo +"""Test promoter API""" + + +from datetime import datetime, timedelta + +from fastapi.testclient import TestClient +from requests_mock import Mocker +from sqlalchemy.orm import Session + +from tests.helpers import create_artefact, create_artefact_builds + + +def test_run_to_move_artefact_snap( + db_session: Session, test_client: TestClient, requests_mock: Mocker +): + """ + If artefact's current stage name is different to its stage name on + snapcraft, the artefact is moved to the next stage + """ + # Arrange + artefact = create_artefact( + db_session, + "edge", + name="core20", + version="1.1.1", + source={"store": "ubuntu"}, + created_at=datetime.utcnow(), + ) + create_artefact_builds(db_session, artefact) + create_artefact( + db_session, + "edge", + name="core20", + version="1.1.0", + source={"store": "ubuntu"}, + created_at=datetime.utcnow() - timedelta(days=1), + ) + requests_mock.get( + "https://api.snapcraft.io/v2/snaps/info/core20", + json={ + "channel-map": [ + { + "channel": { + "architecture": artefact.builds[0].architecture, + "name": "beta", + "released-at": "2023-05-17T12:39:07.471800+00:00", + "risk": "beta", + "track": "latest", + }, + "created-at": "2023-04-10T09:59:22.309277+00:00", + "download": { + "deltas": [], + "sha3-384": "70f0", + "size": 130830336, + "url": "https://api.snapcraft.io/api/v1/snaps/download/...", + }, + "revision": artefact.builds[0].revision, + "type": "app", + "version": "1.1.1", + }, + ] + }, + ) + + # Act + test_client.put("/v0/artefacts/promote") + + db_session.refresh(artefact) + + # Assert + assert artefact.stage.name == "beta" + + +def test_run_to_move_artefact_deb( + db_session: Session, test_client: TestClient, requests_mock: Mocker +): + """ + If artefact's current stage name is different to its stage name on + deb archive, the artefact is moved to the next stage + """ + # Arrange + artefact = create_artefact( + db_session, + "proposed", + name="linux-generic", + version="5.19.0.43.39", + source={"series": "kinetic", "repo": "main"}, + created_at=datetime.utcnow(), + ) + create_artefact_builds(db_session, artefact) + create_artefact( + db_session, + "proposed", + name="linux-generic", + version="5.19.0.43.38", + source={"series": "kinetic", "repo": "main"}, + created_at=datetime.utcnow() - timedelta(days=1), + ) + + with open("tests/test_data/Packages-proposed.gz", "rb") as f: + proposed_content = f.read() + with open("tests/test_data/Packages-updates.gz", "rb") as f: + updates_content = f.read() + + for build in artefact.builds: + requests_mock.get( + "http://us.archive.ubuntu.com/ubuntu/dists/kinetic-proposed/main/" + f"binary-{build.architecture}/Packages.gz", + content=proposed_content, + ) + requests_mock.get( + "http://us.archive.ubuntu.com/ubuntu/dists/kinetic-updates/main/" + f"binary-{build.architecture}/Packages.gz", + content=updates_content, + ) + + # Act + test_client.put("/v0/artefacts/promote") + + db_session.refresh(artefact) + + # Assert + assert artefact.stage.name == "updates" diff --git a/backend/tests/data_access/test_repository.py b/backend/tests/data_access/test_repository.py index 73ef9110..56267b99 100644 --- a/backend/tests/data_access/test_repository.py +++ b/backend/tests/data_access/test_repository.py @@ -31,7 +31,7 @@ get_stage_by_name, ) -from ..helpers import create_artefact +from tests.helpers import create_artefact def test_get_stage_by_name(db_session: Session): diff --git a/backend/tests/helpers.py b/backend/tests/helpers.py index 4443e034..6dbbae76 100644 --- a/backend/tests/helpers.py +++ b/backend/tests/helpers.py @@ -23,7 +23,7 @@ def create_artefact(db_session: Session, stage_name: str, **kwargs) -> Artefact: def create_artefact_builds( db_session: Session, artefact: Artefact, num_builds: int = 5 -): +) -> list[ArtefactBuild]: """ Create a number of ArtefactBuild instances for an Artefact and add them to the database