diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 173d312041..d101e6cae2 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 62.2.1
+current_version = 62.2.5
commit = True
tag = True
tag_name = v{new_version}
diff --git a/cg/__init__.py b/cg/__init__.py
index 8b8595a241..7870f9e682 100644
--- a/cg/__init__.py
+++ b/cg/__init__.py
@@ -1,2 +1,2 @@
__title__ = "cg"
-__version__ = "62.2.1"
+__version__ = "62.2.5"
diff --git a/cg/apps/invoice/render.py b/cg/apps/invoice/render.py
index f6a835bb66..e73278f9db 100644
--- a/cg/apps/invoice/render.py
+++ b/cg/apps/invoice/render.py
@@ -1,8 +1,11 @@
import datetime as dt
+from pathlib import Path
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Border, Font, PatternFill, Side
-from pkg_resources import resource_filename
+
+from cg.constants import FileExtensions
+from cg.utils.files import get_project_root_dir
def render_xlsx(data: dict) -> Workbook:
@@ -34,11 +37,17 @@ def render_xlsx(data: dict) -> Workbook:
}]
}
"""
- pkg_dir = __name__.rpartition(".")[0]
+ project_root_dir = get_project_root_dir()
sample_type = "pool" if data["pooled_samples"] else "sample"
costcenter = data["cost_center"]
- template_path = resource_filename(pkg_dir, f"templates/{costcenter}_{sample_type}_invoice.xlsx")
- workbook = load_workbook(template_path)
+ template_path = Path(
+ project_root_dir,
+ "apps",
+ "invoice",
+ "templates",
+ f"{costcenter}_{sample_type}_invoice{FileExtensions.XLSX}",
+ )
+ workbook = load_workbook(template_path.as_posix())
if data["pooled_samples"]:
worksheet = workbook["Bilaga Prover"]
worksheet["C1"] = costcenter.upper()
diff --git a/cg/cli/post_process/post_process.py b/cg/cli/post_process/post_process.py
index 8c4560fd96..93d43b540a 100644
--- a/cg/cli/post_process/post_process.py
+++ b/cg/cli/post_process/post_process.py
@@ -35,5 +35,4 @@ def post_process_sequencing_run(context: CGConfig, run_name: str, dry_run: bool)
post_processing_service.post_process(run_name=run_name, dry_run=dry_run)
-post_process_group: click.Group
post_process_group.add_command(post_process_sequencing_run)
diff --git a/cg/clients/freshdesk/freshdesk_client.py b/cg/clients/freshdesk/freshdesk_client.py
index 535ef06821..9a9650f9b1 100644
--- a/cg/clients/freshdesk/freshdesk_client.py
+++ b/cg/clients/freshdesk/freshdesk_client.py
@@ -1,19 +1,13 @@
-import logging
from http import HTTPStatus
+from pathlib import Path
-from pydantic import ValidationError
-from requests import RequestException, Response, Session
+from requests import Session
from requests.adapters import HTTPAdapter
from urllib3 import Retry
from cg.clients.freshdesk.constants import EndPoints
-from cg.clients.freshdesk.exceptions import (
- FreshdeskAPIException,
- FreshdeskModelException,
-)
-from cg.clients.freshdesk.models import TicketCreate, TicketResponse
-
-LOG = logging.getLogger(__name__)
+from cg.clients.freshdesk.models import ReplyCreate, TicketCreate, TicketResponse
+from cg.clients.freshdesk.utils import handle_client_errors, prepare_attachments
class FreshdeskClient:
@@ -24,33 +18,23 @@ def __init__(self, base_url: str, api_key: str):
self.api_key = api_key
self.session = self._get_session()
- def create_ticket(self, ticket: TicketCreate) -> TicketResponse:
- """Create a ticket."""
- LOG.debug(ticket.model_dump_json())
- try:
- response: Response = self.session.post(
- url=self._url(EndPoints.TICKETS),
- json=ticket.model_dump(exclude_none=True),
- )
- response.raise_for_status()
- return TicketResponse.model_validate(response.json())
- except RequestException as error:
- LOG.error(f"Could not create ticket: {error}")
- raise FreshdeskAPIException(error) from error
- except ValidationError as error:
- LOG.error(f"Response from Freshdesk does not fit model: {TicketResponse}.\n{error}")
- raise FreshdeskModelException(error) from error
+ @handle_client_errors
+ def create_ticket(self, ticket: TicketCreate, attachments: list[Path] = None) -> TicketResponse:
+ """Create a ticket with multipart form data."""
+ multipart_data = ticket.to_multipart_data()
+ files = prepare_attachments(attachments) if attachments else None
- def _url(self, endpoint: str) -> str:
- """Get the full URL for the endpoint."""
- return f"{self.base_url}{endpoint}"
+ response = self.session.post(
+ url=f"{self.base_url}{EndPoints.TICKETS}", data=multipart_data, files=files
+ )
+ response.raise_for_status()
+ return TicketResponse.model_validate(response.json())
+ @handle_client_errors
def _get_session(self) -> Session:
- """Configures and sets a session to be used for requests."""
session = Session()
- self._configure_retries(session)
session.auth = (self.api_key, "X")
- session.headers.update({"Content-Type": "application/json"})
+ self._configure_retries(session)
return session
@staticmethod
@@ -69,3 +53,14 @@ def _configure_retries(session: Session) -> None:
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("https://", adapter)
+
+ @handle_client_errors
+ def reply_to_ticket(self, reply: ReplyCreate, attachments: list[Path] = None) -> None:
+ """Send a reply to an existing ticket in Freshdesk."""
+ url = f"{self.base_url}{EndPoints.TICKETS}/{reply.ticket_number}/reply"
+
+ files = prepare_attachments(attachments) if attachments else None
+ multipart_data = reply.to_multipart_data()
+
+ response = self.session.post(url=url, data=multipart_data, files=files)
+ response.raise_for_status()
diff --git a/cg/clients/freshdesk/models.py b/cg/clients/freshdesk/models.py
index 7a0cae7f43..aa0be87ab4 100644
--- a/cg/clients/freshdesk/models.py
+++ b/cg/clients/freshdesk/models.py
@@ -1,6 +1,6 @@
-from datetime import datetime
+from typing import Tuple, Union
-from pydantic import BaseModel
+from pydantic import BaseModel, EmailStr, Field
from cg.clients.freshdesk.constants import Priority, Source, Status
@@ -8,9 +8,10 @@
class TicketCreate(BaseModel):
"""Freshdesk ticket."""
- attachments: list[dict[str, str]] = []
- email: str
+ attachments: list[str | bytes] = Field(default_factory=list)
+ email: EmailStr
email_config_id: int | None = None
+ description: str
name: str
priority: int = Priority.LOW
source: int = Source.EMAIL
@@ -18,19 +19,43 @@ class TicketCreate(BaseModel):
subject: str
tags: list[str] = []
type: str | None = None
+ custom_fields: dict[str, str | int | float | None] = Field(default_factory=dict)
+
+ def to_multipart_data(self) -> list[Tuple[str, str | int | bytes]]:
+ """Custom converter to multipart form data."""
+ multipart_data = []
+
+ for field, value in self.model_dump(exclude_none=True).items():
+ if isinstance(value, list):
+ multipart_data.extend([(f"{field}[]", v) for v in value])
+ elif isinstance(value, dict):
+ multipart_data.extend([(f"{field}[{k}]", v) for k, v in value.items()])
+ else:
+ multipart_data.append((field, value))
+
+ return multipart_data
class TicketResponse(BaseModel):
- """Freshdesk ticket response."""
+ """Response from Freshdesk"""
- attachments: list[dict[str, str]] = []
- created_at: datetime | None = None
- email: str
id: int
- name: str | None = None
- priority: int
- source: int
- status: int
+ description: str
subject: str
- tags: list[str] = []
- type: str | None = None
+ to_emails: list[str] | None = None
+ status: int
+ priority: int
+
+
+class ReplyCreate(BaseModel):
+ """Reply to a ticket."""
+
+ ticket_number: str
+ body: str
+
+ def to_multipart_data(self) -> list[Tuple[str, Union[str, int, bytes]]]:
+ """Custom converter to multipart form data."""
+ multipart_data = [
+ ("body", self.body),
+ ]
+ return multipart_data
diff --git a/cg/clients/freshdesk/utils.py b/cg/clients/freshdesk/utils.py
new file mode 100644
index 0000000000..2e78b23cf9
--- /dev/null
+++ b/cg/clients/freshdesk/utils.py
@@ -0,0 +1,86 @@
+import logging
+from functools import wraps
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from pydantic import ValidationError
+from requests import ConnectionError, HTTPError
+from requests.exceptions import MissingSchema
+
+from cg.clients.freshdesk.exceptions import (
+ FreshdeskAPIException,
+ FreshdeskModelException,
+)
+from cg.constants.constants import FileFormat
+from cg.io.controller import WriteFile
+
+LOG = logging.getLogger(__name__)
+
+
+def extract_error_detail(error):
+ """Extract detailed error information from HTTPError."""
+ if error.response is not None:
+ try:
+ return error.response.json()
+ except ValueError:
+ return error.response.text
+ return None
+
+
+def log_and_raise(exception_class, message, error):
+ """Log the error and raise the appropriate exception."""
+ LOG.error(message)
+ raise exception_class(error) from error
+
+
+def handle_client_errors(func):
+ """Decorator to handle and log errors in Freshdesk client methods."""
+
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except HTTPError as error:
+ error_detail = extract_error_detail(error)
+ log_and_raise(
+ FreshdeskAPIException,
+ f"Failed request to Freshdesk: {error} - Status code: "
+ f"{error.response.status_code if error.response else 'N/A'}, Details: {error_detail}",
+ error,
+ )
+ except (MissingSchema, ConnectionError) as error:
+ log_and_raise(FreshdeskAPIException, f"Request to Freshdesk failed: {error}", error)
+ except ValidationError as error:
+ log_and_raise(
+ FreshdeskModelException, f"Invalid response from Freshdesk: {error}", error
+ )
+ except ValueError as error:
+ log_and_raise(FreshdeskAPIException, f"Operation failed: {error}", error)
+ except Exception as error:
+ log_and_raise(
+ FreshdeskAPIException, f"Unexpected error in Freshdesk client: {error}", error
+ )
+
+ return wrapper
+
+
+def prepare_attachments(attachments: list[Path]) -> list[tuple[str, tuple[str, bytes]]]:
+ """Prepare the attachments for a request."""
+ return [
+ ("attachments[]", (attachment.name, open(attachment, "rb"))) for attachment in attachments
+ ]
+
+
+def create_temp_attachment_file(content: dict, file_name: Path) -> TemporaryDirectory:
+ """Create a file-based attachment."""
+ if content and file_name:
+ directory = TemporaryDirectory()
+ WriteFile.write_file_from_content(
+ content=content,
+ file_format=FileFormat.JSON,
+ file_path=Path(directory.name, "order.json"),
+ )
+ return directory
+ else:
+ LOG.error("Content or file path is None. Cannot create file attachment.")
+ raise ValueError("Both content and file path must be provided and cannot be None")
diff --git a/cg/constants/constants.py b/cg/constants/constants.py
index 5e1f7170dc..c7ae23d812 100644
--- a/cg/constants/constants.py
+++ b/cg/constants/constants.py
@@ -223,6 +223,7 @@ class FileExtensions(StrEnum):
TSV: str = ".tsv"
TXT: str = ".txt"
VCF: str = ".vcf"
+ XLSX: str = ".xlsx"
XML: str = ".xml"
YAML: str = ".yaml"
diff --git a/cg/constants/report.py b/cg/constants/report.py
index 86a8890bdb..48716dbc1f 100644
--- a/cg/constants/report.py
+++ b/cg/constants/report.py
@@ -1,15 +1,17 @@
"""Delivery report constants."""
-from importlib.resources import files
from pathlib import Path
from cg.constants import DataDelivery
from cg.constants.constants import CancerAnalysisType, FileExtensions, Workflow
from cg.constants.subject import Sex
+from cg.utils.files import get_project_root_dir
+
+project_root_dir: Path = get_project_root_dir()
DELIVERY_REPORT_FILE_NAME: str = f"delivery-report{FileExtensions.HTML}"
SWEDAC_LOGO_PATH = Path(
- files("cg"), "meta", "report", "templates", "static", "images", "SWEDAC_logo.png"
+ project_root_dir, "meta", "report", "templates", "static", "images", "SWEDAC_logo.png"
)
BALSAMIC_REPORT_ACCREDITED_PANELS: list[str] = ["gmsmyeloid"]
diff --git a/cg/meta/orders/api.py b/cg/meta/orders/api.py
index 0222d4f971..228a9570e0 100644
--- a/cg/meta/orders/api.py
+++ b/cg/meta/orders/api.py
@@ -10,7 +10,6 @@
import logging
from cg.apps.lims import LimsAPI
-from cg.apps.osticket import OsTicket
from cg.meta.orders.ticket_handler import TicketHandler
from cg.models.orders.order import OrderIn, OrderType
from cg.services.orders.submitters.order_submitter_registry import (
@@ -28,13 +27,13 @@ def __init__(
self,
lims: LimsAPI,
status: Store,
- osticket: OsTicket,
+ ticket_handler: TicketHandler,
submitter_registry: OrderSubmitterRegistry,
):
super().__init__()
self.lims = lims
self.status = status
- self.ticket_handler: TicketHandler = TicketHandler(osticket_api=osticket, status_db=status)
+ self.ticket_handler = ticket_handler
self.submitter_registry = submitter_registry
def submit(self, project: OrderType, order_in: OrderIn, user_name: str, user_mail: str) -> dict:
@@ -45,7 +44,7 @@ def submit(self, project: OrderType, order_in: OrderIn, user_name: str, user_mai
submit_handler = self.submitter_registry.get_order_submitter(project)
submit_handler.order_validation_service.validate_order(order_in)
# detect manual ticket assignment
- ticket_number: str | None = TicketHandler.parse_ticket_number(order_in.name)
+ ticket_number: str | None = self.ticket_handler.parse_ticket_number(order_in.name)
if not ticket_number:
ticket_number = self.ticket_handler.create_ticket(
order=order_in, user_name=user_name, user_mail=user_mail, project=project
@@ -54,7 +53,6 @@ def submit(self, project: OrderType, order_in: OrderIn, user_name: str, user_mai
self.ticket_handler.connect_to_ticket(
order=order_in,
user_name=user_name,
- user_mail=user_mail,
project=project,
ticket_number=ticket_number,
)
diff --git a/cg/meta/orders/ticket_handler.py b/cg/meta/orders/ticket_handler.py
index ba6074068f..91d806fdfa 100644
--- a/cg/meta/orders/ticket_handler.py
+++ b/cg/meta/orders/ticket_handler.py
@@ -4,9 +4,8 @@
from tempfile import TemporaryDirectory
from typing import Any
-from sendmail_container import FormDataRequest
-
-from cg.apps.osticket import OsTicket
+from cg.clients.freshdesk.freshdesk_client import FreshdeskClient
+from cg.clients.freshdesk.models import ReplyCreate, TicketCreate, TicketResponse
from cg.models.orders.order import OrderIn
from cg.models.orders.samples import Of1508Sample
from cg.store.models import Customer, Sample
@@ -20,15 +19,17 @@ class TicketHandler:
NEW_LINE = "
"
- def __init__(self, osticket_api: OsTicket, status_db: Store):
- self.osticket: OsTicket = osticket_api
- self.status_db: Store = status_db
+ def __init__(self, db: Store, client: FreshdeskClient, system_email_id: int, env: str):
+ self.client: FreshdeskClient = client
+ self.status_db: Store = db
+ self.system_email_id: int = system_email_id
+ self.env: str = env
@staticmethod
def parse_ticket_number(name: str) -> str | None:
"""Try to parse a ticket number from a string"""
# detect manual ticket assignment
- ticket_match = re.fullmatch(r"#([0-9]{6})", name)
+ ticket_match = re.fullmatch(r"#(\d{6,10})", name)
if ticket_match:
ticket_id = ticket_match.group(1)
LOG.info(f"{ticket_id}: detected ticket in order name")
@@ -45,22 +46,36 @@ def create_ticket(
order=order,
project=project,
)
- attachment: dict = self.create_attachment(order=order)
- ticket_nr: str | None = self.osticket.open_ticket(
- name=user_name,
- email=user_mail,
- subject=order.name,
- message=message,
- attachment=attachment,
- )
- LOG.info(f"{ticket_nr}: opened new ticket")
- return ticket_nr
+ with TemporaryDirectory() as temp_dir:
+ attachments: Path = self.create_attachment_file(order=order, temp_dir=temp_dir)
+
+ freshdesk_ticket = TicketCreate(
+ email=user_mail,
+ description=message,
+ email_config_id=self.system_email_id,
+ name=user_name,
+ subject=order.name,
+ type="Order",
+ tags=[order.samples[0].data_analysis],
+ custom_fields={
+ "cf_environment": self.env,
+ },
+ attachments=[],
+ )
+ ticket_response: TicketResponse = self.client.create_ticket(
+ ticket=freshdesk_ticket, attachments=[attachments]
+ )
+ LOG.info(f"{ticket_response.id}: opened new ticket in Freshdesk")
- def create_attachment(self, order: OrderIn):
- return self.osticket.create_new_ticket_attachment(
- content=self.replace_empty_string_with_none(obj=order.dict()), file_name="order.json"
- )
+ return ticket_response.id
+
+ def create_attachment_file(self, order: OrderIn, temp_dir: str) -> Path:
+ """Create a single attachment file for the ticket"""
+ order_file_path = Path(temp_dir) / "order.json"
+ with order_file_path.open("w") as order_file:
+ order_file.write(order.json())
+ return order_file_path
def create_xml_sample_list(self, order: OrderIn, user_name: str) -> str:
message = ""
@@ -88,10 +103,7 @@ def create_xml_sample_list(self, order: OrderIn, user_name: str) -> str:
@staticmethod
def create_new_ticket_header(message: str, order: OrderIn, project: str) -> str:
- return (
- f"data:text/html;charset=utf-8, New order with {len(order.samples)} {project} samples:"
- + message
- )
+ return f"New order with {len(order.samples)} {project} samples:" + message
@staticmethod
def add_existing_ticket_header(message: str, order: OrderIn, project: str) -> str:
@@ -178,27 +190,25 @@ def replace_empty_string_with_none(cls, obj: Any) -> Any:
return obj
def connect_to_ticket(
- self, order: OrderIn, user_name: str, user_mail: str, project: str, ticket_number: str
+ self, order: OrderIn, user_name: str, project: str, ticket_number: str
) -> None:
"""Appends a new order message to the ticket selected by the customer"""
LOG.info(f"Connecting order to ticket {ticket_number}")
+
message: str = self.add_existing_ticket_header(
message=self.create_xml_sample_list(order=order, user_name=user_name),
order=order,
project=project,
)
- sender_prefix, email_server_alias = user_mail.split("@")
- temp_dir: TemporaryDirectory = self.osticket.create_connecting_ticket_attachment(
- content=self.replace_empty_string_with_none(obj=order.dict())
- )
- email_form = FormDataRequest(
- sender_prefix=sender_prefix,
- email_server_alias=email_server_alias,
- request_uri=self.osticket.email_uri,
- recipients=self.osticket.osticket_email,
- mail_title=f"[#{ticket_number}]",
- mail_body=message,
- attachments=[Path(f"{temp_dir.name}/order.json")],
- )
- email_form.submit()
- temp_dir.cleanup()
+
+ with TemporaryDirectory() as temp_dir:
+ attachments: Path = self.create_attachment_file(order=order, temp_dir=temp_dir)
+
+ reply = ReplyCreate(ticket_number=ticket_number, body=message)
+
+ self.client.reply_to_ticket(
+ reply=reply,
+ attachments=[attachments],
+ )
+
+ LOG.info(f"Connected order to ticket {ticket_number} in Freshdesk")
diff --git a/cg/resources/__init__.py b/cg/resources/__init__.py
index f203a60e1a..36f2723f09 100644
--- a/cg/resources/__init__.py
+++ b/cg/resources/__init__.py
@@ -1,8 +1,9 @@
from pathlib import Path
-import pkg_resources
-
from cg.constants import FileExtensions
+from cg.utils.files import get_project_root_dir
+
+project_root_dir: Path = get_project_root_dir()
RAREDISEASE_BUNDLE_FILENAMES: str = (
Path("resources", "raredisease_bundle_filenames").with_suffix(FileExtensions.YAML).as_posix()
@@ -20,18 +21,10 @@
Path("resources", "tomte_bundle_filenames").with_suffix(FileExtensions.YAML).as_posix()
)
-RAREDISEASE_BUNDLE_FILENAMES_PATH = Path(
- pkg_resources.resource_filename("cg", RAREDISEASE_BUNDLE_FILENAMES)
-)
+RAREDISEASE_BUNDLE_FILENAMES_PATH = Path(project_root_dir, RAREDISEASE_BUNDLE_FILENAMES)
-RNAFUSION_BUNDLE_FILENAMES_PATH: Path = Path(
- pkg_resources.resource_filename("cg", RNAFUSION_BUNDLE_FILENAMES)
-)
+RNAFUSION_BUNDLE_FILENAMES_PATH = Path(project_root_dir, RNAFUSION_BUNDLE_FILENAMES)
-TAXPROFILER_BUNDLE_FILENAMES_PATH: Path = Path(
- pkg_resources.resource_filename("cg", TAXPROFILER_BUNDLE_FILENAMES)
-)
+TAXPROFILER_BUNDLE_FILENAMES_PATH = Path(project_root_dir, TAXPROFILER_BUNDLE_FILENAMES)
-TOMTE_BUNDLE_FILENAMES_PATH: Path = Path(
- pkg_resources.resource_filename("cg", TOMTE_BUNDLE_FILENAMES)
-)
+TOMTE_BUNDLE_FILENAMES_PATH = Path(project_root_dir, TOMTE_BUNDLE_FILENAMES)
diff --git a/cg/server/admin.py b/cg/server/admin.py
index 4dc8b2b171..a77d1bdc82 100644
--- a/cg/server/admin.py
+++ b/cg/server/admin.py
@@ -551,7 +551,13 @@ class SampleView(BaseView):
"last_sequenced_at",
"sex",
]
- column_filters = ["customer.internal_id", "priority", "sex", "application_version.application"]
+ column_filters = [
+ "customer.internal_id",
+ "priority",
+ "sex",
+ "application_version.application",
+ "capture_kit",
+ ]
column_formatters = {
"is_external": is_external_application,
"internal_id": view_case_sample_link,
diff --git a/cg/server/app.py b/cg/server/app.py
index c9f3cd477c..4df92b1db4 100644
--- a/cg/server/app.py
+++ b/cg/server/app.py
@@ -66,8 +66,6 @@ def _configure_extensions(app: Flask):
ext.db.init_app(app)
ext.lims.init_app(app)
ext.analysis_client.init_app(app)
- if app.config["osticket_api_key"]:
- ext.osticket.init_app(app)
ext.admin.init_app(app, index_view=AdminIndexView(endpoint="admin"))
app.json_provider_class = ext.CustomJSONEncoder
diff --git a/cg/server/app_config.py b/cg/server/app_config.py
index 34e8fe96ba..97f38b3235 100644
--- a/cg/server/app_config.py
+++ b/cg/server/app_config.py
@@ -2,25 +2,29 @@
class AppConfig(BaseSettings):
+ """Default values are overridden by environment variable at run-time."""
+
gunicorn_workers: int = 4
gunicorn_threads: int = 4
- gunicorn_bind: str
+ gunicorn_bind: str = "0.0.0.0:8000"
gunicorn_timeout: int = 400
- cg_sql_database_uri: str
+ cg_sql_database_uri: str = "sqlite:///"
cg_secret_key: str = "thisIsNotASafeKey"
invoice_max_price: int = 750_000
- lims_host: str
- lims_username: str
- lims_password: str
- osticket_api_key: str
- osticket_domain: str
- support_system_email: str
- email_uri: str
- google_oauth_client_id: str
- google_oauth_client_secret: str
- trailblazer_host: str
- trailblazer_service_account: str
- trailblazer_service_account_auth_file: str
+ lims_host: str = "lims_host"
+ lims_username: str = "username"
+ lims_password: str = "password"
+ support_system_email: str = "support@mail.com"
+ email_uri: str = "smtp://localhost"
+ google_oauth_client_id: str = "client_id"
+ google_oauth_client_secret: str = "client_secret"
+ trailblazer_host: str = "trailblazer_host"
+ trailblazer_service_account: str = "service_account"
+ trailblazer_service_account_auth_file: str = "auth_file.json"
+ freshdesk_url: str = "https://company.freshdesk.com"
+ freshdesk_api_key: str = "freshdesk_api_key"
+ freshdesk_order_email_id: int = 10
+ freshdesk_environment: str = "Stage"
app_config = AppConfig()
diff --git a/cg/server/endpoints/orders.py b/cg/server/endpoints/orders.py
index 8261233f47..d2bd64bb15 100644
--- a/cg/server/endpoints/orders.py
+++ b/cg/server/endpoints/orders.py
@@ -28,8 +28,12 @@
from cg.meta.orders.ticket_handler import TicketHandler
from cg.models.orders.order import OrderIn, OrderType
from cg.models.orders.orderform_schema import Orderform
-from cg.server.dto.delivery_message.delivery_message_response import DeliveryMessageResponse
-from cg.server.dto.orders.order_delivery_update_request import OrderDeliveredUpdateRequest
+from cg.server.dto.delivery_message.delivery_message_response import (
+ DeliveryMessageResponse,
+)
+from cg.server.dto.orders.order_delivery_update_request import (
+ OrderDeliveredUpdateRequest,
+)
from cg.server.dto.orders.order_patch_request import OrderDeliveredPatch
from cg.server.dto.orders.orders_request import OrdersRequest
from cg.server.dto.orders.orders_response import Order, OrdersResponse
@@ -39,14 +43,10 @@
delivery_message_service,
lims,
order_service,
- osticket,
order_submitter_registry,
+ ticket_handler,
)
-from cg.store.models import (
- Application,
- Customer,
-)
-
+from cg.store.models import Application, Customer
ORDERS_BLUEPRINT = Blueprint("orders", __name__, url_prefix="/api/v1")
ORDERS_BLUEPRINT.before_request(before_request)
@@ -157,7 +157,10 @@ def create_order_from_form():
def submit_order(order_type):
"""Submit an order for samples."""
api = OrdersAPI(
- lims=lims, status=db, osticket=osticket, submitter_registry=order_submitter_registry
+ lims=lims,
+ status=db,
+ ticket_handler=ticket_handler,
+ submitter_registry=order_submitter_registry,
)
error_message: str
try:
@@ -170,7 +173,7 @@ def submit_order(order_type):
)
project = OrderType(order_type)
order_in = OrderIn.parse_obj(request_json, project=project)
- existing_ticket: str | None = TicketHandler.parse_ticket_number(order_in.name)
+ existing_ticket: str | None = ticket_handler.parse_ticket_number(order_in.name)
if existing_ticket and order_service.store.get_order_by_ticket_id(existing_ticket):
raise OrderExistsError(f"Order with ticket id {existing_ticket} already exists.")
diff --git a/cg/server/ext.py b/cg/server/ext.py
index 3200e7742a..c8cfda6286 100644
--- a/cg/server/ext.py
+++ b/cg/server/ext.py
@@ -6,12 +6,11 @@
from flask_wtf.csrf import CSRFProtect
from cg.apps.lims import LimsAPI
-from cg.apps.osticket import OsTicket
from cg.apps.tb.api import TrailblazerAPI
+from cg.clients.freshdesk.freshdesk_client import FreshdeskClient
+from cg.meta.orders.ticket_handler import TicketHandler
+from cg.server.app_config import app_config
from cg.services.delivery_message.delivery_message_service import DeliveryMessageService
-from cg.services.sample_run_metrics_service.sample_run_metrics_service import (
- SampleRunMetricsService,
-)
from cg.services.orders.order_service.order_service import OrderService
from cg.services.orders.order_summary_service.order_summary_service import (
OrderSummaryService,
@@ -20,6 +19,9 @@
OrderSubmitterRegistry,
setup_order_submitter_registry,
)
+from cg.services.sample_run_metrics_service.sample_run_metrics_service import (
+ SampleRunMetricsService,
+)
from cg.services.sample_service.sample_service import SampleService
from cg.store.database import initialize_database
from cg.store.store import Store
@@ -84,7 +86,6 @@ def init_app(self, app):
admin = Admin(name="Clinical Genomics")
lims = FlaskLims()
-osticket = OsTicket()
analysis_client = AnalysisClient()
delivery_message_service = DeliveryMessageService(store=db, trailblazer_api=analysis_client)
summary_service = OrderSummaryService(store=db, analysis_client=analysis_client)
@@ -95,3 +96,12 @@ def init_app(self, app):
lims=lims,
status_db=db,
)
+freshdesk_client = FreshdeskClient(
+ base_url=app_config.freshdesk_url, api_key=app_config.freshdesk_api_key
+)
+ticket_handler = TicketHandler(
+ db=db,
+ client=freshdesk_client,
+ system_email_id=app_config.freshdesk_order_email_id,
+ env=app_config.freshdesk_environment,
+)
diff --git a/cg/server/invoices/views.py b/cg/server/invoices/views.py
index 0c50526e95..32c2554b22 100644
--- a/cg/server/invoices/views.py
+++ b/cg/server/invoices/views.py
@@ -197,7 +197,7 @@ def invoice_template(invoice_id):
workbook = render_xlsx(invoice_dict)
temp_dir = tempfile.gettempdir()
- filename = "Invoice_{}_{}.xlsx".format(invoice_obj.id, cost_center)
+ filename = f"Invoice_{invoice_obj.id}_{cost_center}.xlsx"
excel_path = os.path.join(temp_dir, filename)
workbook.save(excel_path)
diff --git a/cg/services/run_devices/exc.py b/cg/services/run_devices/exc.py
index daa2a4e3e7..0b89724dff 100644
--- a/cg/services/run_devices/exc.py
+++ b/cg/services/run_devices/exc.py
@@ -2,28 +2,42 @@
class PostProcessingRunFileManagerError(CgError):
+ """Error raised if something goes wrong managing the sequencing run files."""
+
pass
class PostProcessingRunDataGeneratorError(CgError):
+ """Error raised if something goes wrong parsing the run directory data."""
+
pass
class PostProcessingParsingError(CgError):
+ """Error raised if something goes wrong parsing the sequencing run metrics."""
+
pass
class PostProcessingDataTransferError(CgError):
+ """Error raised if something goes wrong creating the DTOs for post-processing."""
+
pass
class PostProcessingStoreDataError(CgError):
+ """Error raised if something goes wrong storing the post-processing data in StatusDB."""
+
pass
class PostProcessingStoreFileError(CgError):
+ """Error raised if something goes wrong storing the post-processing files in Housekeeper."""
+
pass
class PostProcessingError(CgError):
+ """Error raised if something goes wrong during post-processing."""
+
pass
diff --git a/cg/services/run_devices/pacbio/run_data_generator/pacbio_run_data_generator.py b/cg/services/run_devices/pacbio/run_data_generator/pacbio_run_data_generator.py
index 7bbafd44b3..adca59b4e0 100644
--- a/cg/services/run_devices/pacbio/run_data_generator/pacbio_run_data_generator.py
+++ b/cg/services/run_devices/pacbio/run_data_generator/pacbio_run_data_generator.py
@@ -1,16 +1,10 @@
from pathlib import Path
from cg.services.run_devices.abstract_classes import RunDataGenerator
-from cg.services.run_devices.error_handler import (
- handle_post_processing_errors,
-)
+from cg.services.run_devices.error_handler import handle_post_processing_errors
from cg.services.run_devices.exc import PostProcessingRunDataGeneratorError
from cg.services.run_devices.pacbio.run_data_generator.run_data import PacBioRunData
-from cg.services.run_devices.validators import (
- validate_has_expected_parts,
- validate_name_pre_fix,
-)
-from cg.utils.string import get_element_from_split
+from cg.services.run_devices.validators import validate_has_expected_parts, validate_name_pre_fix
class PacBioRunDataGenerator(RunDataGenerator):
@@ -40,16 +34,16 @@ def get_run_data(self, run_name: str, sequencing_dir: str) -> PacBioRunData:
@staticmethod
def _get_sequencing_run_name(run_name: str) -> str:
- return get_element_from_split(value=run_name, element_position=0, split="/")
+ return run_name.split("/")[0]
@staticmethod
def _get_plate_well(run_name: str) -> str:
- return get_element_from_split(value=run_name, element_position=-1, split="/")
+ return run_name.split("/")[1]
def _get_plate(self, run_name: str) -> str:
plate_well: str = self._get_plate_well(run_name)
- return get_element_from_split(value=plate_well, element_position=0, split="_")
+ return plate_well.split("_")[0]
def _get_well(self, run_name: str) -> str:
plate_well: str = self._get_plate_well(run_name)
- return get_element_from_split(value=plate_well, element_position=-1, split="_")
+ return plate_well.split("_")[-1]
diff --git a/cg/services/run_devices/pacbio/run_file_manager/run_file_manager.py b/cg/services/run_devices/pacbio/run_file_manager/run_file_manager.py
index 5624dc7467..6e2d230d47 100644
--- a/cg/services/run_devices/pacbio/run_file_manager/run_file_manager.py
+++ b/cg/services/run_devices/pacbio/run_file_manager/run_file_manager.py
@@ -19,9 +19,7 @@ def get_files_to_parse(self, run_data: PacBioRunData) -> list[Path]:
"""Get the file paths required by the PacBioMetricsParser."""
run_path: Path = run_data.full_path
validate_files_or_directories_exist([run_path])
- files_to_parse: list[Path] = self._get_report_files(run_path)
- files_to_parse.append(self._get_ccs_report_file(run_path))
- return files_to_parse
+ return self._get_report_files(run_path)
@handle_post_processing_errors(
to_except=(FileNotFoundError,), to_raise=PostProcessingRunFileManagerError
@@ -29,9 +27,7 @@ def get_files_to_parse(self, run_data: PacBioRunData) -> list[Path]:
def get_files_to_store(self, run_data: PacBioRunData) -> list[Path]:
"""Get the files to store for the PostProcessingHKService."""
run_path: Path = run_data.full_path
- files_to_store: list[Path] = self.get_files_to_parse(run_data)
- files_to_store.append(self._get_hifi_read_file(run_path))
- return files_to_store
+ return self.get_files_to_parse(run_data) + self._get_hifi_read_files(run_path)
@staticmethod
def _get_ccs_report_file(run_path: Path) -> Path:
@@ -44,8 +40,7 @@ def _get_ccs_report_file(run_path: Path) -> Path:
raise FileNotFoundError(f"No CCS report file found in {statistics_dir}")
return files[0]
- @staticmethod
- def _get_report_files(run_path: Path) -> list[Path]:
+ def _get_report_files(self, run_path: Path) -> list[Path]:
"""Return the paths to the unzipped report files."""
unzipped_dir: Path = Path(
run_path, PacBioDirsAndFiles.STATISTICS_DIR, PacBioDirsAndFiles.UNZIPPED_REPORTS_DIR
@@ -55,16 +50,17 @@ def _get_report_files(run_path: Path) -> list[Path]:
Path(unzipped_dir, PacBioDirsAndFiles.LOADING_REPORT),
Path(unzipped_dir, PacBioDirsAndFiles.RAW_DATA_REPORT),
Path(unzipped_dir, PacBioDirsAndFiles.SMRTLINK_DATASETS_REPORT),
+ self._get_ccs_report_file(run_path),
]
validate_files_or_directories_exist(report_files)
return report_files
@staticmethod
- def _get_hifi_read_file(run_path: Path) -> Path:
+ def _get_hifi_read_files(run_path: Path) -> list[Path]:
"""Return the path to the HiFi read file."""
hifi_dir = Path(run_path, PacBioDirsAndFiles.HIFI_READS)
- bam_file: Path = get_files_matching_pattern(
+ bam_files: list[Path] = get_files_matching_pattern(
directory=hifi_dir, pattern=f"*{FileExtensions.BAM}"
- )[0]
- validate_files_or_directories_exist([bam_file])
- return bam_file
+ )
+ validate_files_or_directories_exist(bam_files)
+ return bam_files
diff --git a/cg/utils/files.py b/cg/utils/files.py
index de66999e14..9f8249eba8 100644
--- a/cg/utils/files.py
+++ b/cg/utils/files.py
@@ -1,13 +1,18 @@
-"""Some helper functions for working with files"""
+"""Some helper functions for working with files."""
import logging
import os
import shutil
+from importlib.resources import files
from pathlib import Path
LOG = logging.getLogger(__name__)
+def get_project_root_dir() -> Path:
+ return Path(files("cg"))
+
+
def get_file_in_directory(directory: Path, file_name: str) -> Path:
"""Get a file in a directory and subdirectories.
Raises:
@@ -15,11 +20,10 @@ def get_file_in_directory(directory: Path, file_name: str) -> Path:
"""
if not directory.is_dir() or not directory.exists():
raise FileNotFoundError(f"Directory {directory} does not exist")
- for directory_path, _, files in os.walk(directory):
- for file in files:
+ for directory_path, _, dir_files in os.walk(directory):
+ for file in dir_files:
if file_name == file:
- path_to_file = Path(directory_path, file)
- return path_to_file
+ return Path(directory_path, file)
raise FileNotFoundError(f"File {file_name} not found in {directory}")
@@ -43,10 +47,10 @@ def get_files_in_directory_with_pattern(directory: Path, pattern: str) -> list[P
files_with_pattern: list[Path] = []
if not directory.is_dir() or not directory.exists():
raise FileNotFoundError(f"Directory {directory} does not exist")
- for directory_path, _, files in os.walk(directory):
- for file in files:
- if pattern in file:
- files_with_pattern.append(Path(directory_path, file))
+ for directory_path, _, dir_files in os.walk(directory):
+ files_with_pattern.extend(
+ Path(directory_path, file) for file in dir_files if pattern in file
+ )
if not files_with_pattern:
raise FileNotFoundError(f"No files with pattern {pattern} found in {directory}")
return files_with_pattern
@@ -117,9 +121,9 @@ def link_or_overwrite_file(src: Path, dst: Path) -> None:
def get_all_files_in_directory_tree(directory: Path) -> list[Path]:
"""Get the relative paths of all files in a directory and its subdirectories."""
files_in_directory: list[Path] = []
- for subdir, _, files in os.walk(directory):
+ for subdir, _, dir_files in os.walk(directory):
subdir = Path(subdir).relative_to(directory)
- files_in_directory.extend([Path(subdir, file) for file in files])
+ files_in_directory.extend([Path(subdir, file) for file in dir_files])
return files_in_directory
diff --git a/cg/utils/string.py b/cg/utils/string.py
deleted file mode 100644
index c8c556234e..0000000000
--- a/cg/utils/string.py
+++ /dev/null
@@ -1,10 +0,0 @@
-"""Utils related to string manipulation."""
-
-from cg.exc import CgError
-
-
-def get_element_from_split(value: str, element_position: int, split: str) -> str:
- elements: list[str] = value.split(split)
- if len(elements) < element_position:
- raise CgError(message="Provided element position out of bounds.")
- return elements[element_position]
diff --git a/pyproject.toml b/pyproject.toml
index 94b23998a3..7cfe49704f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "cg"
-version = "62.2.1"
+version = "62.2.5"
description = "Clinical Genomics command center"
authors = ["Clinical Genomics "]
readme = "README.md"
diff --git a/tests/apps/test_osticket.py b/tests/apps/test_osticket.py
deleted file mode 100644
index c16f21b2ca..0000000000
--- a/tests/apps/test_osticket.py
+++ /dev/null
@@ -1,31 +0,0 @@
-""" Test the osticket app """
-
-import logging
-
-import pytest
-
-from cg.apps.osticket import OsTicket
-from cg.exc import TicketCreationError
-
-
-def test_osticket_respone_500(monkeypatch, caplog, response):
- """Test that we log properly when we get a non successful response"""
-
- # GIVEN a ticket server always gives a failure in response
- osticket_api = OsTicket()
- monkeypatch.setattr("requests.post", lambda *args, **kwargs: response)
-
- # WHEN we call open_ticket with ok ticket data
- caplog.set_level(logging.ERROR)
- with pytest.raises(TicketCreationError):
- osticket_api.open_ticket(
- name="dummy_name",
- email="dummy_email",
- subject="dummy_subject",
- message="dummy_message",
- attachment={},
- )
-
- # THEN the response text and reason was logged and a ticket creation error raised
- assert response.reason in caplog.text
- assert response.text in caplog.text
diff --git a/tests/conftest.py b/tests/conftest.py
index 8ca5b26c0f..3f4b3bd4bd 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -75,12 +75,12 @@
from tests.mocks.hk_mock import MockHousekeeperAPI
from tests.mocks.limsmock import LimsSample, LimsUDF, MockLimsAPI
from tests.mocks.madeline import MockMadelineAPI
-from tests.mocks.osticket import MockOsTicket
from tests.mocks.process_mock import ProcessMock
from tests.mocks.scout import MockScoutAPI
from tests.mocks.tb_mock import MockTB
from tests.small_helpers import SmallHelpers
from tests.store_helpers import StoreHelpers
+from cg.clients.freshdesk.freshdesk_client import FreshdeskClient
LOG = logging.getLogger(__name__)
multiqc_json_file = "multiqc_data.json"
@@ -645,11 +645,10 @@ def ticket_id() -> str:
@pytest.fixture
-def osticket(ticket_id: str) -> MockOsTicket:
- """Return a api that mock the os ticket api."""
- api = MockOsTicket()
- api.set_ticket_nr(ticket_id)
- return api
+def freshdesk_client() -> FreshdeskClient:
+ """Return a FreshdeskClient instance with mock parameters."""
+ client = FreshdeskClient(base_url="https://mock.freshdesk.com", api_key="mock_api_key")
+ return client
# Files fixtures
diff --git a/tests/fixtures/devices/pacbio/SMRTcells/r84202_20240522_133539/1_B01/hifi_reads/m84202_240522_155607_s2.hifi_reads.bam.pbi b/tests/fixtures/devices/pacbio/SMRTcells/r84202_20240522_133539/1_B01/hifi_reads/m84202_240522_155607_s2.hifi_reads.bam.pbi
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/tests/meta/orders/conftest.py b/tests/meta/orders/conftest.py
index 24add743eb..5857e67a8a 100644
--- a/tests/meta/orders/conftest.py
+++ b/tests/meta/orders/conftest.py
@@ -2,6 +2,7 @@
import pytest
+from cg.clients.freshdesk.freshdesk_client import FreshdeskClient
from cg.meta.orders import OrdersAPI
from cg.meta.orders.ticket_handler import TicketHandler
from cg.services.orders.submitters.order_submitter_registry import (
@@ -10,27 +11,31 @@
)
from cg.store.store import Store
from tests.mocks.limsmock import MockLimsAPI
-from tests.mocks.osticket import MockOsTicket
+
+
+@pytest.fixture
+def freshdesk_client():
+ return FreshdeskClient(base_url="https://mock.freshdesk.com", api_key="mock_api_key")
@pytest.fixture(scope="function")
def orders_api(
- base_store,
- osticket: MockOsTicket,
+ base_store: Store,
+ ticket_handler: TicketHandler,
lims_api: MockLimsAPI,
order_submitter_registry: OrderSubmitterRegistry,
) -> OrdersAPI:
return OrdersAPI(
lims=lims_api,
status=base_store,
- osticket=osticket,
+ ticket_handler=ticket_handler,
submitter_registry=order_submitter_registry,
)
@pytest.fixture
-def ticket_handler(store: Store, osticket: MockOsTicket) -> TicketHandler:
- return TicketHandler(status_db=store, osticket_api=osticket)
+def ticket_handler(store: Store, freshdesk_client: FreshdeskClient) -> TicketHandler:
+ return TicketHandler(db=store, client=freshdesk_client, system_email_id=12345, env="production")
@pytest.fixture
diff --git a/tests/meta/orders/test_meta_orders_api.py b/tests/meta/orders/test_meta_orders_api.py
index 885753ef03..04ffe40588 100644
--- a/tests/meta/orders/test_meta_orders_api.py
+++ b/tests/meta/orders/test_meta_orders_api.py
@@ -1,8 +1,9 @@
import datetime as dt
-from unittest.mock import patch, Mock
+from unittest.mock import Mock, patch
import pytest
+from cg.clients.freshdesk.models import TicketResponse
from cg.constants import DataDelivery
from cg.constants.constants import Workflow
from cg.constants.subject import Sex
@@ -27,6 +28,22 @@ def monkeypatch_process_lims(monkeypatch, order_data) -> None:
)
+def mock_freshdesk_ticket_creation(mock_create_ticket, ticket_id: str):
+ """Helper function to mock Freshdesk ticket creation."""
+ mock_create_ticket.return_value = TicketResponse(
+ id=int(ticket_id),
+ description="This is a test description.",
+ subject="Support needed..",
+ status=2,
+ priority=1,
+ )
+
+
+def mock_freshdesk_reply_to_ticket(mock_reply_to_ticket):
+ """Helper function to mock Freshdesk reply to ticket."""
+ mock_reply_to_ticket.return_value = None
+
+
def test_too_long_order_name():
# GIVEN order with more than allowed characters name
long_name = "A super long order name that is longer than sixty-four characters."
@@ -38,7 +55,6 @@ def test_too_long_order_name():
OrderIn(name=long_name, customer="", comment="", samples=[])
-@patch("cg.meta.orders.ticket_handler.FormDataRequest.submit", return_value=None)
@pytest.mark.parametrize(
"order_type",
[
@@ -55,37 +71,44 @@ def test_too_long_order_name():
],
)
def test_submit(
- mail_patch,
all_orders_to_submit: dict,
base_store: Store,
- monkeypatch,
+ monkeypatch: pytest.MonkeyPatch,
order_type: OrderType,
orders_api: OrdersAPI,
ticket_id: str,
user_mail: str,
user_name: str,
):
- order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
- monkeypatch_process_lims(monkeypatch, order_data)
+ with patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.create_ticket"
+ ) as mock_create_ticket, patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.reply_to_ticket"
+ ) as mock_reply_to_ticket:
+ mock_freshdesk_ticket_creation(mock_create_ticket, ticket_id)
+ mock_freshdesk_reply_to_ticket(mock_reply_to_ticket)
- # GIVEN an order and an empty store
- assert not base_store._get_query(table=Sample).first()
+ order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
+ monkeypatch_process_lims(monkeypatch, order_data)
- # WHEN submitting the order
+ # GIVEN an order and an empty store
+ assert not base_store._get_query(table=Sample).first()
- result = orders_api.submit(
- project=order_type, order_in=order_data, user_name=user_name, user_mail=user_mail
- )
+ # WHEN submitting the order
+
+ result = orders_api.submit(
+ project=order_type, order_in=order_data, user_name=user_name, user_mail=user_mail
+ )
- # THEN the result should contain the ticket number for the order
- for record in result["records"]:
- if isinstance(record, Pool):
- assert record.ticket == ticket_id
- elif isinstance(record, Sample):
- assert record.original_ticket == ticket_id
- elif isinstance(record, Case):
- for link_obj in record.links:
- assert link_obj.sample.original_ticket == ticket_id
+ # THEN the result should contain the ticket number for the order
+ for record in result["records"]:
+ if isinstance(record, Pool):
+ assert record.ticket == ticket_id
+ elif isinstance(record, Sample):
+ assert record.original_ticket == ticket_id
+ elif isinstance(record, Case):
+ for link_obj in record.links:
+ assert link_obj.sample.original_ticket == ticket_id
@pytest.mark.parametrize(
@@ -99,22 +122,24 @@ def test_submit_ticketexception(
user_mail: str,
user_name: str,
):
- # GIVEN an order that does not have a name (ticket_nr)
- order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
- order_data.name = "dummy_name"
- with patch("cg.apps.osticket.OsTicket") as os_mock:
- orders_api.ticket_handler.osticket = os_mock.return_value
- orders_api.ticket_handler.osticket.open_ticket.side_effect = TicketCreationError("ERROR")
-
- # WHEN the order is submitted and a TicketCreationError raised
- # THEN the TicketCreationError is not excepted
- with pytest.raises(TicketCreationError):
- orders_api.submit(
- project=order_type,
- order_in=order_data,
- user_name=user_name,
- user_mail=user_mail,
- )
+ # GIVEN a mock Freshdesk ticket creation that raises TicketCreationError
+ with patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.create_ticket",
+ side_effect=TicketCreationError("ERROR"),
+ ):
+ # GIVEN an order that does not have a name (ticket_nr)
+ order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
+ order_data.name = "dummy_name"
+
+ # WHEN the order is submitted and a TicketCreationError raised
+ # THEN the TicketCreationError is not excepted
+ with pytest.raises(TicketCreationError):
+ orders_api.submit(
+ project=order_type,
+ order_in=order_data,
+ user_name=user_name,
+ user_mail=user_mail,
+ )
@pytest.mark.parametrize(
@@ -123,7 +148,7 @@ def test_submit_ticketexception(
)
def test_submit_illegal_sample_customer(
all_orders_to_submit: dict,
- monkeypatch,
+ monkeypatch: pytest.MonkeyPatch,
order_type: OrderType,
orders_api: OrdersAPI,
sample_store: Store,
@@ -132,7 +157,6 @@ def test_submit_illegal_sample_customer(
):
order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
monkeypatch_process_lims(monkeypatch, order_data)
- orders_api.ticket_handler = Mock()
# GIVEN we have an order with a customer that is not in the same customer group as customer
# that the samples originate from
new_customer = sample_store.add_customer(
@@ -161,60 +185,65 @@ def test_submit_illegal_sample_customer(
)
-@patch("cg.meta.orders.ticket_handler.FormDataRequest.submit", return_value=None)
@pytest.mark.parametrize(
"order_type",
[OrderType.MIP_DNA, OrderType.MIP_RNA, OrderType.BALSAMIC],
)
def test_submit_scout_legal_sample_customer(
- mail_patch,
all_orders_to_submit: dict,
- monkeypatch,
+ monkeypatch: pytest.MonkeyPatch,
order_type: OrderType,
orders_api: OrdersAPI,
sample_store: Store,
user_mail: str,
user_name: str,
+ ticket_id: str,
):
- order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
- monkeypatch_process_lims(monkeypatch, order_data)
- orders_api.ticket_handler = Mock()
- # GIVEN we have an order with a customer that is in the same customer group as customer
- # that the samples originate from
- collaboration = sample_store.add_collaboration("customer999only", "customer 999 only group")
- sample_store.session.add(collaboration)
- sample_customer = sample_store.add_customer(
- "customer1",
- "customer 1",
- scout_access=True,
- invoice_address="dummy street 1",
- invoice_reference="dummy nr",
- )
- order_customer = sample_store.add_customer(
- "customer2",
- "customer 2",
- scout_access=True,
- invoice_address="dummy street 2",
- invoice_reference="dummy nr",
- )
- sample_customer.collaborations.append(collaboration)
- order_customer.collaborations.append(collaboration)
- sample_store.session.add(sample_customer)
- sample_store.session.add(order_customer)
- existing_sample: Sample = sample_store._get_query(table=Sample).first()
- existing_sample.customer = sample_customer
- sample_store.session.commit()
- order_data.customer = order_customer.internal_id
-
- for sample in order_data.samples:
- sample.internal_id = existing_sample.internal_id
- break
-
- # WHEN calling submit
- # THEN an OrderError should not be raised on illegal customer
- orders_api.submit(
- project=order_type, order_in=order_data, user_name=user_name, user_mail=user_mail
- )
+ with patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.create_ticket"
+ ) as mock_create_ticket, patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.reply_to_ticket"
+ ) as mock_reply_to_ticket:
+ mock_freshdesk_ticket_creation(mock_create_ticket, ticket_id)
+ mock_freshdesk_reply_to_ticket(mock_reply_to_ticket)
+ order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
+ monkeypatch_process_lims(monkeypatch, order_data)
+ # GIVEN we have an order with a customer that is in the same customer group as customer
+ # that the samples originate from
+ collaboration = sample_store.add_collaboration("customer999only", "customer 999 only group")
+ sample_store.session.add(collaboration)
+ sample_customer = sample_store.add_customer(
+ "customer1",
+ "customer 1",
+ scout_access=True,
+ invoice_address="dummy street 1",
+ invoice_reference="dummy nr",
+ )
+ order_customer = sample_store.add_customer(
+ "customer2",
+ "customer 2",
+ scout_access=True,
+ invoice_address="dummy street 2",
+ invoice_reference="dummy nr",
+ )
+ sample_customer.collaborations.append(collaboration)
+ order_customer.collaborations.append(collaboration)
+ sample_store.session.add(sample_customer)
+ sample_store.session.add(order_customer)
+ existing_sample: Sample = sample_store._get_query(table=Sample).first()
+ existing_sample.customer = sample_customer
+ sample_store.session.commit()
+ order_data.customer = order_customer.internal_id
+
+ for sample in order_data.samples:
+ sample.internal_id = existing_sample.internal_id
+ break
+
+ # WHEN calling submit
+ # THEN an OrderError should not be raised on illegal customer
+ orders_api.submit(
+ project=order_type, order_in=order_data, user_name=user_name, user_mail=user_mail
+ )
@pytest.mark.parametrize(
@@ -223,7 +252,7 @@ def test_submit_scout_legal_sample_customer(
)
def test_submit_duplicate_sample_case_name(
all_orders_to_submit: dict,
- monkeypatch,
+ monkeypatch: pytest.MonkeyPatch,
order_type: OrderType,
orders_api: OrdersAPI,
ticket_id: str,
@@ -234,7 +263,6 @@ def test_submit_duplicate_sample_case_name(
order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
store = orders_api.status
customer: Customer = store.get_customer_by_internal_id(customer_internal_id=order_data.customer)
- orders_api.ticket_handler = Mock()
for sample in order_data.samples:
case_id = sample.family_name
if not store.get_case_by_name_and_customer(customer=customer, case_name=case_id):
@@ -262,72 +290,85 @@ def test_submit_duplicate_sample_case_name(
)
-@patch("cg.meta.orders.ticket_handler.FormDataRequest.submit", return_value=None)
@pytest.mark.parametrize(
"order_type",
[OrderType.FLUFFY],
)
def test_submit_fluffy_duplicate_sample_case_name(
- mail_patch,
all_orders_to_submit: dict,
- monkeypatch,
+ monkeypatch: pytest.MonkeyPatch,
order_type: OrderType,
orders_api: OrdersAPI,
user_mail: str,
user_name: str,
+ ticket_id: str,
):
- # GIVEN we have an order with a case that is already in the database
- order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
- monkeypatch_process_lims(monkeypatch, order_data)
-
- orders_api.submit(
- project=order_type, order_in=order_data, user_name=user_name, user_mail=user_mail
- )
+ with patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.create_ticket"
+ ) as mock_create_ticket, patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.reply_to_ticket"
+ ) as mock_reply_to_ticket:
+ mock_freshdesk_ticket_creation(mock_create_ticket, ticket_id)
+ mock_freshdesk_reply_to_ticket(mock_reply_to_ticket)
+ # GIVEN we have an order with a case that is already in the database
+ order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
+ monkeypatch_process_lims(monkeypatch, order_data)
- # WHEN calling submit
- # THEN an OrderError should be raised on duplicate case name
- with pytest.raises(OrderError):
orders_api.submit(
- project=order_type,
- order_in=order_data,
- user_name=user_name,
- user_mail=user_mail,
+ project=order_type, order_in=order_data, user_name=user_name, user_mail=user_mail
)
+ # WHEN calling submit
+ # THEN an OrderError should be raised on duplicate case name
+ with pytest.raises(OrderError):
+ orders_api.submit(
+ project=order_type,
+ order_in=order_data,
+ user_name=user_name,
+ user_mail=user_mail,
+ )
+
-@patch("cg.meta.orders.ticket_handler.FormDataRequest.submit", return_value=None)
def test_submit_unique_sample_case_name(
- mail_patch,
orders_api: OrdersAPI,
mip_order_to_submit: dict,
user_name: str,
user_mail: str,
- monkeypatch,
+ monkeypatch: pytest.MonkeyPatch,
+ ticket_id: str,
):
- # GIVEN we have an order with a case that is not existing in the database
- order_data = OrderIn.parse_obj(obj=mip_order_to_submit, project=OrderType.MIP_DNA)
+ with patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.create_ticket"
+ ) as mock_create_ticket, patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.reply_to_ticket"
+ ) as mock_reply_to_ticket:
+ mock_freshdesk_ticket_creation(mock_create_ticket, ticket_id)
+ mock_freshdesk_reply_to_ticket(mock_reply_to_ticket)
+
+ # GIVEN we have an order with a case that is not existing in the database
+ order_data = OrderIn.parse_obj(obj=mip_order_to_submit, project=OrderType.MIP_DNA)
+
+ store = orders_api.status
+
+ sample: MipDnaSample
+ for sample in order_data.samples:
+ case_id = sample.family_name
+ customer: Customer = store.get_customer_by_internal_id(
+ customer_internal_id=order_data.customer
+ )
+ assert not store.get_case_by_name_and_customer(customer=customer, case_name=case_id)
- store = orders_api.status
+ monkeypatch_process_lims(monkeypatch, order_data)
- sample: MipDnaSample
- for sample in order_data.samples:
- case_id = sample.family_name
- customer: Customer = store.get_customer_by_internal_id(
- customer_internal_id=order_data.customer
+ # WHEN calling submit
+ orders_api.submit(
+ project=OrderType.MIP_DNA,
+ order_in=order_data,
+ user_name=user_name,
+ user_mail=user_mail,
)
- assert not store.get_case_by_name_and_customer(customer=customer, case_name=case_id)
-
- monkeypatch_process_lims(monkeypatch, order_data)
- # WHEN calling submit
- orders_api.submit(
- project=OrderType.MIP_DNA,
- order_in=order_data,
- user_name=user_name,
- user_mail=user_mail,
- )
-
- # Then no exception about duplicate names should be thrown
+ # Then no exception about duplicate names should be thrown
def test_validate_sex_inconsistent_sex(
@@ -454,7 +495,6 @@ def test_validate_sex_unknown_new_sex(
# THEN no OrderError should be raised on non-matching sex
-@patch("cg.meta.orders.ticket_handler.FormDataRequest.submit", return_value=None)
@pytest.mark.parametrize(
"order_type",
[
@@ -470,27 +510,34 @@ def test_validate_sex_unknown_new_sex(
],
)
def test_submit_unique_sample_name(
- mail_patch,
all_orders_to_submit: dict,
- monkeypatch,
+ monkeypatch: pytest.MonkeyPatch,
order_type: OrderType,
orders_api: OrdersAPI,
user_mail: str,
user_name: str,
+ ticket_id: str,
):
- # GIVEN we have an order with a sample that is not existing in the database
- order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
- store = orders_api.status
- assert not store._get_query(table=Sample).first()
-
- monkeypatch_process_lims(monkeypatch, order_data)
-
- # WHEN calling submit
- orders_api.submit(
- project=order_type, order_in=order_data, user_name=user_name, user_mail=user_mail
- )
+ with patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.create_ticket"
+ ) as mock_create_ticket, patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.reply_to_ticket"
+ ) as mock_reply_to_ticket:
+ mock_freshdesk_ticket_creation(mock_create_ticket, ticket_id)
+ mock_freshdesk_reply_to_ticket(mock_reply_to_ticket)
+ # GIVEN we have an order with a sample that is not existing in the database
+ order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
+ store = orders_api.status
+ assert not store._get_query(table=Sample).first()
+
+ monkeypatch_process_lims(monkeypatch, order_data)
+
+ # WHEN calling submit
+ orders_api.submit(
+ project=order_type, order_in=order_data, user_name=user_name, user_mail=user_mail
+ )
- # Then no exception about duplicate names should be thrown
+ # Then no exception about duplicate names should be thrown
@pytest.mark.parametrize(
@@ -500,7 +547,7 @@ def test_submit_unique_sample_name(
def test_sarscov2_submit_duplicate_sample_name(
all_orders_to_submit: dict,
helpers: StoreHelpers,
- monkeypatch,
+ monkeypatch: pytest.MonkeyPatch,
order_type: OrderType,
orders_api: OrdersAPI,
user_mail: str,
@@ -536,7 +583,6 @@ def store_samples_with_names_from_order(store: Store, helpers: StoreHelpers, ord
store.session.commit()
-@patch("cg.meta.orders.ticket_handler.FormDataRequest.submit", return_value=None)
@pytest.mark.parametrize(
"order_type",
[
@@ -549,24 +595,30 @@ def store_samples_with_names_from_order(store: Store, helpers: StoreHelpers, ord
],
)
def test_not_sarscov2_submit_duplicate_sample_name(
- mail_patch,
all_orders_to_submit: dict,
helpers: StoreHelpers,
- monkeypatch,
+ monkeypatch: pytest.MonkeyPatch,
order_type: OrderType,
orders_api: OrdersAPI,
- sample_store: Store,
user_mail: str,
user_name: str,
+ ticket_id: str,
):
- # GIVEN we have an order with samples that is already in the database
- order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
- monkeypatch_process_lims(monkeypatch, order_data)
- store_samples_with_names_from_order(orders_api.status, helpers, order_data)
-
- # WHEN calling submit
- orders_api.submit(
- project=order_type, order_in=order_data, user_name=user_name, user_mail=user_mail
- )
+ with patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.create_ticket"
+ ) as mock_create_ticket, patch(
+ "cg.clients.freshdesk.freshdesk_client.FreshdeskClient.reply_to_ticket"
+ ) as mock_reply_to_ticket:
+ mock_freshdesk_ticket_creation(mock_create_ticket, ticket_id)
+ mock_freshdesk_reply_to_ticket(mock_reply_to_ticket)
+ # GIVEN we have an order with samples that is already in the database
+ order_data = OrderIn.parse_obj(obj=all_orders_to_submit[order_type], project=order_type)
+ monkeypatch_process_lims(monkeypatch, order_data)
+ store_samples_with_names_from_order(orders_api.status, helpers, order_data)
+
+ # WHEN calling submit
+ orders_api.submit(
+ project=order_type, order_in=order_data, user_name=user_name, user_mail=user_mail
+ )
- # THEN no OrderError should be raised on duplicate sample name
+ # THEN no OrderError should be raised on duplicate sample name
diff --git a/tests/mocks/osticket.py b/tests/mocks/osticket.py
deleted file mode 100644
index 7ed223fc44..0000000000
--- a/tests/mocks/osticket.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""Mock the os ticket api"""
-
-import logging
-import os.path
-
-from flask import Flask
-
-from cg.apps.osticket import OsTicket
-from cg.exc import TicketCreationError
-
-LOG = logging.getLogger(__name__)
-
-
-class MockOsTicket(OsTicket):
- """Interface to ticket system."""
-
- def __init__(self):
- self.headers = None
- self.url = None
- self.osticket_email = "james.holden@scilifelab.se"
- self.mail_container_uri = "dummy_uri"
- self._ticket_nr: str = "123456"
- self._should_fail: bool = False
- self._return_none: bool = False
- self.email_uri = "http://localhost:0000/sendmail"
-
- def set_ticket_nr(self, ticket_id: str) -> None:
- self._ticket_nr = ticket_id
-
- def init_app(self, app: Flask):
- """Initialize the API in Flask."""
-
- def setup(
- self,
- api_key: str = None,
- domain: str = None,
- osticket_email: str = None,
- email_uri: str = None,
- ):
- """Initialize the API."""
- self.headers = {"X-API-Key": api_key}
- self.url = os.path.join(domain, "api/tickets.json")
-
- def open_ticket(
- self, attachment: dict, email: str, message: str, name: str, subject: str
- ) -> str | None:
- """Open a new ticket through the REST API."""
- if self._should_fail:
- LOG.error("res.text: %s, reason: %s", self._ticket_nr, "Unknown reason")
- raise TicketCreationError("FAIL")
- if self._return_none:
- return None
-
- return self._ticket_nr
diff --git a/tests/server/conftest.py b/tests/server/conftest.py
index b0f1e107a4..2c1290e47b 100644
--- a/tests/server/conftest.py
+++ b/tests/server/conftest.py
@@ -28,9 +28,6 @@
os.environ["TRAILBLAZER_HOST"] = "dummy_value"
os.environ["CG_SECRET_KEY"] = "dummy_value"
os.environ["GUNICORN_BIND"] = "0.0.0.0:8000"
-os.environ["OSTICKET_API_KEY"] = "dummy_value"
-os.environ["OSTICKET_API_URL"] = "dummy_value"
-os.environ["OSTICKET_DOMAIN"] = "dummy_value"
os.environ["SUPPORT_SYSTEM_EMAIL"] = "dummy_value"
os.environ["EMAIL_URI"] = "dummy_value"
diff --git a/tests/services/run_devices/pacbio/post_processing/test_post_processing.py b/tests/services/run_devices/pacbio/post_processing/test_post_processing.py
index 519922247d..1dcdfd6d4b 100644
--- a/tests/services/run_devices/pacbio/post_processing/test_post_processing.py
+++ b/tests/services/run_devices/pacbio/post_processing/test_post_processing.py
@@ -37,7 +37,7 @@ def test_pac_bio_post_processing_run_name_error(pac_bio_context):
def test_pac_bio_post_processing_store_data_error(
pac_bio_context: CGConfig, pac_bio_sequencing_run_name: str
):
- # GIVEN a PacBioPostProcessingService and a wrong run name
+ # GIVEN a PacBioPostProcessingService that raises an error when storing data in StatusDB
post_processing_service: PacBioPostProcessingService = (
pac_bio_context.post_processing_services.pacbio
@@ -55,7 +55,7 @@ def test_pac_bio_post_processing_store_data_error(
def test_pac_bio_post_processing_store_files_error(
pac_bio_context: CGConfig, pac_bio_sequencing_run_name: str
):
- # GIVEN a PacBioPostProcessingService
+ # GIVEN a PacBioPostProcessingService that raises an error when storing files in Housekeeper
post_processing_service: PacBioPostProcessingService = (
pac_bio_context.post_processing_services.pacbio
)
diff --git a/tests/services/run_devices/pacbio/run_data_generator/test_pacbio_run_data_generator.py b/tests/services/run_devices/pacbio/run_data_generator/test_pacbio_run_data_generator.py
index 42df23971a..7dc753eff1 100644
--- a/tests/services/run_devices/pacbio/run_data_generator/test_pacbio_run_data_generator.py
+++ b/tests/services/run_devices/pacbio/run_data_generator/test_pacbio_run_data_generator.py
@@ -12,6 +12,7 @@
def test_get_run_data(
+ pac_bio_run_data_generator: PacBioRunDataGenerator,
pac_bio_runs_dir: Path,
pac_bio_test_run_name: str,
pac_bio_smrt_cell_name: str,
@@ -21,8 +22,7 @@ def test_get_run_data(
run_name: str = "/".join([pac_bio_test_run_name, pac_bio_smrt_cell_name])
# WHEN Generating run data
- run_data_generator = PacBioRunDataGenerator()
- run_data: PacBioRunData = run_data_generator.get_run_data(
+ run_data: PacBioRunData = pac_bio_run_data_generator.get_run_data(
run_name=run_name, sequencing_dir=pac_bio_runs_dir.as_posix()
)
@@ -30,18 +30,18 @@ def test_get_run_data(
assert run_data == expected_pac_bio_run_data
-@pytest.mark.parametrize("run_name", ["rimproper_name", "d_improper_name "])
+@pytest.mark.parametrize("wrong_run_name", ["rimproper_name", "d_improper_name "])
def test_get_run_data_improper_name(
+ pac_bio_run_data_generator: PacBioRunDataGenerator,
pac_bio_runs_dir: Path,
- run_name: str,
+ wrong_run_name: str,
):
- # GIVEN a PacBioRunDataGenerator and an improper run name
- run_data_generator = PacBioRunDataGenerator()
+ # GIVEN a PacBioRunDataGenerator and a wrong run name
- # WHEN Generating run data
+ # WHEN Generating run data with the wrong run name
# THEN an PostProcessingRunDataGeneratorError is raised
with pytest.raises(PostProcessingRunDataGeneratorError):
- run_data_generator.get_run_data(
- run_name=run_name, sequencing_dir=pac_bio_runs_dir.as_posix()
+ pac_bio_run_data_generator.get_run_data(
+ run_name=wrong_run_name, sequencing_dir=pac_bio_runs_dir.as_posix()
)
diff --git a/tests/services/run_devices/pacbio/run_file_manager/test_pacbio_run_file_manager.py b/tests/services/run_devices/pacbio/run_file_manager/test_pacbio_run_file_manager.py
index 6d45f8d6fa..f6e87897d9 100644
--- a/tests/services/run_devices/pacbio/run_file_manager/test_pacbio_run_file_manager.py
+++ b/tests/services/run_devices/pacbio/run_file_manager/test_pacbio_run_file_manager.py
@@ -1,25 +1,22 @@
+from pathlib import Path
from unittest import mock
import pytest
-from pathlib import Path
from cg.services.run_devices.exc import PostProcessingRunFileManagerError
from cg.services.run_devices.pacbio.run_data_generator.run_data import PacBioRunData
-from cg.services.run_devices.pacbio.run_file_manager.run_file_manager import (
- PacBioRunFileManager,
-)
+from cg.services.run_devices.pacbio.run_file_manager.run_file_manager import PacBioRunFileManager
def test_get_files_to_parse(
- expected_pac_bio_run_data: PacBioRunData, pac_bio_report_files_to_parse: list[Path]
+ expected_pac_bio_run_data: PacBioRunData,
+ pac_bio_report_files_to_parse: list[Path],
+ pac_bio_run_file_manager: PacBioRunFileManager,
):
- # GIVEN a run data object
-
- # GIVEN a PacBio run file manager
- file_manager = PacBioRunFileManager()
+ # GIVEN a run data object and a PacBio run file manager
# WHEN getting the files to parse
- files: list[Path] = file_manager.get_files_to_parse(expected_pac_bio_run_data)
+ files: list[Path] = pac_bio_run_file_manager.get_files_to_parse(expected_pac_bio_run_data)
# THEN the correct files are returned
assert files == pac_bio_report_files_to_parse
@@ -27,16 +24,14 @@ def test_get_files_to_parse(
def test_get_files_to_store(
expected_pac_bio_run_data: PacBioRunData,
+ pac_bio_run_file_manager: PacBioRunFileManager,
pac_bio_report_files_to_parse: list[Path],
pac_bio_hifi_read_file: Path,
):
- # GIVEN a run data object
-
- # GIVEN a PacBio run file manager
- file_manager = PacBioRunFileManager()
+ # GIVEN a run data object and a PacBio file manager
# WHEN getting the files to store
- files: list[Path] = file_manager.get_files_to_store(expected_pac_bio_run_data)
+ files: list[Path] = pac_bio_run_file_manager.get_files_to_store(expected_pac_bio_run_data)
# THEN the correct files are returned
full_list: list[Path] = pac_bio_report_files_to_parse + [pac_bio_hifi_read_file]
@@ -45,32 +40,32 @@ def test_get_files_to_store(
def test_get_files_to_store_error(
expected_pac_bio_run_data: PacBioRunData,
+ pac_bio_run_file_manager: PacBioRunFileManager,
):
# GIVEN a run data object
- # GIVEN a PacBio run file manager
- file_manager = PacBioRunFileManager()
+ # GIVEN a PacBio run file manager that can't find the HiFi read file
with mock.patch.object(
- file_manager,
- attribute="_get_hifi_read_file",
+ pac_bio_run_file_manager,
+ attribute="_get_hifi_read_files",
side_effect=FileNotFoundError,
):
# WHEN getting the files to store
# THEN an PostProcessingRunFileManagerError is raised
with pytest.raises(PostProcessingRunFileManagerError):
- file_manager.get_files_to_store(expected_pac_bio_run_data)
+ pac_bio_run_file_manager.get_files_to_store(expected_pac_bio_run_data)
def test_get_files_to_parse_error(
expected_pac_bio_run_data: PacBioRunData,
+ pac_bio_run_file_manager: PacBioRunFileManager,
):
# GIVEN a run data object
- # GIVEN a PacBio run file manager
- file_manager = PacBioRunFileManager()
+ # GIVEN a PacBio run file manager that can't find the CCS report file
with mock.patch.object(
- file_manager,
+ pac_bio_run_file_manager,
attribute="_get_ccs_report_file",
side_effect=FileNotFoundError,
):
@@ -78,4 +73,4 @@ def test_get_files_to_parse_error(
# THEN an PostProcessingRunFileManagerError is raised
with pytest.raises(PostProcessingRunFileManagerError):
- file_manager.get_files_to_parse(expected_pac_bio_run_data)
+ pac_bio_run_file_manager.get_files_to_parse(expected_pac_bio_run_data)
diff --git a/tests/services/run_devices/pacbio/store_service/test_store_service.py b/tests/services/run_devices/pacbio/store_service/test_store_service.py
index 8a526176a0..5fe8df1028 100644
--- a/tests/services/run_devices/pacbio/store_service/test_store_service.py
+++ b/tests/services/run_devices/pacbio/store_service/test_store_service.py
@@ -5,8 +5,8 @@
import pytest
from cg.services.run_devices.exc import (
- PostProcessingStoreDataError,
PostProcessingDataTransferError,
+ PostProcessingStoreDataError,
)
from cg.services.run_devices.pacbio.data_storage_service.pacbio_store_service import (
PacBioStoreService,
@@ -17,7 +17,6 @@
from cg.services.run_devices.pacbio.data_transfer_service.dto import PacBioDTOs
from cg.services.run_devices.pacbio.run_data_generator.run_data import PacBioRunData
from cg.store.models import PacBioSampleSequencingMetrics, PacBioSequencingRun, PacBioSMRTCell
-
from cg.store.store import Store
@@ -28,7 +27,7 @@ def test_store_post_processing_data(
):
# GIVEN a PacBioStoreService
- # GIVEN a successful data transfer service
+ # GIVEN a data transfer service that returns the correct DTOs
# WHEN storing data for a PacBio instrument run
with mock.patch(
@@ -67,7 +66,7 @@ def test_store_post_processing_data_error_database(
):
# GIVEN a PacBioStoreService
- # GIVEN a successful data transfer service
+ # GIVEN a data transfer service that returns the correct DTOs
# WHEN storing data for a PacBio instrument run
with mock.patch(
@@ -85,7 +84,7 @@ def test_store_post_processing_data_error_parser(
):
# GIVEN a PacBioStoreService
- # GIVEN a successful data transfer service
+ # GIVEN a data transfer service that returns the correct DTOs
# WHEN storing data for a PacBio instrument run
with mock.patch(
diff --git a/tests/utils/test_files.py b/tests/utils/test_files.py
index 7ef3e7972f..59572a5051 100644
--- a/tests/utils/test_files.py
+++ b/tests/utils/test_files.py
@@ -9,12 +9,21 @@
get_file_with_pattern_from_list,
get_files_in_directory_with_pattern,
get_files_matching_pattern,
+ get_project_root_dir,
get_source_creation_time_stamp,
remove_directory_and_contents,
rename_file,
)
+def test_get_project_root_dir():
+ # WHEN getting the project root dir
+ root_dir: Path = get_project_root_dir()
+
+ # THEN return the dir path
+ assert root_dir.name == "cg"
+
+
def test_get_file_in_directory(nested_directory_with_file: Path, some_file: str):
"""Test function to get a file in a directory and subdirectories."""
# GIVEN a directory with subdirectories with a file
diff --git a/tests/utils/test_string_utils.py b/tests/utils/test_string_utils.py
deleted file mode 100644
index f70cdc4ec1..0000000000
--- a/tests/utils/test_string_utils.py
+++ /dev/null
@@ -1,30 +0,0 @@
-"""Test for the string utilities."""
-
-import pytest
-
-from cg.exc import CgError
-from cg.utils.string import get_element_from_split
-
-
-def test_get_element_from_split():
-
- # GIVEN a string with a seperator
- separated_string: str = "zero_one_two_three"
-
- # WHEN getting an element divided by a separator based on the position
- element: str = get_element_from_split(value=separated_string, element_position=2, split="_")
-
- # THEN the expected element is returned
- assert element == "two"
-
-
-def test_get_element_from_split_error():
-
- # GIVEN a string with a seperator
- separated_string: str = "zero_one_two_three"
-
- # WHEN getting an element divided by a separator based on the position that is out of bounds
- with pytest.raises(CgError):
- get_element_from_split(value=separated_string, element_position=12, split="_")
-
- # THEN an error is raised