#!/usr/bin/env python

"""
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
See the file 'LICENSE' for copying permission

Assorted pure helpers: stats, set ops, value predicates, value/counter stacks,
enum helpers, DBMS alias/version checks, column prioritization.
"""

import os
import sys
import unittest

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _testutils import bootstrap, set_dbms, reset_dbms
bootstrap()

from lib.core import common as C
from lib.core.settings import NULL
from lib.core.enums import DBMS


class TestStats(unittest.TestCase):
    def test_average(self):
        self.assertEqual(C.average([1, 2, 3, 4]), 2.5)
        self.assertEqual(C.average([5]), 5)

    def test_stdev(self):
        self.assertAlmostEqual(C.stdev([1, 2, 3, 4]), 1.2909944, places=5)
        self.assertIsNone(C.stdev([5]))   # undefined for single sample


class TestSetOps(unittest.TestCase):
    def test_intersect(self):
        self.assertEqual(C.intersect([1, 2, 3], [2, 3, 4]), [2, 3])
        self.assertEqual(C.intersect([1], [2]), [])

    def test_filterPairValues(self):
        self.assertEqual(C.filterPairValues([[1, 2], [3], [4, 5], []]), [[1, 2], [4, 5]])


class TestValuePredicates(unittest.TestCase):
    def test_isNoneValue(self):
        for v in (None, [], "", {}):
            self.assertTrue(C.isNoneValue(v), msg="isNoneValue(%r)" % (v,))

    def test_isNullValue(self):
        self.assertTrue(C.isNullValue(NULL))
        # discriminating negatives: an always-True impl must fail these
        self.assertFalse(C.isNullValue(None))
        self.assertFalse(C.isNullValue(""))
        self.assertFalse(C.isNullValue("x"))

    def test_isNumPosStrValue(self):
        for v, exp in [("5", True), ("0", False), ("-1", False), ("a", False), ("12", True)]:
            self.assertEqual(bool(C.isNumPosStrValue(v)), exp, msg="isNumPosStrValue(%r)" % v)

    def test_firstNotNone(self):
        self.assertEqual(C.firstNotNone(None, None, 5, 6), 5)
        self.assertIsNone(C.firstNotNone(None, None))


class TestValueStackAndCounters(unittest.TestCase):
    def test_push_pop(self):
        C.pushValue(7)
        C.pushValue("x")
        self.assertEqual(C.popValue(), "x")
        self.assertEqual(C.popValue(), 7)

    def test_counters(self):
        C.resetCounter("UNITTEST")
        C.incrementCounter("UNITTEST")
        C.incrementCounter("UNITTEST")
        self.assertEqual(C.getCounter("UNITTEST"), 2)


class TestEnumAndDbmsHelpers(unittest.TestCase):
    def test_aliasToDbmsEnum(self):
        self.assertEqual(C.aliasToDbmsEnum("mysql"), DBMS.MYSQL)
        self.assertEqual(C.aliasToDbmsEnum("postgres"), DBMS.PGSQL)

    def test_getPublicTypeMembers(self):
        members = list(C.getPublicTypeMembers(DBMS, onlyValues=True))
        # goal is correct EXTRACTION, not a magic count: real members present, no private/dunder leak
        self.assertIn(DBMS.MYSQL, members)
        self.assertIn(DBMS.MSSQL, members)
        self.assertIn(DBMS.ORACLE, members)
        self.assertFalse(any(str(m).startswith("_") for m in members), msg="leaked private member: %r" % members)

    def test_isDBMSVersionAtLeast(self):
        set_dbms(DBMS.MYSQL)
        C.Backend.setVersion("5.7")
        self.assertTrue(C.isDBMSVersionAtLeast("5.0"))
        self.assertFalse(C.isDBMSVersionAtLeast("8.0"))


class TestColumnPriority(unittest.TestCase):
    def test_prioritySortColumns(self):
        # assert the FULL ordering, not just the first element (id-like floats to front,
        # rest keep their relative order)
        self.assertEqual(C.prioritySortColumns(["data", "id", "name"]), ["id", "data", "name"])

    def test_prioritySortColumns_empty(self):
        self.assertEqual(C.prioritySortColumns([]), [])


class TestArrayHelpers(unittest.TestCase):
    def test_unArrayizeValue(self):
        self.assertEqual(C.unArrayizeValue([5]), 5)        # single-element list -> the element
        self.assertEqual(C.unArrayizeValue([1, 2]), 1)     # multi -> first
        self.assertEqual(C.unArrayizeValue(7), 7)          # scalar -> unchanged
        self.assertIsNone(C.unArrayizeValue([]))           # empty -> None

    def test_arrayizeValue(self):
        self.assertEqual(C.arrayizeValue(5), [5])          # scalar -> wrapped
        self.assertEqual(C.arrayizeValue([5]), [5])        # list -> unchanged

    def test_roundtrip_scalar(self):
        for v in (0, 1, "x", "value"):
            self.assertEqual(C.unArrayizeValue(C.arrayizeValue(v)), v)


if __name__ == "__main__":
    unittest.main(verbosity=2)


def tearDownModule():
    reset_dbms()   # clear any DBMS forced via set_dbms() so it can't leak into later test modules
