# Copyright (C) 2021 Intel Corporation
# SPDX-License-Identifier: GPL-3.0-or-later

"""
CVE-bin-tool OutputEngine tests
"""
import csv
import importlib.util
import json
import logging
import os
import tempfile
import unittest
from datetime import datetime

from rich.console import Console

from cve_bin_tool.output_engine import OutputEngine, output_csv, output_json, output_pdf
from cve_bin_tool.output_engine.console import output_console
from cve_bin_tool.output_engine.html import output_html
from cve_bin_tool.output_engine.util import format_output
from cve_bin_tool.util import CVE, CVEData, ProductInfo, VersionInfo


class TestOutputEngine(unittest.TestCase):
    """Test the OutputEngine class functions"""

    MOCK_OUTPUT = {
        ProductInfo("vendor0", "product0", "1.0"): CVEData(
            cves=[
                CVE(
                    "CVE-1234-1234",
                    "MEDIUM",
                    score=4.2,
                    cvss_version=2,
                    cvss_vector="C:H",
                ),
                CVE(
                    "CVE-1234-1234",
                    "LOW",
                    score=1.2,
                    cvss_version=2,
                    cvss_vector="CVSS2.0/C:H",
                ),
            ],
            paths={""},
        ),
        ProductInfo("vendor0", "product0", "2.8.6"): CVEData(
            cves=[
                CVE(
                    "CVE-1234-1234",
                    "LOW",
                    score=2.5,
                    cvss_version=3,
                    cvss_vector="CVSS3.0/C:H/I:L/A:M",
                )
            ],
            paths={""},
        ),
        ProductInfo("vendor1", "product1", "3.2.1.0"): CVEData(
            cves=[
                CVE(
                    "CVE-1234-1234",
                    "HIGH",
                    score=7.5,
                    cvss_version=2,
                    cvss_vector="C:H/I:L/A:M",
                )
            ],
            paths={""},
        ),
    }

    MOCK_PDF_OUTPUT = {
        ProductInfo("vendor0", "product0", "1.0"): CVEData(
            cves=[
                CVE(
                    "CVE-1234-1234",
                    "MEDIUM",
                    score=4.2,
                    cvss_version=2,
                    cvss_vector="C:H",
                ),
                CVE(
                    "CVE-1234-1234",
                    "LOW",
                    score=1.2,
                    cvss_version=2,
                    cvss_vector="CVSS2.0/C:H",
                ),
            ],
            paths={""},
        ),
        ProductInfo("vendor0", "product0", "2.8.6"): CVEData(
            cves=[
                CVE(
                    "CVE-1234-1234",
                    "LOW",
                    score=2.5,
                    cvss_version=3,
                    cvss_vector="CVSS3.0/C:H/I:L/A:M",
                )
            ],
            paths={""},
        ),
        ProductInfo("vendor1", "product1", "3.2.1.0"): CVEData(
            cves=[
                CVE(
                    "CVE-1234-1234",
                    "unknown",
                    score=7.5,
                    cvss_version=2,
                    cvss_vector="C:H/I:L/A:M",
                )
            ],
            paths={""},
        ),
    }

    MOCK_ALL_CVE_DATA = {
        ProductInfo("vendor0", "product0", "1.0"): CVEData(
            cves=[
                CVE(
                    "UNKNOWN",
                    "UNKNOWN",
                    score=0,
                    cvss_version=0,
                    cvss_vector="C:H",
                ),
                CVE(
                    "CVE-9999-0001",
                    "MEDIUM",
                    score=4.2,
                    cvss_version=2,
                    cvss_vector="C:H",
                ),
                CVE(
                    "CVE-9999-0002",
                    "MEDIUM",
                    score=4.2,
                    cvss_version=2,
                    cvss_vector="C:H",
                ),
                CVE(
                    "CVE-9999-0003",
                    "MEDIUM",
                    score=4.2,
                    cvss_version=2,
                    cvss_vector="C:H",
                ),
                CVE(
                    "CVE-9999-0004",
                    "MEDIUM",
                    score=4.2,
                    cvss_version=2,
                    cvss_vector="C:H",
                ),
                CVE(
                    "CVE-9999-0005",
                    "MEDIUM",
                    score=4.2,
                    cvss_version=2,
                    cvss_vector="C:H",
                ),
                CVE(
                    "CVE-9999-0006",
                    "MEDIUM",
                    score=4.2,
                    cvss_version=2,
                    cvss_vector="C:H",
                ),
                CVE(
                    "CVE-9999-0007",
                    "MEDIUM",
                    score=4.2,
                    cvss_version=2,
                    cvss_vector="C:H",
                ),
                CVE(
                    "CVE-9999-0008",
                    "MEDIUM",
                    score=4.2,
                    cvss_version=2,
                    cvss_vector="C:H",
                ),
                CVE(
                    "CVE-9999-9999",
                    "LOW",
                    score=1.2,
                    cvss_version=2,
                    cvss_vector="CVSS2.0/C:H",
                ),
            ],
            paths={""},
        ),
    }

    MOCK_ALL_CVE_VERSION_INFO = {
        "UNKNOWN": VersionInfo("", "", "", ""),
        "CVE-9999-0001": VersionInfo("0.9.0", "", "1.2.0", ""),
        "CVE-9999-0002": VersionInfo("0.9.0", "", "", "1.2.0"),
        "CVE-9999-0003": VersionInfo("", "0.9.0", "1.2.0", ""),
        "CVE-9999-0004": VersionInfo("", "0.9.0", "", "1.2.0"),
        "CVE-9999-0005": VersionInfo("0.9.0", "", "", ""),
        "CVE-9999-0006": VersionInfo("", "0.9.0", "", ""),
        "CVE-9999-0007": VersionInfo("", "", "1.2.0", ""),
        "CVE-9999-0008": VersionInfo("", "", "", "1.2.0"),
    }

    FORMATTED_OUTPUT = [
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "1.0",
            "cve_number": "CVE-1234-1234",
            "severity": "MEDIUM",
            "score": "4.2",
            "cvss_version": "2",
            "cvss_vector": "C:H",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "1.0",
            "cve_number": "CVE-1234-1234",
            "severity": "LOW",
            "score": "1.2",
            "cvss_version": "2",
            "cvss_vector": "CVSS2.0/C:H",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "2.8.6",
            "cve_number": "CVE-1234-1234",
            "severity": "LOW",
            "score": "2.5",
            "cvss_version": "3",
            "cvss_vector": "CVSS3.0/C:H/I:L/A:M",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
        {
            "vendor": "vendor1",
            "product": "product1",
            "version": "3.2.1.0",
            "cve_number": "CVE-1234-1234",
            "severity": "HIGH",
            "score": "7.5",
            "cvss_version": "2",
            "cvss_vector": "C:H/I:L/A:M",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
    ]

    FORMATTED_OUTPUT_AFFECTED_VERSIONS = [
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "1.0",
            "cve_number": "UNKNOWN",
            "severity": "UNKNOWN",
            "score": "0",
            "cvss_version": "0",
            "affected_versions": "-",
            "cvss_vector": "C:H",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "1.0",
            "cve_number": "CVE-9999-0001",
            "severity": "MEDIUM",
            "score": "4.2",
            "cvss_version": "2",
            "affected_versions": "[0.9.0 - 1.2.0]",
            "cvss_vector": "C:H",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "1.0",
            "cve_number": "CVE-9999-0002",
            "severity": "MEDIUM",
            "score": "4.2",
            "cvss_version": "2",
            "affected_versions": "[0.9.0 - 1.2.0)",
            "cvss_vector": "C:H",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "1.0",
            "cve_number": "CVE-9999-0003",
            "severity": "MEDIUM",
            "score": "4.2",
            "cvss_version": "2",
            "affected_versions": "(0.9.0 - 1.2.0]",
            "cvss_vector": "C:H",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "1.0",
            "cve_number": "CVE-9999-0004",
            "severity": "MEDIUM",
            "score": "4.2",
            "cvss_version": "2",
            "affected_versions": "(0.9.0 - 1.2.0)",
            "cvss_vector": "C:H",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "1.0",
            "cve_number": "CVE-9999-0005",
            "severity": "MEDIUM",
            "score": "4.2",
            "cvss_version": "2",
            "affected_versions": ">= 0.9.0",
            "cvss_vector": "C:H",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "1.0",
            "cve_number": "CVE-9999-0006",
            "severity": "MEDIUM",
            "score": "4.2",
            "cvss_version": "2",
            "affected_versions": "> 0.9.0",
            "cvss_vector": "C:H",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "1.0",
            "cve_number": "CVE-9999-0007",
            "severity": "MEDIUM",
            "score": "4.2",
            "cvss_version": "2",
            "affected_versions": "<= 1.2.0",
            "cvss_vector": "C:H",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "1.0",
            "cve_number": "CVE-9999-0008",
            "severity": "MEDIUM",
            "score": "4.2",
            "cvss_version": "2",
            "affected_versions": "< 1.2.0",
            "cvss_vector": "C:H",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
        {
            "vendor": "vendor0",
            "product": "product0",
            "version": "1.0",
            "cve_number": "CVE-9999-9999",
            "severity": "LOW",
            "score": "1.2",
            "cvss_version": "2",
            "affected_versions": "-",
            "cvss_vector": "CVSS2.0/C:H",
            "paths": "",
            "remarks": "NewFound",
            "comments": "",
        },
    ]

    PDF_OUTPUT = (
        "3. List of Identified Vulnerabilities"
        "The following vulnerabilities are reported against the identified versions of the"
        "libraries."
        "Vendor"
        "Product"
        "Version"
        "CVE Number"
        "Severity"
        "vendor0"
        "product0"
        "1.0"
        "CVE-1234-1234"
        "MEDIUM"
        "vendor0"
        "product0"
        "1.0"
        "CVE-1234-1234"
        "LOW"
        "vendor0"
        "product0"
        "2.8.6"
        "CVE-1234-1234"
        "LOW"
        "vendor1"
        "product1"
        "3.2.1.0"
        "CVE-1234-1234"
        "unknown"
        "Page 2"
        ""
    )

    VEX_FORMATTED_OUTPUT = [
        {
            "bomFormat": "CycloneDX",
            "specVersion": "1.4",
            "version": 1,
            "vulnerabilities": [
                {
                    "id": "CVE-1234-1234",
                    "source": {
                        "name": "NVD",
                        "url": "https://nvd.nist.gov/vuln/detail/CVE-1234-1234",
                    },
                    "ratings": [
                        {
                            "source": {
                                "name": "NVD",
                                "url": "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=CVE-1234-1234&vector=C:H&version=2.0",
                            },
                            "score": "4.2",
                            "severity": "MEDIUM",
                            "method": "CVSSv2",
                            "vector": "C:H",
                        }
                    ],
                    "cwes": [],
                    "description": "",
                    "recommendation": "",
                    "advisories": [],
                    "created": "NOT_KNOWN",
                    "published": "NOT_KNOWN",
                    "updated": "NOT_KNOWN",
                    "analysis": {
                        "state": "under_review",
                        "response": "Outstanding",
                        "justification": "",
                        "detail": "",
                    },
                    "affects": [{"ref": "urn:cdx:NOTKNOWN/1#product0-1.0"}],
                },
                {
                    "id": "CVE-1234-1234",
                    "source": {
                        "name": "NVD",
                        "url": "https://nvd.nist.gov/vuln/detail/CVE-1234-1234",
                    },
                    "ratings": [
                        {
                            "source": {
                                "name": "NVD",
                                "url": "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=CVE-1234-1234&vector=CVSS2.0/C:H&version=2.0",
                            },
                            "score": "1.2",
                            "severity": "LOW",
                            "method": "CVSSv2",
                            "vector": "CVSS2.0/C:H",
                        }
                    ],
                    "cwes": [],
                    "description": "",
                    "recommendation": "",
                    "advisories": [],
                    "created": "NOT_KNOWN",
                    "published": "NOT_KNOWN",
                    "updated": "NOT_KNOWN",
                    "analysis": {
                        "state": "under_review",
                        "response": "Outstanding",
                        "justification": "",
                        "detail": "",
                    },
                    "affects": [{"ref": "urn:cdx:NOTKNOWN/1#product0-1.0"}],
                },
                {
                    "id": "CVE-1234-1234",
                    "source": {
                        "name": "NVD",
                        "url": "https://nvd.nist.gov/vuln/detail/CVE-1234-1234",
                    },
                    "ratings": [
                        {
                            "source": {
                                "name": "NVD",
                                "url": "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=CVE-1234-1234&vector=CVSS3.0/C:H/I:L/A:M&version=3.1",
                            },
                            "score": "2.5",
                            "severity": "LOW",
                            "method": "CVSSv3",
                            "vector": "CVSS3.0/C:H/I:L/A:M",
                        }
                    ],
                    "cwes": [],
                    "description": "",
                    "recommendation": "",
                    "advisories": [],
                    "created": "NOT_KNOWN",
                    "published": "NOT_KNOWN",
                    "updated": "NOT_KNOWN",
                    "analysis": {
                        "state": "under_review",
                        "response": "Outstanding",
                        "justification": "",
                        "detail": "",
                    },
                    "affects": [{"ref": "urn:cdx:NOTKNOWN/1#product0-2.8.6"}],
                },
                {
                    "id": "CVE-1234-1234",
                    "source": {
                        "name": "NVD",
                        "url": "https://nvd.nist.gov/vuln/detail/CVE-1234-1234",
                    },
                    "ratings": [
                        {
                            "source": {
                                "name": "NVD",
                                "url": "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=CVE-1234-1234&vector=C:H/I:L/A:M&version=2.0",
                            },
                            "score": "7.5",
                            "severity": "HIGH",
                            "method": "CVSSv2",
                            "vector": "C:H/I:L/A:M",
                        }
                    ],
                    "cwes": [],
                    "description": "",
                    "recommendation": "",
                    "advisories": [],
                    "created": "NOT_KNOWN",
                    "published": "NOT_KNOWN",
                    "updated": "NOT_KNOWN",
                    "analysis": {
                        "state": "under_review",
                        "response": "Outstanding",
                        "justification": "",
                        "detail": "",
                    },
                    "affects": [{"ref": "urn:cdx:NOTKNOWN/1#product1-3.2.1.0"}],
                },
            ],
        }
    ]

    def setUp(self) -> None:
        self.output_engine = OutputEngine(
            all_cve_data=self.MOCK_OUTPUT,
            scanned_dir="",
            filename="",
            themes_dir="",
            time_of_last_update=datetime.today(),
            tag="",
        )
        self.mock_file = tempfile.NamedTemporaryFile("w+", encoding="utf-8")

    def tearDown(self) -> None:
        self.mock_file.close()

    def test_formatted_output(self):
        """Test reformatting products"""
        self.assertEqual(format_output(self.MOCK_OUTPUT), self.FORMATTED_OUTPUT)

    def test_output_json(self):
        """Test formatting output as JSON"""
        output_json(self.MOCK_OUTPUT, self.mock_file)
        self.mock_file.seek(0)  # reset file position
        self.assertEqual(json.load(self.mock_file), self.FORMATTED_OUTPUT)

    def test_output_csv(self):
        """Test formatting output as CSV"""
        output_csv(self.MOCK_OUTPUT, self.mock_file)
        self.mock_file.seek(0)  # reset file position
        reader = csv.DictReader(self.mock_file)
        expected_value = [dict(x) for x in reader]
        self.assertEqual(expected_value, self.FORMATTED_OUTPUT)

    def test_output_vex(self):
        """Test creating VEX formatted file"""
        self.output_engine.generate_vex(self.MOCK_OUTPUT, "test.vex")
        self.assertEqual(json.load(open("test.vex")), self.VEX_FORMATTED_OUTPUT[0])
        os.remove("test.vex")

    @unittest.skipUnless(
        importlib.util.find_spec("reportlab") is not None
        and importlib.util.find_spec("pdftotext") is not None,
        "Skipping PDF tests. Please install reportlab and pdftotext to run these tests.",
    )
    def test_output_pdf(self):
        """Test formatting output as PDF"""
        import pdftotext

        output_pdf(self.MOCK_PDF_OUTPUT, False, 1, "cve_test.pdf", False)
        with open("cve_test.pdf", "rb") as f:
            pdf = pdftotext.PDF(f)
            # Only interested in section 3 of the report which contains table of CVEs. This is on the second page
            page = pdf[1]
            # Find start of section 3 header
            section2_start = page.find("3. List of Identified Vulnerabilities")
        self.assertEqual(
            page[section2_start:]
            .replace(" ", "")
            .replace("\r", "")
            .replace("\n", "")
            .strip(),
            self.PDF_OUTPUT.replace(" ", ""),
        )

    def test_output_console(self):
        """Test Formatting Output as console"""

        console = Console(file=self.mock_file)
        output_console(
            self.MOCK_OUTPUT,
            self.MOCK_ALL_CVE_VERSION_INFO,
            time_of_last_update=datetime.today(),
            affected_versions=0,
            console=console,
        )

        expected_output = "│ vendor0 │ product0 │ 1.0     │ CVE-1234-1234 │ MEDIUM   │ 4.2 (v2)             │\n│ vendor0 │ product0 │ 1.0     │ CVE-1234-1234 │ LOW      │ 1.2 (v2)             │\n│ vendor0 │ product0 │ 2.8.6   │ CVE-1234-1234 │ LOW      │ 2.5 (v3)             │\n│ vendor1 │ product1 │ 3.2.1.0 │ CVE-1234-1234 │ HIGH     │ 7.5 (v2)             │\n└─────────┴──────────┴─────────┴───────────────┴──────────┴──────────────────────┘\n"
        self.mock_file.seek(0)  # reset file position
        result = self.mock_file.read()
        self.assertIn(expected_output, result)

    def test_output_console_affected_versions(self):
        """Test Formatting Output as console with affected-versions"""

        console = Console(file=self.mock_file)
        output_console(
            self.MOCK_ALL_CVE_DATA,
            self.MOCK_ALL_CVE_VERSION_INFO,
            time_of_last_update=datetime.today(),
            affected_versions=1,
            console=console,
        )

        expected_output = "│ vendor0 │ product0 │ 1.0     │ UNKNOWN       │ UNKNOWN  │ 0 (v0)               │ -                 │\n│ vendor0 │ product0 │ 1.0     │ CVE-9999-0001 │ MEDIUM   │ 4.2 (v2)             │ [0.9.0 - 1.2.0]   │\n│ vendor0 │ product0 │ 1.0     │ CVE-9999-0002 │ MEDIUM   │ 4.2 (v2)             │ [0.9.0 - 1.2.0)   │\n│ vendor0 │ product0 │ 1.0     │ CVE-9999-0003 │ MEDIUM   │ 4.2 (v2)             │ (0.9.0 - 1.2.0]   │\n│ vendor0 │ product0 │ 1.0     │ CVE-9999-0004 │ MEDIUM   │ 4.2 (v2)             │ (0.9.0 - 1.2.0)   │\n│ vendor0 │ product0 │ 1.0     │ CVE-9999-0005 │ MEDIUM   │ 4.2 (v2)             │ >= 0.9.0          │\n│ vendor0 │ product0 │ 1.0     │ CVE-9999-0006 │ MEDIUM   │ 4.2 (v2)             │ > 0.9.0           │\n│ vendor0 │ product0 │ 1.0     │ CVE-9999-0007 │ MEDIUM   │ 4.2 (v2)             │ <= 1.2.0          │\n│ vendor0 │ product0 │ 1.0     │ CVE-9999-0008 │ MEDIUM   │ 4.2 (v2)             │ < 1.2.0           │\n│ vendor0 │ product0 │ 1.0     │ CVE-9999-9999 │ LOW      │ 1.2 (v2)             │ -                 │\n└─────────┴──────────┴─────────┴───────────────┴──────────┴──────────────────────┴───────────────────┘\n"
        self.mock_file.seek(0)  # reset file position
        result = self.mock_file.read()
        self.assertIn(expected_output, result)

    def test_output_html(self):
        """Test formatting output as HTML"""

        logger = logging.getLogger()

        output_html(
            self.MOCK_OUTPUT,
            "",
            "",
            "",
            3,
            3,
            0,
            None,
            logger,
            self.mock_file,
        )

        # sample report
        with open(
            os.path.join(os.path.dirname(__file__), "sample_report", "html_report.html")
        ) as report:
            expected_output = report.read()
            report.close()

        # Changing some dynamic data in html reports
        # 1. date
        # 2. div IDs (dynamic ones)

        self.mock_file.seek(0)
        result = self.mock_file.read()

        result_lines = result.splitlines()

        # date
        # date is replaced by {{date}} in the result
        # The respective change is also made in the sample report

        result = result.replace(datetime.now().strftime("%d %b %Y"), r"{{date}}")

        # div IDs
        # div IDs, in the dashboard, which were created dynamically is being replaced by their respective line numbers
        # The respective changes are also made in the sample report

        # This signature is always a line or two before the dynamic data
        signature = '<div class="card-body">'

        for line_number in range(len(result_lines)):
            if signature in result_lines[line_number]:

                if "<!--" in result_lines[line_number + 1]:
                    # Sometimes there is a comment line after the signature line, hence it's skipped
                    line_number += 2
                else:
                    line_number += 1

                line = result_lines[line_number]  # line with the dynamic data
                index_div_id = line.index('<div id="') + len('<div id="')
                div_id = ""  # div ID of the dynamic part

                char = line[index_div_id]
                while char != '"':
                    div_id += char
                    index_div_id += 1
                    char = line[index_div_id]

                result = result.replace(
                    div_id, f"{line_number+1}"
                )  # div IDs replaced with line numbers

        assert expected_output, result

    def test_output_file(self):
        """Test file generation logic in output_file"""
        logger = logging.getLogger()

        with self.assertLogs(logger, logging.INFO) as cm:
            self.output_engine.output_file(output_type="json")

        contains_filename = False
        contains_msg = False

        filename = self.output_engine.filename

        if os.path.isfile(filename):
            contains_filename = True

        if "Output stored at" in cm.output[0]:
            contains_msg = True

        # reset everything back
        os.remove(filename)
        self.output_engine.filename = ""

        self.assertEqual(contains_filename, True)
        self.assertEqual(contains_msg, True)

    def test_output_file_filename_already_exists(self):
        """Tests output_file when filename already exist"""

        # update the filename in output_engine
        self.output_engine.filename = "testfile.csv"

        # create a file with the same name as output_engine.filename
        with open("testfile.csv", "w") as f:
            f.write("testing")

        logger = logging.getLogger()

        # setup the context manager
        with self.assertLogs(logger, logging.INFO) as cm:
            self.output_engine.output_file(output_type="csv")

        # logs to check in cm
        msg_generate_filename = (
            "Generating a new filename with Default Naming Convention"
        )
        msg_failed_to_write = "Failed to write at 'testfile.csv'. File already exists"

        # flags for logs
        contains_fail2write = False
        contains_gen_file = False

        # check if the logger contains msg
        for log in cm.output:
            if msg_generate_filename in log:
                contains_gen_file = True
            elif msg_failed_to_write in log:
                contains_fail2write = True

        # remove the generated files and reset updated variables
        os.remove("testfile.csv")
        os.remove(self.output_engine.filename)
        self.output_engine.filename = ""

        # assert
        self.assertEqual(contains_gen_file, True)
        self.assertEqual(contains_fail2write, True)

    def test_output_file_incorrect_filename(self):
        """Tests filenames that are incorrect or are not accessible"""

        # update the filename in output_engine
        self.output_engine.filename = "/not/a/good_filename"

        logger = logging.getLogger()

        # setup the context manager
        with self.assertLogs(logger, logging.INFO) as cm:
            self.output_engine.output_file(output_type="csv")

        # log to check
        msg_switch_back = "Switching Back to Default Naming Convention"

        # flags
        contains_sb = False

        for log in cm.output:
            if msg_switch_back in log:
                contains_sb = True

        # remove the generated files and reset updated variables
        os.remove(self.output_engine.filename)
        self.output_engine.filename = ""

        # assert
        self.assertEqual(contains_sb, True)

    def test_csv_macros(self):
        """tests that output engine will not output leading -, =, + or @
        characters, used in spreadsheet macros"""

        bad_input = {
            ProductInfo("=vendor0", "+product0", "@1.0"): CVEData(
                cves=[
                    CVE(
                        "-CVE-1234-1234",
                        "@-=+MEDIUM",
                        score=4.2,
                        cvss_version=2,
                        cvss_vector="C:H",
                    ),
                ],
                paths={"@@@@bad"},
            ),
        }
        expected_output = [
            {
                "vendor": "vendor0",
                "product": "product0",
                "version": "1.0",
                "cve_number": "CVE-1234-1234",
                "severity": "MEDIUM",
                "score": "4.2",
                "cvss_version": "2",
                "cvss_vector": "C:H",
                "paths": "bad",
                "remarks": "NewFound",
                "comments": "",
            },
        ]

        output_csv(bad_input, self.mock_file)
        self.mock_file.seek(0)  # reset file position
        reader = csv.DictReader(self.mock_file)
        actual_output = [dict(x) for x in reader]
        self.assertEqual(actual_output, expected_output)
