From 1837a64de5d0c1ddc32edb316a05860224011637 Mon Sep 17 00:00:00 2001
From: Karel van Klink <karel.vanklink@geant.org>
Date: Fri, 1 Dec 2023 17:39:35 +0100
Subject: [PATCH] add test fixture

---
 lso/playbook.py        |  5 ++++-
 lso/routes/ip_trunk.py | 22 ++++++++++++++++------
 lso/routes/playbook.py |  6 ++++--
 lso/routes/router.py   |  6 ++++--
 test/conftest.py       | 10 ++++++++++
 5 files changed, 38 insertions(+), 11 deletions(-)

diff --git a/lso/playbook.py b/lso/playbook.py
index 4b6dcfe..25dc8ee 100644
--- a/lso/playbook.py
+++ b/lso/playbook.py
@@ -12,7 +12,7 @@ import ansible_runner
 import requests
 import xmltodict
 from dictdiffer import diff
-from fastapi import status
+from fastapi import Response, status
 from pydantic import BaseModel, HttpUrl
 
 from lso import config
@@ -181,6 +181,7 @@ def run_playbook(
     extra_vars: dict[str, Any],
     inventory: dict[str, Any] | str,
     callback: HttpUrl,
+    response: Response,
 ) -> PlaybookLaunchResponse:
     """Run an Ansible playbook against a specified inventory.
 
@@ -193,10 +194,12 @@ def run_playbook(
     :rtype: :class:`PlaybookLaunchResponse`
     """
     if not Path.exists(playbook_path):
+        response.status_code = status.HTTP_404_NOT_FOUND
         msg = f"Filename '{playbook_path}' does not exist."
         return playbook_launch_error(msg)
 
     if not ansible_runner.utils.isinventory(inventory):
+        response.status_code = status.HTTP_400_BAD_REQUEST
         msg = "Invalid inventory provided. Should be a string, or JSON object."
         return playbook_launch_error(msg)
 
diff --git a/lso/routes/ip_trunk.py b/lso/routes/ip_trunk.py
index 086ec21..3ec7e59 100644
--- a/lso/routes/ip_trunk.py
+++ b/lso/routes/ip_trunk.py
@@ -1,6 +1,6 @@
 """Routes for handling events related to the IP trunk service."""
 
-from fastapi import APIRouter
+from fastapi import APIRouter, Response
 from pydantic import BaseModel, HttpUrl
 
 from lso.playbook import PlaybookLaunchResponse, get_playbook_path, run_playbook
@@ -72,7 +72,7 @@ class IPTrunkDeleteParams(IPTrunkParams):
 
 
 @router.post("/")
-def provision_ip_trunk(params: IPTrunkProvisioningParams) -> PlaybookLaunchResponse:
+def provision_ip_trunk(params: IPTrunkProvisioningParams, response: Response) -> PlaybookLaunchResponse:
     """Launch a playbook to provision a new IP trunk service.
 
     The response will contain either a job ID, or error information.
@@ -80,6 +80,7 @@ def provision_ip_trunk(params: IPTrunkProvisioningParams) -> PlaybookLaunchRespo
     :param params: The parameters that define the new subscription object that
         is to be deployed.
     :type params: :class:`IPTrunkProvisioningParams`
+    :param Response response: A FastAPI response used for returning error codes if needed
     :return: Response from the Ansible runner, including a run ID.
     :rtype: :class:`lso.playbook.PlaybookLaunchResponse`
     """
@@ -99,15 +100,17 @@ def provision_ip_trunk(params: IPTrunkProvisioningParams) -> PlaybookLaunchRespo
         f"{params.subscription['iptrunk']['iptrunk_sides'][1]['iptrunk_side_node']['router_fqdn']}\n",
         extra_vars=extra_vars,
         callback=params.callback,
+        response=response,
     )
 
 
 @router.put("/")
-def modify_ip_trunk(params: IPTrunkModifyParams) -> PlaybookLaunchResponse:
+def modify_ip_trunk(params: IPTrunkModifyParams, response: Response) -> PlaybookLaunchResponse:
     """Launch a playbook that modifies an existing IP trunk service.
 
     :param params: The parameters that define the change in configuration.
     :type params: :class:`IPTrunkModifyParams`
+    :param Response response: A FastAPI response used for returning error codes if needed
     :return: Response from the Ansible runner, including a run ID.
     :rtype: :class:`lso.playbook.PlaybookLaunchResponse`
     """
@@ -127,16 +130,18 @@ def modify_ip_trunk(params: IPTrunkModifyParams) -> PlaybookLaunchResponse:
         f"{params.subscription['iptrunk']['iptrunk_sides'][1]['iptrunk_side_node']['router_fqdn']}\n",
         extra_vars=extra_vars,
         callback=params.callback,
+        response=response,
     )
 
 
 @router.delete("/")
-def delete_ip_trunk(params: IPTrunkDeleteParams) -> PlaybookLaunchResponse:
+def delete_ip_trunk(params: IPTrunkDeleteParams, response: Response) -> PlaybookLaunchResponse:
     """Launch a playbook that deletes an existing IP trunk service.
 
     :param params: Parameters that define the subscription that should get
         terminated.
     :type params: :class:`IPTrunkDeleteParams`
+    :param Response response: A FastAPI response used for returning error codes if needed
     :return: Response from the Ansible runner, including a run ID.
     :rtype: :class:`lso.playbook.PlaybookLaunchResponse`
     """
@@ -156,16 +161,18 @@ def delete_ip_trunk(params: IPTrunkDeleteParams) -> PlaybookLaunchResponse:
         f"{params.subscription['iptrunk']['iptrunk_sides'][1]['iptrunk_side_node']['router_fqdn']}\n",
         extra_vars=extra_vars,
         callback=params.callback,
+        response=response,
     )
 
 
 @router.post("/perform_check")
-def check_ip_trunk(params: IPTrunkCheckParams) -> PlaybookLaunchResponse:
+def check_ip_trunk(params: IPTrunkCheckParams, response: Response) -> PlaybookLaunchResponse:
     """Launch a playbook that performs a check on an IP trunk service instance.
 
     :param params: Parameters that define the check that is going to be
         executed, including on which relevant subscription.
     :type params: :class:`IPTrunkCheckParams`
+    :param Response response: A FastAPI response used for returning error codes if needed
     :return: Response from the Ansible runner, including a run ID.
     :rtype: :class:`lso.playbook.PlaybookLaunchResponse`
     """
@@ -178,17 +185,19 @@ def check_ip_trunk(params: IPTrunkCheckParams) -> PlaybookLaunchResponse:
         inventory=params.subscription["iptrunk"]["iptrunk_sides"][0]["iptrunk_side_node"]["router_fqdn"],
         extra_vars=extra_vars,
         callback=params.callback,
+        response=response,
     )
 
 
 @router.post("/migrate")
-def migrate_ip_trunk(params: IPTrunkMigrationParams) -> PlaybookLaunchResponse:
+def migrate_ip_trunk(params: IPTrunkMigrationParams, response: Response) -> PlaybookLaunchResponse:
     """Launch a playbook to provision a new IP trunk service.
 
     The response will contain either a job ID, or error information.
 
     :param params: The parameters that define the new subscription object that is to be migrated.
     :type params: :class:`IPTrunkMigrationParams`
+    :param Response response: A FastAPI response used for returning error codes if needed
     :return: Response from the Ansible runner, including a run ID.
     :rtype: :class:`lso.playbook.PlaybookLaunchResponse`
     """
@@ -212,4 +221,5 @@ def migrate_ip_trunk(params: IPTrunkMigrationParams) -> PlaybookLaunchResponse:
         f"{params.new_side['new_node']['router']['router_fqdn']}\n",
         extra_vars=extra_vars,
         callback=params.callback,
+        response=response,
     )
diff --git a/lso/routes/playbook.py b/lso/routes/playbook.py
index 670848e..8827ea5 100644
--- a/lso/routes/playbook.py
+++ b/lso/routes/playbook.py
@@ -2,7 +2,7 @@
 
 from typing import Any
 
-from fastapi import APIRouter
+from fastapi import APIRouter, Response
 from pydantic import BaseModel, HttpUrl
 
 from lso.playbook import PlaybookLaunchResponse, get_playbook_path, run_playbook
@@ -29,12 +29,13 @@ class PlaybookRunParams(BaseModel):
 
 
 @router.post("/")
-def run_playbook_endpoint(params: PlaybookRunParams) -> PlaybookLaunchResponse:
+def run_playbook_endpoint(params: PlaybookRunParams, response: Response) -> PlaybookLaunchResponse:
     """Launch an Ansible playbook to modify or deploy a subscription instance.
 
     The response will contain either a job ID, or error information.
 
     :param params :class:`PlaybookRunParams`: Parameters for executing a playbook.
+    :param Response response: A FastAPI response used for returning error codes if needed
     :return: Response from the Ansible runner, including a run ID.
     :rtype: :class:`lso.playbook.PlaybookLaunchResponse`
     """
@@ -43,4 +44,5 @@ def run_playbook_endpoint(params: PlaybookRunParams) -> PlaybookLaunchResponse:
         extra_vars=params.extra_vars,
         inventory=params.inventory,
         callback=params.callback,
+        response=response,
     )
diff --git a/lso/routes/router.py b/lso/routes/router.py
index 6753f63..6861e5b 100644
--- a/lso/routes/router.py
+++ b/lso/routes/router.py
@@ -1,6 +1,6 @@
 """Routes for handling device/base_config-related requests."""
 
-from fastapi import APIRouter
+from fastapi import APIRouter, Response
 from pydantic import BaseModel, HttpUrl
 
 from lso import playbook
@@ -35,11 +35,12 @@ class NodeProvisioningParams(BaseModel):
 
 
 @router.post("/")
-async def provision_node(params: NodeProvisioningParams) -> playbook.PlaybookLaunchResponse:
+async def provision_node(params: NodeProvisioningParams, response: Response) -> playbook.PlaybookLaunchResponse:
     """Launch a playbook to provision a new node. The response will contain either a job id or error information.
 
     :param params: Parameters for provisioning a new node
     :type params: :class:`NodeProvisioningParams`
+    :param Response response: A FastAPI response used for returning error codes if needed
     :return: Response from the Ansible runner, including a run ID.
     :rtype: :class:`lso.playbook.PlaybookLaunchResponse`
     """
@@ -55,4 +56,5 @@ async def provision_node(params: NodeProvisioningParams) -> playbook.PlaybookLau
         inventory=f"{params.subscription['router']['router_fqdn']}",
         extra_vars=extra_vars,
         callback=params.callback,
+        response=response,
     )
diff --git a/test/conftest.py b/test/conftest.py
index cb4725e..13407eb 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -4,6 +4,7 @@ import tempfile
 from collections.abc import Callable, Generator
 from io import StringIO
 from typing import Any
+from unittest.mock import patch
 
 import pytest
 from faker import Faker
@@ -55,3 +56,12 @@ def client(data_config_filename: str) -> TestClient:
 @pytest.fixture(scope="session")
 def faker() -> Faker:
     return Faker(locale="en_GB")
+
+
+@pytest.fixture(scope="session", autouse=True)
+def path_exists() -> bool:
+    """A patch that prevents all test requests from failing since the playbook file does not exist on the filesystem."""
+    with patch("pathlib.Path.exists") as mock_patch:
+        mock_patch.return_value = True
+
+        yield mock_patch
-- 
GitLab