From 61a2a1cee1ab74ca420cc46008488e85014a9edb Mon Sep 17 00:00:00 2001 From: nadzyah Date: Thu, 15 Jun 2023 12:38:58 +0300 Subject: [PATCH 1/6] Fix the get_artefacts_by_family_name function --- backend/test_observer/data_access/repository.py | 2 ++ backend/test_observer/external_apis/archive.py | 3 +-- backend/tests/data_access/test_repository.py | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) 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/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( From eb8c56517315a373cc5526637f393a3c8880e536 Mon Sep 17 00:00:00 2001 From: nadzyah Date: Fri, 16 Jun 2023 16:19:46 +0300 Subject: [PATCH 2/6] Return only latest artefacts to frontend --- backend/test_observer/controllers/families/families.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/test_observer/controllers/families/families.py b/backend/test_observer/controllers/families/families.py index 01f5f2be..986576f9 100644 --- a/backend/test_observer/controllers/families/families.py +++ b/backend/test_observer/controllers/families/families.py @@ -14,6 +14,7 @@ from sqlalchemy.orm import Session from test_observer.data_access.models import Family 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 +28,14 @@ def read_family(family_name: str, db: Session = Depends(get_db)): if family is None: raise HTTPException(status_code=404, detail="Family not found") + latest_artefacts = get_artefacts_by_family_name(db, family_name, latest_only=True) + + stage_artefact_dict = {stage.id: [] for stage in family.stages} + for artefact in latest_artefacts: + stage_artefact_dict[artefact.stage_id].append(artefact) + + for stage in family.stages: + stage.artefacts = stage_artefact_dict[stage.id] + family.stages = sorted(family.stages, key=lambda x: x.position) return family From 81b51730f41979e0d7df83f4f4c2b8861211ff1f Mon Sep 17 00:00:00 2001 From: nadzyah Date: Fri, 16 Jun 2023 16:30:06 +0300 Subject: [PATCH 3/6] Fix style issues --- backend/test_observer/controllers/families/families.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/test_observer/controllers/families/families.py b/backend/test_observer/controllers/families/families.py index 986576f9..98ac65f3 100644 --- a/backend/test_observer/controllers/families/families.py +++ b/backend/test_observer/controllers/families/families.py @@ -28,9 +28,9 @@ def read_family(family_name: str, db: Session = Depends(get_db)): if family is None: raise HTTPException(status_code=404, detail="Family not found") - latest_artefacts = get_artefacts_by_family_name(db, family_name, latest_only=True) + latest_artefacts = get_artefacts_by_family_name(db, family_name) # type: ignore - stage_artefact_dict = {stage.id: [] for stage in family.stages} + stage_artefact_dict: dict = {stage.id: [] for stage in family.stages} for artefact in latest_artefacts: stage_artefact_dict[artefact.stage_id].append(artefact) From 247e77cff37d52386c3b588e365e9eec0f03985a Mon Sep 17 00:00:00 2001 From: nadzyah Date: Mon, 19 Jun 2023 14:33:39 +0300 Subject: [PATCH 4/6] Use groupby from itertools and check family name --- .../controllers/families/families.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/backend/test_observer/controllers/families/families.py b/backend/test_observer/controllers/families/families.py index 98ac65f3..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,9 +13,14 @@ # 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 @@ -28,14 +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") - latest_artefacts = get_artefacts_by_family_name(db, family_name) # type: ignore + family_name_enum_value = FamilyName(family_name) + latest_artefacts = get_artefacts_by_family_name(db, family_name_enum_value) - stage_artefact_dict: dict = {stage.id: [] for stage in family.stages} - for artefact in latest_artefacts: - stage_artefact_dict[artefact.stage_id].append(artefact) + stage_artefact_dict = groupby(latest_artefacts, lambda art: art.stage) - for stage in family.stages: - stage.artefacts = stage_artefact_dict[stage.id] + for stage, artefacts in stage_artefact_dict: + stage.artefacts = list(artefacts) family.stages = sorted(family.stages, key=lambda x: x.position) return family From e9590716abcb841beff1b01994bbfe981cdc1ab9 Mon Sep 17 00:00:00 2001 From: nadzyah Date: Mon, 19 Jun 2023 14:33:53 +0300 Subject: [PATCH 5/6] Add test_families --- .../controllers/families/test_families.py | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 backend/tests/controllers/families/test_families.py diff --git a/backend/tests/controllers/families/test_families.py b/backend/tests/controllers/families/test_families.py new file mode 100644 index 00000000..eddd4b12 --- /dev/null +++ b/backend/tests/controllers/families/test_families.py @@ -0,0 +1,92 @@ +# 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 requests_mock import Mocker +from sqlalchemy.orm import Session + +from tests.helpers import create_artefact + + +def test_retreive_family( + db_session: Session, test_client: TestClient, requests_mock: Mocker +): + """ + 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)), + ) + ) + stage = artefacts[0].stage + + # Act + response = test_client.get("/v1/families/snap") + + # Assert + assert response.json() == { + "id": stage.family_id, + "name": 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": []}, + ], + } From 154958585d649691c1fec60fbbf62e59e0c76592 Mon Sep 17 00:00:00 2001 From: nadzyah Date: Mon, 19 Jun 2023 14:39:10 +0300 Subject: [PATCH 6/6] Fix style issues --- backend/tests/controllers/families/test_families.py | 11 ++++------- backend/tests/helpers.py | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/backend/tests/controllers/families/test_families.py b/backend/tests/controllers/families/test_families.py index eddd4b12..0e669788 100644 --- a/backend/tests/controllers/families/test_families.py +++ b/backend/tests/controllers/families/test_families.py @@ -22,15 +22,12 @@ from random import randint from fastapi.testclient import TestClient -from requests_mock import Mocker from sqlalchemy.orm import Session from tests.helpers import create_artefact -def test_retreive_family( - db_session: Session, test_client: TestClient, requests_mock: Mocker -): +def test_retreive_family(db_session: Session, test_client: TestClient): """ We should get json for a specific family with its stages and artefacts """ @@ -52,15 +49,15 @@ def test_retreive_family( version=str(randint(1, 100)), ) ) - stage = artefacts[0].stage + snap_stage = artefacts[0].stage # Act response = test_client.get("/v1/families/snap") # Assert assert response.json() == { - "id": stage.family_id, - "name": stage.family.name, + "id": snap_stage.family_id, + "name": snap_stage.family.name, "stages": [ { "id": 1, 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(