From 62809eb5c320fa1d05cb11f930333eb6a1143f64 Mon Sep 17 00:00:00 2001 From: Nadzya Hut <84857215+nadzyah@users.noreply.github.com> Date: Mon, 19 Jun 2023 14:40:21 +0200 Subject: [PATCH] Return only latest artefacts to frontend (#27) * Fix the get_artefacts_by_family_name function * Return only latest artefacts to frontend * Fix style issues * Use groupby from itertools and check family name * Add test_families * Fix style issues --- .../controllers/families/families.py | 17 ++++ .../test_observer/data_access/repository.py | 2 + .../test_observer/external_apis/archive.py | 3 +- .../controllers/families/test_families.py | 89 +++++++++++++++++++ backend/tests/data_access/test_repository.py | 5 +- backend/tests/helpers.py | 2 +- 6 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 backend/tests/controllers/families/test_families.py diff --git a/backend/test_observer/controllers/families/families.py b/backend/test_observer/controllers/families/families.py index 01f5f2be..645010ac 100644 --- a/backend/test_observer/controllers/families/families.py +++ b/backend/test_observer/controllers/families/families.py @@ -1,3 +1,6 @@ +# Copyright 2023 Canonical Ltd. +# All rights reserved. +# # 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 @@ -10,10 +13,16 @@ # Nadzeya Hutsko # Omar Abou Selo + +from itertools import groupby + + from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from test_observer.data_access.models import Family +from test_observer.data_access.models_enums import FamilyName from test_observer.data_access.setup import get_db +from test_observer.data_access.repository import get_artefacts_by_family_name from .models import FamilyDTO @@ -27,5 +36,13 @@ def read_family(family_name: str, db: Session = Depends(get_db)): if family is None: raise HTTPException(status_code=404, detail="Family not found") + family_name_enum_value = FamilyName(family_name) + latest_artefacts = get_artefacts_by_family_name(db, family_name_enum_value) + + stage_artefact_dict = groupby(latest_artefacts, lambda art: art.stage) + + for stage, artefacts in stage_artefact_dict: + stage.artefacts = list(artefacts) + family.stages = sorted(family.stages, key=lambda x: x.position) return family diff --git a/backend/test_observer/data_access/repository.py b/backend/test_observer/data_access/repository.py index 9fddc6bd..b0eb75f6 100644 --- a/backend/test_observer/data_access/repository.py +++ b/backend/test_observer/data_access/repository.py @@ -69,6 +69,8 @@ def get_artefacts_by_family_name( Artefact.source, func.max(Artefact.created_at).label("max_created"), ) + .join(Stage) + .filter(Stage.family.has(Family.name == family_name)) .group_by(Artefact.stage_id, Artefact.name, Artefact.source) .subquery() ) diff --git a/backend/test_observer/external_apis/archive.py b/backend/test_observer/external_apis/archive.py index c575f2ea..f1c31e59 100644 --- a/backend/test_observer/external_apis/archive.py +++ b/backend/test_observer/external_apis/archive.py @@ -90,8 +90,7 @@ def get_deb_version(self, debname: str) -> str | None: pkg_ver = re.search("Version: (.+)", pkg) if pkg_name and pkg_ver: # Periods in json keys are bad, convert them to _ - pkg_name_key = pkg_name.group(1).replace(".", "_") - json_data[pkg_name_key] = pkg_ver.group(1) + json_data[pkg_name.group(1)] = pkg_ver.group(1) return json_data.get(debname) def _create_download_and_extract_filepaths(self) -> None: diff --git a/backend/tests/controllers/families/test_families.py b/backend/tests/controllers/families/test_families.py new file mode 100644 index 00000000..0e669788 --- /dev/null +++ b/backend/tests/controllers/families/test_families.py @@ -0,0 +1,89 @@ +# 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 + + +from datetime import datetime, timedelta +from random import randint + +from fastapi.testclient import TestClient +from sqlalchemy.orm import Session + +from tests.helpers import create_artefact + + +def test_retreive_family(db_session: Session, test_client: TestClient): + """ + We should get json for a specific family with its stages and artefacts + """ + # Arrange + artefact_name_stage_pair = [ + ("core20", "edge", datetime.utcnow()), + ("oem-jammy", "proposed", datetime.utcnow()), + ("core20", "edge", datetime.utcnow() - timedelta(days=10)), + ("core20", "beta", datetime.utcnow() - timedelta(days=20)), + ] + artefacts = [] + for name, stage, created_at in artefact_name_stage_pair: + artefacts.append( + create_artefact( + db_session, + stage, + name=name, + created_at=created_at, + version=str(randint(1, 100)), + ) + ) + snap_stage = artefacts[0].stage + + # Act + response = test_client.get("/v1/families/snap") + + # Assert + assert response.json() == { + "id": snap_stage.family_id, + "name": snap_stage.family.name, + "stages": [ + { + "id": 1, + "name": "edge", + "artefacts": [ + { + "id": artefacts[0].id, + "name": artefacts[0].name, + "version": artefacts[0].version, + "source": artefacts[0].source, + } + ], + }, + { + "id": 2, + "name": "beta", + "artefacts": [ + { + "id": artefacts[-1].id, + "name": artefacts[-1].name, + "version": artefacts[-1].version, + "source": artefacts[-1].source, + } + ], + }, + {"id": 3, "name": "candidate", "artefacts": []}, + {"id": 4, "name": "stable", "artefacts": []}, + ], + } diff --git a/backend/tests/data_access/test_repository.py b/backend/tests/data_access/test_repository.py index 5263e082..73ef9110 100644 --- a/backend/tests/data_access/test_repository.py +++ b/backend/tests/data_access/test_repository.py @@ -85,14 +85,15 @@ def test_get_artefacts_by_family_name(db_session: Session): def test_get_artefacts_by_family_name_latest(db_session: Session): - """We should get a only latest artefacts in each stage""" + """We should get a only latest artefacts in each stage for the specified family""" # Arrange artefact_name_stage_pair = [ ("core20", "edge", datetime.utcnow()), + ("oem-jammy", "proposed", datetime.utcnow()), ("core20", "edge", datetime.utcnow() - timedelta(days=10)), ("core20", "beta", datetime.utcnow() - timedelta(days=20)), ] - expected_artefacts = {artefact_name_stage_pair[0], artefact_name_stage_pair[2]} + expected_artefacts = {artefact_name_stage_pair[0], artefact_name_stage_pair[-1]} for name, stage, created_at in artefact_name_stage_pair: create_artefact( diff --git a/backend/tests/helpers.py b/backend/tests/helpers.py index 4c9f581e..4443e034 100644 --- a/backend/tests/helpers.py +++ b/backend/tests/helpers.py @@ -6,7 +6,7 @@ from test_observer.data_access.models_enums import FamilyName -def create_artefact(db_session: Session, stage_name: str, **kwargs): +def create_artefact(db_session: Session, stage_name: str, **kwargs) -> Artefact: """Create a dummy artefact""" stage = db_session.query(Stage).filter(Stage.name == stage_name).first() artefact = Artefact(