def __init__(self):
     self.config = api.config
     self.common = Common()
     self.ifFinish = False
     self.environment = {}
     self.db = Database(api.config['DATABASE_IP'],
                        api.config['DATABASE_PORT'],
                        api.config['REPORT_DB_NAME'],
                        api.config['REPORT_DB_COLLECTION'])
Exemple #2
0
def finish(report_id):
    try:
        path = request.args.get('path')
        type = request.args.get('type')
        return ReportController().update_status(type, report_id, path)
    except ValidationError as error:
        return Common().create_response(400, error.errors)
Exemple #3
0
def setup_module(module):
    """
        Instantiate variables
    """
    global test_service
    global report_service
    global set_service
    global assertions
    global common
    global helpers

    global set_chrome_report, set_chrome_report_id, feature_1_chrome_id, feature_2_chrome_id, feature_3_chrome_id
    global set_firefox_report, set_firefox_report_id, feature_1_firefox_id, feature_2_firefox_id, feature_3_firefox_id
    global set_safari_report, set_safari_report_id, feature_1_safari_id, feature_2_safari_id, feature_3_safari_id
    global set_edge_report, set_edge_report_id, feature_1_edge_id, feature_2_edge_id, feature_3_edge_id
    global set_ie11_report, set_ie11_report_id, feature_1_ie11_id, feature_2_ie11_id, feature_3_ie11_id
    global _set
    global dashboard

    helpers = Helpers()
    common = Common()
    test_service = TestService()
    report_service = ReportService()
    set_service = SetService()
    assertions = ReportAssertions()
    feature_1_id = helpers.setup_feature(FEATURE_1)
    feature_2_id = helpers.setup_feature(FEATURE_2)
    feature_3_id = helpers.setup_feature(FEATURE_3)
    _set = helpers.create_set(SET)
    set_chrome_report, set_chrome_report_id, feature_1_chrome_id, feature_2_chrome_id, feature_3_chrome_id = init_report(
        "chrome")
    set_firefox_report, set_firefox_report_id, feature_1_firefox_id, feature_2_firefox_id, feature_3_firefox_id = init_report(
        "firefox")
    set_safari_report, set_safari_report_id, feature_1_safari_id, feature_2_safari_id, feature_3_safari_id = init_report(
        "safari")
    set_edge_report, set_edge_report_id, feature_1_edge_id, feature_2_edge_id, feature_3_edge_id = init_report(
        "edge")
    set_ie11_report, set_ie11_report_id, feature_1_ie11_id, feature_2_ie11_id, feature_3_ie11_id = init_report(
        "ie11")

    query = {"environment": SET["environment"]}
    dashboard = helpers.get_dashboard(query)
Exemple #4
0
def setup_module(module):
    """
        Instantiate variables
    """
    global test_service
    global report_service
    global set_service
    global assertions
    global common
    global helpers

    global set_report
    global set_report_id
    global feature_1_id
    global feature_2_id
    global feature_3_id
    global _set
    global dashboard

    helpers = Helpers()
    common = Common()
    test_service = TestService()
    report_service = ReportService()
    set_service = SetService()
    assertions = ReportAssertions()
    feature_1_id = helpers.setup_feature(FEATURE_1)
    feature_2_id = helpers.setup_feature(FEATURE_2)
    feature_3_id = helpers.setup_feature(FEATURE_3)
    _set = helpers.create_set(SET)
    helpers.create_report(_set)
    time.sleep(3)
    set_report_id = helpers.create_report(_set)
    set_report = helpers.get_report(set_report_id, True)
    feature_1_id = set_report["reports"][0]["_id"]
    feature_2_id = set_report["reports"][1]["_id"]
    feature_3_id = set_report["reports"][2]["_id"]
    query = {"environment": SET["environment"]}
    dashboard = helpers.get_dashboard(query)
Exemple #5
0
def search():
    try:
        deep = request.args.get('deep')
        return ReportController().search(request.get_json(), deep)
    except ValidationError as error:
        return Common().create_response(400, error.errors)
Exemple #6
0
def rerun_feature(report_id):
    try:
        return ReportController().rerun(report_id, request.get_json())
    except ValidationError as error:
        return Common().create_response(400, error.errors)
Exemple #7
0
def add_test(report_id):
    try:
        return ReportController().add(report_id, request.get_json())
    except ValidationError as error:
        return Common().create_response(400, error.errors)
Exemple #8
0
def post_report():
    try:
        return ReportController().create(request.get_json())
    except ValidationError as error:
        return Common().create_response(400, error.errors)
Exemple #9
0
from honeypy.common import Common
from honeypy.errors import ValidationError

from honeypy_report import api
from honeypy_report.controller import ReportController

"""
    Allow Cross origin requests while in development
"""
CORS(api, resources={r'\/?.*': {'origins': ['http://*****:*****@api.route("/", methods = ["POST"])
@basic_auth.required
def post_report():
    try:
        return ReportController().create(request.get_json())
    except ValidationError as error:
        return Common().create_response(400, error.errors)

"""
    Get a report by ID endpoint
Exemple #10
0
    def __init__(self):
        """
            Verify the kind of report received from paylaod
        """
        self.kind = {
            "kind": {
                "type": "string",
                "allowed": ["feature", "set"],
                "required": True
            }
        }
        """
            Verify basic report object without defining if it is a 'feature' or a 'set'
        """
        self.report = {
            "kind": {
                "type": "string",
                "allowed": ["feature", "set"],
                "required": True
            },
            "host": {
                "type": "string",
                "default": ""
            },
            "result": {
                "type": "boolean",
                "default": None,
                "nullable": True
            },
            "status": {
                "type": "string",
                "default": "Queued"
            },
            "message": {
                "type": "string",
                "nullable": True,
                "default": None
            },
            "fail": {
                "type": "boolean",
                "default": False
            },
            "tickets": {
                "type": "list",
                "default": []
            },
            "url": {
                "type": "string",
                "default": ""
            },
            "name": {
                "type": "string",
                "required": True
            },
            "environment": {
                "type": "string",
                "required": True,
                "default": None,
                "nullable": True
            },
            "queue": {
                "type": "string",
                "required": True,
                "default": "normal-queue"
            },
            "browser": {
                "type": "string",
                "required": True,
                "allowed": api.config["BROWSERS"],
                "default": api.config["BROWSERS"][0]
            }
        }
        """
            Define a feature report
            It inherits from the 'report' object
        """
        self.feature_report = {
            "path": {
                "type": "string",
                "required": True
            },
            "tests": {
                "type": "list",
                "default": []
            },
            "contents": {
                "type": "list",
                "required": True,
                "schema": {
                    "type": "string"
                }
            },
            "parentId": {
                "type": "string"
            }
        }
        self.feature_report.update(self.report)
        """
            Define a set report
            It inherits from the 'report' object
        """
        self.set_report = {
            "inherit": {
                "type": "boolean",
                "required": True,
                "default": False
            },
            "features": {
                "type": "list",
                "schema": {
                    "type": "string",
                    "regex": "^((?!\/\/).)+\.feature$"
                },
                "default": []
            },
            "reports": {
                "type": "list",
                "default": []
            }
        }
        self.set_report.update(self.report)
        """
            Define a test object
            Test objects are added to feature reports
        """
        self.test = {
            "text": {
                "type": "string",
                "required": True,
                "minlength": 1
            },
            "message": {
                "type": "string",
                "required": True
            },
            "result": {
                "type": "boolean",
                "required": True,
            }
        }
        """
            Define a step object
            Steps inherit from the 'test' object
        """
        self.step = {
            "type": {
                "type": "string",
                "required": True,
                "allowed": ["step"]
            },
            "test": {
                "type": "string",
                "required": True,
                "minlength": 1
            },
            "variables": {
                "type": "dict",
                "default": {}
            },
            "scenarioId": {
                "type": "string"
            }
        }
        self.step.update(self.test)
        """
            Define a scenario object
            Scenarios inherit from the 'test' object
        """
        self.scenario = {
            "type": {
                "required": True,
                "type": "string",
                "allowed": ["scenario"]
            },
            "name": {
                "type": "string",
                "default": ""
            },
            "loop": {
                "type": "dict",
                "default": {}
            },
            "tests": {
                "type": "list",
                "default": [],
                "required": True,
                "schema": self.step
            },
            "scenarioId": {
                "type": "string",
                "required": True
            }
        }
        self.scenario.update(self.test)
        """
            Verify a basic add payload received from the request
        """
        self.add = {
            "type": {
                "type": "string",
                "required": True,
                "allowed": ["step", "scenario"]
            },
            "scenarioId": {
                "type": "string",
                "minlength": 24,
                "maxlength": 24
            }
        }

        self.search = {
            "search": {
                "type": "dict",
                "required": True,
                "schema": {
                    "kind": {
                        "type": "string",
                        "required": True,
                        "allowed": ["feature", "set"]
                    },
                    "name": {
                        "type": "string"
                    },
                    "path": {
                        "type": "string"
                    },
                    "fail": {
                        "type": "boolean"
                    },
                    "host": {
                        "type": "string"
                    },
                    "url": {
                        "type": "string"
                    },
                    "browser": {
                        "type": "string"
                    },
                    "created": {
                        "type": "dict",
                        "schema": {
                            "min": {
                                "type": "number",
                                "default": Common().get_timestamp() - 604800000
                            },
                            "max": {
                                "type": "number",
                                "default": Common().get_timestamp()
                            }
                        },
                        "default": {
                            "$gte": Common().get_timestamp() - 604800000,
                            "$lte": Common().get_timestamp()
                        }
                    }
                }
            },
            "pagination": {
                "type": "dict",
                "schema": {
                    "page": {
                        "type": "number",
                        "default": 1
                    },
                    "limit": {
                        "type": "number",
                        "default": 50
                    },
                    "sort": {
                        "type": "string",
                        "default": "_id"
                    }
                },
                "default": {
                    "page": 1,
                    "limit": 50,
                    "default": "_id"
                }
            }
        }

        self.dashboard = {
            "kind": {
                "type": "string",
                "default": "set",
                "allowed": ["set"]
            },
            "created": {
                "type": "dict",
                "schema": {
                    "min": {
                        "type": "number",
                        "default": Common().get_timestamp() - 604800000
                    },
                    "max": {
                        "type": "number",
                        "default": Common().get_timestamp()
                    }
                },
                "default": {
                    "$gte": Common().get_timestamp() - 604800000,
                    "$lte": Common().get_timestamp()
                }
            },
            "hosts": {
                "type": "list",
                "schema": {
                    "type": "string"
                }
            },
            "url": {
                "type": "string"
            },
            "browsers": {
                "type": "list",
                "default": api.config["BROWSERS"],
                "allowed": api.config["BROWSERS"]
            },
            "environment": {
                "type": "string",
                "required": True
            }
        }
Exemple #11
0
class ReportController(object):
    def __init__(self):
        self.config = api.config
        self.common = Common()
        self.ifFinish = False
        self.environment = {}
        self.db = Database(api.config['DATABASE_IP'],
                           api.config['DATABASE_PORT'],
                           api.config['REPORT_DB_NAME'],
                           api.config['REPORT_DB_COLLECTION'])

    def get(self, report_id, deep=False):
        """
            Get a report by ID and kind

            :kind: the type of report
            :report_id: the id of the requested report
        """
        report = self.db.find_one({"_id": ObjectId(report_id)})
        if report:
            if deep:
                report = self.get_set_features(report)
            return self.common.create_response(200, report)
        else:
            return self.common.create_response(
                400, {"reportId": [f"Report ID does not exist ({report_id})"]})

    def get_set_features(self, report):
        """
            Get all feature reports of a set and combine them with set report

            :report: the set report
        """
        if report["kind"] == "set":
            for feature in report["reports"]:
                index = report["reports"].index(feature)
                if "reportId" in feature and feature["reportId"]:
                    feature_report = self.db.find_one(
                        {"_id": ObjectId(feature["reportId"])})
                    feature_report["_id"] = str(feature_report["_id"])
                    report["reports"][index] = feature_report
        return report

    def create(self, data):
        """
            Create a report

            :data: the request payload
        """
        data = self.validate_report(data)
        response = None
        data = self.get_environment_variables(data, init=True)
        data = self.check_base_url(data)
        if data["kind"] == "set":
            response = self.create_set_report(data)
        elif data["kind"] == "feature":
            response = self.create_feature_report(data)
        return self.common.create_response(201,
                                           {"id": str(response.inserted_id)})

    def create_feature_report(self, data):
        if "parentId" in data:
            self.check_inheritance(data["parentId"], data)
        return self.db.insert_one(data)

    def create_set_report(self, data):
        """
            Create a set report

            :data: the request payload
        """
        response = self.db.insert_one(data)
        for path in data["features"]:
            parentId = response.inserted_id
            self.create_set_feature(path, parentId, data)
        return response

    def get_environment_variables(self, data, init=False):
        response = EnvironmentService().get(data["environment"])
        if response.status_code == 200:
            self.environment = response.json()
            self.variables = self.environment["variables"]
        else:
            self.environment = {"name": data["environment"], "variables": {}}
        data["environment"] = self.environment
        return data

    def create_set_feature(self, path, parentId, _set):
        """
            Create a set feature report
            Check to see if feature exists before creating a report

            :path: path to the feature
            :parentId: the set report ID
        """
        feature = {"path": path, "name": os.path.basename(path)}
        response = TestService().get(path, "feature")
        if response.status_code == 200:
            data = response.json()
            data["environment"] = _set["environment"]["name"]
            data["parentId"] = str(parentId)
            response = self.create(data)
            feature["reportId"] = json.loads(response.response[0])["id"]
            feature["message"] = None
            feature["fail"] = data["fail"]
            feature["status"] = "Queued"
            feature["result"] = None
            feature["parentId"] = str(parentId)
            self.db.update_one({"_id": ObjectId(parentId)},
                               {"$push": {
                                   "reports": feature
                               }})
        else:
            feature = {
                "kind": "feature",
                "path": path,
                "result": None,
                "message": None,
                "status": None,
                "parentId": str(parentId),
                "fail": False
            }
            self.db.update_one({"_id": ObjectId(parentId)},
                               {"$push": {
                                   "reports": feature
                               }})

    def check_inheritance(self, setId, feature):
        """
            Check whether to make features of set inherit from set
        """
        response = self.get(setId)
        setReport = json.loads(response.response[0])
        if setReport["inherit"] == True:
            feature["browser"] = setReport["browser"]
            feature["url"] = setReport["url"]
            feature["host"] = setReport["host"]
            feature["environment"] = setReport["environment"]
        return feature

    def check_base_url(self, data):
        """
            Check if environment contains an overwriting base url
            If so, change the report base url
        """
        if "environment" in data:
            if "base_url" in data["environment"]["variables"]:
                data["url"] = data["environment"]["variables"]["base_url"]
        return data

    def validate_report(self, data, update=False, normalize=True):
        """
            Validate a feature/set report

            :data: the request payload
            :update: should service validate report as an update action
            :normalize: should default values be defined
        """
        validator = Validator(Schemas().report, allow_unknown=True)
        data = self.normalize(normalize, data, validator)
        validation = validator.validate(data, update=update)
        if not validation:
            raise ValidationError(validator.errors)
        return self.validate_report_type(data, update, normalize)

    def validate_report_type(self, data, update, normalize):
        """
            Validate report type

            :data: the report
            :update: should service validate report as an update action
            :normalize: should default values be defined
        """
        validator = Validator(Schemas().kind, allow_unknown=True)
        data = self.normalize(normalize, data, validator)
        validation = validator.validate(data)
        if not validation:
            raise ValidationError(validator.errors)
        if data["kind"] == "set":
            data = self.validate_set_report(data, update, normalize)
        elif data["kind"] == "feature":
            data = self.validate_feature_report(data, update, normalize)
        return data

    def validate_set_report(self, data, update, normalize):
        """
            Validate a set report

            :data: the set data
            :update: should service validate report as an update action
            :normalize: should default values be defined
        """
        validator = Validator(Schemas().set_report, purge_unknown=True)
        data = self.normalize(normalize, data, validator)
        validation = validator.validate(data, update=update)
        if not validation:
            raise ValidationError(validator.errors)
        return data

    def validate_feature_report(self, data, update, normalize):
        """
            Validate a feature report

            :data: the feature data
            :update: should service validate report as an update action
            :normalize: should default values be defined
        """
        validator = Validator(Schemas().feature_report, purge_unknown=True)
        data = self.normalize(normalize, data, validator)
        validation = validator.validate(data, update=update)
        if not validation:
            raise ValidationError(validator.errors)
        return data

    def normalize(self, normalize, data, validator):
        """
            Check if we need to define default values

            :normalize: should default values be defined
            :data: the request data
            :validation: the Cerberus validator instance
        """
        if normalize:
            data = validator.normalized(data)
        return data

    def save(self, report_id, data):
        """
            Save to a report

            :report_id: the report id
            :data: the data being saved to the report
        """
        data = self.validate_report(data, True, False)
        self.db.update_one({"_id": ObjectId(report_id)}, {"$set": data})
        return self.common.create_response(204)

    def add(self, report_id, data):
        """
            Check to see where the data will be sent

            :report_id: the report id
            :data: the data being add to the report
        """
        data = self.validate_add(report_id, data)
        if data["type"] == "step":
            self.add_test(report_id, data)
        elif data["type"] == "scenario":
            self.add_scenario(report_id, data)
        self.update_report_result(report_id, data)
        return self.common.create_response(204)

    def validate_add(self, report_id, data):
        """
            Validate add type

            :report_id: the report id we are modifying
            :data: the data we want to add to the report
        """
        validator = Validator(Schemas().add, allow_unknown=True)
        validation = validator.validate(data)
        if not validation:
            raise ValidationError(validator.errors)
        if data["type"] == "test":
            data = self.validate_step_add(report_id, data)
        elif data["type"] == "scenario":
            data = self.validate_scenario_add(report_id, data)
        return data

    def validate_step_add(self, report_id, step):
        """
            Validate a step object

            :report_id: the report id
            :step: the step data
        """
        validator = Validator(Schemas().step, purge_unknown=True)
        step = validator.normalized(step)
        validation = validator.validate(step)
        if not validation:
            raise ValidationError(validator.errors)
        return step

    def validate_scenario_add(self, report_id, scenario):
        """
            Validate a scenario object

            :report_id: the report id
            :scenario: the scenario data
        """
        validator = Validator(Schemas().scenario, purge_unknown=True)
        scenario = validator.normalized(scenario)
        validation = validator.validate(scenario)
        if not validation:
            raise ValidationError(validator.errors)
        return scenario

    def add_scenario(self, report_id, scenario):
        """
            Add a scenario

            :report_id: the report id
            :scenario: the scenario object
        """
        scenario["created"] = self.common.get_timestamp()
        self.db.update_one({"_id": ObjectId(report_id)},
                           {"$push": {
                               "tests": scenario
                           }})

    def add_test(self, report_id, test):
        """
            Add a test to a report

            :report_id: the report id
            :scenario: the test object
        """
        test["created"] = self.common.get_timestamp()
        if "scenarioId" not in test or not test["scenarioId"]:
            response = self.db.update_one({"_id": ObjectId(report_id)},
                                          {"$push": {
                                              "tests": test
                                          }})
        else:
            response = self.db.update_one(
                {
                    "_id": ObjectId(report_id),
                    "tests.scenarioId": test["scenarioId"]
                }, {'$push': {
                    'tests.$.tests': test
                }})

    def update_report_result(self, report_id, data):
        """
            Update a report if it failed

            :report_id: the report id
            :data: the test object
        """
        if data["result"] == False:
            report = self.db.find_one({"_id": ObjectId(report_id)})
            self.update_set_result(report, data)
            self.update_feature_result(report, data)
            self.update_scenario_result(report, data)

    def update_set_result(self, report, data):
        """
            Update a set report if a feature failed

            :report: the report
            :data: the test object
        """
        if "parentId" in report:
            self.db.update_one(
                {
                    "_id": ObjectId(report["parentId"]),
                    "reports.reportId": report["_id"]
                }, {
                    "$set": {
                        "result": False,
                        "message": "Failure",
                        "reports.$.result": False,
                        "reports.$.message": "Failure"
                    }
                })

    def update_feature_result(self, report, data):
        """
            Update a feature report if a feature failed

            :report: the report
            :data: the test object
        """
        self.db.update_one({"_id": ObjectId(report["_id"])},
                           {"$set": {
                               "result": False,
                               "message": "Failure"
                           }})

    def update_scenario_result(self, report, data):
        """
            Update a scenario within a feature report if a it failed

            :report: the report
            :data: the test object
        """
        if "scenarioId" in data:
            self.db.update_one(
                {
                    "_id": ObjectId(report["_id"]),
                    "tests.scenarioId": data["scenarioId"]
                }, {
                    '$set': {
                        'tests.$.result': False,
                        "tests.$.message": "Failure"
                    }
                })

    def update_status(self, _type, report_id, path):
        """
            Check what status
        """
        if _type == "start":
            return self.start(report_id, path)
        elif _type == "finish":
            return self.finish(report_id, path)
        else:
            return self.common.create_response(
                400, {"type": [f"Type value of '{_type}' is invalid"]})

    def start(self, report_id, path):
        """
            Change report status to running

            :report_id: the id of the report
            :path: the path string of the test
        """
        report = self.db.find_one({"_id": ObjectId(report_id)})
        if "parentId" in report:
            response = self.db.update_one(
                {
                    "_id": ObjectId(report["parentId"]),
                    "reports.reportId": report_id
                },
                {"$set": {
                    "status": "Running",
                    "reports.$.status": "Running"
                }})
            response = self.db.update_one({"_id": ObjectId(report_id)},
                                          {"$set": {
                                              "status": "Running"
                                          }})
        else:
            response = self.db.update_one({"_id": ObjectId(report["_id"])},
                                          {"$set": {
                                              "status": "Running"
                                          }})
        return self.common.create_response(204)

    def rerun(self, feature_report_id, report):
        """
            Setup a feature report to be rerun
        """
        if "parentId" not in report:
            raise ValidationError(
                errors=
                "Please provide a parent set ID with the report rerun request")
        self.validate_report(report)
        if "end" in report:
            report.pop("end")
        response = self.create_feature_report(report)
        rerun_report_id = str(response.inserted_id)
        set_report_id = report["parentId"]
        feature = {
            "path": report["path"],
            "name": report["name"],
            "message": None,
            "status": "Queued",
            "result": None,
            "fail": report["fail"],
            "parentId": set_report_id,
            "reportId": rerun_report_id
        }
        response = self.db.update_one(
            {
                "_id": ObjectId(set_report_id),
                "reports.reportId": feature_report_id
            }, {"$set": {
                "reports.$": feature
            }})
        if response.matched_count > 0:
            return self.common.create_response(201,
                                               {"id": str(rerun_report_id)})
        else:
            return self.common.create_response(
                404, {"reportId": "Report ID could not be found"})

    def finish(self, report_id, path):
        """
            Finish a report

            :report_id: the report id
            :path: the path of the feature
        """
        report = self.db.find_one({"_id": ObjectId(report_id)})
        if not report:
            return self.common.create_response(
                400, {"reportId": [f"Report ID does not exist ({report_id})"]})
        self.finish_feature_report(report_id, report, path)
        if "parentId" in report:
            self.finish_set_report(report["parentId"])
        return self.common.create_response(204)

    def finish_feature_report(self, report_id, report, path):
        """
            Finish a feature reports

            :report_id: the report id
            :report: the report
            :path: the path of the feature
        """
        report = self.check_report_result(report)
        current_time = self.common.get_timestamp()
        duration = self.get_duration(current_time, report)
        self.db.update_one({"_id": ObjectId(report_id)}, {
            "$set": {
                "end": current_time,
                "result": report["result"],
                "message": report["message"],
                "status": "Done",
                "duration": duration
            }
        })
        if "parentId" in report:
            self.db.update_one(
                {
                    "_id": ObjectId(report["parentId"]),
                    "reports.reportId": report["_id"]
                }, {
                    "$set": {
                        "reports.$.message": report["message"],
                        "reports.$.result": report["result"],
                        "reports.$.status": "Done",
                        "reports.$.fail": report["fail"]
                    }
                })

    def check_report_result(self, report):
        """
            Check the final report result (pass/fail)

            :report: the report object
        """
        message = ""
        result = None
        if report["result"] == True or report["result"] == None:
            message = "Success"
            result = True
        elif report["result"] == False or report["status"] == "Queued":
            message = "Failure"
            result = False
        report["message"] = message
        report["result"] = result
        return report

    def finish_set_report(self, report_id):
        """
            Finish a set report

            :report_id: the report id
        """
        set_report = self.db.find_one({"_id": ObjectId(report_id)})
        finished = True
        result = True
        message = "Success"
        for feature in set_report["reports"]:
            if "reportId" in feature:
                feature_report = self.db.find_one(
                    {"_id": ObjectId(feature["reportId"])})
                feature_report = self.check_report_result(feature_report)
                if not "end" in feature_report:
                    finished = False
                    break
                if feature_report["result"] == False:
                    result = feature_report["result"]
                    message = feature_report["message"]
        if finished == True:
            current_time = self.common.get_timestamp()
            duration = self.get_duration(current_time, set_report)
            self.db.update_one({"_id": ObjectId(report_id)}, {
                "$set": {
                    "end": current_time,
                    "message": message,
                    "result": result,
                    "status": "Done",
                    "duration": duration
                }
            })

    def get_duration(self, current_time, report):
        """
            Calculate the test duration

            :current_time: the current time in milliseconds
            :report: the report used to compare against the current time
        """
        ms = int(current_time) - int(report["created"])
        return time.strftime('%M:%S', time.gmtime(ms / 1000))

    def get_dashboard(self, query):
        """
            Get the full dashboard
        """
        query = self.validate_dashboard_query(query)
        self.init_query_hosts(query)
        self.init_query_dates(query)
        self.init_query_environment(query)
        browsers, dashboard = self.init_query_browsers(query)
        dashboard = self.organize_dashboard(dashboard, browsers)
        return self.common.create_response(200, dashboard)

    def init_query_browsers(self, query):
        """
            Setup query with browsers
        """
        browsers = query["browsers"]
        query.pop("browsers")
        dashboard = {}
        for browser in browsers:
            query["browser"] = browser
            reports = self.db.aggregate(query)
            dashboard[browser] = reports
        return browsers, dashboard

    def init_query_environment(self, query):
        """
            Setup query with the environment
        """
        query["environment.name"] = query["environment"]
        query.pop("environment")

    def init_query_dates(self, query):
        """
            Adjust query to use correct DTO
        """
        query["created"]["$gte"] = query["created"].pop("min")
        query["created"]["$lte"] = query["created"].pop("max")

    def init_query_hosts(self, query):
        """
            Setup query with hosts
        """
        if "hosts" in query:
            if len(query["hosts"]) > 0:
                hosts = query["hosts"]
                query["host"] = {"$in": hosts}
            query.pop("hosts")

    def validate_dashboard_query(self, query):
        """
            Validate the dashboard search query

            :query: the search query to get the dashboard data
        """
        validator = Validator(Schemas().dashboard, purge_unknown=True)
        query = validator.normalized(query)
        validation = validator.validate(query)
        if not validation:
            raise ValidationError(validator.errors)
        return query

    def organize_dashboard(self, dashboard, browsers):
        """
            Create a new dashboard object
            Organize the reports by browser

            :dashboard: the returned mongo query organized by set/browser
            :browsers: all the browsers searched in the query
        """
        new_dashboard = {}
        for browser, reports in dashboard.items():
            for report in reports:
                name = report["_id"]
                new_dashboard = self.add_set_to_dashboard(
                    name, new_dashboard, browsers)
                new_dashboard = self.add_report_to_dashboard_set(
                    new_dashboard, name, browser, report)
        return new_dashboard

    def add_set_to_dashboard(self, name, new_dashboard, browsers):
        """
            Add set name to the new dashboard object
            We want to add each set returned to the object

            :name: the name of the set
            :new_dashboard: the new dashboard object
            :browsers: the browsers used in the query
        """
        if name not in new_dashboard:
            new_dashboard[name] = {}
            for supported_browser in browsers:
                new_dashboard[name][supported_browser] = []
        return new_dashboard

    def add_report_to_dashboard_set(self, new_dashboard, name, browser,
                                    report):
        """
            Add a set report to the dashboard
            Insert the set report within the set name and browser/device type
        """
        new_dashboard[name][browser].append(report)
        return new_dashboard

    def search(self, query, deep):
        """
            Search reports

            :query: the search query object
            :deep: specifies if this query is a deep search query
        """
        reports = None
        total = None
        search_total = None
        query = self.validate_search(query)
        query["search"]["created"]["$gte"] = query["search"]["created"].pop(
            "min")
        query["search"]["created"]["$lte"] = query["search"]["created"].pop(
            "max")
        fields = self.return_specific_fields(deep)
        query, skip, page_number = self.get_pagination(query)
        reports, total, search_total = self.db.search(
            query["search"], skip, query["pagination"]["limit"],
            query["pagination"]["sort"])
        # reports = self.check_search_results(query["search"]["kind"], reports, fields)
        return self.common.create_response(
            200, {
                "results": reports,
                "pagination": {
                    "page": query["pagination"]["page"],
                    "limit": query["pagination"]["limit"],
                    "total": total,
                    "amount": search_total
                }
            })

    def set_query_date(self, query):
        """
            Rename date query keys
        """
        query["search"]["created"]["$gte"] = query["search"]["created"].pop(
            "min")
        query["search"]["created"]["$lte"] = query["search"]["created"].pop(
            "max")
        return query

    def get_pagination(self, query):
        """
            Get pagination details
            Return the skip value
            Return the page number value
        """
        page_number = query["pagination"]["page"] - 1
        skip = query["pagination"]["page"] * page_number
        query["pagination"].pop("pagination", None)
        return query, skip, page_number

    def check_search_results(self, kind, reports, fields):
        """
            Check if search was for sets
            If so, get feature reports within sets

            :kind: search kind
            :reports: the return of the search query
        """
        if kind == "set":
            for report in reports:
                result_index = reports.index(report)
                for feature in report["reports"]:
                    set_index = report["reports"].index(feature)
                    _id = None
                    if "reportId" in feature:
                        _id = feature["reportId"]
                    elif "_id" in feature:
                        _id = feature["_id"]
                    feature_report = self.db.find_one({"_id": ObjectId(_id)},
                                                      fields)
                    report["reports"][set_index] = feature_report
                reports[result_index] = report
        return reports

    def return_specific_fields(self, deep):
        """
            Specify what fields to return from database query
        """
        if not deep:
            return {
                "path": 1,
                "name": 1,
                "message": 1,
                "result": 1,
                "status": 1,
                "fail": 1,
                'kind': 1,
                'parentId': 1
            }

    def validate_search(self, query):
        """
            Validate the search query payload

            :query: the search query object
        """
        validator = Validator(Schemas().search, purge_unknown=True)
        query = validator.normalized(query)
        validation = validator.validate(query)
        if not validation:
            raise ValidationError(validator.errors)
        return query