"""SAP patch-level matching — run: python tests/test_sap_patch_level.py

SAP enumerates one CPE per patch level instead of stating a range, so
"affected" is set membership. The dangerous failure is silent: if the level
can't be read, the base release (7.70) matches every CVE ever filed against
it. Unknown level must therefore report NOTHING.
"""
import os
import sys

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from app.services.app_cve_scanner_service import _sap_affected, _sap_patch_level


def demo():
    # VERBATIM syscollector rows from the tester's SAP host — the level lives
    # in the version field for Business Client and in the NAME for SAP GUI,
    # whose version field states a compilation instead.
    assert _sap_patch_level("SAP Business Client 8.00", "8.00 PL26") == 26
    assert _sap_patch_level("SAP GUI for Windows 8.00 64bit  (Patch 17)",
                            "8.00 Compilation 1") == 17
    assert _sap_patch_level("Adobe LiveCycle Designer 11.0 for SAP solutions",
                            "11.0 Patch Level 37") == 37
    # "Compilation" contains the letters p-l; it must not read as a level.
    assert _sap_patch_level("SAP GUI for Windows 8.00", "8.00 Compilation 1") is None
    # Other spellings from wazuh/wazuh#30334.
    assert _sap_patch_level("SAP GUI for Windows 7.70", "7.70 PL 12") == 12
    assert _sap_patch_level("SAP Business Client 6.0", "6.0 Patch Level 16") == 16
    # No level stated anywhere → unknown, never a guess.
    assert _sap_patch_level("SAP Business Client", "6.0") is None
    assert _sap_patch_level("", "") is None

    # Real criteria strings from CVE-2023-32113 / CVE-2021-38150.
    pl4 = {"criteria": "cpe:2.3:a:sap:gui_for_windows:7.70:patch_level4:*:*:*:*:*:*"}
    pl17 = {"criteria": "cpe:2.3:a:sap:gui_for_windows:7.70:patch_level17:*:*:*:*:*:*"}
    v8pl1 = {"criteria": "cpe:2.3:a:sap:gui_for_windows:8.0:patch_level1:*:*:*:*:*:*"}
    pl10 = {"criteria": "cpe:2.3:a:sap:business_client:6.0:patch_level10:*:*:*:*:*:*"}
    base = {"criteria": "cpe:2.3:a:sap:business_client:6.0:-:*:*:*:*:*:*"}

    assert _sap_affected(pl4, "7.70", 4)
    assert not _sap_affected(pl4, "7.70", 5)   # patched past it
    assert not _sap_affected(pl4, "7.70", 3)   # a DIFFERENT level, not "older"
    assert _sap_affected(pl10, "6.0", 10)
    assert _sap_affected(base, "6.0", 0)       # ':-' is the unpatched base
    assert not _sap_affected(base, "6.0", 1)

    # The release has to match too. Every release carries the same level
    # NUMBERS, so level-only matching drops a 7.70 CVE onto an 8.00 host —
    # exactly the tester's box (SAP GUI 8.00, Patch 17).
    assert not _sap_affected(pl17, "8.00", 17)
    assert _sap_affected(pl17, "7.70", 17)
    # Wazuh says "8.00", NVD says "8.0" — same release, compared numerically.
    assert _sap_affected(v8pl1, "8.00", 1)
    assert not _sap_affected(v8pl1, "7.70", 1)

    # The whole point: unknown level matches nothing at all.
    for m in (pl4, pl10, base, v8pl1):
        assert not _sap_affected(m, "7.70", None)

    # A SAP CPE with a WILDCARD update field states no patch level, so it says
    # nothing about patch state — CVE-2021-38175 lists
    # `analysis_for_microsoft_office:2.8:*` with no range at all, and treating
    # that as a hit marks every 2.8 build affected forever. Never match it.
    wild = {"criteria": "cpe:2.3:a:sap:analysis_for_microsoft_office:2.8:*:*:*:*:*:*:*"}
    for pl in (None, 0, 3, 99):
        assert not _sap_affected(wild, "2.8", pl)

    print("sap patch level OK")


if __name__ == "__main__":
    demo()
