from .base import ModuleTestBase


class TestRetireJS(ModuleTestBase):
    targets = ["http://127.0.0.1:8888"]
    modules_overrides = ["httpx", "excavate", "retirejs"]

    # HTML page with vulnerable JavaScript libraries
    vulnerable_html = """<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>retire.js test page</title>
</head>
<body>
  <h1>retire.js test page</h1>
  <p>This page includes JavaScript libraries for testing.</p>

  <!-- jQuery 3.4.1 -->
  <script src="/jquery-3.4.1.min.js"></script>

  <!-- Lodash 4.17.11 -->
  <script src="/lodash.min.js"></script>

  <!-- Handlebars 4.0.5 -->
  <script src="/handlebars.min.js"></script>

  <script>
    console.log('Libraries loaded');
  </script>
</body>
</html>"""

    # Sample jQuery 3.4.1 content
    jquery_content = """/*!
 * jQuery JavaScript Library v3.4.1
 * https://jquery.com/
 */
(function( global, factory ) {
    "use strict";
    factory( global );
})(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
    var jQuery = function( selector, context ) {
        return new jQuery.fn.init( selector, context );
    };
    jQuery.fn = jQuery.prototype = {};
    jQuery.fn.jquery = "3.4.1";
    if ( typeof noGlobal === "undefined" ) {
        window.jQuery = window.$ = jQuery;
    }
    return jQuery;
});"""

    # Sample Lodash 4.17.11 content
    lodash_content = """/**
 * @license
 * Lodash lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
 */
;(function(){
var i="4.17.11";
var Mn={VERSION:i};
if(typeof define=="function"&&define.amd)define(function(){return Mn});else if(typeof module=="object"&&module.exports)module.exports=Mn;else this._=Mn}());"""

    # Sample Handlebars 4.0.5 content
    handlebars_content = """/*!
 handlebars v4.0.5
*/
!function(a,b){"object"==typeof exports&&"object"==typeof module?module.exports=b():"function"==typeof define&&define.amd?define([],b):"object"==typeof exports?exports.Handlebars=b():a.Handlebars=b()}(this,function(){
var Handlebars={};
Handlebars.VERSION="4.0.5";
return Handlebars;
});"""

    async def setup_after_prep(self, module_test):
        expect_args = {"uri": "/"}
        respond_args = {"response_data": self.vulnerable_html}
        module_test.set_expect_requests(expect_args, respond_args)

        expect_args = {"uri": "/jquery-3.4.1.min.js"}
        respond_args = {"response_data": self.jquery_content}
        module_test.set_expect_requests(expect_args, respond_args)

        expect_args = {"uri": "/lodash.min.js"}
        respond_args = {"response_data": self.lodash_content}
        module_test.set_expect_requests(expect_args, respond_args)

        expect_args = {"uri": "/handlebars.min.js"}
        respond_args = {"response_data": self.handlebars_content}
        module_test.set_expect_requests(expect_args, respond_args)

    def check(self, module_test, events):
        # Check that excavate found the JavaScript URLs
        url_unverified_events = [e for e in events if e.type == "URL_UNVERIFIED"]
        js_url_events = [e for e in url_unverified_events if "extension-js" in e.tags]

        # Two out of the three URLs should be in the output
        # The third, non-vulnerable URL (lodash.min.js) is not output because it's a "special URL", and
        # nothing interesting has been discovered from it.
        vuln_urls = {"http://127.0.0.1:8888/handlebars.min.js", "http://127.0.0.1:8888/jquery-3.4.1.min.js"}
        assert {e.data for e in js_url_events} == vuln_urls, "Expected to find the vulnerable URLs in the output"

        # Check for FINDING events generated by retirejs
        finding_events = [e for e in events if e.type == "FINDING"]
        retirejs_findings = [
            e
            for e in finding_events
            if "vulnerable javascript library detected:" in e.data.get("description", "").lower()
        ]

        # We should have at least some findings from our vulnerable libraries
        assert len(retirejs_findings) > 0, (
            f"Expected retirejs to find vulnerabilities, but got {len(retirejs_findings)} findings"
        )

        # Check for specific expected vulnerability descriptions
        descriptions = [finding.data.get("description", "") for finding in retirejs_findings]
        all_descriptions = "\n".join(descriptions)

        # Look for specific vulnerabilities we expect to find
        expected_handlebars_vuln = "Vulnerable JavaScript library detected: handlebars v4.0.5 Severity: HIGH Summary: Regular Expression Denial of Service in Handlebars JavaScript URL: http://127.0.0.1:8888/handlebars.min.js CVE(s): CVE-2019-20922 Affected versions: [4.0.0 to 4.4.5)"
        expected_jquery_vuln = "Vulnerable JavaScript library detected: jquery v3.4.1 Severity: MEDIUM Summary: Regex in its jQuery.htmlPrefilter sometimes may introduce XSS JavaScript URL: http://127.0.0.1:8888/jquery-3.4.1.min.js CVE(s): CVE-2020-11022 Affected versions: [1.2.0 to 3.5.0)"

        # Verify at least one of the expected vulnerabilities is found
        handlebars_found = expected_handlebars_vuln in all_descriptions
        jquery_found = expected_jquery_vuln in all_descriptions

        assert handlebars_found and jquery_found, (
            f"Expected to find specific vulnerabilities but didn't find them. Found descriptions:\n{all_descriptions}"
        )

        # Basic validation of findings structure
        for finding in retirejs_findings:
            assert "description" in finding.data, "Finding should have description"
            assert "url" in finding.data, "Finding should have url"
            assert finding.parent.type == "URL_UNVERIFIED", "Parent should be URL_UNVERIFIED"


class TestRetireJSNoExcavate(ModuleTestBase):
    targets = ["http://127.0.0.1:8888"]
    modules_overrides = ["httpx", "retirejs"]
    force_start = True  # Allow scan to continue even if modules fail setup
    config_overrides = {
        "excavate": False,
    }

    def check(self, module_test, events):
        # When excavate is disabled, retirejs should fail setup but scan should still run
        retirejs_module = module_test.scan.modules.get("retirejs")

        if retirejs_module:
            # Check that the module exists but setup failed
            setup_status = getattr(retirejs_module, "_setup_status", None)
            if setup_status is not None:
                success, error_msg = setup_status
                assert success is False, "retirejs setup should have failed without excavate"
                expected_error = "retirejs will not function without excavate enabled"
                assert error_msg == expected_error, f"Expected error message '{expected_error}', but got '{error_msg}'"

        # No retirejs findings should be generated since setup failed
        retirejs_findings = [e for e in events if e.type == "FINDING" and getattr(e, "module", None) == "retirejs"]
        assert len(retirejs_findings) == 0, "retirejs should not generate findings when setup fails"
