"""Tests for security audit checkers (SEC012-SEC017)."""

import pytest

from stablestack.checkers.security.node_env_security_gate import NodeEnvSecurityGateChecker
from stablestack.checkers.security.missing_security_headers import MissingSecurityHeadersChecker
from stablestack.checkers.security.error_detail_leakage import ErrorDetailLeakageChecker
from stablestack.checkers.security.fail_open_security import FailOpenSecurityChecker
from stablestack.checkers.security.overly_broad_query import OverlyBroadQueryChecker
from stablestack.checkers.security.regex_html_sanitizer import RegexHtmlSanitizerChecker


# ─── SEC012: node-env-security-gate ────────────────────────────────────────


class TestNodeEnvSecurityGateChecker:
    """Tests for SEC012: node-env-security-gate."""

    @pytest.fixture
    def checker(self):
        return NodeEnvSecurityGateChecker()

    def test_detects_production_and_secret_guard(self, checker, temp_ts_file):
        code = '''
if (process.env.NODE_ENV === "production" && webhookSecret) {
    verifySignature(req);
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1
        assert all(f.rule_id == "SEC012" for f in findings)

    def test_detects_not_production_return(self, checker, temp_ts_file):
        code = '''
if (process.env.NODE_ENV !== "production") return;
verifyCronSecret(req);
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_ternary_env_check(self, checker, temp_ts_file):
        code = '''
const result = process.env.NODE_ENV === "production" ? verifySignature(req) : null;
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_skip_bypass_flag(self, checker, temp_ts_file):
        code = '''
const skipAuth = process.env.NODE_ENV !== "production";
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_isprod_guard(self, checker, temp_ts_file):
        code = '''
if (!isProd) {
    return next();
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_production_cron_guard(self, checker, temp_ts_file):
        code = '''
if (process.env.NODE_ENV === "production") {
    const cronSecret = process.env.CRON_SECRET;
    verifySignature(cronSecret, req);
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_allows_non_security_env_check(self, checker, temp_ts_file):
        """NODE_ENV check for logging or feature flags should not trigger."""
        code = '''
if (process.env.NODE_ENV === "production") {
    enableAnalytics();
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_env_check_without_security_keywords(self, checker, temp_ts_file):
        code = '''
const isDebug = process.env.NODE_ENV !== "production";
console.log("debug mode:", isDebug);
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_skips_test_files(self, checker, tmp_path):
        code = '''
if (process.env.NODE_ENV !== "production") return;
'''
        path = tmp_path / "webhook.test.ts"
        path.write_text(code)
        assert checker.should_skip_file(path) is True

    def test_skips_comment_lines(self, checker, temp_ts_file):
        code = '''
// if (process.env.NODE_ENV === "production" && webhookSecret) {
const x = 1;
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_correct_metadata(self, checker):
        assert checker.rule_id == "SEC012"
        assert checker.rule_name == "node-env-security-gate"

    def test_detects_not_production_with_continue(self, checker, temp_ts_file):
        code = '''
for (const req of requests) {
    if (NODE_ENV !== "production") continue;
    validateToken(req);
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_not_production_with_next(self, checker, temp_ts_file):
        code = '''
if (process.env.NODE_ENV !== "production") {
    return next();
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_isproduction_guard(self, checker, temp_ts_file):
        code = '''
if (!isProduction) {
    return next();
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_production_token_guard(self, checker, temp_ts_file):
        code = '''
if (process.env.NODE_ENV === "production" && token) {
    verify(token);
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1


# ─── SEC013: missing-security-headers ──────────────────────────────────────


class TestMissingSecurityHeadersChecker:
    """Tests for SEC013: missing-security-headers."""

    @pytest.fixture
    def checker(self):
        return MissingSecurityHeadersChecker()

    def test_detects_next_config_no_headers(self, checker, tmp_path):
        code = '''
module.exports = {
    reactStrictMode: true,
    images: { domains: ["example.com"] },
};
'''
        path = tmp_path / "next.config.js"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1
        assert any("missing security headers" in f.message.lower() for f in findings)

    def test_detects_next_config_headers_no_hsts(self, checker, tmp_path):
        code = '''
module.exports = {
    async headers() {
        return [{
            source: "/(.*)",
            headers: [
                { key: "X-Frame-Options", value: "DENY" },
            ],
        }];
    },
};
'''
        path = tmp_path / "next.config.js"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert any("HSTS" in f.message or "Strict-Transport" in f.message for f in findings)

    def test_detects_next_config_headers_no_csp(self, checker, tmp_path):
        code = '''
module.exports = {
    async headers() {
        return [{
            source: "/(.*)",
            headers: [
                { key: "Strict-Transport-Security", value: "max-age=63072000" },
            ],
        }];
    },
};
'''
        path = tmp_path / "next.config.js"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert any("CSP" in f.message or "Content-Security-Policy" in f.message for f in findings)

    def test_allows_next_config_with_both_headers(self, checker, tmp_path):
        code = '''
module.exports = {
    async headers() {
        return [{
            source: "/(.*)",
            headers: [
                { key: "Strict-Transport-Security", value: "max-age=63072000" },
                { key: "Content-Security-Policy", value: "default-src 'self'" },
            ],
        }];
    },
};
'''
        path = tmp_path / "next.config.js"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_detects_express_without_helmet(self, checker, tmp_path):
        code = '''
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('hello'));
'''
        path = tmp_path / "server.js"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1
        assert any("helmet" in f.message.lower() for f in findings)

    def test_allows_express_with_helmet(self, checker, tmp_path):
        code = '''
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet());
'''
        path = tmp_path / "server.js"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_ignores_non_config_files(self, checker, temp_ts_file):
        """Regular .ts files should not trigger."""
        code = '''
const x = 1;
export default x;
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_correct_metadata(self, checker):
        assert checker.rule_id == "SEC013"
        assert checker.rule_name == "missing-security-headers"

    def test_detects_next_config_mjs(self, checker, tmp_path):
        code = '''
export default {
    reactStrictMode: true,
};
'''
        path = tmp_path / "next.config.mjs"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_skips_test_files(self, checker, tmp_path):
        path = tmp_path / "next.config.test.js"
        path.write_text("test")
        assert checker.should_skip_file(path) is True


# ─── SEC014: error-detail-leakage ─────────────────────────────────────────


class TestErrorDetailLeakageChecker:
    """Tests for SEC014: error-detail-leakage."""

    @pytest.fixture
    def checker(self):
        return ErrorDetailLeakageChecker()

    def test_detects_res_json_error_message(self, checker, temp_ts_file):
        code = '''
try {
    await doSomething();
} catch (err) {
    res.status(500).json({ error: err.message });
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1
        assert all(f.rule_id == "SEC014" for f in findings)

    def test_detects_res_json_error_stack(self, checker, temp_ts_file):
        code = '''
catch (err) {
    res.json({ error: err.stack });
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_return_error_message(self, checker, temp_ts_file):
        code = '''
catch (e) {
    return { error: e.message };
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_string_error_in_response(self, checker, temp_ts_file):
        code = '''
catch (e) {
    res.json({ error: String(e) });
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_next_response_error(self, checker, temp_ts_file):
        code = '''
catch (error) {
    return NextResponse.json({ error: error.message }, { status: 500 });
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_python_str_e(self, checker, temp_py_file):
        code = '''
except Exception as e:
    return jsonify({"error": str(e)}), 500
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_python_traceback(self, checker, temp_py_file):
        code = '''
except Exception as e:
    return jsonify({"error": traceback.format_exc()}), 500
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_health_endpoint_db_info(self, checker, temp_ts_file):
        code = '''
app.get("/health", async (req, res) => {
    const pool = await db.pool;
    res.json({ status: "ok", database: pool.totalCount, host: db.host });
});
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_allows_generic_error_response(self, checker, temp_ts_file):
        """Generic error messages should not trigger."""
        code = '''
catch (err) {
    console.error(err);
    res.status(500).json({ error: "Internal server error" });
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_logging_error_details(self, checker, temp_ts_file):
        """Logging error details is fine — only API responses matter."""
        code = '''
catch (err) {
    console.error("Failed:", err.message, err.stack);
    res.status(500).json({ error: "Something went wrong" });
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_skips_test_files(self, checker, tmp_path):
        path = tmp_path / "api.test.ts"
        path.write_text("test")
        assert checker.should_skip_file(path) is True

    def test_correct_metadata(self, checker):
        assert checker.rule_id == "SEC014"
        assert checker.rule_name == "error-detail-leakage"

    def test_allows_python_generic_error(self, checker, temp_py_file):
        code = '''
except Exception as e:
    logger.error("Request failed", exc_info=True)
    return jsonify({"error": "Internal server error"}), 500
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_detects_return_string_error(self, checker, temp_ts_file):
        code = '''
catch (e) {
    return { message: String(e) };
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1


# ─── SEC015: fail-open-security ───────────────────────────────────────────


class TestFailOpenSecurityChecker:
    """Tests for SEC015: fail-open-security."""

    @pytest.fixture
    def checker(self):
        return FailOpenSecurityChecker()

    def test_detects_missing_key_returns_data(self, checker, temp_ts_file):
        code = '''
if (!ENCRYPTION_KEY) return data;
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1
        assert all(f.rule_id == "SEC015" for f in findings)

    def test_detects_missing_secret_returns_true(self, checker, temp_ts_file):
        code = '''
if (!webhookSecret) return true;
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_catch_return_true_in_verify(self, checker, temp_ts_file):
        code = '''
function verifyToken(token) {
    try {
        return jwt.verify(token, SECRET);
    } catch (err) {
        return true;
    }
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_encrypt_fallback_to_plaintext(self, checker, temp_ts_file):
        code = '''
try {
    return encrypt(data);
} catch (err) {
    return data;
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_python_except_return_true(self, checker, temp_py_file):
        code = '''
def verify_token(token):
    try:
        return jwt.decode(token, SECRET)
    except Exception:
        return True
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_python_missing_key_returns_data(self, checker, temp_py_file):
        code = '''
if not encryption_key:
    return data
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_allows_missing_key_throws(self, checker, temp_ts_file):
        """Throwing on missing key is the correct pattern."""
        code = '''
if (!ENCRYPTION_KEY) {
    throw new Error("ENCRYPTION_KEY is required");
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_catch_return_false(self, checker, temp_ts_file):
        """Returning false (deny) on error is fail-closed."""
        code = '''
function checkAuth(token) {
    try {
        return jwt.verify(token, SECRET);
    } catch (err) {
        return false;
    }
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_python_missing_key_raises(self, checker, temp_py_file):
        code = '''
if not encryption_key:
    raise ValueError("ENCRYPTION_KEY is required")
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_skips_test_files(self, checker, tmp_path):
        path = tmp_path / "auth.test.ts"
        path.write_text("test")
        assert checker.should_skip_file(path) is True

    def test_correct_metadata(self, checker):
        assert checker.rule_id == "SEC015"
        assert checker.rule_name == "fail-open-security"

    def test_detects_missing_signing_key(self, checker, temp_ts_file):
        code = '''
if (!signing_key) return payload;
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_missing_hmac_key(self, checker, temp_ts_file):
        code = '''
if (!HMAC_KEY) return true;
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_missing_auth_secret(self, checker, temp_ts_file):
        code = '''
if (!AUTH_SECRET) return true;
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_allows_normal_null_check(self, checker, temp_ts_file):
        """Non-security null checks should not trigger."""
        code = '''
if (!username) return "Anonymous";
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0


# ─── SEC016: overly-broad-query ───────────────────────────────────────────


class TestOverlyBroadQueryChecker:
    """Tests for SEC016: overly-broad-query."""

    @pytest.fixture
    def checker(self):
        return OverlyBroadQueryChecker()

    def test_detects_public_procedure_no_select(self, checker, temp_ts_file):
        code = '''
export const userRouter = createTRPCRouter({
    getUser: publicProcedure
        .input(z.object({ id: z.string() }))
        .query(async ({ ctx, input }) => {
            return ctx.prisma.user.findUnique({
                where: { id: input.id },
            });
        }),
});
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1
        assert all(f.rule_id == "SEC016" for f in findings)

    def test_detects_include_sensitive_model(self, checker, temp_ts_file):
        code = '''
const post = await prisma.post.findUnique({
    where: { id },
    include: { user: true },
});
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_public_findmany_no_args(self, checker, temp_ts_file):
        code = '''
export const listUsers = publicProcedure
    .query(async ({ ctx }) => {
        return ctx.prisma.user.findMany();
    });
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_api_route_no_select(self, checker, temp_ts_file):
        code = '''
export async function GET(req) {
    const user = await prisma.user.findUnique({
        where: { id: req.params.id },
    });
    return Response.json(user);
}
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_allows_query_with_select(self, checker, temp_ts_file):
        code = '''
export const userRouter = createTRPCRouter({
    getUser: publicProcedure
        .input(z.object({ id: z.string() }))
        .query(async ({ ctx, input }) => {
            return ctx.prisma.user.findUnique({
                where: { id: input.id },
                select: { id: true, name: true, email: true },
            });
        }),
});
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_private_procedure(self, checker, temp_ts_file):
        """Private/protected procedures are internal — less risky."""
        code = '''
export const userRouter = createTRPCRouter({
    getUser: protectedProcedure
        .input(z.object({ id: z.string() }))
        .query(async ({ ctx, input }) => {
            return ctx.prisma.user.findUnique({
                where: { id: input.id },
            });
        }),
});
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_include_non_sensitive(self, checker, temp_ts_file):
        """Including non-sensitive models should not trigger."""
        code = '''
const post = await prisma.post.findUnique({
    where: { id },
    include: { comments: true, tags: true },
});
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_skips_test_files(self, checker, tmp_path):
        path = tmp_path / "users.test.ts"
        path.write_text("test")
        assert checker.should_skip_file(path) is True

    def test_correct_metadata(self, checker):
        assert checker.rule_id == "SEC016"
        assert checker.rule_name == "overly-broad-query"

    def test_detects_include_account(self, checker, temp_ts_file):
        code = '''
const data = await db.organization.findMany({
    include: { account: true },
});
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_include_session(self, checker, temp_ts_file):
        code = '''
const user = await prisma.user.findFirst({
    include: { session: true },
});
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_express_route_no_select(self, checker, temp_ts_file):
        code = '''
router.get("/users/:id", async (req, res) => {
    const user = await prisma.user.findUnique({
        where: { id: req.params.id },
    });
    res.json(user);
});
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1


# ─── SEC017: regex-html-sanitizer ─────────────────────────────────────────


class TestRegexHtmlSanitizerChecker:
    """Tests for SEC017: regex-html-sanitizer."""

    @pytest.fixture
    def checker(self):
        return RegexHtmlSanitizerChecker()

    def test_detects_replace_script_tag(self, checker, temp_ts_file):
        code = '''
const clean = input.replace(/<script[^>]*>/gi, "");
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1
        assert all(f.rule_id == "SEC017" for f in findings)

    def test_detects_replace_all_tags(self, checker, temp_ts_file):
        code = '''
const clean = input.replace(/<[^>]*>/g, "");
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_custom_sanitize_function(self, checker, temp_ts_file):
        code = (
            'function sanitizeHtml(input) {\n'
            '    return input.replace(/<script>/gi, "").replace(/<\\/script>/gi, "");\n'
            '}\n'
        )
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_python_re_sub(self, checker, temp_py_file):
        code = '''
clean = re.sub(r'<[^>]*>', '', user_input)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_python_sanitize_function(self, checker, temp_py_file):
        code = '''
def sanitize_html(text):
    return re.sub(r'<script.*?</script>', '', text)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_replace_event_handlers(self, checker, temp_ts_file):
        code = 'const safe = input.replace(/on\\w+=/gi, "");\n'
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_replace_iframe(self, checker, temp_ts_file):
        code = '''
const clean = html.replace(/<iframe[^>]*>/gi, "");
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_allows_dompurify(self, checker, temp_ts_file):
        """DOMPurify usage should not trigger."""
        code = '''
import DOMPurify from "dompurify";
const clean = DOMPurify.sanitize(input);
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_sanitize_html_lib(self, checker, temp_ts_file):
        """sanitize-html library usage should not trigger."""
        code = '''
import sanitizeHtml from "sanitize-html";
const clean = sanitizeHtml(input);
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_bleach(self, checker, temp_py_file):
        """bleach library usage should not trigger."""
        code = '''
import bleach
clean = bleach.clean(user_input)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_non_html_regex(self, checker, temp_ts_file):
        """Regex that doesn't target HTML should not trigger."""
        code = '''
const clean = input.replace(/\\s+/g, " ");
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_skips_test_files(self, checker, tmp_path):
        path = tmp_path / "sanitizer.test.ts"
        path.write_text("test")
        assert checker.should_skip_file(path) is True

    def test_correct_metadata(self, checker):
        assert checker.rule_id == "SEC017"
        assert checker.rule_name == "regex-html-sanitizer"

    def test_detects_striphtml_function(self, checker, temp_ts_file):
        code = '''
const stripHtml = (input) => input.replace(/<[^>]+>/g, "");
'''
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_replace_style_tag(self, checker, temp_ts_file):
        code = 'const clean = html.replace(/<style[^>]*>.*<\\/style>/gis, "");\n'
        path = temp_ts_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1

    def test_detects_python_strip_tags(self, checker, temp_py_file):
        code = '''
def strip_tags(html):
    return re.sub(r'<[^>]+>', '', html)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) >= 1
