@@ -1617,3 +1617,341 @@ describe('QuerySchema - Complex Queries', () => {
16171617 expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
16181618 } ) ;
16191619} ) ;
1620+
1621+ describe ( 'QuerySchema - Edge Cases and Null Handling' , ( ) => {
1622+ it ( 'should handle null values in filter expressions' , ( ) => {
1623+ const query : QueryAST = {
1624+ object : 'account' ,
1625+ fields : [ 'name' ] ,
1626+ filters : [ 'deleted_at' , 'is_null' , null ] ,
1627+ } ;
1628+
1629+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1630+ } ) ;
1631+
1632+ it ( 'should handle undefined for optional fields' , ( ) => {
1633+ const query : QueryAST = {
1634+ object : 'account' ,
1635+ fields : undefined ,
1636+ filters : undefined ,
1637+ sort : undefined ,
1638+ aggregations : undefined ,
1639+ joins : undefined ,
1640+ groupBy : undefined ,
1641+ having : undefined ,
1642+ windowFunctions : undefined ,
1643+ } ;
1644+
1645+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1646+ } ) ;
1647+
1648+ it ( 'should handle empty arrays' , ( ) => {
1649+ const query : QueryAST = {
1650+ object : 'account' ,
1651+ fields : [ ] ,
1652+ aggregations : [ ] ,
1653+ joins : [ ] ,
1654+ windowFunctions : [ ] ,
1655+ groupBy : [ ] ,
1656+ sort : [ ] ,
1657+ } ;
1658+
1659+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1660+ } ) ;
1661+
1662+ it ( 'should handle zero and negative values in pagination' , ( ) => {
1663+ const queries = [
1664+ { object : 'account' , top : 0 , skip : 0 } ,
1665+ { object : 'account' , top : 1 , skip : 0 } ,
1666+ { object : 'account' , top : 100 , skip : 1000 } ,
1667+ ] ;
1668+
1669+ queries . forEach ( query => {
1670+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1671+ } ) ;
1672+ } ) ;
1673+
1674+ it ( 'should handle complex nested null filters' , ( ) => {
1675+ const query : QueryAST = {
1676+ object : 'order' ,
1677+ fields : [ 'id' ] ,
1678+ filters : [
1679+ [ 'approved_at' , 'is_null' , null ] ,
1680+ 'and' ,
1681+ [ 'rejected_at' , 'is_null' , null ] ,
1682+ ] ,
1683+ } ;
1684+
1685+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1686+ } ) ;
1687+
1688+ it ( 'should handle optional alias in field nodes' , ( ) => {
1689+ const query : QueryAST = {
1690+ object : 'account' ,
1691+ fields : [
1692+ 'name' ,
1693+ { field : 'owner' , fields : [ 'name' , 'email' ] } ,
1694+ { field : 'manager' , fields : [ 'name' ] , alias : 'mgr' } ,
1695+ ] ,
1696+ } ;
1697+
1698+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1699+ } ) ;
1700+
1701+ it ( 'should handle aggregation without field for COUNT' , ( ) => {
1702+ const query : QueryAST = {
1703+ object : 'order' ,
1704+ aggregations : [
1705+ { function : 'count' , alias : 'total_count' } ,
1706+ ] ,
1707+ } ;
1708+
1709+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1710+ } ) ;
1711+
1712+ it ( 'should handle optional distinct flag in aggregation' , ( ) => {
1713+ const query : QueryAST = {
1714+ object : 'order' ,
1715+ aggregations : [
1716+ { function : 'count' , field : 'customer_id' , alias : 'unique_customers' , distinct : true } ,
1717+ { function : 'sum' , field : 'amount' , alias : 'total_amount' } , // distinct undefined
1718+ ] ,
1719+ groupBy : [ 'region' ] ,
1720+ } ;
1721+
1722+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1723+ } ) ;
1724+
1725+ it ( 'should handle optional properties in window functions' , ( ) => {
1726+ const query : QueryAST = {
1727+ object : 'sales' ,
1728+ fields : [ 'amount' ] ,
1729+ windowFunctions : [
1730+ {
1731+ function : 'row_number' ,
1732+ alias : 'row_num' ,
1733+ over : {
1734+ // partitionBy and orderBy are optional
1735+ } ,
1736+ } ,
1737+ {
1738+ function : 'sum' ,
1739+ field : 'amount' ,
1740+ alias : 'total' ,
1741+ over : {
1742+ partitionBy : [ 'region' ] ,
1743+ // orderBy is optional
1744+ } ,
1745+ } ,
1746+ ] ,
1747+ } ;
1748+
1749+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1750+ } ) ;
1751+
1752+ it ( 'should handle optional frame in window specification' , ( ) => {
1753+ const query : QueryAST = {
1754+ object : 'transactions' ,
1755+ fields : [ 'amount' ] ,
1756+ windowFunctions : [
1757+ {
1758+ function : 'sum' ,
1759+ field : 'amount' ,
1760+ alias : 'running_total' ,
1761+ over : {
1762+ orderBy : [ { field : 'date' , order : 'asc' } ] ,
1763+ frame : {
1764+ type : 'rows' ,
1765+ start : 'UNBOUNDED PRECEDING' ,
1766+ end : 'CURRENT ROW' ,
1767+ } ,
1768+ } ,
1769+ } ,
1770+ ] ,
1771+ } ;
1772+
1773+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1774+ } ) ;
1775+
1776+ it ( 'should handle optional subquery in joins' , ( ) => {
1777+ const query : QueryAST = {
1778+ object : 'customer' ,
1779+ joins : [
1780+ {
1781+ type : 'left' ,
1782+ object : 'order' ,
1783+ on : [ 'customer.id' , '=' , 'order.customer_id' ] ,
1784+ } ,
1785+ {
1786+ type : 'inner' ,
1787+ object : 'filtered_orders' ,
1788+ alias : 'fo' ,
1789+ on : [ 'customer.id' , '=' , 'fo.customer_id' ] ,
1790+ subquery : {
1791+ object : 'order' ,
1792+ fields : [ 'customer_id' , 'amount' ] ,
1793+ filters : [ 'amount' , '>' , 1000 ] ,
1794+ } ,
1795+ } ,
1796+ ] ,
1797+ } ;
1798+
1799+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1800+ } ) ;
1801+
1802+ it ( 'should reject invalid object type' , ( ) => {
1803+ expect ( ( ) => QuerySchema . parse ( {
1804+ object : 123 , // Should be string
1805+ fields : [ 'name' ] ,
1806+ } ) ) . toThrow ( ) ;
1807+ } ) ;
1808+
1809+ it ( 'should reject invalid field types in array' , ( ) => {
1810+ expect ( ( ) => QuerySchema . parse ( {
1811+ object : 'account' ,
1812+ fields : [ 123 , 456 ] , // Should be strings or objects
1813+ } ) ) . toThrow ( ) ;
1814+ } ) ;
1815+
1816+ it ( 'should reject invalid aggregation function' , ( ) => {
1817+ expect ( ( ) => QuerySchema . parse ( {
1818+ object : 'order' ,
1819+ aggregations : [
1820+ { function : 'invalid_func' , alias : 'test' } ,
1821+ ] ,
1822+ } ) ) . toThrow ( ) ;
1823+ } ) ;
1824+
1825+ it ( 'should reject invalid join type' , ( ) => {
1826+ expect ( ( ) => QuerySchema . parse ( {
1827+ object : 'order' ,
1828+ joins : [
1829+ {
1830+ type : 'invalid_join' ,
1831+ object : 'customer' ,
1832+ on : [ 'order.customer_id' , '=' , 'customer.id' ] ,
1833+ } ,
1834+ ] ,
1835+ } ) ) . toThrow ( ) ;
1836+ } ) ;
1837+
1838+ it ( 'should reject invalid window function' , ( ) => {
1839+ expect ( ( ) => QuerySchema . parse ( {
1840+ object : 'sales' ,
1841+ windowFunctions : [
1842+ {
1843+ function : 'invalid_window_func' ,
1844+ alias : 'test' ,
1845+ over : { } ,
1846+ } ,
1847+ ] ,
1848+ } ) ) . toThrow ( ) ;
1849+ } ) ;
1850+
1851+ it ( 'should reject invalid sort order' , ( ) => {
1852+ expect ( ( ) => QuerySchema . parse ( {
1853+ object : 'account' ,
1854+ sort : [ { field : 'name' , order : 'invalid' } ] ,
1855+ } ) ) . toThrow ( ) ;
1856+ } ) ;
1857+ } ) ;
1858+
1859+ describe ( 'QuerySchema - Type Coercion Edge Cases' , ( ) => {
1860+ it ( 'should handle various data types in filter values' , ( ) => {
1861+ const queries = [
1862+ { object : 'account' , filters : [ 'age' , '>' , 18 ] } , // number
1863+ { object : 'account' , filters : [ 'active' , '=' , true ] } , // boolean
1864+ { object : 'account' , filters : [ 'name' , '=' , 'John' ] } , // string
1865+ { object : 'account' , filters : [ 'tags' , 'in' , [ 'a' , 'b' , 'c' ] ] } , // array
1866+ { object : 'account' , filters : [ 'value' , 'between' , [ 0 , 100 ] ] } , // array
1867+ ] ;
1868+
1869+ queries . forEach ( query => {
1870+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1871+ } ) ;
1872+ } ) ;
1873+
1874+ it ( 'should handle boolean flags' , ( ) => {
1875+ const query : QueryAST = {
1876+ object : 'account' ,
1877+ fields : [ 'name' ] ,
1878+ distinct : true ,
1879+ } ;
1880+
1881+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1882+
1883+ const query2 : QueryAST = {
1884+ object : 'account' ,
1885+ fields : [ 'name' ] ,
1886+ distinct : false ,
1887+ } ;
1888+
1889+ expect ( ( ) => QuerySchema . parse ( query2 ) ) . not . toThrow ( ) ;
1890+ } ) ;
1891+
1892+ it ( 'should handle default sort order' , ( ) => {
1893+ const query : QueryAST = {
1894+ object : 'account' ,
1895+ sort : [ { field : 'name' } ] , // order defaults to 'asc'
1896+ } ;
1897+
1898+ const result = QuerySchema . parse ( query ) ;
1899+ expect ( result . sort ?. [ 0 ] . order ) . toBe ( 'asc' ) ;
1900+ } ) ;
1901+
1902+ it ( 'should handle mixed field types' , ( ) => {
1903+ const query : QueryAST = {
1904+ object : 'account' ,
1905+ fields : [
1906+ 'simple_field' ,
1907+ {
1908+ field : 'related_field' ,
1909+ fields : [ 'nested_field' ] ,
1910+ alias : 'rel' ,
1911+ } ,
1912+ ] ,
1913+ } ;
1914+
1915+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1916+ } ) ;
1917+
1918+ it ( 'should handle deeply nested filters' , ( ) => {
1919+ const query : QueryAST = {
1920+ object : 'order' ,
1921+ filters : [
1922+ [
1923+ [ 'status' , '=' , 'active' ] ,
1924+ 'and' ,
1925+ [ 'amount' , '>' , 100 ] ,
1926+ ] ,
1927+ 'or' ,
1928+ [
1929+ [ 'priority' , '=' , 'high' ] ,
1930+ 'and' ,
1931+ [ 'urgent' , '=' , true ] ,
1932+ ] ,
1933+ ] ,
1934+ } ;
1935+
1936+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1937+ } ) ;
1938+
1939+ it ( 'should handle complex having clauses' , ( ) => {
1940+ const query : QueryAST = {
1941+ object : 'order' ,
1942+ fields : [ 'customer_id' ] ,
1943+ aggregations : [
1944+ { function : 'count' , alias : 'order_count' } ,
1945+ { function : 'sum' , field : 'amount' , alias : 'total' } ,
1946+ ] ,
1947+ groupBy : [ 'customer_id' ] ,
1948+ having : [
1949+ [ 'order_count' , '>' , 5 ] ,
1950+ 'and' ,
1951+ [ 'total' , '>' , 1000 ] ,
1952+ ] ,
1953+ } ;
1954+
1955+ expect ( ( ) => QuerySchema . parse ( query ) ) . not . toThrow ( ) ;
1956+ } ) ;
1957+ } ) ;
0 commit comments