#exonware/xwquery/tests/0.core/test_core_all_grammars.py
"""
Core grammar tests - Comprehensive validation of all 30 grammar files.
This test file implements the 80/20 rule: 20% of tests covering 80% of grammar functionality.
Tests basic parsing capability for all supported query language grammars.
Company: eXonware.com
Author: eXonware Backend Team
Email: connect@exonware.com
Version: 0.0.1
Generation Date: 29-Oct-2024
Test Categories:
- Grammar loading and initialization
- Basic query parsing for each format
- AST generation validation
- Error handling for invalid queries
Priority: Security #1, Usability #2, Maintainability #3, Performance #4, Extensibility #5
"""

import pytest
from pathlib import Path
import sys
# Add src to path for imports
src_path = Path(__file__).parent.parent.parent / "src"
sys.path.insert(0, str(src_path))
from exonware.xwsyntax import XWSyntaxEngine
# Query grammars are authored in xwsyntax and consumed by xwquery.
GRAMMAR_DIR = Path(__file__).parent.parent.parent.parent / "xwsyntax" / "src" / "exonware" / "xwsyntax" / "grammars"
# Test data: (format_name, simple_query, expected_to_parse)
GRAMMAR_TEST_DATA = [
    # ✅ PASSING GRAMMARS (12)
    pytest.param("sql", "SELECT * FROM users WHERE age > 18", True, id="sql_basic"),
    pytest.param("xpath", "//users/user[@age>18]/name", True, id="xpath_basic"),
    pytest.param("cypher", "MATCH (n:Person) WHERE n.age > 18 RETURN n", True, id="cypher_basic"),
    pytest.param("xwqueryscript", "SELECT * FROM users WHERE age > 30", True, id="xwqueryscript_basic"),
    pytest.param("mongodb", 'db.users.find({age: {$gt: 18}})', True, id="mongodb_basic"),
    pytest.param("elasticsearch", '{"match": {"status": "active"}}', True, id="elasticsearch_basic"),
    pytest.param("eql", "process where process_name == 'cmd.exe'", True, id="eql_basic"),
    pytest.param("gql", "MATCH (n:Person) RETURN n.name", True, id="gql_basic"),
    pytest.param("graphql", "query { user(id: 1) { name email } }", True, id="graphql_basic"),
    pytest.param("json_query", "$.users[?(@.age > 18)].name", True, id="json_query_basic"),
    pytest.param("xquery", "for $user in //users/user where $user/age > 18 return $user/name", True, id="xquery_basic"),
    pytest.param("hiveql", "SELECT * FROM users WHERE age > 18", True, id="hiveql_basic"),
    pytest.param("promql", "rate(http_requests_total[5m])", True, id="promql_basic"),
    pytest.param("jsoniq", "for $user in $users where $user.age > 18 return $user.name", True, id="jsoniq_basic"),
    pytest.param("logql", '{job="varlogs"} |= "error"', True, id="logql_basic"),
    # ⚠️ PARSING FAILURES (1) - Load OK but parse fails  
    pytest.param("xml_query", "for $user in //users/user return $user/name", False, id="xml_query_basic"),
    # 🔴 LOADING FAILURES (13) - Cannot load grammar due to conflicts
    pytest.param("datalog", "ancestor(X, Y) :- parent(X, Y).", False, id="datalog_basic"),
    pytest.param("partiql", "SELECT * FROM users WHERE age > 18", False, id="partiql_basic"),
    pytest.param("n1ql", "SELECT * FROM users WHERE age > 18", False, id="n1ql_basic"),
    pytest.param("kql", "users | where age > 18 | project name", False, id="kql_basic"),
    pytest.param("hql", "FROM users SELECT name WHERE age > 18", False, id="hql_basic"),
    pytest.param("jmespath", "users[?age > `18`].name", False, id="jmespath_basic"),
    pytest.param("gremlin", "g.V().has('age', gt(18)).values('name')", False, id="gremlin_basic"),
    pytest.param("sparql", "SELECT ?name WHERE { ?person foaf:name ?name . }", False, id="sparql_basic"),
    pytest.param("flux", 'from(bucket: "telegraf") |> range(start: -1h)', False, id="flux_basic"),
    pytest.param("jq", '.users[] | select(.age > 18) | .name', False, id="jq_basic"),
    pytest.param("pig", "users = LOAD 'users.csv';", False, id="pig_basic"),
    pytest.param("linq", "from u in users where u.age > 18 select u", False, id="linq_basic"),
    pytest.param("cql", "SELECT * FROM users WHERE age > 18", False, id="cql_basic"),
    # Original JSON grammar
    pytest.param("json", '{"name": "Alice", "age": 30}', True, id="json_basic"),
]
@pytest.mark.xwquery_core

class TestAllGrammars:
    """
    Core grammar validation tests.
    Tests all 30 grammar files for basic parsing capability.
    Follows 80/20 rule: quick validation that grammars can parse simple queries.
    Priority Alignment:
    - Security (#1): Valid grammars prevent injection attacks
    - Usability (#2): Working parsers enable intuitive query API
    - Maintainability (#3): Grammar-based approach is clean and maintainable
    - Performance (#4): Fast parsing for common queries
    - Extensibility (#5): Easy to add new grammar files
    """
    @pytest.mark.parametrize("format_name,query,expected_to_parse", GRAMMAR_TEST_DATA)

    def test_grammar_loads_successfully(self, format_name, query, expected_to_parse):
        """
        Test that bidirectional grammar files exist and are valid.
        xwquery uses bidirectional grammars:
        - <format>.in.grammar - Parse input query
        - <format>.out.grammar - Generate output query
        This test validates:
        1. Both files exist
        2. Both files are non-empty
        3. Files have valid grammar content
        Args:
            format_name: Name of the query language format
            query: Not used in this test, but part of parametrization
            expected_to_parse: Not used in this test, but part of parametrization
        """
        # Check for bidirectional grammar files
        in_grammar_path = GRAMMAR_DIR / f"{format_name}.grammar.in.lark"
        out_grammar_path = GRAMMAR_DIR / f"{format_name}.grammar.out.lark"
        # Verify both grammar files exist
        assert in_grammar_path.exists(), f"Input grammar file not found: {in_grammar_path}"
        assert out_grammar_path.exists(), f"Output grammar file not found: {out_grammar_path}"
        # Verify files are not empty
        assert in_grammar_path.stat().st_size > 0, f"Input grammar file is empty: {in_grammar_path}"
        assert out_grammar_path.stat().st_size > 0, f"Output grammar file is empty: {out_grammar_path}"
        # Verify files contain grammar content (basic validation)
        in_content = in_grammar_path.read_text(encoding='utf-8')
        out_content = out_grammar_path.read_text(encoding='utf-8')
        # Grammar files should have rules/patterns
        assert len(in_content) > 10, f"Input grammar too short: {len(in_content)} chars"
        assert len(out_content) > 10, f"Output grammar too short: {len(out_content)} chars"
    @pytest.mark.parametrize("format_name,query,expected_to_parse", GRAMMAR_TEST_DATA)

    def test_grammar_parses_basic_query(self, format_name, query, expected_to_parse):
        """
        Test that grammar can parse a basic representative query.
        This is the core validation - can the grammar parse a simple query?
        Args:
            format_name: Name of the query language format
            query: Simple query to test parsing
            expected_to_parse: Whether we expect parsing to succeed
        """
        # Verify input grammar file exists
        in_grammar_path = GRAMMAR_DIR / f"{format_name}.grammar.in.lark"
        assert in_grammar_path.exists(), f"Input grammar not found: {in_grammar_path}"
        # Read grammar content to verify it's valid
        grammar_content = in_grammar_path.read_text(encoding='utf-8')
        assert len(grammar_content) > 0, f"Grammar file is empty: {in_grammar_path}"
        # TODO: Actual parsing test when xwsyntax supports .in.grammar extension
        # For now, just validate file exists and has content
        # Future implementation:
        # engine = XWSyntaxEngine(grammar_dir=GRAMMAR_DIR)
        # grammar = engine.load_grammar(format_name, extension=".in.grammar")
        # ast = grammar.parse(query)
        # assert ast is not None
@pytest.mark.xwquery_core

class TestGrammarValidation:
    """
    Grammar validation tests for common patterns.
    NOTE: Skipped until xwsyntax supports bidirectional grammar files.
    xwquery uses .in.grammar and .out.grammar, xwsyntax expects .grammar
    Tests that grammars handle common query patterns correctly.
    """

    def test_sql_handles_multiple_conditions(self):
        """Test SQL grammar with multiple WHERE conditions."""
        engine = XWSyntaxEngine(grammar_dir=GRAMMAR_DIR)
        grammar = engine.load_grammar("sql")
        query = "SELECT name, age FROM users WHERE age > 18 AND status = 'active'"
        ast = grammar.parse(query)
        assert ast is not None
        assert len(ast.children) > 0

    def test_xpath_handles_predicates(self):
        """Test XPath grammar with multiple predicates."""
        engine = XWSyntaxEngine(grammar_dir=GRAMMAR_DIR)
        grammar = engine.load_grammar("xpath")
        query = "//book[@price>35][@category='cooking']/title"
        ast = grammar.parse(query)
        assert ast is not None
        assert len(ast.children) > 0

    def test_cypher_handles_relationships(self):
        """Test Cypher grammar with relationship patterns."""
        engine = XWSyntaxEngine(grammar_dir=GRAMMAR_DIR)
        grammar = engine.load_grammar("cypher")
        query = "MATCH (a:Person)-[r:KNOWS]->(b:Person) WHERE b.age > 18 RETURN a.name, b.name"
        ast = grammar.parse(query)
        assert ast is not None
        assert len(ast.children) > 0

    def test_mongodb_handles_nested_queries(self):
        """Test MongoDB grammar with nested query operators."""
        engine = XWSyntaxEngine(grammar_dir=GRAMMAR_DIR)
        grammar = engine.load_grammar("mongodb")
        query = 'db.users.find({age: 18})'
        ast = grammar.parse(query)
        assert ast is not None
        assert len(ast.children) > 0
@pytest.mark.xwquery_core
@pytest.mark.xwquery_security

class TestGrammarSecurity:
    """
    Security validation for grammars.
    NOTE: Skipped until xwsyntax supports bidirectional grammar files.
    Priority #1: Security - Ensure grammars don't expose vulnerabilities.
    """

    def test_sql_handles_injection_attempts(self):
        """
        Test that SQL grammar can parse (but not execute) injection attempts.
        Security Priority #1: Grammar should parse injection patterns without crashing.
        Actual injection prevention happens at execution layer, not parsing.
        """
        engine = XWSyntaxEngine(grammar_dir=GRAMMAR_DIR)
        grammar = engine.load_grammar("sql")
        # SQL injection pattern - grammar should parse it (execution layer will validate)
        query = "SELECT * FROM users WHERE name = 'admin' OR '1'='1'"
        # Grammar should parse this (it's syntactically valid SQL)
        ast = grammar.parse(query)
        assert ast is not None

    def test_xpath_handles_path_traversal_patterns(self):
        """
        Test that XPath grammar handles path traversal patterns.
        Security Priority #1: Grammar should not crash on malicious patterns.
        """
        engine = XWSyntaxEngine(grammar_dir=GRAMMAR_DIR)
        grammar = engine.load_grammar("xpath")
        # Path traversal pattern
        query = "//../../etc/passwd"
        try:
            ast = grammar.parse(query)
            # Grammar may or may not parse this - depends on grammar rules
            # The key is it shouldn't crash
            assert True
        except Exception:
            # If it doesn't parse, that's fine - grammar is restrictive
            assert True
@pytest.mark.xwquery_core
@pytest.mark.xwquery_performance

class TestGrammarPerformance:
    """
    Performance validation for grammars.
    NOTE: Skipped until xwsyntax supports bidirectional grammar files.
    Priority #4: Performance - Ensure grammars parse quickly.
    """

    def test_sql_parses_quickly(self):
        """Test that SQL grammar parses standard queries in < 100ms."""
        import time
        engine = XWSyntaxEngine(grammar_dir=GRAMMAR_DIR)
        grammar = engine.load_grammar("sql")
        query = "SELECT * FROM users WHERE age > 18"
        start = time.time()
        ast = grammar.parse(query)
        elapsed = time.time() - start
        assert ast is not None
        assert elapsed < 0.1, f"SQL parsing took {elapsed:.3f}s, expected < 0.1s"

    def test_complex_query_parses_in_reasonable_time(self):
        """Test that complex queries parse in < 500ms."""
        import time
        engine = XWSyntaxEngine(grammar_dir=GRAMMAR_DIR)
        grammar = engine.load_grammar("sql")
        # Complex query with multiple JOINs and conditions
        query = """
        SELECT u.name, o.order_id, o.total 
        FROM users u 
        JOIN orders o ON u.id = o.user_id 
        WHERE u.age > 18 AND o.total > 100
        """
        start = time.time()
        ast = grammar.parse(query)
        elapsed = time.time() - start
        assert ast is not None
        assert elapsed < 0.5, f"Complex query parsing took {elapsed:.3f}s, expected < 0.5s"
if __name__ == "__main__":
    # Run tests with verbose output
    pytest.main([__file__, "-v", "--tb=short", "-x"])
