Refactor the code of osccore-qa-testing-tools to comply with PEP8.
Related-prod: PRODX-42195
Change-Id: Id05e7584e0d024127ce1bd5042cfe681a1b52e2d
diff --git a/testrail_bot/control/admin.py b/testrail_bot/control/admin.py
index 89a2511..2270674 100644
--- a/testrail_bot/control/admin.py
+++ b/testrail_bot/control/admin.py
@@ -1,6 +1,6 @@
from django.contrib import admin
-from .models import TestRailTestRun, TestRailReport
+from .models import TestRailReport, TestRailTestRun
admin.site.register(TestRailTestRun)
admin.site.register(TestRailReport)
diff --git a/testrail_bot/control/apps.py b/testrail_bot/control/apps.py
index a5f7085..7d84cd0 100644
--- a/testrail_bot/control/apps.py
+++ b/testrail_bot/control/apps.py
@@ -2,4 +2,4 @@
class ControlConfig(AppConfig):
- name = 'control'
+ name = "control"
diff --git a/testrail_bot/control/celery_tasks/filters.py b/testrail_bot/control/celery_tasks/filters.py
index 4407eed..b0f0852 100644
--- a/testrail_bot/control/celery_tasks/filters.py
+++ b/testrail_bot/control/celery_tasks/filters.py
@@ -2,17 +2,18 @@
def filter_ip(data: str) -> str:
- ip_addr_regex = re.compile(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b')
+ ip_addr_regex = re.compile(r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b")
return re.sub(ip_addr_regex, "x.x.x.x", data)
def filter_uuid(data: str) -> str:
uuid4hex = re.compile(
- r'[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}\Z', re.I)
+ r"[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}\Z", re.I
+ )
return re.sub(uuid4hex, "xxxx", data)
def last_traceback_filter(data: str) -> str:
if data.rfind("Traceback") < 0:
return data
- return data[data.rfind("Traceback"):]
+ return data[data.rfind("Traceback") :]
diff --git a/testrail_bot/control/celery_tasks/jenkins_pipeline.py b/testrail_bot/control/celery_tasks/jenkins_pipeline.py
index e3c9904..1d93535 100644
--- a/testrail_bot/control/celery_tasks/jenkins_pipeline.py
+++ b/testrail_bot/control/celery_tasks/jenkins_pipeline.py
@@ -1,21 +1,18 @@
-from datetime import datetime, timedelta, timezone
import json
import os
+from datetime import datetime, timedelta, timezone
from django.conf import settings
-from matplotlib import pyplot as plt
-from matplotlib import dates as mdates
-
from jenkins import Jenkins
+from matplotlib import dates as mdates
+from matplotlib import pyplot as plt
from .. import models
-
-__all__ = ('update_plot',)
+__all__ = ("update_plot",)
-jenkins_client = Jenkins(
- "https://ci.mcp.mirantis.net/")
+jenkins_client = Jenkins("https://ci.mcp.mirantis.net/")
VIEW_NAME = "MCP2.0 Openstack Periodic CI"
@@ -24,13 +21,22 @@
def get_attr(attr):
return getattr(original_obj, attr, 0)
- return datetime(year=2000, month=1, day=1, hour=get_attr("hour"),
- minute=get_attr("minute"), second=get_attr("second"))
+ return datetime(
+ year=2000,
+ month=1,
+ day=1,
+ hour=get_attr("hour"),
+ minute=get_attr("minute"),
+ second=get_attr("second"),
+ )
def build_start_time(timestamp):
- return build_time_obj(datetime.utcfromtimestamp(
- timestamp / 1000)).replace(tzinfo=timezone.utc).timestamp()
+ return (
+ build_time_obj(datetime.utcfromtimestamp(timestamp / 1000))
+ .replace(tzinfo=timezone.utc)
+ .timestamp()
+ )
def process_build(job_name, build):
@@ -38,8 +44,10 @@
if build_info["result"] != "SUCCESS":
return None
- return build_start_time(build_info["timestamp"]), \
- build_info["duration"] / 1000
+ return (
+ build_start_time(build_info["timestamp"]),
+ build_info["duration"] / 1000,
+ )
def calculate_average(values):
@@ -52,15 +60,18 @@
start_times, durations = zip(*filter(None, builds_info))
- avg_start_time = datetime.utcfromtimestamp(
- calculate_average(start_times))
- return {"duration": calculate_average(durations),
- "start_time": avg_start_time}
+ avg_start_time = datetime.utcfromtimestamp(calculate_average(start_times))
+ return {
+ "duration": calculate_average(durations),
+ "start_time": avg_start_time,
+ }
def get_aggregated_build_stats():
- return {job_name["name"]: process_job(job_name["name"])
- for job_name in jenkins_client.get_jobs(view_name=VIEW_NAME)}
+ return {
+ job_name["name"]: process_job(job_name["name"])
+ for job_name in jenkins_client.get_jobs(view_name=VIEW_NAME)
+ }
def get_lines(current, standard_datetime, next_day):
@@ -70,7 +81,8 @@
if end >= next_day:
return [
(standard_datetime, standard_datetime + (end - next_day)),
- (start_time, next_day - timedelta(seconds=1))]
+ (start_time, next_day - timedelta(seconds=1)),
+ ]
return [(start_time, end)]
@@ -78,8 +90,10 @@
def build_data_for_jobs_time_plot(jobs):
standard_datetime = build_time_obj()
next_day = standard_datetime + timedelta(days=1)
- return {job_name: get_lines(jobs[job_name], standard_datetime, next_day)
- for job_name in jobs}
+ return {
+ job_name: get_lines(jobs[job_name], standard_datetime, next_day)
+ for job_name in jobs
+ }
def draw_plot(plot_data):
@@ -100,7 +114,8 @@
# Set date limits
start_time = build_time_obj()
ax.set_xlim(
- start_time, start_time + timedelta(days=1) + timedelta(seconds=1))
+ start_time, start_time + timedelta(days=1) + timedelta(seconds=1)
+ )
# Set y axes limits
jobs_num = len(plot_data) + 1
@@ -136,7 +151,8 @@
try:
log_record = models.ActionLog.objects.get(
- name="update_jenkins_plot")
+ name="update_jenkins_plot"
+ )
except models.ActionLog.DoesNotExist:
log_record = models.ActionLog(name="update_jenkins_plot")
log_record.date = datetime.now()
diff --git a/testrail_bot/control/celery_tasks/schedules_pipeline.py b/testrail_bot/control/celery_tasks/schedules_pipeline.py
index b121a14..1ad090a 100644
--- a/testrail_bot/control/celery_tasks/schedules_pipeline.py
+++ b/testrail_bot/control/celery_tasks/schedules_pipeline.py
@@ -1,9 +1,9 @@
-from datetime import datetime, timedelta, timezone
import os
+from datetime import datetime
from .. import models
-from .test_rail_api import get_planid_by_name
from . import tasks
+from .test_rail_api import get_planid_by_name
def task_to_check_today_testplan():
@@ -17,8 +17,8 @@
today = datetime.today().strftime("%Y-%m-%d")
plan_name = f"[MCP2.0]OSCORE-{today}"
plan_id = get_planid_by_name(
- name=plan_name,
- project_name="Mirantis Cloud Platform")
+ name=plan_name, project_name="Mirantis Cloud Platform"
+ )
if not plan_id:
print(f"Can't found {plan_name} TestPlan")
return
@@ -36,15 +36,17 @@
pass
report_obj, _ = models.TestRailReport.objects.get_or_create(
- report_name=report_name,
- path=path)
+ report_name=report_name, path=path
+ )
report_obj.finished = False
report_obj.save()
- return tasks.process_run(bot_run_id=testrun_obj.id,
- report_id=report_obj.id,
- path=path,
- is_testplan=True)
+ return tasks.process_run(
+ bot_run_id=testrun_obj.id,
+ report_id=report_obj.id,
+ path=path,
+ is_testplan=True,
+ )
def task_to_check_testplan(testplan_id: int):
@@ -68,12 +70,14 @@
pass
report_obj, _ = models.TestRailReport.objects.get_or_create(
- report_name=report_name,
- path=path)
+ report_name=report_name, path=path
+ )
report_obj.finished = False
report_obj.save()
- return tasks.process_run(bot_run_id=testrun_obj.id,
- report_id=report_obj.id,
- path=path,
- is_testplan=True)
+ return tasks.process_run(
+ bot_run_id=testrun_obj.id,
+ report_id=report_obj.id,
+ path=path,
+ is_testplan=True,
+ )
diff --git a/testrail_bot/control/celery_tasks/tasks.py b/testrail_bot/control/celery_tasks/tasks.py
index dcc9cc1..cd18f6d 100644
--- a/testrail_bot/control/celery_tasks/tasks.py
+++ b/testrail_bot/control/celery_tasks/tasks.py
@@ -1,22 +1,26 @@
from __future__ import absolute_import, unicode_literals
import traceback
+
from celery import shared_task
-from . import jenkins_pipeline, testrail_pipeline, schedules_pipeline
+from . import jenkins_pipeline, schedules_pipeline, testrail_pipeline
@shared_task
def process_run(bot_run_id, report_id, path, is_testplan):
try:
- testrail_pipeline.process_test_run(bot_run_id, report_id, path,
- is_testplan)
+ testrail_pipeline.process_test_run(
+ bot_run_id, report_id, path, is_testplan
+ )
except BaseException as e:
- with open(path, 'a') as f:
+ with open(path, "a") as f:
print(f"Caught next exception: {e}")
traceback.print_exc()
- f.write("<b style='color:red;background-color:pink'>Task "
- "completed unsuccessfully</b>\n")
+ f.write(
+ "<b style='color:red;background-color:pink'>Task "
+ "completed unsuccessfully</b>\n"
+ )
f.flush()
@@ -33,6 +37,7 @@
print(f"Caught next exception: {e}")
traceback.print_exc()
from .. import models
+
r = models.SuitePassRate.objects.get(pk=report_id)
r.status = "Unexpected fail"
r.finished = True
@@ -61,4 +66,3 @@
:return:
"""
schedules_pipeline.task_to_check_testplan(testplan_id)
-
diff --git a/testrail_bot/control/celery_tasks/test_rail_api.py b/testrail_bot/control/celery_tasks/test_rail_api.py
index 142f403..47c4e6a 100644
--- a/testrail_bot/control/celery_tasks/test_rail_api.py
+++ b/testrail_bot/control/celery_tasks/test_rail_api.py
@@ -1,61 +1,71 @@
-from testrail_api import TestRailAPI, StatusCodeError
-from requests.exceptions import ReadTimeout
+from typing import Iterator, List, Optional
+
from django.conf import settings
from django.utils.html import escape
-from typing import Optional, List, Iterator
-
+from requests.exceptions import ReadTimeout
from retry import retry
-from .enums import StatusEnum, TimeEnum
+from testrail_api import StatusCodeError, TestRailAPI
+
from ..utils import cached
+from .enums import StatusEnum, TimeEnum
api = TestRailAPI(
"https://mirantis.testrail.com/",
settings.TESTRAIL_EMAIL,
- settings.TESTRAIL_PASSWORD)
+ settings.TESTRAIL_PASSWORD,
+)
@cached()
def get_project_id(project_name: str) -> Optional[int]:
- project = list(filter(
- lambda x: x["name"] == project_name,
- api.projects.get_projects()['projects']))
+ project = list(
+ filter(
+ lambda x: x["name"] == project_name,
+ api.projects.get_projects()["projects"],
+ )
+ )
if project:
return project[0]["id"]
else:
return None
-@cached(timeout=1*TimeEnum.DAYS)
+@cached(timeout=1 * TimeEnum.DAYS)
def get_suite_by_id(suite_id: int) -> dict:
return api.suites.get_suite(suite_id)
@cached()
def get_suite_name_by_id(suite_id: int) -> str:
- return api.suites.get_suite(suite_id)['name']
+ return api.suites.get_suite(suite_id)["name"]
@cached()
def get_suite_test_type(suite_id: int) -> str:
suite_name = get_suite_name_by_id(suite_id)
- return suite_name.split(']')[1]
+ return suite_name.split("]")[1]
-@cached(timeout=1*TimeEnum.HOURS)
+@cached(timeout=1 * TimeEnum.HOURS)
def get_plans(project_id: int, **kwargs) -> List[dict]:
- plans = api.plans.get_plans(project_id=project_id, **kwargs)['plans']
+ plans = api.plans.get_plans(project_id=project_id, **kwargs)["plans"]
return plans
-@cached(timeout=2*TimeEnum.HOURS,
- condition_for_endless_cache=lambda x: x is not None)
-def get_planid_by_name(name: str, project_name: str, **kwargs) \
- -> Optional[int]:
+@cached(
+ timeout=2 * TimeEnum.HOURS,
+ condition_for_endless_cache=lambda x: x is not None,
+)
+def get_planid_by_name(
+ name: str, project_name: str, **kwargs
+) -> Optional[int]:
limit_step = 100
for offset in range(0, 500, limit_step):
- plans = get_plans(project_id=get_project_id(project_name),
- limit=limit_step,
- offset=offset)
+ plans = get_plans(
+ project_id=get_project_id(project_name),
+ limit=limit_step,
+ offset=offset,
+ )
if not plans:
return
for plan in plans:
@@ -65,7 +75,7 @@
return
-@cached(timeout=1*TimeEnum.HOURS)
+@cached(timeout=1 * TimeEnum.HOURS)
def get_entries(plan_id: int) -> List[dict]:
return api.plans.get_plan(plan_id)["entries"]
@@ -83,25 +93,28 @@
return api.plans.get_plan(plan_id)
-def get_result_history_for_case(case_id: int,
- status_id: int = None,
- project_name: str = "Mirantis Cloud Platform",
- plan_name: str = None,
- created_after: str = None,
- created_before: str = None,
- created_by: int = None,
- testrun_pattern: str = None,
- **kwargs) -> \
- Iterator[List[dict]]:
+def get_result_history_for_case(
+ case_id: int,
+ status_id: int = None,
+ project_name: str = "Mirantis Cloud Platform",
+ plan_name: str = None,
+ created_after: str = None,
+ created_before: str = None,
+ created_by: int = None,
+ testrun_pattern: str = None,
+ **kwargs,
+) -> Iterator[List[dict]]:
limit_step = 100
suite_id = api.cases.get_case(case_id=case_id)["suite_id"]
for offset in range(0, 2000, limit_step):
- plans = get_plans(project_id=get_project_id(project_name),
- limit=limit_step,
- offset=offset,
- created_after=created_after,
- created_before=created_before,
- created_by=created_by)
+ plans = get_plans(
+ project_id=get_project_id(project_name),
+ limit=limit_step,
+ offset=offset,
+ created_after=created_after,
+ created_before=created_before,
+ created_by=created_by,
+ )
if not plans:
return
for plan in plans:
@@ -117,50 +130,48 @@
if type(status_id) is list:
status_id = ",".join(map(lambda x: str(x), status_id))
- results = get_result_for_case(run_id=run["id"],
- case_id=case_id,
- status_id=status_id)
+ results = get_result_for_case(
+ run_id=run["id"], case_id=case_id, status_id=status_id
+ )
if results:
yield results
def get_run_id(entries: List[dict], run_name: str) -> Optional[int]:
- entries = list(filter(
- lambda x: x["name"] == run_name,
- entries))
+ entries = list(filter(lambda x: x["name"] == run_name, entries))
if not entries:
return None
return entries[0]["runs"][0]["id"]
-@cached(timeout=2*TimeEnum.HOURS,
- condition_for_endless_cache=lambda x: x is None)
+@cached(
+ timeout=2 * TimeEnum.HOURS, condition_for_endless_cache=lambda x: x is None
+)
@retry(ReadTimeout, delay=1, jitter=2, tries=3)
-def get_result_for_case(run_id: int,
- case_id: int,
- **kwargs) -> Optional[List[dict]]:
+def get_result_for_case(
+ run_id: int, case_id: int, **kwargs
+) -> Optional[List[dict]]:
try:
- results = api.results.get_results_for_case(run_id, case_id, **kwargs
- )['results']
+ results = api.results.get_results_for_case(run_id, case_id, **kwargs)[
+ "results"
+ ]
except StatusCodeError:
return None
return results
def get_failed_tests(last_run_id: int, by_plans=False) -> List[dict]:
- failed_statuses = [StatusEnum.failed,
- StatusEnum.blocked]
+ failed_statuses = [StatusEnum.failed, StatusEnum.blocked]
status_id = ",".join(map(str, failed_statuses))
if by_plans:
failed_tests = []
for entry in get_entries(last_run_id):
for run in entry["runs"]:
failed_tests += api.tests.get_tests(
- run_id=run["id"],
- status_id=status_id)['tests']
+ run_id=run["id"], status_id=status_id
+ )["tests"]
return failed_tests
- return api.tests.get_tests(
- last_run_id, status_id=status_id)['tests']
+ return api.tests.get_tests(last_run_id, status_id=status_id)["tests"]
def add_result(test_id: int, update_dict: dict) -> None:
@@ -178,5 +189,7 @@
def html_link(type: str, id: int, title: str) -> str:
- return f"<a href='https://mirantis.testrail.com/index.php?/{type}s/view/" \
- f"{id}'>{escape(title)}</a>"
+ return (
+ f"<a href='https://mirantis.testrail.com/index.php?/{type}s/view/"
+ f"{id}'>{escape(title)}</a>"
+ )
diff --git a/testrail_bot/control/celery_tasks/testrail_pipeline.py b/testrail_bot/control/celery_tasks/testrail_pipeline.py
index d0eae11..472046d 100644
--- a/testrail_bot/control/celery_tasks/testrail_pipeline.py
+++ b/testrail_bot/control/celery_tasks/testrail_pipeline.py
@@ -1,16 +1,15 @@
import datetime
import difflib
import json
-from typing import TextIO, List, Tuple, Optional, Iterator, Dict
-
from datetime import datetime as dt
from datetime import timedelta
from itertools import islice
-from . import filters
-from .enums import StatusEnum
-from . import test_rail_api
+from typing import Dict, Iterator, List, Optional, TextIO, Tuple
+
from .. import models
from ..jira_manager import JiraIssue
+from . import filters, test_rail_api
+from .enums import StatusEnum
__all__ = ("process_test_run",)
@@ -25,12 +24,13 @@
report.save()
-def apply_filters(data: str,
- filter_last_traceback: bool,
- ip_filter: bool,
- uuid_filter: bool,
- filter_func: str
- ) -> str:
+def apply_filters(
+ data: str,
+ filter_last_traceback: bool,
+ ip_filter: bool,
+ uuid_filter: bool,
+ filter_func: str,
+) -> str:
"""
Applies various text modifiers (filtering, masking, etc.) to the input
text.
@@ -65,9 +65,9 @@
return data
-def get_runs_by_pattern(runs_in_plan: List[dict],
- test_pattern: str,
- suite_id: int) -> List[int]:
+def get_runs_by_pattern(
+ runs_in_plan: List[dict], test_pattern: str, suite_id: int
+) -> List[int]:
"""
Returns a list of run IDs that are related to a specific Test Suite
and have names containing a pattern (test_pattern)
@@ -82,64 +82,68 @@
"""
run = []
for t_run in runs_in_plan:
- if test_pattern in t_run['name'] and t_run['suite_id'] == suite_id:
- run.append(t_run['runs'][0]['id'])
+ if test_pattern in t_run["name"] and t_run["suite_id"] == suite_id:
+ run.append(t_run["runs"][0]["id"])
return run
def find_fail_with_same_comment(
- case_id: int,
- last_comment: str,
- plan_name: str,
- testrun_pattern: str,
- created_by_id: int,
- created_after: int,
- created_before: int,
- text_filters: dict,
+ case_id: int,
+ last_comment: str,
+ plan_name: str,
+ testrun_pattern: str,
+ created_by_id: int,
+ created_after: int,
+ created_before: int,
+ text_filters: dict,
) -> Iterator[Tuple[Optional[dict], float, int]]:
"""
- Searches for similar failures within a test plan based on specific
- criteria.
+ Searches for similar failures within a test plan based on specific
+ criteria.
- :param case_id: The ID of the test case for which the failure is
- being searched
- :param last_comment: The last comment associated with the failed test
- :param plan_name: The name of the test plan to search within
- :param testrun_pattern: A pattern for filtering test runs
- :param created_by_id: The ID of the user who created the test plan
- :param created_after: The date (created_after) after which the test
- plan was created
- :param created_before: The date (created_before) before which the test
- plan was created
- :param run_name: The name of the test run
- :param text_filters: A dictionary of text filters to apply when
- comparing comments
+ :param case_id: The ID of the test case for which the failure is
+ being searched
+ :param last_comment: The last comment associated with the failed test
+ :param plan_name: The name of the test plan to search within
+ :param testrun_pattern: A pattern for filtering test runs
+ :param created_by_id: The ID of the user who created the test plan
+ :param created_after: The date (created_after) after which the test
+ plan was created
+ :param created_before: The date (created_before) before which the test
+ plan was created
+ :param run_name: The name of the test run
+ :param text_filters: A dictionary of text filters to apply when
+ comparing comments
- :return: An iterator that yields tuples containing information
- about matching test results, including test result data, similarity
- ratio, and the associated run ID.
- """
+ :return: An iterator that yields tuples containing information
+ about matching test results, including test result data, similarity
+ ratio, and the associated run ID.
+ """
end_lookup_date = dt.strptime(
- f"{created_before} 23:59:59", "%Y-%m-%d %H:%M:%S")
+ f"{created_before} 23:59:59", "%Y-%m-%d %H:%M:%S"
+ )
start_lookup_date = dt.strptime(
- f"{created_after} 00:00:00", "%Y-%m-%d %H:%M:%S")
+ f"{created_after} 00:00:00", "%Y-%m-%d %H:%M:%S"
+ )
filters = {
"created_by": created_by_id,
"created_before": int(dt.timestamp(end_lookup_date)),
"created_after": int(dt.timestamp(start_lookup_date)),
"plan_name": plan_name,
- "status_id": [StatusEnum.test_failed,
- StatusEnum.failed,
- StatusEnum.blocked,
- StatusEnum.product_failed,
- StatusEnum.wont_fix,
- StatusEnum.retest],
- "testrun_pattern": testrun_pattern
+ "status_id": [
+ StatusEnum.test_failed,
+ StatusEnum.failed,
+ StatusEnum.blocked,
+ StatusEnum.product_failed,
+ StatusEnum.wont_fix,
+ StatusEnum.retest,
+ ],
+ "testrun_pattern": testrun_pattern,
}
- for n, results in enumerate(test_rail_api.get_result_history_for_case(
- case_id,
- **filters)):
+ for n, results in enumerate(
+ test_rail_api.get_result_history_for_case(case_id, **filters)
+ ):
if n >= 500 or not results:
yield None, None, None
return
@@ -147,17 +151,21 @@
comment = apply_filters(results[-1]["comment"], **text_filters)
ratio = difflib.SequenceMatcher(
lambda symbol: symbol in [" ", ",", "\n"],
- last_comment, comment, autojunk=False).ratio()
+ last_comment,
+ comment,
+ autojunk=False,
+ ).ratio()
if ratio > 0.7:
run_id = test_rail_api.api.tests.get_test(results[0]["test_id"])[
- "run_id"]
+ "run_id"
+ ]
yield results[0], ratio, run_id
-def get_project_id(f: TextIO,
- test_run: models.TestRailTestRun,
- report: models.TestRailReport) -> Optional[int]:
+def get_project_id(
+ f: TextIO, test_run: models.TestRailTestRun, report: models.TestRailReport
+) -> Optional[int]:
"""
Returns the TestRail Project ID associated with a specific test run
@@ -171,17 +179,20 @@
"""
project_id = test_rail_api.get_project_id(test_run.project_name)
if not project_id:
- f.write("Incorrect Project {}. Stopping processing\n".format(
- test_run.project_name))
+ f.write(
+ "Incorrect Project {}. Stopping processing\n".format(
+ test_run.project_name
+ )
+ )
f.flush()
finish_report(report)
return None
return project_id
-def get_plans(test_run: models.TestRailTestRun,
- run_date: dt,
- project_id: int) -> List[dict]:
+def get_plans(
+ test_run: models.TestRailTestRun, run_date: dt, project_id: int
+) -> List[dict]:
"""
Get plans which will be processed
@@ -209,20 +220,20 @@
:return: A string containing the filtered last comment for the specified
test case in the given test run
"""
- last_result = test_rail_api.get_result_for_case(
- run_id, case_id)
+ last_result = test_rail_api.get_result_for_case(run_id, case_id)
- return apply_filters(
- last_result[0]["comment"], **text_filters)
+ return apply_filters(last_result[0]["comment"], **text_filters)
-def process_old_test(f: TextIO,
- case_id: int,
- last_comment: str,
- run_id: int,
- test: dict,
- testrail_filters: dict,
- text_filters: dict) -> bool:
+def process_old_test(
+ f: TextIO,
+ case_id: int,
+ last_comment: str,
+ run_id: int,
+ test: dict,
+ testrail_filters: dict,
+ text_filters: dict,
+) -> bool:
"""
Writes to report file similarity info about the TestCase under the test
@@ -230,109 +241,130 @@
"""
found_unknown_fail = 0
for sim_result, ratio, old_run_id in find_fail_with_same_comment(
- case_id,
- last_comment,
- text_filters=text_filters,
- **testrail_filters):
+ case_id, last_comment, text_filters=text_filters, **testrail_filters
+ ):
if str(run_id) == str(old_run_id):
continue
per = round(100.0 * ratio, 2)
- run_link = test_rail_api.html_link('run', old_run_id, old_run_id)
+ run_link = test_rail_api.html_link("run", old_run_id, old_run_id)
if type(sim_result) is not dict:
- f.write(f"Similarity not found due to similarity: {per}, "
- f"in run {run_link}\n")
+ f.write(
+ f"Similarity not found due to similarity: {per}, "
+ f"in run {run_link}\n"
+ )
f.flush()
return False
- prod_link = "None" \
- if str(sim_result["defects"]) == "None" \
+ prod_link = (
+ "None"
+ if str(sim_result["defects"]) == "None"
else JiraIssue(sim_result["defects"]).html()
- test_link = test_rail_api.html_link('test',
- sim_result["test_id"],
- str(sim_result["test_id"]))
- status_id = int(sim_result['status_id'])
- if status_id in [StatusEnum.retest, StatusEnum.failed,
- StatusEnum.blocked]:
- f.write(f"Found a similar result on the test "
- f"{test_link} with similarity {per}% and "
- f"{StatusEnum(status_id).name} status and {prod_link} "
- f"defect. <i>Continuing...</i>\n")
+ )
+ test_link = test_rail_api.html_link(
+ "test", sim_result["test_id"], str(sim_result["test_id"])
+ )
+ status_id = int(sim_result["status_id"])
+ if status_id in [
+ StatusEnum.retest,
+ StatusEnum.failed,
+ StatusEnum.blocked,
+ ]:
+ f.write(
+ f"Found a similar result on the test "
+ f"{test_link} with similarity {per}% and "
+ f"{StatusEnum(status_id).name} status and {prod_link} "
+ f"defect. <i>Continuing...</i>\n"
+ )
f.flush()
found_unknown_fail += 1
if found_unknown_fail >= 10:
- f.write(f"<b style='color:red;'>"
- f"Detected 10+ consecutive unknown failures\n </b>")
+ f.write(
+ "<b style='color:red;'>"
+ "Detected 10+ consecutive unknown failures\n </b>"
+ )
f.flush()
return False
continue
elif ratio > 0.9:
- comment = f"Marked by TestRailBot because of similarity " \
- f"with test {sim_result['test_id']} {per}%"
+ comment = (
+ f"Marked by TestRailBot because of similarity "
+ f"with test {sim_result['test_id']} {per}%"
+ )
# Copy the original comment if it was not created by this bot
- if str(sim_result["status_id"]) == StatusEnum.wont_fix \
- and sim_result["comment"] \
- and "Marked by TestRailBot" not in sim_result["comment"]:
+ if (
+ str(sim_result["status_id"]) == StatusEnum.wont_fix
+ and sim_result["comment"]
+ and "Marked by TestRailBot" not in sim_result["comment"]
+ ):
comment = sim_result["comment"]
update_dict = {
"status_id": sim_result["status_id"],
"comment": comment,
- "defects": sim_result["defects"]
+ "defects": sim_result["defects"],
}
- f.write(f"Found a similar result on the test "
- f"{test_link} with similarity {per}% and "
- f"{StatusEnum(status_id).name} status and {prod_link} "
- f"defect\n"
- f"<i style='color:ForestGreen;'>Pushing to TestRail "
- f"{update_dict}"
- f"</i>\n\n")
+ f.write(
+ f"Found a similar result on the test "
+ f"{test_link} with similarity {per}% and "
+ f"{StatusEnum(status_id).name} status and {prod_link} "
+ f"defect\n"
+ f"<i style='color:ForestGreen;'>Pushing to TestRail "
+ f"{update_dict}"
+ f"</i>\n\n"
+ )
f.flush()
test_rail_api.add_result(test["id"], update_dict)
return True
elif ratio > 0.7:
- f.write(f"<b style='color:red;'> "
- f"Found a similar result on the test "
- f"{test_link} with similarity {per}% and "
- f"{StatusEnum(status_id).name} status and {prod_link} "
- f"defect,\n but NOT marked by "
- f"TestRailBot because of similarity only, "
- f"you can update manually \n </b>")
+ f.write(
+ f"<b style='color:red;'> "
+ f"Found a similar result on the test "
+ f"{test_link} with similarity {per}% and "
+ f"{StatusEnum(status_id).name} status and {prod_link} "
+ f"defect,\n but NOT marked by "
+ f"TestRailBot because of similarity only, "
+ f"you can update manually \n </b>"
+ )
f.flush()
return True
-def process_test(f: TextIO,
- test: dict,
- testrail_filters: dict,
- text_filters: dict) -> None:
+def process_test(
+ f: TextIO, test: dict, testrail_filters: dict, text_filters: dict
+) -> None:
"""
Starts processing for the TestCase for each TestPlan
"""
case_id = test["case_id"]
run_id = test["run_id"]
run_name = test_rail_api.get_run_name(run_id)
- test_link = test_rail_api.html_link('test', test['id'], test["title"])
- run_link = test_rail_api.html_link('run', run_id, run_name)
+ test_link = test_rail_api.html_link("test", test["id"], test["title"])
+ run_link = test_rail_api.html_link("run", run_id, run_name)
- f.write(f"<br><b>Proceeding test {test_link} <br>"
- f"in {run_link} run</b>\n")
+ f.write(
+ f"<br><b>Proceeding test {test_link} <br>" f"in {run_link} run</b>\n"
+ )
f.flush()
last_comment = get_last_comment(case_id, run_id, text_filters)
found = process_old_test(
- f, case_id, last_comment, run_id, test, testrail_filters, text_filters)
+ f, case_id, last_comment, run_id, test, testrail_filters, text_filters
+ )
if found:
return
else:
- f.write(f"<b style='color:red;'>Automatic test processing failed. "
- "Please process test manually "
- f"{test_link}</b>\n\n")
+ f.write(
+ f"<b style='color:red;'>Automatic test processing failed. "
+ "Please process test manually "
+ f"{test_link}</b>\n\n"
+ )
f.flush()
-def process_test_run(bot_run_id: int, report_id: int, path: str,
- is_testplan: bool) -> None:
+def process_test_run(
+ bot_run_id: int, report_id: int, path: str, is_testplan: bool
+) -> None:
"""
This function processes a created bot test run. It retrieves a list
of test plans to process, gathers the failed tests from the test run,
@@ -354,7 +386,9 @@
else:
test_run = test_rail_api.get_run_by_id(bot_test_run.run_id)
run_type = "run"
- link = test_rail_api.html_link(run_type, test_run['id'], test_run['name'])
+ link = test_rail_api.html_link(
+ run_type, test_run["id"], test_run["name"]
+ )
f.write(f"Start processing {run_type} {link}\n")
f.flush()
@@ -363,15 +397,21 @@
return
# failed_tests: all failed tests in test run/plan
- failed_tests = test_rail_api.get_failed_tests(bot_test_run.run_id,
- by_plans=is_testplan)
+ failed_tests = test_rail_api.get_failed_tests(
+ bot_test_run.run_id, by_plans=is_testplan
+ )
for test in failed_tests:
- if bot_test_run.caching_tests_enabled and \
- test["id"] in bot_test_run.checked_tests:
+ if (
+ bot_test_run.caching_tests_enabled
+ and test["id"] in bot_test_run.checked_tests
+ ):
continue
- process_test(f, test,
- bot_test_run.testrail_filters,
- bot_test_run.text_filters)
+ process_test(
+ f,
+ test,
+ bot_test_run.testrail_filters,
+ bot_test_run.text_filters,
+ )
bot_test_run.checked_tests.append(test["id"])
bot_test_run.save()
f.write("Test processing finished")
@@ -379,13 +419,15 @@
finish_report(report)
-def get_cases(project_id: int, suite_id: int,
- limit: int = 250,
- max_limit: int = 1000,
- filter: str = None,
- created_after: int = None,
- created_before: int = None,
- ) -> Iterator[Dict]:
+def get_cases(
+ project_id: int,
+ suite_id: int,
+ limit: int = 250,
+ max_limit: int = 1000,
+ filter: str = None,
+ created_after: int = None,
+ created_before: int = None,
+) -> Iterator[Dict]:
for offset in range(0, max_limit, limit):
cases = test_rail_api.api.cases.get_cases(
project_id=project_id,
@@ -394,7 +436,8 @@
offset=offset,
created_after=created_after,
created_before=created_before,
- filter=filter).get("cases")
+ filter=filter,
+ ).get("cases")
if not cases:
return
@@ -423,10 +466,12 @@
}
"""
- diff_obj: models.DiffOfSuitesPassRates = \
+ diff_obj: models.DiffOfSuitesPassRates = (
models.DiffOfSuitesPassRates.objects.get(pk=diff_id)
+ )
report: models.SuitePassRate = models.SuitePassRate.objects.get(
- pk=report_id)
+ pk=report_id
+ )
suite_id = report.suite_id
project_id = test_rail_api.get_project_id("Mirantis Cloud Platform")
@@ -442,32 +487,40 @@
}
passrate_by_cases = dict()
- params = dict(project_id=project_id,
- suite_id=suite_id,
- filter=diff_obj.test_keyword,
- limit=200)
+ params = dict(
+ project_id=project_id,
+ suite_id=suite_id,
+ filter=diff_obj.test_keyword,
+ limit=200,
+ )
for _n, case in enumerate(get_cases(**params), start=1):
- case_title = case['title']
+ case_title = case["title"]
case_id = case["id"]
report.status = f"Current case: {_n}"
report.save()
# Limit generator to the list with the length defined in the
# DiffOfSuitesPassRates
- last_case_results = list(islice(
- test_rail_api.get_result_history_for_case(case_id, **_filters),
- diff_obj.limit))
+ last_case_results = list(
+ islice(
+ test_rail_api.get_result_history_for_case(case_id, **_filters),
+ diff_obj.limit,
+ )
+ )
passrate_by_cases[case_title] = dict()
- passrate_by_cases[case_title]['case_id'] = case_id
+ passrate_by_cases[case_title]["case_id"] = case_id
if last_case_results:
- passed_tests = [x for x in last_case_results
- if x[-1]["status_id"] == StatusEnum.passed]
- passrate_by_cases[case_title]['rate'] = \
+ passed_tests = [
+ x
+ for x in last_case_results
+ if x[-1]["status_id"] == StatusEnum.passed
+ ]
+ passrate_by_cases[case_title]["rate"] = (
len(passed_tests) * 100 / diff_obj.limit
+ )
else:
- passrate_by_cases[case_title]['rate'] = "No result found"
- report.passrate_by_tests = json.dumps(passrate_by_cases,
- indent=4)
+ passrate_by_cases[case_title]["rate"] = "No result found"
+ report.passrate_by_tests = json.dumps(passrate_by_cases, indent=4)
report.status = f"Current case: {_n}"
report.save()
report.finished = True
diff --git a/testrail_bot/control/forms.py b/testrail_bot/control/forms.py
index 363035b..8d1ef8c 100644
--- a/testrail_bot/control/forms.py
+++ b/testrail_bot/control/forms.py
@@ -1,18 +1,24 @@
from datetime import date
+
from django import forms
-from .models import TestRailTestRun, \
- DiffOfSuitesPassRates, \
- SuitePassRate, \
- CronPeriodicTask
+
+from .models import (
+ CronPeriodicTask,
+ DiffOfSuitesPassRates,
+ SuitePassRate,
+ TestRailTestRun,
+)
class TestRunForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.fields["created_after"].widget = forms.SelectDateWidget(years=[
- date.today().year + i for i in range(-3, 5)])
- self.fields["created_before"].widget = forms.SelectDateWidget(years=[
- date.today().year + i for i in range(-3, 5)])
+ self.fields["created_after"].widget = forms.SelectDateWidget(
+ years=[date.today().year + i for i in range(-3, 5)]
+ )
+ self.fields["created_before"].widget = forms.SelectDateWidget(
+ years=[date.today().year + i for i in range(-3, 5)]
+ )
class Meta:
model = TestRailTestRun
@@ -28,11 +34,10 @@
"ip_filter": "Mask all IP with x.x.x.x",
"uuid_filter": "Mask all UUID with xxxx",
"filter_last_traceback": "Use only traceback to "
- "compare comments",
+ "compare comments",
"created_after": "Search in period from",
"created_before": "till",
- "caching_tests_enabled": "Don't check already checked results"
-
+ "caching_tests_enabled": "Don't check already checked results",
}
help_texts = {
"filter_func": "Leave blank if not used",
@@ -48,7 +53,7 @@
labels = {
"test_keyword": "Pattern to search by tests",
"limit": "Count of tests to define the passrate. Don't recommend "
- "to use a number greater that 10"
+ "to use a number greater that 10",
}
@@ -56,12 +61,17 @@
class Meta:
model = SuitePassRate
fields = ["suite_id"]
- labels = {
- "suite_id": "Suite ID"
- }
+ labels = {"suite_id": "Suite ID"}
class PeriodicTaskForm(forms.ModelForm):
class Meta:
model = CronPeriodicTask
- fields = ["id", "enabled", "name", "cron", "task_name", "testplan_id_arg"]
+ fields = [
+ "id",
+ "enabled",
+ "name",
+ "cron",
+ "task_name",
+ "testplan_id_arg",
+ ]
diff --git a/testrail_bot/control/jira_manager.py b/testrail_bot/control/jira_manager.py
index ee81efa..6068a84 100644
--- a/testrail_bot/control/jira_manager.py
+++ b/testrail_bot/control/jira_manager.py
@@ -1,15 +1,19 @@
from django.conf import settings
from jira import JIRA, Issue
-jira = JIRA(server=settings.JIRA_SERVER,
- basic_auth=(settings.JIRA_USER, settings.JIRA_PASSWORD))
+jira = JIRA(
+ server=settings.JIRA_SERVER,
+ basic_auth=(settings.JIRA_USER, settings.JIRA_PASSWORD),
+)
class JiraIssue:
def __init__(self, key: str):
self.key = key
self._issue = None
- self._style_prod_area = "background-color:aliceblue;border-color:CornflowerBlue;"
+ self._style_prod_area = (
+ "background-color:aliceblue;border-color:CornflowerBlue;"
+ )
self._style_prod_text = "color:blue"
@property
@@ -23,12 +27,16 @@
return f"{settings.JIRA_SERVER}/browse/{self.issue.key}"
def html(self) -> str:
- if self.issue.fields.status.name in ['Completed', 'Released']:
+ if self.issue.fields.status.name in ["Completed", "Released"]:
self._style_prod_area = "background-color:red;border-color:black;"
self._style_prod_text = "color:white"
- return f"<div style='display:inline-block;border:1px solid;{self._style_prod_area}" \
- f"border-radius:5px;padding:2px;'>" \
- f"<a style={self._style_prod_text} href='{self.get_link()}'>" \
- f"{self.key}: {self.issue.fields.summary[:50]}</a>" \
- f"<b style={self._style_prod_text}> {self.issue.fields.status.name}</b>" \
- f"</div>"
+ return (
+ f"<div style='display:inline-block;border:1px solid;"
+ f"{self._style_prod_area}"
+ f"border-radius:5px;padding:2px;'>"
+ f"<a style={self._style_prod_text} href='{self.get_link()}'>"
+ f"{self.key}: {self.issue.fields.summary[:50]}</a>"
+ f"<b style={self._style_prod_text}> "
+ f"{self.issue.fields.status.name}</b>"
+ f"</div>"
+ )
diff --git a/testrail_bot/control/migrations/0001_initial.py b/testrail_bot/control/migrations/0001_initial.py
index b8d4697..a450f5d 100644
--- a/testrail_bot/control/migrations/0001_initial.py
+++ b/testrail_bot/control/migrations/0001_initial.py
@@ -1,6 +1,7 @@
# Generated by Django 4.2.6 on 2023-11-04 10:46
import datetime
+
import django.core.files.storage
from django.db import migrations, models
@@ -9,69 +10,83 @@
initial = True
- dependencies = [
- ]
+ dependencies = []
operations = [
migrations.CreateModel(
- name='ActionLog',
+ name="ActionLog",
fields=[
- ('id', models.AutoField(
- auto_created=True,
- primary_key=True,
- serialize=False,
- verbose_name='ID')),
- ('name', models.CharField(max_length=500)),
- ('date', models.DateTimeField(null=True)),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("name", models.CharField(max_length=500)),
+ ("date", models.DateTimeField(null=True)),
],
),
migrations.CreateModel(
- name='TestRailReport',
+ name="TestRailReport",
fields=[
- ('id', models.AutoField(
- auto_created=True,
- primary_key=True,
- serialize=False,
- verbose_name='ID')),
- ('path', models.FileField(
- blank=True,
- max_length=500,
- null=True,
- storage=django.core.files.storage.FileSystemStorage(),
- upload_to='')),
- ('report_name', models.CharField(max_length=300)),
- ('finished', models.BooleanField(default=False)),
- ('created_at', models.DateTimeField(auto_now_add=True)),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "path",
+ models.FileField(
+ blank=True,
+ max_length=500,
+ null=True,
+ storage=django.core.files.storage.FileSystemStorage(),
+ upload_to="",
+ ),
+ ),
+ ("report_name", models.CharField(max_length=300)),
+ ("finished", models.BooleanField(default=False)),
+ ("created_at", models.DateTimeField(auto_now_add=True)),
],
),
migrations.CreateModel(
- name='TestRailTestRun',
+ name="TestRailTestRun",
fields=[
- ('id', models.AutoField(
- auto_created=True,
- primary_key=True,
- serialize=False,
- verbose_name='ID')),
- ('project_name', models.CharField(
- default='Mirantis Cloud Platform',
- max_length=300)),
- ('plan_name', models.CharField(
- default='[MCP2.0]OSCORE',
- max_length=300)),
- ('run_name', models.CharField(
- blank=True,
- max_length=300)),
- ('test_pattern', models.CharField(
- blank=True,
- max_length=300)),
- ('run_id', models.CharField(max_length=300)),
- ('created_by_id', models.IntegerField(default='109')),
- ('filter_func', models.TextField(blank=True, null=True)),
- ('ip_filter', models.BooleanField(default=True)),
- ('uuid_filter', models.BooleanField(default=True)),
- ('filter_last_traceback', models.BooleanField(default=False)),
- ('timestamp', models.DateField(
- default=datetime.date.today())),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "project_name",
+ models.CharField(
+ default="Mirantis Cloud Platform", max_length=300
+ ),
+ ),
+ (
+ "plan_name",
+ models.CharField(default="[MCP2.0]OSCORE", max_length=300),
+ ),
+ ("run_name", models.CharField(blank=True, max_length=300)),
+ ("test_pattern", models.CharField(blank=True, max_length=300)),
+ ("run_id", models.CharField(max_length=300)),
+ ("created_by_id", models.IntegerField(default="109")),
+ ("filter_func", models.TextField(blank=True, null=True)),
+ ("ip_filter", models.BooleanField(default=True)),
+ ("uuid_filter", models.BooleanField(default=True)),
+ ("filter_last_traceback", models.BooleanField(default=False)),
+ ("timestamp", models.DateField(default=datetime.date.today())),
],
),
]
diff --git a/testrail_bot/control/migrations/0002_testrailtestrun_checked_tests.py b/testrail_bot/control/migrations/0002_testrailtestrun_checked_tests.py
index 4e62602..3ad73d2 100644
--- a/testrail_bot/control/migrations/0002_testrailtestrun_checked_tests.py
+++ b/testrail_bot/control/migrations/0002_testrailtestrun_checked_tests.py
@@ -7,13 +7,13 @@
class Migration(migrations.Migration):
dependencies = [
- ('control', '0001_initial'),
+ ("control", "0001_initial"),
]
operations = [
migrations.AddField(
- model_name='testrailtestrun',
- name='checked_tests',
+ model_name="testrailtestrun",
+ name="checked_tests",
field=control.models.IntegerListField(default=[], editable=False),
),
]
diff --git a/testrail_bot/control/migrations/0003_suitepassrate_diffofsuitespassrates.py b/testrail_bot/control/migrations/0003_suitepassrate_diffofsuitespassrates.py
index 6215a62..13be3e1 100644
--- a/testrail_bot/control/migrations/0003_suitepassrate_diffofsuitespassrates.py
+++ b/testrail_bot/control/migrations/0003_suitepassrate_diffofsuitespassrates.py
@@ -1,36 +1,97 @@
# Generated by Django 4.2.7 on 2023-11-20 17:46
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('control', '0002_testrailtestrun_checked_tests'),
+ ("control", "0002_testrailtestrun_checked_tests"),
]
operations = [
migrations.CreateModel(
- name='SuitePassRate',
+ name="SuitePassRate",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('suite_id', models.CharField(choices=[('10651', '[MCP2.0_ROCKY]Tempest'), ('10635', '[MCP2.0_STEIN]Tempest'), ('10653', '[MCP2.0_TRAIN]Tempest'), ('10710', '[MCP2.0_USSURI]Tempest'), ('10888', '[MCP2.0_VICTORIA]Tempest'), ('11167', '[MCP2.0_WALLABY]Tempest'), ('11188', '[MCP2.0_XENA]Tempest'), ('11170', '[MCP2.0_YOGA]Tempest'), ('11192', '[MCP2.0_ANTELOPE]Tempest'), ('11193', '[MCP2.0_ANTELOPE]Stepler'), ('10886', '[MCP2.0_USSURI]Stepler'), ('10887', '[MCP2.0_VICTORIA]Stepler'), ('11171', '[MCP2.0_YOGA]Stepler')], max_length=20)),
- ('suite_name', models.CharField(blank=True, max_length=100)),
- ('passrate_by_tests', models.JSONField(blank=True, default='{}')),
- ('status', models.TextField(blank=True, max_length=300)),
- ('finished', models.BooleanField(blank=True, default=False)),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "suite_id",
+ models.CharField(
+ choices=[
+ ("10651", "[MCP2.0_ROCKY]Tempest"),
+ ("10635", "[MCP2.0_STEIN]Tempest"),
+ ("10653", "[MCP2.0_TRAIN]Tempest"),
+ ("10710", "[MCP2.0_USSURI]Tempest"),
+ ("10888", "[MCP2.0_VICTORIA]Tempest"),
+ ("11167", "[MCP2.0_WALLABY]Tempest"),
+ ("11188", "[MCP2.0_XENA]Tempest"),
+ ("11170", "[MCP2.0_YOGA]Tempest"),
+ ("11192", "[MCP2.0_ANTELOPE]Tempest"),
+ ("11193", "[MCP2.0_ANTELOPE]Stepler"),
+ ("10886", "[MCP2.0_USSURI]Stepler"),
+ ("10887", "[MCP2.0_VICTORIA]Stepler"),
+ ("11171", "[MCP2.0_YOGA]Stepler"),
+ ],
+ max_length=20,
+ ),
+ ),
+ ("suite_name", models.CharField(blank=True, max_length=100)),
+ (
+ "passrate_by_tests",
+ models.JSONField(blank=True, default="{}"),
+ ),
+ ("status", models.TextField(blank=True, max_length=300)),
+ ("finished", models.BooleanField(blank=True, default=False)),
],
),
migrations.CreateModel(
- name='DiffOfSuitesPassRates',
+ name="DiffOfSuitesPassRates",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('started_at', models.DateTimeField(auto_created=True, auto_now=True)),
- ('limit', models.IntegerField(blank=True, default=10)),
- ('test_keyword', models.CharField(blank=True, default='', max_length=300)),
- ('report1', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='report1', to='control.suitepassrate')),
- ('report2', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='report2', to='control.suitepassrate')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "started_at",
+ models.DateTimeField(auto_created=True, auto_now=True),
+ ),
+ ("limit", models.IntegerField(blank=True, default=10)),
+ (
+ "test_keyword",
+ models.CharField(blank=True, default="", max_length=300),
+ ),
+ (
+ "report1",
+ models.ForeignKey(
+ blank=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="report1",
+ to="control.suitepassrate",
+ ),
+ ),
+ (
+ "report2",
+ models.ForeignKey(
+ blank=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="report2",
+ to="control.suitepassrate",
+ ),
+ ),
],
),
]
diff --git a/testrail_bot/control/migrations/0004_rename_timestamp_testrailtestrun_created_before.py b/testrail_bot/control/migrations/0004_rename_timestamp_testrailtestrun_created_before.py
index aee1b1f..f4a345a 100644
--- a/testrail_bot/control/migrations/0004_rename_timestamp_testrailtestrun_created_before.py
+++ b/testrail_bot/control/migrations/0004_rename_timestamp_testrailtestrun_created_before.py
@@ -1,24 +1,25 @@
# Generated by Django 4.2.7 on 2023-11-28 14:07
import datetime
+
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('control', '0003_suitepassrate_diffofsuitespassrates'),
+ ("control", "0003_suitepassrate_diffofsuitespassrates"),
]
operations = [
migrations.RenameField(
- model_name='testrailtestrun',
- old_name='timestamp',
- new_name='created_before',
+ model_name="testrailtestrun",
+ old_name="timestamp",
+ new_name="created_before",
),
migrations.AddField(
- model_name='testrailtestrun',
- name='created_after',
+ model_name="testrailtestrun",
+ name="created_after",
field=models.DateField(default=datetime.date(2023, 8, 30)),
),
]
diff --git a/testrail_bot/control/migrations/0005_alter_suitepassrate_suite_id_and_more.py b/testrail_bot/control/migrations/0005_alter_suitepassrate_suite_id_and_more.py
index 27d098c..6478349 100644
--- a/testrail_bot/control/migrations/0005_alter_suitepassrate_suite_id_and_more.py
+++ b/testrail_bot/control/migrations/0005_alter_suitepassrate_suite_id_and_more.py
@@ -1,19 +1,45 @@
# Generated by Django 4.2.7 on 2023-11-30 13:03
-import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('control', '0004_rename_timestamp_testrailtestrun_created_before'),
+ ("control", "0004_rename_timestamp_testrailtestrun_created_before"),
]
operations = [
migrations.AlterField(
- model_name='suitepassrate',
- name='suite_id',
- field=models.CharField(choices=[('Tempest', (('10651', '[MCP2.0_ROCKY]Tempest'), ('10635', '[MCP2.0_STEIN]Tempest'), ('10653', '[MCP2.0_TRAIN]Tempest'), ('10710', '[MCP2.0_USSURI]Tempest'), ('10888', '[MCP2.0_VICTORIA]Tempest'), ('11167', '[MCP2.0_WALLABY]Tempest'), ('11188', '[MCP2.0_XENA]Tempest'), ('11170', '[MCP2.0_YOGA]Tempest'), ('11192', '[MCP2.0_ANTELOPE]Tempest'))), ('Stepler', (('10886', '[MCP2.0_USSURI]Stepler'), ('10887', '[MCP2.0_VICTORIA]Stepler'), ('11171', '[MCP2.0_YOGA]Stepler'), ('11193', '[MCP2.0_ANTELOPE]Stepler')))], max_length=20),
+ model_name="suitepassrate",
+ name="suite_id",
+ field=models.CharField(
+ choices=[
+ (
+ "Tempest",
+ (
+ ("10651", "[MCP2.0_ROCKY]Tempest"),
+ ("10635", "[MCP2.0_STEIN]Tempest"),
+ ("10653", "[MCP2.0_TRAIN]Tempest"),
+ ("10710", "[MCP2.0_USSURI]Tempest"),
+ ("10888", "[MCP2.0_VICTORIA]Tempest"),
+ ("11167", "[MCP2.0_WALLABY]Tempest"),
+ ("11188", "[MCP2.0_XENA]Tempest"),
+ ("11170", "[MCP2.0_YOGA]Tempest"),
+ ("11192", "[MCP2.0_ANTELOPE]Tempest"),
+ ),
+ ),
+ (
+ "Stepler",
+ (
+ ("10886", "[MCP2.0_USSURI]Stepler"),
+ ("10887", "[MCP2.0_VICTORIA]Stepler"),
+ ("11171", "[MCP2.0_YOGA]Stepler"),
+ ("11193", "[MCP2.0_ANTELOPE]Stepler"),
+ ),
+ ),
+ ],
+ max_length=20,
+ ),
)
]
diff --git a/testrail_bot/control/migrations/0006_alter_testrailtestrun_created_after_and_more.py b/testrail_bot/control/migrations/0006_alter_testrailtestrun_created_after_and_more.py
index 4fcbc75..7e5ec61 100644
--- a/testrail_bot/control/migrations/0006_alter_testrailtestrun_created_after_and_more.py
+++ b/testrail_bot/control/migrations/0006_alter_testrailtestrun_created_after_and_more.py
@@ -1,29 +1,29 @@
# Generated by Django 4.2.7 on 2024-01-10 10:16
-from django.db import migrations, models
import django.utils.timezone
+from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('control', '0005_alter_suitepassrate_suite_id_and_more'),
+ ("control", "0005_alter_suitepassrate_suite_id_and_more"),
]
operations = [
migrations.AlterField(
- model_name='testrailtestrun',
- name='created_after',
+ model_name="testrailtestrun",
+ name="created_after",
field=models.DateField(default=django.utils.timezone.now),
),
migrations.AlterField(
- model_name='testrailtestrun',
- name='created_before',
+ model_name="testrailtestrun",
+ name="created_before",
field=models.DateField(default=django.utils.timezone.now),
),
migrations.AlterField(
- model_name='testrailtestrun',
- name='filter_last_traceback',
+ model_name="testrailtestrun",
+ name="filter_last_traceback",
field=models.BooleanField(default=True),
),
]
diff --git a/testrail_bot/control/migrations/0007_testrailtestrun_caching_tests_enabled.py b/testrail_bot/control/migrations/0007_testrailtestrun_caching_tests_enabled.py
index a33fc75..a902a2b 100644
--- a/testrail_bot/control/migrations/0007_testrailtestrun_caching_tests_enabled.py
+++ b/testrail_bot/control/migrations/0007_testrailtestrun_caching_tests_enabled.py
@@ -6,13 +6,13 @@
class Migration(migrations.Migration):
dependencies = [
- ('control', '0006_alter_testrailtestrun_created_after_and_more'),
+ ("control", "0006_alter_testrailtestrun_created_after_and_more"),
]
operations = [
migrations.AddField(
- model_name='testrailtestrun',
- name='caching_tests_enabled',
+ model_name="testrailtestrun",
+ name="caching_tests_enabled",
field=models.BooleanField(default=False),
),
]
diff --git a/testrail_bot/control/migrations/0008_rename_test_pattern_testrailtestrun_testrun_pattern.py b/testrail_bot/control/migrations/0008_rename_test_pattern_testrailtestrun_testrun_pattern.py
index ec350ca..73a2873 100644
--- a/testrail_bot/control/migrations/0008_rename_test_pattern_testrailtestrun_testrun_pattern.py
+++ b/testrail_bot/control/migrations/0008_rename_test_pattern_testrailtestrun_testrun_pattern.py
@@ -6,13 +6,13 @@
class Migration(migrations.Migration):
dependencies = [
- ('control', '0007_testrailtestrun_caching_tests_enabled'),
+ ("control", "0007_testrailtestrun_caching_tests_enabled"),
]
operations = [
migrations.RenameField(
- model_name='testrailtestrun',
- old_name='test_pattern',
- new_name='testrun_pattern',
+ model_name="testrailtestrun",
+ old_name="test_pattern",
+ new_name="testrun_pattern",
),
]
diff --git a/testrail_bot/control/migrations/0009_cronperiodictask_alter_testrailtestrun_created_after.py b/testrail_bot/control/migrations/0009_cronperiodictask_alter_testrailtestrun_created_after.py
index 443115b..fabe448 100644
--- a/testrail_bot/control/migrations/0009_cronperiodictask_alter_testrailtestrun_created_after.py
+++ b/testrail_bot/control/migrations/0009_cronperiodictask_alter_testrailtestrun_created_after.py
@@ -1,32 +1,47 @@
# Generated by Django 4.2.7 on 2024-02-20 14:18
import control.models
-from django.db import migrations, models
import django.db.models.deletion
+from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('django_celery_beat', '0018_improve_crontab_helptext'),
- ('control', '0008_rename_test_pattern_testrailtestrun_testrun_pattern'),
+ ("django_celery_beat", "0018_improve_crontab_helptext"),
+ (
+ "control",
+ "0008_rename_test_pattern_testrailtestrun_testrun_pattern",
+ ),
]
operations = [
migrations.CreateModel(
- name='CronPeriodicTask',
+ name="CronPeriodicTask",
fields=[
- ('periodictask_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='django_celery_beat.periodictask')),
- ('cron', models.CharField(default='', max_length=300)),
+ (
+ "periodictask_ptr",
+ models.OneToOneField(
+ auto_created=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ parent_link=True,
+ primary_key=True,
+ serialize=False,
+ to="django_celery_beat.periodictask",
+ ),
+ ),
+ ("cron", models.CharField(default="", max_length=300)),
],
options={
- 'ordering': ['id'],
+ "ordering": ["id"],
},
- bases=('django_celery_beat.periodictask',),
+ bases=("django_celery_beat.periodictask",),
),
migrations.AlterField(
- model_name='testrailtestrun',
- name='created_after',
- field=models.DateField(default=control.models.default_created_after),
+ model_name="testrailtestrun",
+ name="created_after",
+ field=models.DateField(
+ default=control.models.default_created_after
+ ),
),
]
diff --git a/testrail_bot/control/migrations/0010_alter_testrailtestrun_options.py b/testrail_bot/control/migrations/0010_alter_testrailtestrun_options.py
index ca00143..1d9afd9 100644
--- a/testrail_bot/control/migrations/0010_alter_testrailtestrun_options.py
+++ b/testrail_bot/control/migrations/0010_alter_testrailtestrun_options.py
@@ -6,12 +6,15 @@
class Migration(migrations.Migration):
dependencies = [
- ('control', '0009_cronperiodictask_alter_testrailtestrun_created_after'),
+ (
+ "control",
+ "0009_cronperiodictask_alter_testrailtestrun_created_after",
+ ),
]
operations = [
migrations.AlterModelOptions(
- name='testrailtestrun',
- options={'ordering': ['-run_id']},
+ name="testrailtestrun",
+ options={"ordering": ["-run_id"]},
),
]
diff --git a/testrail_bot/control/migrations/0011_cronperiodictask_task_name_and_more.py b/testrail_bot/control/migrations/0011_cronperiodictask_task_name_and_more.py
index 2901309..ce5926d 100644
--- a/testrail_bot/control/migrations/0011_cronperiodictask_task_name_and_more.py
+++ b/testrail_bot/control/migrations/0011_cronperiodictask_task_name_and_more.py
@@ -6,18 +6,31 @@
class Migration(migrations.Migration):
dependencies = [
- ('control', '0010_alter_testrailtestrun_options'),
+ ("control", "0010_alter_testrailtestrun_options"),
]
operations = [
migrations.AddField(
- model_name='cronperiodictask',
- name='task_name',
- field=models.CharField(choices=[('control.celery_tasks.tasks.check_today_testplan', 'Check today testplan'), ('control.celery_tasks.tasks.check_specific_testplan', 'Check specific testplan')], default='control.celery_tasks.tasks.check_today_testplan', max_length=300),
+ model_name="cronperiodictask",
+ name="task_name",
+ field=models.CharField(
+ choices=[
+ (
+ "control.celery_tasks.tasks.check_today_testplan",
+ "Check today testplan",
+ ),
+ (
+ "control.celery_tasks.tasks.check_specific_testplan",
+ "Check specific testplan",
+ ),
+ ],
+ default="control.celery_tasks.tasks.check_today_testplan",
+ max_length=300,
+ ),
),
migrations.AddField(
- model_name='cronperiodictask',
- name='testplan_id_arg',
+ model_name="cronperiodictask",
+ name="testplan_id_arg",
field=models.CharField(blank=True, max_length=30, null=True),
),
]
diff --git a/testrail_bot/control/migrations/0012_alter_suitepassrate_suite_id.py b/testrail_bot/control/migrations/0012_alter_suitepassrate_suite_id.py
index 62e0193..4650a53 100644
--- a/testrail_bot/control/migrations/0012_alter_suitepassrate_suite_id.py
+++ b/testrail_bot/control/migrations/0012_alter_suitepassrate_suite_id.py
@@ -6,13 +6,43 @@
class Migration(migrations.Migration):
dependencies = [
- ('control', '0011_cronperiodictask_task_name_and_more'),
+ ("control", "0011_cronperiodictask_task_name_and_more"),
]
operations = [
migrations.AlterField(
- model_name='suitepassrate',
- name='suite_id',
- field=models.CharField(choices=[('Tempest', (('10650', '[MCP2.0_QUEENS]Tempest'), ('10651', '[MCP2.0_ROCKY]Tempest'), ('10635', '[MCP2.0_STEIN]Tempest'), ('10653', '[MCP2.0_TRAIN]Tempest'), ('10710', '[MCP2.0_USSURI]Tempest'), ('10888', '[MCP2.0_VICTORIA]Tempest'), ('11167', '[MCP2.0_WALLABY]Tempest'), ('11188', '[MCP2.0_XENA]Tempest'), ('11170', '[MCP2.0_YOGA]Tempest'), ('11192', '[MCP2.0_ANTELOPE]Tempest'), ('11195', '[MCP2.0_CARACAL]Tempest'))), ('Stepler', (('10886', '[MCP2.0_USSURI]Stepler'), ('10887', '[MCP2.0_VICTORIA]Stepler'), ('11171', '[MCP2.0_YOGA]Stepler'), ('11193', '[MCP2.0_ANTELOPE]Stepler'), ('11196', '[MCP2.0_CARACAL]Stepler')))], max_length=20),
+ model_name="suitepassrate",
+ name="suite_id",
+ field=models.CharField(
+ choices=[
+ (
+ "Tempest",
+ (
+ ("10650", "[MCP2.0_QUEENS]Tempest"),
+ ("10651", "[MCP2.0_ROCKY]Tempest"),
+ ("10635", "[MCP2.0_STEIN]Tempest"),
+ ("10653", "[MCP2.0_TRAIN]Tempest"),
+ ("10710", "[MCP2.0_USSURI]Tempest"),
+ ("10888", "[MCP2.0_VICTORIA]Tempest"),
+ ("11167", "[MCP2.0_WALLABY]Tempest"),
+ ("11188", "[MCP2.0_XENA]Tempest"),
+ ("11170", "[MCP2.0_YOGA]Tempest"),
+ ("11192", "[MCP2.0_ANTELOPE]Tempest"),
+ ("11195", "[MCP2.0_CARACAL]Tempest"),
+ ),
+ ),
+ (
+ "Stepler",
+ (
+ ("10886", "[MCP2.0_USSURI]Stepler"),
+ ("10887", "[MCP2.0_VICTORIA]Stepler"),
+ ("11171", "[MCP2.0_YOGA]Stepler"),
+ ("11193", "[MCP2.0_ANTELOPE]Stepler"),
+ ("11196", "[MCP2.0_CARACAL]Stepler"),
+ ),
+ ),
+ ],
+ max_length=20,
+ ),
),
]
diff --git a/testrail_bot/control/models.py b/testrail_bot/control/models.py
index a8a0910..b438dec 100644
--- a/testrail_bot/control/models.py
+++ b/testrail_bot/control/models.py
@@ -6,28 +6,28 @@
class IntegerListField(models.Field):
def __init__(self, *args, **kwargs):
- kwargs['editable'] = False
+ kwargs["editable"] = False
super(IntegerListField, self).__init__(*args, **kwargs)
def db_type(self, connection):
- return 'text'
+ return "text"
def from_db_value(self, value, expression, connection):
if not value:
return []
- return [int(x) for x in value.split(',')]
+ return [int(x) for x in value.split(",")]
def to_python(self, value):
if isinstance(value, list):
return value
if not value:
return []
- return [int(x) for x in value.split(',')]
+ return [int(x) for x in value.split(",")]
def get_prep_value(self, value):
if not value:
- return ''
- return ','.join(str(int(x)) for x in value)
+ return ""
+ return ",".join(str(int(x)) for x in value)
def default_created_after():
@@ -35,15 +35,16 @@
class TestRailTestRun(models.Model):
- project_name = models.CharField(max_length=300,
- default="Mirantis Cloud Platform")
+ project_name = models.CharField(
+ max_length=300, default="Mirantis Cloud Platform"
+ )
plan_name = models.CharField(max_length=300, default="[MCP2.0]OSCORE")
run_name = models.CharField(max_length=300, blank=True)
testrun_pattern = models.CharField(max_length=300, blank=True)
run_id = models.CharField(max_length=300)
checked_tests = IntegerListField(default=list())
caching_tests_enabled = models.BooleanField(default=False)
- created_by_id = models.IntegerField(default='109')
+ created_by_id = models.IntegerField(default="109")
filter_func = models.TextField(null=True, blank=True)
ip_filter = models.BooleanField(default=True)
uuid_filter = models.BooleanField(default=True)
@@ -91,26 +92,32 @@
class SuitePassRate(models.Model):
SUITE_CHOICES = [
- ("Tempest", (
- ("10650", "[MCP2.0_QUEENS]Tempest"),
- ("10651", "[MCP2.0_ROCKY]Tempest"),
- ("10635", "[MCP2.0_STEIN]Tempest"),
- ("10653", "[MCP2.0_TRAIN]Tempest"),
- ("10710", "[MCP2.0_USSURI]Tempest"),
- ("10888", "[MCP2.0_VICTORIA]Tempest"),
- ("11167", "[MCP2.0_WALLABY]Tempest"),
- ("11188", "[MCP2.0_XENA]Tempest"),
- ("11170", "[MCP2.0_YOGA]Tempest"),
- ("11192", "[MCP2.0_ANTELOPE]Tempest"),
- ("11195", "[MCP2.0_CARACAL]Tempest"))
- ),
- ("Stepler", (
- ("10886", "[MCP2.0_USSURI]Stepler"),
- ("10887", "[MCP2.0_VICTORIA]Stepler"),
- ("11171", "[MCP2.0_YOGA]Stepler"),
- ("11193", "[MCP2.0_ANTELOPE]Stepler"),
- ("11196", "[MCP2.0_CARACAL]Stepler"))
- ),
+ (
+ "Tempest",
+ (
+ ("10650", "[MCP2.0_QUEENS]Tempest"),
+ ("10651", "[MCP2.0_ROCKY]Tempest"),
+ ("10635", "[MCP2.0_STEIN]Tempest"),
+ ("10653", "[MCP2.0_TRAIN]Tempest"),
+ ("10710", "[MCP2.0_USSURI]Tempest"),
+ ("10888", "[MCP2.0_VICTORIA]Tempest"),
+ ("11167", "[MCP2.0_WALLABY]Tempest"),
+ ("11188", "[MCP2.0_XENA]Tempest"),
+ ("11170", "[MCP2.0_YOGA]Tempest"),
+ ("11192", "[MCP2.0_ANTELOPE]Tempest"),
+ ("11195", "[MCP2.0_CARACAL]Tempest"),
+ ),
+ ),
+ (
+ "Stepler",
+ (
+ ("10886", "[MCP2.0_USSURI]Stepler"),
+ ("10887", "[MCP2.0_VICTORIA]Stepler"),
+ ("11171", "[MCP2.0_YOGA]Stepler"),
+ ("11193", "[MCP2.0_ANTELOPE]Stepler"),
+ ("11196", "[MCP2.0_CARACAL]Stepler"),
+ ),
+ ),
]
suite_id = models.CharField(max_length=20, choices=SUITE_CHOICES)
suite_name = models.CharField(max_length=100, blank=True)
@@ -122,38 +129,42 @@
class DiffOfSuitesPassRates(models.Model):
limit = models.IntegerField(default=10, blank=True)
test_keyword = models.CharField(default="", max_length=300, blank=True)
- report1 = models.ForeignKey(to=SuitePassRate,
- related_name="report1",
- on_delete=models.CASCADE,
- blank=True)
- report2 = models.ForeignKey(to=SuitePassRate,
- related_name="report2",
- on_delete=models.CASCADE,
- blank=True)
- started_at = models.DateTimeField(auto_created=True,
- auto_now=True)
+ report1 = models.ForeignKey(
+ to=SuitePassRate,
+ related_name="report1",
+ on_delete=models.CASCADE,
+ blank=True,
+ )
+ report2 = models.ForeignKey(
+ to=SuitePassRate,
+ related_name="report2",
+ on_delete=models.CASCADE,
+ blank=True,
+ )
+ started_at = models.DateTimeField(auto_created=True, auto_now=True)
TASKS = [
- ("control.celery_tasks.tasks.check_today_testplan",
- "Check today testplan",
- []
- ),
- ("control.celery_tasks.tasks.check_specific_testplan",
- "Check specific testplan",
- ["testplan_id_arg"]
- ),
- ]
+ (
+ "control.celery_tasks.tasks.check_today_testplan",
+ "Check today testplan",
+ [],
+ ),
+ (
+ "control.celery_tasks.tasks.check_specific_testplan",
+ "Check specific testplan",
+ ["testplan_id_arg"],
+ ),
+]
-TASK_CHOICES = list(map(lambda x: x[:-1], TASKS))
+TASK_CHOICES = list(map(lambda x: x[:-1], TASKS))
class CronPeriodicTask(PeriodicTask):
- cron = models.CharField(default="",
- max_length=300,
- blank=False)
- task_name = models.CharField(max_length=300, choices=TASK_CHOICES,
- default=TASK_CHOICES[0][0])
+ cron = models.CharField(default="", max_length=300, blank=False)
+ task_name = models.CharField(
+ max_length=300, choices=TASK_CHOICES, default=TASK_CHOICES[0][0]
+ )
testplan_id_arg = models.CharField(max_length=30, blank=True, null=True)
class Meta:
diff --git a/testrail_bot/control/tests.py b/testrail_bot/control/tests.py
index 7ce503c..a39b155 100644
--- a/testrail_bot/control/tests.py
+++ b/testrail_bot/control/tests.py
@@ -1,3 +1 @@
-from django.test import TestCase
-
# Create your tests here.
diff --git a/testrail_bot/control/urls.py b/testrail_bot/control/urls.py
index d385891..d0249f7 100644
--- a/testrail_bot/control/urls.py
+++ b/testrail_bot/control/urls.py
@@ -2,7 +2,6 @@
from . import views
-
urlpatterns = [
path("", views.redirect_to_index, name="redirect"),
path("runs/", views.create_run, name="create_run"),
@@ -10,28 +9,41 @@
path("runs/<int:run_id>/submit/", views.submit_run, name="submit_run"),
path("runs/<int:run_id>/delete/", views.delete_run, name="delete_run"),
path("reports/", views.list_reports, name="list_reports"),
- path("reports/<int:report_id>/", views.single_report, name="single_report"),
- path("reports/<int:report_id>/delete", views.delete_report,
- name="delete_report"),
- path('index/', views.index, name='index'),
+ path(
+ "reports/<int:report_id>/", views.single_report, name="single_report"
+ ),
+ path(
+ "reports/<int:report_id>/delete",
+ views.delete_report,
+ name="delete_report",
+ ),
+ path("index/", views.index, name="index"),
path("help/", views.show_help, name="help"),
- path("update_jenkins_plot",
- views.update_jenkins_plot,
- name="update_jenkins"),
+ path(
+ "update_jenkins_plot", views.update_jenkins_plot, name="update_jenkins"
+ ),
path("jenkins_plot", views.jenkins_plot, name="jenkins_plot"),
path("schedulers/", views.schedulers, name="schedulers"),
-
path("scheduler/new/", views.scheduler, name="create_scheduler"),
path("scheduler/<int:pk>/", views.scheduler, name="scheduler"),
- path("scheduler/<int:pk>/save", views.save_scheduler, name="save_scheduler"),
- path("scheduler/<int:pk>/delete/", views.delete_scheduler,
- name="delete_scheduler"),
-
+ path(
+ "scheduler/<int:pk>/save", views.save_scheduler, name="save_scheduler"
+ ),
+ path(
+ "scheduler/<int:pk>/delete/",
+ views.delete_scheduler,
+ name="delete_scheduler",
+ ),
path("compare_suites/new/", views.compare_suites, name="compare_suites"),
- path("compare_suites/",
- views.list_of_comparing_reports,
- name="list_of_comparing_reports"),
+ path(
+ "compare_suites/",
+ views.list_of_comparing_reports,
+ name="list_of_comparing_reports",
+ ),
path("compare_suites/submit/", views.submit_suites, name="submit_suites"),
- path("compare_suites/<int:report_id>/", views.report_comparing_suites,
- name="report_comparing_suites"),
+ path(
+ "compare_suites/<int:report_id>/",
+ views.report_comparing_suites,
+ name="report_comparing_suites",
+ ),
]
diff --git a/testrail_bot/control/utils.py b/testrail_bot/control/utils.py
index 2038aba..6c41f58 100644
--- a/testrail_bot/control/utils.py
+++ b/testrail_bot/control/utils.py
@@ -1,6 +1,7 @@
-from parse import parse
-from typing import Dict, List, Callable, Any
+from typing import Any, Callable, Dict, List
+
from django.core.cache import cache
+from parse import parse
def parse_title(test_name):
@@ -22,7 +23,7 @@
return f"{r['test_title']}[{r['id']}]"
except TypeError:
# return file_name.test_class.test_name in other complicated cases
- return '.'.join(test_name.split(".")[:3])
+ return ".".join(test_name.split(".")[:3])
def short_names_for_dict(_dict):
@@ -36,16 +37,17 @@
return __dict
-def get_dict_diff(dict1: dict,
- dict2: dict,
- compare_by_key=None) -> Dict[str, List]:
+def get_dict_diff(
+ dict1: dict, dict2: dict, compare_by_key=None
+) -> Dict[str, List]:
all_keys = sorted(set(list(dict1.keys()) + list(dict2.keys())))
result = dict()
for k in all_keys:
if compare_by_key:
if dict1.get(k, {}).get(compare_by_key) == dict2.get(k, {}).get(
- compare_by_key):
+ compare_by_key
+ ):
continue
else:
if dict1.get(k) == dict2.get(k):
@@ -61,9 +63,10 @@
return r
-def cached(timeout: int = None,
- condition_for_endless_cache: Callable = lambda x: False
- ) -> Callable:
+def cached(
+ timeout: int = None,
+ condition_for_endless_cache: Callable = lambda x: False,
+) -> Callable:
"""
:param timeout: (in seconds) usage accordingly
https://docs.djangoproject.com/en/4.2/topics/cache/#basic-usage
@@ -74,17 +77,18 @@
:return: decorator
"""
+
def decorator(func: Callable) -> Callable:
def wrapper(*args, **kwargs) -> Any:
- cache_key = f'{func.__name__}_{args}_{kwargs}'
- cache_key = replace_all(cache_key, "{}()\'\" .,:", "_")
+ cache_key = f"{func.__name__}_{args}_{kwargs}"
+ cache_key = replace_all(cache_key, "{}()'\" .,:", "_")
cached_value = cache.get(cache_key)
if cached_value is None:
print(f"{func.__name__} MISS")
result = func(*args, **kwargs)
- _timeout = None \
- if condition_for_endless_cache(result) \
- else timeout
+ _timeout = (
+ None if condition_for_endless_cache(result) else timeout
+ )
cache.set(cache_key, result, timeout=_timeout)
return result
@@ -99,7 +103,9 @@
# # ENDFIXME
return cached_value
+
return wrapper
+
return decorator
diff --git a/testrail_bot/control/views.py b/testrail_bot/control/views.py
index e6655ad..8a362ef 100644
--- a/testrail_bot/control/views.py
+++ b/testrail_bot/control/views.py
@@ -1,16 +1,18 @@
import datetime
import json
import os
-from .celery_tasks import test_rail_api
-from django.shortcuts import render, redirect, HttpResponse
+from django.shortcuts import HttpResponse, redirect, render
from django_celery_beat.models import CrontabSchedule, PeriodicTasks
-from . import models
-from . import forms
-from .celery_tasks.tasks import process_run, update_plot_data, \
- get_test_passability_in_suite
-from .utils import short_names_for_dict, get_dict_diff
+from . import forms, models
+from .celery_tasks import test_rail_api
+from .celery_tasks.tasks import (
+ get_test_passability_in_suite,
+ process_run,
+ update_plot_data,
+)
+from .utils import get_dict_diff, short_names_for_dict
def index(request):
@@ -32,9 +34,11 @@
else:
form = forms.TestRunForm(instance=run)
- return render(request, "control/update_run.html",
- {"form": form, "run_id": run_id, "checked_tests":
- run.checked_tests})
+ return render(
+ request,
+ "control/update_run.html",
+ {"form": form, "run_id": run_id, "checked_tests": run.checked_tests},
+ )
def create_run(request):
@@ -46,8 +50,9 @@
else:
form = forms.TestRunForm()
- form.fields["created_after"].initial = datetime.date.today() + \
- datetime.timedelta(days=-3 * 30)
+ form.fields["created_after"].initial = (
+ datetime.date.today() + datetime.timedelta(days=-3 * 30)
+ )
form.fields["created_before"].initial = datetime.date.today()
return render(request, "control/update_run.html", {"form": form})
@@ -60,20 +65,26 @@
def single_report(request, report_id):
report = models.TestRailReport.objects.get(pk=report_id)
data = report.path.read().decode("utf-8")
- if request.method == "POST" \
- and request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest':
+ if (
+ request.method == "POST"
+ and request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"
+ ):
return HttpResponse(
json.dumps({"data": data, "finished": report.finished}),
- content_type="application/json")
+ content_type="application/json",
+ )
- return render(request, "control/report.html",
- {"report_id": report.id,
- "report": data,
- "finished": report.finished})
+ return render(
+ request,
+ "control/report.html",
+ {"report_id": report.id, "report": data, "finished": report.finished},
+ )
def delete_report(request, report_id):
- report:models.TestRailReport = models.TestRailReport.objects.get(pk=report_id)
+ report: models.TestRailReport = models.TestRailReport.objects.get(
+ pk=report_id
+ )
try:
os.remove(report.path.path)
except FileNotFoundError:
@@ -94,8 +105,9 @@
_name = f"Plan {testrail_run['name']}"
else:
parent_plan_id = testrail_run["plan_id"]
- parent_plan_name = \
- test_rail_api.get_plan_by_id(parent_plan_id)["name"]
+ parent_plan_name = test_rail_api.get_plan_by_id(parent_plan_id)[
+ "name"
+ ]
_name = f"Run {testrail_run['name']} from {parent_plan_name}"
run.run_name = _name
run.save()
@@ -103,14 +115,13 @@
report_name = "{}-run_id-{}-date-{}".format(
run.run_name,
run.run_id,
- datetime.datetime.isoformat(datetime.datetime.now()))
+ datetime.datetime.isoformat(datetime.datetime.now()),
+ )
path = os.path.join(models.fs.location, report_name)
with open(path, "w"):
pass
- report = models.TestRailReport(
- report_name=report_name,
- path=path)
+ report = models.TestRailReport(report_name=report_name, path=path)
report.save()
process_run.delay(run_id, report.id, path, is_testplan)
return redirect("single_report", report.id)
@@ -133,7 +144,8 @@
except models.ActionLog.DoesNotExist:
pass
update = models.ActionLog(
- name="update_plot_started", date=datetime.datetime.now())
+ name="update_plot_started", date=datetime.datetime.now()
+ )
update.save()
update_plot_data.delay()
return HttpResponse("Started Update", status=200)
@@ -142,7 +154,8 @@
def jenkins_plot(request):
try:
update_date = models.ActionLog.objects.get(
- name="update_jenkins_plot").date
+ name="update_jenkins_plot"
+ ).date
except models.ActionLog.DoesNotExist:
update_date = None
try:
@@ -157,13 +170,18 @@
try:
with open(job_names_path, "r") as f:
job_names = json.load(f)
- except:
+ except Exception:
pass
return render(
- request, "control/jenkins_plot.html",
- {"update_date": update_date, "update_started": update_started,
- "job_names": enumerate(job_names, 1)})
+ request,
+ "control/jenkins_plot.html",
+ {
+ "update_date": update_date,
+ "update_started": update_started,
+ "job_names": enumerate(job_names, 1),
+ },
+ )
def submit_suites(request):
@@ -180,7 +198,7 @@
report1=report1,
report2=report2,
limit=form.cleaned_data["limit"],
- test_keyword=form.cleaned_data["test_keyword"]
+ test_keyword=form.cleaned_data["test_keyword"],
)
diff_model.save()
get_test_passability_in_suite.delay(diff_model.id, report1.id)
@@ -194,60 +212,72 @@
return submit_suites(request)
diff_form = forms.DiffPassRatesForm()
- report1_form = forms.SuitePassRateForm(prefix='first')
- report2_form = forms.SuitePassRateForm(prefix='second')
+ report1_form = forms.SuitePassRateForm(prefix="first")
+ report2_form = forms.SuitePassRateForm(prefix="second")
return render(
- request, "control/compare_suites.html",
+ request,
+ "control/compare_suites.html",
{
- "diff_form": diff_form,
- "report1_form": report1_form,
- "report2_form": report2_form,
- "finished": None
- })
+ "diff_form": diff_form,
+ "report1_form": report1_form,
+ "report2_form": report2_form,
+ "finished": None,
+ },
+ )
def list_of_comparing_reports(request):
list_of_reports = models.DiffOfSuitesPassRates.objects.all()
return render(
- request, "control/list_comparing_suites.html",
- {
- "reports": list_of_reports
- })
+ request,
+ "control/list_comparing_suites.html",
+ {"reports": list_of_reports},
+ )
def report_comparing_suites(request, report_id):
report = models.DiffOfSuitesPassRates.objects.get(pk=report_id)
- passrate1 = short_names_for_dict(json.loads(
- report.report1.passrate_by_tests))
- passrate2 = short_names_for_dict(json.loads(
- report.report2.passrate_by_tests))
+ passrate1 = short_names_for_dict(
+ json.loads(report.report1.passrate_by_tests)
+ )
+ passrate2 = short_names_for_dict(
+ json.loads(report.report2.passrate_by_tests)
+ )
- diff_table = get_dict_diff(dict1=passrate1,
- dict2=passrate2,
- compare_by_key="rate")
+ diff_table = get_dict_diff(
+ dict1=passrate1, dict2=passrate2, compare_by_key="rate"
+ )
diff_form = forms.DiffPassRatesForm(instance=report)
- report1_form = forms.SuitePassRateForm(instance=report.report1,
- prefix="first")
- report2_form = forms.SuitePassRateForm(instance=report.report2,
- prefix="second")
+ report1_form = forms.SuitePassRateForm(
+ instance=report.report1, prefix="first"
+ )
+ report2_form = forms.SuitePassRateForm(
+ instance=report.report2, prefix="second"
+ )
return render(
- request, "control/compare_suites.html",
- {"diff_form": diff_form,
- "report1_form": report1_form,
- "report2_form": report2_form,
- "report1": report.report1,
- "report2": report.report2,
- "is_finished": report.report1.finished and report.report2.finished,
- "diff_table": diff_table})
+ request,
+ "control/compare_suites.html",
+ {
+ "diff_form": diff_form,
+ "report1_form": report1_form,
+ "report2_form": report2_form,
+ "report1": report.report1,
+ "report2": report.report2,
+ "is_finished": report.report1.finished and report.report2.finished,
+ "diff_table": diff_table,
+ },
+ )
def schedulers(request):
return render(
- request, "control/schedulers.html",
- {"schedulers": models.CronPeriodicTask.objects.all()})
+ request,
+ "control/schedulers.html",
+ {"schedulers": models.CronPeriodicTask.objects.all()},
+ )
def scheduler(request, pk=None):
@@ -261,33 +291,31 @@
form = forms.PeriodicTaskForm()
return render(
- request, "control/scheduler.html",
- {
- "form": form,
- "pk": pk,
- "TASKS": models.TASKS
- }
+ request,
+ "control/scheduler.html",
+ {"form": form, "pk": pk, "TASKS": models.TASKS},
)
def save_scheduler(request, pk=None):
print(f"{request.POST=}")
- minute, hour, day_of_month, month_of_year, day_of_week = \
- request.POST.get("cron", "* * * * *").split(" ")
+ minute, hour, day_of_month, month_of_year, day_of_week = request.POST.get(
+ "cron", "* * * * *"
+ ).split(" ")
if pk is None:
sch = CrontabSchedule.objects.create(
minute=minute,
hour=hour,
day_of_month=day_of_month,
month_of_year=month_of_year,
- day_of_week=day_of_week
+ day_of_week=day_of_week,
)
task = models.CronPeriodicTask.objects.create(
crontab=sch,
cron=request.POST.get("cron"),
name=request.POST.get("name"),
task_name=request.POST.get("task_name"),
- enabled=request.POST.get("enabled") == 'on',
+ enabled=request.POST.get("enabled") == "on",
testplan_id_arg=request.POST.get("testplan_id_arg"),
)
else:
@@ -303,7 +331,7 @@
hour=hour,
day_of_month=day_of_month,
month_of_year=month_of_year,
- day_of_week=day_of_week
+ day_of_week=day_of_week,
)
if not form.is_valid():
print(f"{form.errors=}")
@@ -311,12 +339,9 @@
form.save()
PeriodicTasks.update_changed()
return render(
- request, "control/scheduler.html",
- {
- "form": form,
- "pk": task.id,
- "TASKS": models.TASKS
- }
+ request,
+ "control/scheduler.html",
+ {"form": form, "pk": task.id, "TASKS": models.TASKS},
)
diff --git a/testrail_bot/manage.py b/testrail_bot/manage.py
index ff64df6..14ab9a8 100755
--- a/testrail_bot/manage.py
+++ b/testrail_bot/manage.py
@@ -5,7 +5,7 @@
def main():
- os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testrail_bot.settings')
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testrail_bot.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
@@ -17,5 +17,5 @@
execute_from_command_line(sys.argv)
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/testrail_bot/testrail_bot/__init__.py b/testrail_bot/testrail_bot/__init__.py
index 070e835..0165ba0 100644
--- a/testrail_bot/testrail_bot/__init__.py
+++ b/testrail_bot/testrail_bot/__init__.py
@@ -4,4 +4,4 @@
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
-__all__ = ('celery_app',)
+__all__ = ("celery_app",)
diff --git a/testrail_bot/testrail_bot/asgi.py b/testrail_bot/testrail_bot/asgi.py
index be76c4c..9d066a5 100644
--- a/testrail_bot/testrail_bot/asgi.py
+++ b/testrail_bot/testrail_bot/asgi.py
@@ -11,6 +11,6 @@
from django.core.asgi import get_asgi_application
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testrail_bot.settings')
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testrail_bot.settings")
application = get_asgi_application()
diff --git a/testrail_bot/testrail_bot/celery.py b/testrail_bot/testrail_bot/celery.py
index 9e4cd7b..310932d 100644
--- a/testrail_bot/testrail_bot/celery.py
+++ b/testrail_bot/testrail_bot/celery.py
@@ -4,15 +4,15 @@
from celery import Celery
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testrail_bot.settings')
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testrail_bot.settings")
-app = Celery('testrail_bot')
+app = Celery("testrail_bot")
-app.config_from_object('django.conf:settings', namespace='CELERY')
+app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
- print('Request: {0!r}'.format(self.request))
+ print("Request: {0!r}".format(self.request))
diff --git a/testrail_bot/testrail_bot/settings.py b/testrail_bot/testrail_bot/settings.py
index 1b7f1ef..4436916 100644
--- a/testrail_bot/testrail_bot/settings.py
+++ b/testrail_bot/testrail_bot/settings.py
@@ -31,49 +31,47 @@
# Application definition
INSTALLED_APPS = [
- 'control.apps.ControlConfig',
- 'bootstrap5',
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'django_celery_beat',
+ "control.apps.ControlConfig",
+ "bootstrap5",
+ "django.contrib.admin",
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.messages",
+ "django.contrib.staticfiles",
+ "django_celery_beat",
]
MIDDLEWARE = [
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
]
-ROOT_URLCONF = 'testrail_bot.urls'
+ROOT_URLCONF = "testrail_bot.urls"
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [],
+ "APP_DIRS": True,
+ "OPTIONS": {
+ "context_processors": [
+ "django.template.context_processors.debug",
+ "django.template.context_processors.request",
+ "django.contrib.auth.context_processors.auth",
+ "django.contrib.messages.context_processors.messages",
],
- 'libraries': {
- 'custom_tags': 'control.template_tags.custom_tags'
- }
+ "libraries": {"custom_tags": "control.template_tags.custom_tags"},
},
},
]
-WSGI_APPLICATION = 'testrail_bot.wsgi.application'
+WSGI_APPLICATION = "testrail_bot.wsgi.application"
# Database
@@ -82,7 +80,9 @@
DATABASES = {
"default": {
"ENGINE": os.environ.get("SQL_ENGINE", "django.db.backends.sqlite3"),
- "NAME": os.environ.get("SQL_DATABASE", os.path.join(BASE_DIR, "db.sqlite3")),
+ "NAME": os.environ.get(
+ "SQL_DATABASE", os.path.join(BASE_DIR, "db.sqlite3")
+ ),
"USER": os.environ.get("SQL_USER", "user"),
"PASSWORD": os.environ.get("SQL_PASSWORD", "password"),
"HOST": os.environ.get("SQL_HOST", "localhost"),
@@ -90,7 +90,7 @@
}
}
-DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
+DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
CACHES = {
"default": {
@@ -104,16 +104,20 @@
AUTH_PASSWORD_VALIDATORS = [
{
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ "NAME": "django.contrib.auth.password_validation"
+ ".UserAttributeSimilarityValidator",
},
{
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ "NAME": "django.contrib.auth.password_validation"
+ ".MinimumLengthValidator",
},
{
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ "NAME": "django.contrib.auth.password_validation"
+ ".CommonPasswordValidator",
},
{
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ "NAME": "django.contrib.auth.password_validation"
+ ".NumericPasswordValidator",
},
]
@@ -121,9 +125,9 @@
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
-LANGUAGE_CODE = 'en-us'
+LANGUAGE_CODE = "en-us"
-TIME_ZONE = 'UTC'
+TIME_ZONE = "UTC"
USE_I18N = True
@@ -135,8 +139,8 @@
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
-STATIC_URL = '/staticfiles/'
-STATIC_ROOT = '/staticfiles'
+STATIC_URL = "/staticfiles/"
+STATIC_ROOT = "/staticfiles"
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "media/images"),
@@ -148,11 +152,11 @@
# Celery configs
-CELERY_BROKER_URL = 'redis://redis:6379'
-CELERY_RESULT_BACKEND = 'redis://redis:6379'
-CELERY_ACCEPT_CONTENT = ['application/json']
-CELERY_TASK_SERIALIZER = 'json'
-CELERY_RESULT_SERIALIZER = 'json'
+CELERY_BROKER_URL = "redis://redis:6379"
+CELERY_RESULT_BACKEND = "redis://redis:6379"
+CELERY_ACCEPT_CONTENT = ["application/json"]
+CELERY_TASK_SERIALIZER = "json"
+CELERY_RESULT_SERIALIZER = "json"
# TestRail configs
TESTRAIL_EMAIL = os.environ.get("TESTRAIL_EMAIL", default="123")
@@ -161,5 +165,6 @@
# Jira settings
JIRA_USER = os.environ.get("JIRA_USER")
JIRA_PASSWORD = os.environ.get("JIRA_PASSWORD")
-JIRA_SERVER = os.environ.get("JIRA_SERVER",
- default="https://mirantis.jira.com")
+JIRA_SERVER = os.environ.get(
+ "JIRA_SERVER", default="https://mirantis.jira.com"
+)
diff --git a/testrail_bot/testrail_bot/urls.py b/testrail_bot/testrail_bot/urls.py
index d8b6e77..ecb7151 100644
--- a/testrail_bot/testrail_bot/urls.py
+++ b/testrail_bot/testrail_bot/urls.py
@@ -13,11 +13,11 @@
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
+
from django.contrib import admin
from django.urls import include, path
-
urlpatterns = [
path("", include("control.urls")),
- path('admin/', admin.site.urls),
+ path("admin/", admin.site.urls),
]
diff --git a/testrail_bot/testrail_bot/wsgi.py b/testrail_bot/testrail_bot/wsgi.py
index 5e7bafa..a8dde5e 100644
--- a/testrail_bot/testrail_bot/wsgi.py
+++ b/testrail_bot/testrail_bot/wsgi.py
@@ -11,6 +11,6 @@
from django.core.wsgi import get_wsgi_application
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testrail_bot.settings')
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testrail_bot.settings")
application = get_wsgi_application()