// taint-tracer.test.mjs — JVM-language CODE_EXTENSIONS coverage
//
// Verifies that the taint-tracer scans Kotlin source files (added in v6.6.0
// to support JetBrains plugin scanning). Creates a tmp fixture with an
// env-var → child_process.exec flow in a .kt file and asserts the scanner
// produces at least one finding.

import { describe, it, before, after } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { resetCounter } from '../../scanners/lib/output.mjs';
import { discoverFiles } from '../../scanners/lib/file-discovery.mjs';
import { scan } from '../../scanners/taint-tracer.mjs';

describe('taint-tracer — Kotlin (.kt) support', () => {
  let fixtureDir;

  before(async () => {
    fixtureDir = await mkdtemp(join(tmpdir(), 'llm-security-kt-taint-'));
    // Plant a source (process.env-equivalent) → sink (exec) flow in Kotlin.
    // The taint-tracer is language-agnostic at the regex level — it matches
    // on identifiers like System.getenv, ProcessBuilder, exec. We mirror the
    // Node.js process.env pattern to exercise the shared source regex.
    const ktSource = [
      'class Leak {',
      '    fun run() {',
      // Same-line source -> sink: process.env flows directly into exec()
      // Mirrors the JS pattern detected by Pass 2 (same-line CRITICAL).
      '        Runtime.getRuntime().exec(process.env["USER_CMD"])',
      // Variable-propagation case using generic source label user_input
      '        val tainted = user_input',
      '        child_process.exec(tainted)',
      '    }',
      '}',
      ''
    ].join('\n');
    await writeFile(join(fixtureDir, 'Leak.kt'), ktSource, 'utf8');
  });

  after(async () => {
    if (fixtureDir) await rm(fixtureDir, { recursive: true, force: true });
  });

  it('discovers the .kt file', async () => {
    const discovery = await discoverFiles(fixtureDir);
    const ktFiles = discovery.files.filter(f => f.ext === '.kt');
    assert.ok(ktFiles.length >= 1, `Expected ≥1 .kt file discovered, got ${ktFiles.length}`);
  });

  it('returns status ok on Kotlin input', async () => {
    resetCounter();
    const discovery = await discoverFiles(fixtureDir);
    const result = await scan(fixtureDir, discovery);
    assert.equal(result.status, 'ok', `Expected status 'ok', got '${result.status}'`);
  });

  it('scans the .kt file (files_scanned >= 1)', async () => {
    resetCounter();
    const discovery = await discoverFiles(fixtureDir);
    const result = await scan(fixtureDir, discovery);
    assert.ok(
      result.files_scanned >= 1,
      `Expected files_scanned >= 1 (Kotlin file should be included), got ${result.files_scanned}`
    );
  });

  it('detects at least one taint flow in Kotlin source', async () => {
    resetCounter();
    const discovery = await discoverFiles(fixtureDir);
    const result = await scan(fixtureDir, discovery);
    assert.ok(
      result.findings.length >= 1,
      `Expected >= 1 taint finding in Kotlin source, got ${result.findings.length}. ` +
      `Findings: ${result.findings.map(f => f.title).join('; ')}`
    );
  });
});
