UnitTest

Provides for automated testing of automations, playbooks, and sub-playbooks.

python · Content Testing

Source

import io
import uuid

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401


def GetAutomationId(name):
    autoId = execute_command("core-api-post", {"uri": f"/automation/load/{name}"})["response"]["id"]
    return autoId


def GetPlaybookId(name):
    playbook = execute_command("core-api-post", {"uri": "/playbook/search", "body": {"query": "name:" + f'"{name}"'}})[
        "response"]["playbooks"]
    if playbook is None:
        return "-1"
    pbid = playbook[0]["id"]
    return pbid


def RunUTResults(args):
    try:
        # Set up the task arguments
        addafter = args["addafter"]
        incid = args["incid"]
        gridfld = args["gridfield"]
        scriptid = GetAutomationId("UnitTestResults")
        newargs = {
            "cmds": {"simple": ",".join(args["cmds"])},
            "tasks": {"simple": ",".join(args["tasks"])},
            "gridfield": {"simple": gridfld},
            "status": {"simple": str(args["status"]).replace("[", "").replace("]", "")},
        }
        task = {
            "name": "DisplayUnitTestResults",
            "description": "",
            "loop": {},
            "tags": [],
            # ID for "UnitTestResults" automation
            "automationScript": scriptid,
            "type": "regular",
            "neighborInvPBTaskId": addafter,
            "addAfter": True,
            "playbookId": "",
            "ignoreWorker": True,
            "scriptArguments": newargs,
            "separateContext": None,
            "pageSize": 50,
            "version": -1,
        }

        # Add the task to display unit test results
        attempts = 0
        while attempts < 5:
            results = execute_command("core-api-post", {"uri": f"/inv-playbook/task/add/{incid}", "body": task})
            if "response" in results:
                results = results["response"]
                if "tasks" in results:
                    tasks = results["tasks"]
                    break
            attempts = attempts + 1

        # Find the task ID of the last "DisplayUnitTestResults" task that was just added to the playbook
        new_id = "0"
        task_id = "0"
        if attempts < 5:
            for _key, val in tasks.items():
                if (
                    "scriptId" in val["task"]
                    and val["task"]["scriptId"] == scriptid
                    and val["task"]["name"] == "DisplayUnitTestResults"
                ):
                    new_id = val["id"]
                    if int(new_id) > int(task_id):
                        task_id = new_id
        else:
            raise DemistoException("unable to add DisplayUnitTestResults task to playbook")

        # Execute the task inside a demisto lock to prevent concurrent updates to the unit test status grid field
        attempts = 0
        contents = execute_command("demisto-lock-get", {"name": gridfld, "timeout": 60})
        if "Lock acquired successfully" in contents:
            while attempts < 5:
                start_response = execute_command(
                    "core-api-post",
                    {
                        "uri": "/inv-playbook/task/execute",
                        "body": {
                            "taskinfo": {"invId": incid, "inTaskID": task_id, "version": -1, "args": newargs, "loopArgs": {}},
                            "playbooksdebuginfo": {},
                            "pageSize": 50,
                        },
                    },
                )
                if "response" not in start_response:
                    attempts = attempts + 1
                else:
                    break
            execute_command("demisto-lock-release", {"name": gridfld})
        else:
            raise DemistoException(f"unable to acquire lock name: {gridfld}")
    except Exception as ex:
        execute_command("demisto-lock-release", {"name": gridfld})
        demisto.error(traceback.format_exc())
        return_error(f"RunUTResults: Exception failed to execute. Error: {ex!s}")


def RunAdhocPlaybook(playbookname, taskname, addafter, incid):
    try:
        pbid = GetPlaybookId(playbookname)
        if pbid == "-1":
            raise DemistoException(f"Playbook '{playbookname}' not found")
        task = {
            "name": taskname,
            "loop": {},
            "type": "playbook",
            "neighborInvPBTaskId": addafter,
            "addAfter": True,
            "playbookId": pbid,
            "scriptArguments": {},
            "separateContext": True,
            "pageSize": 50,
            "version": -1,
        }
        # Add the playbook as a task
        tasks = execute_command("core-api-post", {"uri": f"/inv-playbook/task/add/{incid}", "body": task})[
            "response"
        ]["tasks"]

        # Find the new task ID in the updated playbook and execute the task
        for key, task in tasks.items():
            if "name" in task["task"] and task["task"]["name"] == taskname:
                execute_command(
                    "core-api-post",
                    {
                        "uri": "/inv-playbook/task/execute",
                        "body": {
                            "taskinfo": {"invId": incid, "inTaskID": key, "version": -1, "args": {}, "loopArgs": {}},
                            "playbooksdebuginfo": {},
                            "pageSize": 50,
                        },
                    },
                )
    except Exception as ex:
        demisto.error(traceback.format_exc())
        return_error(f"RunAdhocPlaybook: Exception failed to execute. Error: {ex!s}")


def RunAutomations(buf):
    cmds = []
    tasks = []
    status = []
    line = buf.readline()

    while line != "":
        words = line.split(",", 1)
        command = words[0].strip()
        params = json.loads(words[1])
        name = words[1].replace(",", " ").replace("{", "(").replace("}", ")").replace('"', "")
        cmds.append(command)
        tasks.append(f"UT_{name}")
        demisto.executeCommand(command, params)
        status.append(True)
        line = buf.readline()

    return cmds, tasks, status


def RunSubplaybooks(buf, addAfter, incId):
    playbooks = []
    line = buf.readline()

    while line != "":
        words = line.split(",", 1)
        playbooks.append(words[0].strip())
        line = buf.readline()

    cmds, tasks, status = RunPlaybooks(playbooks, addAfter, incId)
    return cmds, tasks, status


def RunPlaybooks(playbooks, addAfter, incId):
    cmds = []
    tasks = []
    status = []

    for p in playbooks:
        p = p.strip()
        if p == "":
            continue
        taskName = f"UT_{p}_{uuid.uuid4().hex}"
        cmds.append(p)
        tasks.append(taskName)
        RunAdhocPlaybook(p, taskName, addAfter, incId)
        status.append(True)

    return cmds, tasks, status


def main():
    try:
        cmds = []
        tasks = []
        status = []
        incId = demisto.incident()["id"]
        args = demisto.args()
        playbooks = args.get("playbook", "").split(",")
        addAfter = args.get("addAfter", "")
        testType = args.get("testType", "")
        listName = args.get("listName", "")
        if listName != "":
            listlines = execute_command("getList", {"listName": listName})
            buf = io.StringIO(listlines)
        else:
            buf = None
            demisto.debug(f"{listName=} -> {buf=}")

        # Add the tasks to the playbook and execute it
        if testType == "Automation":
            cmds, tasks, status = RunAutomations(buf)
        elif testType == "Subplaybook":
            cmds, tasks, status = RunSubplaybooks(buf, addAfter, incId)
        elif testType == "Playbook":
            cmds, tasks, status = RunPlaybooks(playbooks, addAfter, incId)
        elif testType == "Multiselect":
            # demisto.incident()['id'] does not work from field change trigger script
            incId = demisto.executeCommand("getIncidents", {})[0]["Metadata"]["investigationId"]
            cmds, tasks, status = RunPlaybooks(playbooks, addAfter, incId)
        else:
            return

        # Add a task to display the results. Does this inside demisto locks to ensure
        # serial updates to the unit test status grid field when test butten selection add tasks quickly
        if len(cmds) > 0:
            newargs = {
                "playbookname": "UnitTestResults",
                "addafter": addAfter,
                "incid": incId,
                "cmds": cmds,
                "tasks": tasks,
                "gridfield": "contenttestingunittestresults",
                "status": status,
            }
            RunUTResults(newargs)
    except Exception as ex:
        demisto.error(traceback.format_exc())
        status.append(False)
        newargs = {
            "playbookname": "UnitTestResults",
            "taskname": "main: exception occured",
            "addafter": addAfter,
            "incid": incId,
            "cmds": cmds,
            "tasks": tasks,
            "gridfield": "contenttestingunittestresults",
            "status": status,
        }
        RunUTResults(newargs)
        return_error(f"UnitTest: Exception failed to execute. Error: {ex!s}")


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()

README

Provides for automated testing of automations, playbooks, and sub-playbooks.

Script Data


Name Description
Script Type python3

Inputs


Argument Name Description
playbook Playbook to Execute.
addAfter Playbook Task to Add This Task After.
testType Type of Test to Run.
listName List to Read Subplaybooks or Automations From.

Outputs


There are no outputs for this script.