jBPHOznjzmyGkNusHahyFQ1eyqPQZnHqQSv4n4VQVlTovwKGD1Mi89Bica' \
                       'KZWVsstFd35MLSUZoqXwcxLNJQBI699TENzYWDs4mya+hBadYOFjFp9YMlaKuVAw5rYwagb93gA1HYxtefKoeaeyRjf' \
                       'GYTkeZlK6TxofE2bFxHWCibn6oeG+zfatiOmgsn4foHOPEqehu1VJrEXWkOU5EKyhtPkQO9OSjZAdpIJDsOAVcOYccR' \
                       'bSJnvExjZzphuJGigzf8jzBz6gxG3u5HAs4JRrhGYGmthkK9xFaYpu41hWbkwVzbyTsdHb59AMtsyGVTahnRZ9hPJ13' \
                       'cjfQ4V89djSKcm71Ho/A9KDXs8/9v7cAAAAABJRU5ErkJggg=='
DEFAULT_DBOT_IMAGE_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAEIAAABlCAYAAAD5/TVmAAAfJElEQVR4nNWceZxUxbX4v1X39jLdPQszI8uwCI' \
                            'iAiEuICyIxqHHFLT41MeLPZ4zRaDT5PWPM+vxEf3n56UtiTJTkPde4xaiJcU/QoA9QEYEgAUTWYWT2raf3vkvV' \
                            '+6N7hu6e7p4ehLzf73w+d+7tulV1zzl16tSpc06N4H8Ifrnq5LmNoWm/agwcvlBpu6s9uvFnu7bv/eWdl693Ku' \
                            'xCAupA4WMeqI5GA/euXFBvmOLJOv+hR0+qOR5XpZu6Ex/9+/hpgQ7gqQq7OWBMgH2MkKNsp9g3IsXu5dpgmPIE' \
                            'BHObB1awN7IGhYurLAlcdfQpDU9vXNE7rE2J36XKRg2DjNgf7qoR7qXaZJDWoNHYOjn0Umshx4zzFvZf7rlcGQ' \
                            'wfpJLlMuc6mJA3qgMRuTaVklu1zhRoIG0J1dsnf/dfz7YrRia8sM9SUGpwhpUPcuSAzrcyCAHI75+1oqerR17V' \
                            '2Wmu7+w06Wg3+7q6zR81f5h8IgevQig2JQ4YiAPZWQ6U0+gSUNIQnH/pRQu0I1cieKx2XvM1j393nSrTvpROGg' \
                            '0OuWV5/ZgMV0aVdDwSMmUQEJx73ue9SjPFSciZ2feNyc2zjv7hvQt7phzu6/jq4n9XWutCvEaL20hMy50e8mBJ' \
                            'RB48818/lEJ46l//w+7T2ppjF0opFpmGUaM1Xsu2vYYhldfjsTTaCtR5wtNm1q499jMT/hKq8fzZVXrvOUfeer' \
                            'CnbtGpMaKGLVM3t5zDZjep//zT/55umsaN776x97J3l7VO8nk8eDyZxUophe24SCnxmAYAWoNl20yaEeSCq2aF' \
                            'DUO8nHK496G7X1z/zMNvFVtOC3HMfTfSkj5U76BJxPKtPw0ZprwBuAUY+9rvd7Dtb/1U+X0AaK3RQM4fhNiHjv' \
                            'QqvnzbMRimBIi5micsl9u/uvj/drXs7Drg+BqD3yUjHTr7rIvULVc+2JZQTRXLNt09QxriceA6IAjQNDVIf6SH' \
                            'gf40ibhD2rJJW1b2nrks28bVDrWTExy32EtNvR9T+BBCeKXgOI9k8TmXL9y47e979rbu6RFl8ClGR265yMUZDv' \
                            'CqUVcf4oX37jgWwbPADABXp+lPtRBO7SHtxEglINLhIdZjkopKXFsgDfAEFKEGh5pxDlW1LtLQCGHgM0KM8U2l' \
                            'zj8ZQ3gB+lKuuObt19a9eMc3nzhgukOwb/7kzjdyymSJ58G6CmDM4Yfy7Ms3H+sxxAvAFKVd+tPN9CS2Y6tEzu' \
                            'eKDeIIGGrwGAHGVs2mzj8ZgRGxXX3Vv97y+xffeXlNLm4j0VBYd+h+oCRCLt/287FC8rqEuZYboy22gZjTndF+' \
                            'BwwE1d5xNAWPxWMEejSctWjGv6w/ED3LgudC7TqS+S0BXlhzh6mFuFfC3Jjdxa6BlcTsroqYMDo2aaJWB7sHVh' \
                            'C3exq15qFn199VP6ouioMcyWgZyZBRk2dNpHpM9UWm0BdFrQ72RtfgqGSZJvmwPyJpqQQfR98j4XQd3VDtufWG' \
                            'X3zlE++XKsGjrCm7fPvP66TgvYTdPbMlshpX2wXdlx5z1xa4lkApgZAa06sxPHrEdoNgCi+Tak6K+Y0xJ54+81' \
                            'tbRsC5UL/lvSvmmClmrpYmRotLlI7PbI2tL2ACZPfaaMCKSyJdHuLdHhJ9XpIDHpykiXYlOssIYSo8VQ6BeotA' \
                            'g0XtBItQo4Nh6qJD5miL9tj60PjQ/G8s/sap171y75uDr0azVQdQn0hZ/nHt3f4xteL9j6PvzY1aHRnSlSDaZd' \
                            'LX4ife4yUdNUlliZbCQFaonzWgXAWGQ/CQNKGxaYINNtXjLKoPsRE5EyHoGReu9cw55vNH39myv7TkeqhGvSaH' \
                            'qj0nDKT3zIxanQC4jmDrX2vo3VaLgSfPUjSNUr0UBwEYhgS8pLq9pLqr6dYahUPtoQPMOWsA05eZPgmnuy7lbj' \
                            'gPWDpaGgah3LwpC6dedAKC+IXdyQ+9g/O5fbOfrs21mMKLEAIpJE0TJuD3+ZgwYQJer5cJE8YTCARobGwY1mcw' \
                            'GKShoYG6ujpqaqqprx9DKBRi3Lix+Hw+hBAYwkP/zjp2vRccaqe1wlX9n//VOwv32wdbbJxKmdJ5cM9TX/dH1c' \
                            '7bY1bnxMGyPeuDWP2hIfGvravlttu+xcBAhGuv/TItLR9zw9euI5VOcc7ZZ/HOu6vz+ly06BROP/1UZhw2nYkT' \
                            'J3LCCcfRUF/PxRdfRE9PLx0dmeknpcCyHCYemcqdInVC8PCrD7bER0PHYL1iHKzI5+Cae+qj6dY5ud8SEhKpFB' \
                            '7TxGMaRAYiPPrbx2lubmZgYICdu3bz8CO/paO9g927m4d9eN26v7Fjx05s28a2Hfw+L4lkkm3bd9Da2obWGqU1' \
                            'tu0gVebLORqnXmvmAIM7skqkvKTPMve5cCueB12JrVNtNxHKRaV2go2rFKm0RTSeBMPg8su/wOEzZ3LllVcwaf' \
                            'Ikliy5nLlHHcn55y1GKZ25tEZrzbHHHs2555zNaact4sQTj+dznzudeZ/6FF/4wqVMmjSJWCJJLJ4klbaonWAj' \
                            'jfwBF4KjKyA+F4Zo3O9V4/7VC/8ZeCS3zE5L1j1TR9/HQ55ovF4vjmNjmia27eAxTVzlIqWBbduIQZNBgJSZVU' \
                            'XrzDZdCoHSGiklruugVIbwuiabT18SxhdyC9FaeuP8VTfuDz2jUS55U0Qpxmfs0kFeajw+xbyLwzSvC9K900u0' \
                            'y4NlWQBYVsbGsOzM3c0SNWSFa3BdF3eINoFLxn5wXRchINTgMGFOikM/ncAbGC75WjN+FPTkwWgYkfdl2xGmz6' \
                            'sp1EfeoGLmKVFmnCywEpKBdg/hVg8DHR7ivQZOWuLYZbYhAgxDY/oUwQaH2iabugk2NeMc/NVu1vIsDrYr9nvV' \
                            '2G+JSKSElWFEicqGxl/t4q92GTczBWRM6lTUIBWRmXtM4loCrQXS1Hj9Cl9IUVXrUlXrFh31cmBbIlUC55Hu+y' \
                            '8RkZjsqAlqDKPy/aPh0QTrHYIHYr9YCBoicVnow6s4GicL7oXPJSGRFJv6I/LAepeLqW5R/F1h1WhCEomLv+UU' \
                            'lVsRh7kbii2VFRHnOHprd7+xuqPHxLLF6PwvpeoWK9fF3w36fB1X0Bs2aOs2O1yHZTlViklBSQnJdd7qIs+5kF' \
                            'dn7Z+areM/P219Mi0WhKPGuERS4DgC0wRjpH2FKL9u9+/1EqgpPR6uKwhHJT1hg64+k2hctmnN9csf2Pp+z55Y' \
                            'btVcJ26ek7mQtkriGmXfXf/bRfWGKc8DLgUW+H267tAJjszTHZW6KrP1/vZ8HUeeHcFbpYa900Brp6kiMRkDNq' \
                            'D1C66jn3vlZxtbPv57XyGugzCSW2GIEfu1+8wFf8jDl3+9sEZIcWsoqH4wcayDUUTbuLZACJDmcM7EekxaN1Xx' \
                            '8YYq6qdYzD41RnCMs292aOjqM+gbMJ5zLHXT2uebu9a+0FzMcVsuIDUIeY7ekSzLUe9Or390UcDwykdCVeqypr' \
                            'EuZsGq4tqCPesC2ElJVW3GekpGDAbaTFJRg4apFlM+lcD0aYQAf42bZYKgs0fSHzXWa83i+69Y3lEB7qUidlDA' \
                            'rIMS6brxydNrQD/i9eiLxze6hIrYA4mwwUCbBzslMbyaQJ1D7XinqKSk0oKOHoNESq4XcOl9VyzfdaBxrkQiRh' \
                            't6B+D6RxfVSI+8yxB8pTqkzMY6l3IGWDFwHEHvgCQcNXBdlmnNNUuXLN87ii4qxv+gRsOvvn+hDNR5nwcuEEAw' \
                            'oKgJKQJ+jWnofRoqe9c6Q3zKEkTjkkhcojJkNGulj1h65ZuFluMBg9z8iJH0gATUuKY6AsEg/T1JtMpsijweL+' \
                            'lUUnp9fqVcl2QqIQH++L3NasnSealMthTEEpJYQmJIME2NaYCUGgEolbEJHFfgusMWGWfDy61OIBCUpsej6sbU' \
                            '093ZIaVhgNZUBYIKIJGIy0DIq0I1Hpq3d5ZzQg9TqBVLxILTZ/OjpV86VsN16aTb2NueYteWCHs+itLblUKXYG' \
                            'P1NGt+3WHuJE+1izZcEBothnJGhoMGtERogVASN2EQbTFi/Vt8y3SJwaqt9zJ5RojD5tYyblIVVSEjIYR4cs+O' \
                            '7je+svhXFSn63OVzEIrFOHlt8+31HtNYqWFObgfJmEPn3iQ7N0do3hohGi506YPhgcAYQfV4CDSA4dMZGREFbn' \
                            'otEFqgHEGyH2IdgniPxk4P1y3+gMGUw6s5bG4NTVODhGo9iHzGhrXSnznziNu3UHwZzdt8lUovHMZFw5RzbJWe' \
                            'o7QNSKQwMIRJVchk6uxqps6uRrlN9HSk+Hh7jLbmBL2dSRJRF9tyiXZpol0Zd543KPAFJaZ/nxWqXHAssOKadE' \
                            'xndIMenHqSqpBJXaOXCYcGmDwjxPgpAUzPvvHTKBxlo7WLRiGEUWcI3+eATcO4WMTELmlQ/X7Vt80xjaF5oENS' \
                            'SAbSbSf0pLb9xFFW1kNtYgoffrOWgFlPlaceU2Q9U1kr0LYU/R02nS020X6bRNwmnXKwLBfXVrhKZ/QMmSQRaQ' \
                            'gMQ+D1Gfj8JlUBD8EaD2Mne2ho8uAP5NvuSjsknTAJp4+UE8ZWSZR20FohhUGtb/KjDb4Zj4uMVnYi4eT6fzrx' \
                            'JzGK2BRFGbHwjCO4/b7L7wVuGKwct3toja0vsTMVCCQ+M0TArMdv1uEzghjCixAGWgmSEYj3a5w0gytB3kZN5D' \
                            'wIAaYXArUQqBNIQ6NRuNrCUcks8f2knIGsBBRflhuqZqgG//QhvgEr+ntjZ1224O5h+d5FleUb2+7wJ51Ie3+q' \
                            'uU7pTBtXWaTcSAk2DN9KSGFgykzGi8eowpR+DLwo24OTMnFSEmXLTNwTQGoMU2P4XDx+F+m1UcLCUWlslcBRaR' \
                            'yVwtWV5qyDRwbwGoEsjpIaX5MTNA85/szZt2+gYPCLOmYc1x3fEd8UsNxYRRumYq+VdrHcOBZxGMI905kwBVST' \
                            '3USJbKnORsZ0ZgVKleo5r6uy5bZK5CSpQMLpl03BY6YAGyhQBUUZ0Z3cguXGS1NZMVaFkHXYDm4j9/3KPI3GqT' \
                            'Ean0YWlHboSnxYdHoXLYxY7XJ0KRwHMivmYILGcmNFcymKSkRPv6EaxwyLGewXKAXhqIHlsF/8Mk2oq1b7drH7' \
                            'kYaVCylLOhQxD4oyorNXquqgGvUmqRA00Nc5Bk/HUdS4gUwcpwwhWmRsrAxkKrrSpnfMhxxyaFvGsfgJUFJK0N' \
                            '5tOFQqEckOs6evRlsTxjreYu8r/rBjEOo8Dk96eOR7NODrCeGOex0ZSIxcuQxEYlL17fHGKOPFzoOuFbXz2z/y' \
                            '+ePJT5SWhFRePG4wr0wAUsqilxDFtz6G8mEW9DNasB1Be4vH7FpVc+ERRx49skScdsa5Eri2f3OV+fF4m8mTHI' \
                            'JVFe1bhkExsqprazj99FMzcySbhjw4W1LJFBs/2EhbW3tFfZcJluW9S1uC1g6T7o1VaJdLpk+fedeHmzfmxUCG' \
                            'McLvr5oKfC7db9K1NogmTm2tor7WxevJ+hBG5cUQeYh5PB4mTsqkVOzd28qkSRPzak+aNJGnf/cMsVgsJ6o6/I' \
                            'PlVIUmk8JkuxCOSsIDBr1bqoi3+ACmIDgTeCK3TS4jMk5MIS4G0YjWxHb7cBMS+4REVzgmGz2Glg11GaaMQHte' \
                            '8kIe0tkfWmveWfUuU6cdimmaGIbBUUfPxV/lp2liE9s+2lac2JFWDZHxe3R0G9iOwHVER//6QH10t8+b0+7aw2' \
                            'bMemrnjo8gJz9iENRZ514kgatyv5Ts9IR71gTOR3OF7Yg/JdPCKs+FHGKVxnGc/L2AGLwJUqkka957n3feeZeV' \
                            'K1eRTqeBbO5UDhMzKQEqr+9y37YsoSxbrNCaG52YPD66y7e2oN38WUfMnU0pE9s0zYXk+BuyzH9r08oda//wwO' \
                            'Y1wNPf/uOiD6CyhIy0bfPumvepMuqoq62htqYGf5U/i6/OSwkYjFwBpFJpenr7iESihCMRBiIDfHpKlDG1lXwV' \
                            'bJuIcvXnf/2/3gwDavEFlzwkYEEOL7xCiKura2pvjUYGIDfP8vBZcyRwJTlSkm34yEdbNw9xznZEs1IcLStYUD' \
                            'IuOEVfNExfOAzAQGxfJGrdBxvp7c2c8dQarvrnJVRRxa7mPazf8PchSRKjXLzStgj37IlF2DfiL2roA3LDzxd8' \
                            '5rOf+8mrL/2hD1BDnzh81hFNwLkFfTa7jvNGboGrxBbLrjxXshCUUkPLZDyRJBKNEYnGiMZiQ3sNpXXJrfVIml' \
                            'opsGyx7dkfvj80eLt3busD/lhQdYZALBr8McQIgTgThmWcPLN50wd5nmPX5e9pqzJGFNNrSmni8TiJRAKt8pfl' \
                            '/D1XqW+UVxKWLXBdsYmc+b9l80YF/A6w9p1ZERLBFYN1TICjjvm0BK4t6DMG+smPW3YXGhHrEylp1VariqzOQn' \
                            'I6elq49e4vAqDEWM44/VQgk2sVDGZ8BxnJyCW4crs6kRK4ircp8Lsq5b4jpbEJ9Lyc6uede/7Fk1596Y97TYBz' \
                            'zz5nXjqdPq6qqor+cJi2jk5c111r286Wgu/gOrotmRbNSjFzJD0xJN45ouGrdpl7bhg0tP/1SG6+6Wv5bbRmz5' \
                            '7CTOLKp2IiJRMU8Te89vLzqcUXXPJ7r9czb/LEiYypqyWVSnsj0egXJzZN/Kn5y1/ctyAUCj0//bBpZqAqwK7d' \
                            'u9m2bTubPvzo8WeefmyYO8hJu5GUx1ydtsTMKv/IHpvc1QAyTtpE2AAtSKdtOjo7h+qm02lefW0ZO3ftrojwQl' \
                            'CuIJkSW9C6aFx0+qFTXp0wftxddbW1fGresWitaWtt++HcI2ZvMoGfjJ8wbmwgECAWjTEQHqCutpaTTzy+7pmn' \
                            'HxvW2YNfXcGNT572eiQul1T53bIyIaQodLGT6DNZ9WBjFvG93PzNbw+9cxwH2x4eDhAVWrPxpMB2xFvP3b52UK' \
                            '/lue0PP2z6BZDJ0uvq7GLc+HFMaJpQ09fXf6cJLNi9czctzS3ZND6R/bg4Gfh5sQ9qxRvRuAwfMkbVS1laKgJB' \
                            'L7VjgnTFcv8jADhDylaRTI58yCUQ9FFXFwDK23LhmLSAlzp3RkplxnwaMh7z1r2ttLa2IRDYjj3PBPpcpcZqYN' \
                            'r0aQhgx46dAD2UCKK+8ZstHWfeOGdFLCEuqgmVZoQSac665HBWvthBuC9esl45qAr4OPmsQ3FqVmCVsezTliCZ' \
                            'lM3JAWtNTnFhIKcNoLa2lslTJrFj+04SiQRAmwn8BviB1lp2dXaRTCZxlcK27ScpEWLb9nYHZ9ww56H+iHFBdV' \
                            'DJErtntFbEqt/lgutOwisqNAsLwNFJ2hNrSVjlGdkfMXAVjz98w6pBsckdxOzKoR8A/dVwOOy1bXswGdYB7jGB' \
                            'u2Kx+HSvz7tERSLEE0l27m6ms7vbX/CtPOmwU+6KhDC2xJJybnWZfMi0E2VHOJvj9QndbKXAsgUDURlWrn6K4d' \
                            'MBskvpsjff6jukoYHpU6dgWRaWbRMeGFjm93iWmjd/8+uJ8y685H7TNL8kpZSO4+Jm8oDPgWFZakPMeO/ZXZGF' \
                            'Vx7+H919xr1Bv5bldMUQHCQfb3efgat4bqAr2VyiSibiLcRpPX193r5wGENKlNa4rvvCKy8+l5IAqVRqvW07Pe' \
                            'm0NcgEgNPOPPeCQqkY4vIHf/4YAU+k02JHX0T+j/mxYwlJNC4jwD1PfWt1SdE878JLARYD2cP5Dq7rOsAbkBWZ' \
                            'N/7yskX+6APMMA3PHMrAg9evDGu4szdsqFT6H/IfGPLAcTIpRUrz4Pt/2l1o/MmCqxGYX1Bn657mXc0UnPt8pa' \
                            'BSQAhx2qFTp1MKUlEbrXjOdVnW1mViO/84ZigF7T0Gli22oblrzbPDjDCVd2mOA5oK6ry8aeN6Re7uU2u9msyS' \
                            'mQvnT51+eNl87aVXLk9pzS1pS3S0dZk5xwwOHmgNnb0m0bhMALe8es/Gsv9HYcbMI6RGn0++/8VB69cGfwwxwr' \
                            'LSe4G1BX3MD4WqC7k4DJYuWb4FuCmeFKm9nQdXMpSCjh6TbB74XS0bel/eva5w/PJh1uw5fuC8guJdSquNgz+G' \
                            'Eg527dzGzFlzQsB5vpBi1skORyzwGdNP7p944U1Nm/o70z17t5Vey2eePG6rP+RxbEcsSiSl9Ps0ngP8f89sR9' \
                            'DW5SESkwp4zEo43/vdd9aUDI9PnhXixy8d3zTtuNT3m6YFzm6YLIj1u1iZMMWz772z8vlkMqEh/5SfnjlrTlug' \
                            'zr1x8TWHmGcsXMysqcfgeHuPjNvdXzrmlMa3XnmwpbXUR/++bK844rMT3vYFTMtxxSkDsUwujN+ri2bgloNCeV' \
                            'IKBqKS1k6TlCUV8Ggyat/80HUrcyM+w/LJ/8+fjp9imOJNr8+z+LNHLuGo2ccwda7J7h0dKhkR3/lgw9rBfM38' \
                            'jfSWTR+0zT0nsmbSxIko7dIe+4C+5A6Aeo2+/bq7jyhHknrsG++ovr3xnwJf05pwd5/B7lYP3WFjVNNlkBpXZZ' \
                            'LP97R5aO8xcVxhgb7bsdRND1+/MlbQrPCEgTQ84hvADKVsdvYvJ2H3cUh9E8ec5XSh9arc+sOwu3/1wpXAQoHA' \
                            'kD5M6SXlRFCKloGImPWDs1dWlOt4w5OnHi0Q9wKnAFIICPgVwSpNZtpopAQhMn5/rUFpcBxIW5J4UhBPDuVZAm' \
                            'xCc+vqZ3ctW5fJvx4J5C/f+cybhtSnmNKHKf2knQgI0Fp33Th/1bjcyoN5lrkdtwEY0sOcxvOJWd3sCr9Ff8To' \
                            '273Drjhd5eHrV228+r6Tz5emvAi4TWvmxJNSxrObTSkyTlmBzka9MozYR/iQPb4XuFcp/ejTt73X099WcfxT9f' \
                            'TLjrH1LkFPI7MazmZb7zLC6Y8BhvkrhkvEuwsXIXgJRMhjVJG2k4SjkmRa/GLiWOevwK7WHbGt/7ZkQ8VxwCvv' \
                            'OckMNfoXSCkuBRYAU4A6hkfaFBAhMxhr0foP6aT7xoPXrqiY+ruXzfcGq82FCOb1R+RE1xXfHFOj8Hl8KG2jtW' \
                            'tpzdduPGnVw2UZcdsjx8jGqbUXJNPix0oz22tq6fNqfD5tCfACYeC+dMK9819Oe3fkYE8BXPfIIr/hkeOBsULQ' \
                            'iBAhAVKjEyh6gC7l6o7lD3wY+2jVSAn4+fCrt0+ukYb4GbAE8EMmm9eyIZWWKE2L36vvchOpB7933hqLkTJv/d' \
                            'Uefrps/iUC/R8ISh1Fe1kprr3trNUdiWjlCV45sL9nRIa1mzN/DF//xZHTNTxORuLQSmSO7ezbDFpa892l39r6' \
                            '8y1vD7c7ijLi3hULAqZXbgeaNBCNSQZiEinhkDGZYHAW1iqlr7hpwdvbDgRB+9vuvtULjxPwJDATMi67rr6MlV' \
                            'sdVBxSr5CZDJSeVMKddstp7xauOMXzI0yvnAmMBegfMGjtMnuicXnHQERe3dJuPpNIycGV4zgpxV/vX71w0c+X' \
                            'nzTaZIr9PTE01O4HT31K3v/uwosE/IUsEyJx2dfaaX4/kRRHWpY4ozdsrO/uG0Ktxuc3phTrtBTyocGH/ohMob' \
                            'n21Z9t/NH9S5Y/Gh1wr+jsNa5LpsWgApsE/MFXZXxx/uKxlTCj1JHDUu+GHU0EuPSW6XLCtODXETxJNpQXT4qe' \
                            '7j7jn9p2xv5t6ZLlW39z9VtvaM3VkZgRzmlbGI+RpZAhd8a4LpscS728e32PAnjw2hXO3u2Jx7r7jOssWwyKWD' \
                            '2Ch6784czrb77vqJGYUcyDVO7dsDzxxiY/iy5tugzB7UAAIG2Jrp5+4wvP3vHBW89+/30A5dqKrSvaN2n0+pwz' \
                            '7EXzzouOjhAMGU1Ssuk3V7+Vpw2f/s57NG+NPxWJyRuVIgFsBX6ilX6jfVfJ/Ujht8pJxOBzbirg0LuethR7Nk' \
                            'efA04H7lZabIgl5BXP//TDt3JO+kmA5f/5oTIkO7IJGwpEqti3i2bna62HGGEaDFMsAL//7vvqsjvmPXHiZ4Nr' \
                            'TOnufeyO7Yl1r3eXm/elXOyFdQaVYd4pvML6d1/zgQNsWHztlI0nXdD0o2fuaUlsf6cT8pWpBJTHHKRHozXFLO' \
                            'Oi/34J0FaGe+D16pK2wjP/ul49k5GGSqDcMcTcOhTUKatUX3mgRb3yQEuuwTWsnS+HBq1VMXpUUUZohTOYk+Av' \
                            'w4j9hEqk5oBClW+IBoUWRRNOSynLIZ0Q8Kv9i8wMh4NCZCVQ5dPbyYbJhCyOR3FGZBRsDFjqNfVvDg56ZeGTJX' \
                            'gWQH974gngLGCNLmU7FcVCEtGaM1a/0rX+8Tv3x2j8xHBApedHl61zgLfufn3+GdFee7/6LjcyRQ2d/axfaqks' \
                            'VVbYxyd99/80/H+BZCGMBumi5yUq6G+0EliufaEE/sNgJBEv1WY0v0eLT959NAGIYYbQxIkTefXPf5ZVPr/0+r' \
                            'ym3+evkdJodJVbL6WsEUKEyOwFAhq8IuMsMQFPtgszeznsO/llZ58tMie7EkBCaRVDE9FKhTX0KNcNt7a1OR3t' \
                            '7eorX7lGdXd3V4x3MRCUMGFHgvnz5/PiSy+fCdxEJlt3fJboAxb+H6GbFBmX3jbg12MPaXyx4H0xBhRat0N3s0' \
                            'jliiBUXY1lWWs9Hs+PgZlCMAXNBIRoRFMH1JDZzgeyl5/MFthk+FY4FyyyEqEzxKbI2DQxMv7MMJnQZDvovVqz' \
                            'A1HUzK9kdzt0/2+exnQr4g2hrAAAAABJRU5ErkJggg=='
# file types regexes
PIPFILE_REGEX = r'.*/Pipfile(\.lock)?'
TEST_DATA_REGEX = r'.*test_data.*'
TEST_FILES_REGEX = r'.*test_files.*'
DOCS_REGEX = r'.*docs.*'
IMAGE_REGEX = r'.*\.png$'
DESCRIPTION_REGEX = r'.*\.md'
SCHEMA_REGEX = 'Tests/schemas/.*.yml'
CONF_PATH = 'Tests/conf.json'

PACKS_DIR_REGEX = fr'{CAN_START_WITH_DOT_SLASH}{PACKS_DIR}'
PACK_DIR_REGEX = fr'{PACKS_DIR_REGEX}\/([^\\\/]+)'

INTEGRATIONS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{INTEGRATIONS_DIR}'
INTEGRATION_PACKAGE_REGEX = fr'{INTEGRATIONS_DIR_REGEX}\/([^\\/]+)'

PACKS_INTEGRATION_PY_REGEX = fr'{INTEGRATION_PACKAGE_REGEX}\/\2\.py'
PACKS_INTEGRATION_TEST_PY_REGEX = fr'{INTEGRATION_PACKAGE_REGEX}/\2_test\.py'
PACKS_INTEGRATION_PS_REGEX = fr'{INTEGRATION_PACKAGE_REGEX}/\2.ps1$'
PACKS_INTEGRATION_PS_TEST_REGEX = fr'{INTEGRATION_PACKAGE_REGEX}/\2\.Tests\.ps1$'
PACKS_INTEGRATION_YML_REGEX = fr'{INTEGRATION_PACKAGE_REGEX}/\2\.yml'
PACKS_INTEGRATION_README_REGEX = fr'{INTEGRATION_PACKAGE_REGEX}/README.md$'

PACKS_INTEGRATION_NON_SPLIT_BASE_REGEX = fr'{INTEGRATIONS_DIR_REGEX}/integration-([^\\/]+)'
PACKS_INTEGRATION_NON_SPLIT_YML_REGEX = fr'{PACKS_INTEGRATION_NON_SPLIT_BASE_REGEX}\.yml$'
PACKS_INTEGRATION_NON_SPLIT_README_REGEX = fr'{PACKS_INTEGRATION_NON_SPLIT_BASE_REGEX}_README.md$'

SCRIPTS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{SCRIPTS_DIR}'
SCRIPT_DIR_REGEX = fr'{SCRIPTS_DIR_REGEX}\/([^\\/]+)'
SCRIPT_TYPE_REGEX = '.*script-.*.yml'
PACKS_SCRIPT_PY_REGEX = fr'{SCRIPT_DIR_REGEX}/\2\.py'
PACKS_SCRIPT_TEST_PY_REGEX = fr'{SCRIPT_DIR_REGEX}/\2_test\.py'
PACKS_SCRIPT_PS_REGEX = fr'{SCRIPT_DIR_REGEX}/\2.ps1$'
PACKS_SCRIPT_TEST_PS_REGEX = fr'{SCRIPT_DIR_REGEX}/\2\.Tests\.ps1$'
PACKS_SCRIPT_YML_REGEX = fr'{SCRIPT_DIR_REGEX}\/\2\.yml'
PACKS_SCRIPT_README_REGEX = fr'{SCRIPT_DIR_REGEX}/README.md$'

PACKS_SCRIPT_NON_SPLIT_BASE_REGEX = fr'{SCRIPTS_DIR_REGEX}/script-([^\\/]+)'
PACKS_SCRIPT_TEST_PLAYBOOK = fr'{PACK_DIR_REGEX}/{TEST_PLAYBOOKS_DIR}/script-([^\\/]+).yml$'
PACKS_SCRIPT_NON_SPLIT_YML_REGEX = fr'{PACKS_SCRIPT_NON_SPLIT_BASE_REGEX}\.yml$'
PACKS_SCRIPT_NON_SPLIT_README_REGEX = fr'{PACKS_SCRIPT_NON_SPLIT_BASE_REGEX}_README.md$'

PACKS_LAYOUTS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{LAYOUTS_DIR}'
PACKS_LAYOUT_JSON_REGEX = fr'{PACKS_LAYOUTS_DIR_REGEX}\/(?!layoutscontainer)([^/]+)\.json'

PACKS_LAYOUTS_CONTAINER_JSON_REGEX = fr'{PACKS_LAYOUTS_DIR_REGEX}\/layoutscontainer([^/]+)\.json'

PACKS_PRE_PROCESS_RULES_DIR_REGEX = fr'{PACK_DIR_REGEX}/{PRE_PROCESS_RULES_DIR}'
PACKS_PRE_PROCESS_RULES_JSON_REGEX = fr'{PACKS_PRE_PROCESS_RULES_DIR_REGEX}/(?:preprocessrule-)?([^/]+)\.json'

PACKS_WIDGETS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{WIDGETS_DIR}'
PACKS_WIDGET_JSON_REGEX = fr'{PACKS_WIDGETS_DIR_REGEX}\/([^/]+)\.json'

PACKS_DASHBOARDS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{DASHBOARDS_DIR}'
PACKS_DASHBOARD_JSON_REGEX = fr'{PACKS_DASHBOARDS_DIR_REGEX}\/([^/]+)\.json'

PACKS_REPORTS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{REPORTS_DIR}'
PACKS_REPORT_JSON_REGEX = fr'{PACKS_REPORTS_DIR_REGEX}\/([^/]+)\.json'

PACKS_INCIDENT_TYPES_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{INCIDENT_TYPES_DIR}'
PACKS_INCIDENT_TYPE_JSON_REGEX = fr'{PACKS_INCIDENT_TYPES_DIR_REGEX}\/([^/]+)\.json'

PACKS_INCIDENT_FIELDS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{INCIDENT_FIELDS_DIR}'
PACKS_INCIDENT_FIELD_JSON_REGEX = fr'{PACKS_INCIDENT_FIELDS_DIR_REGEX}\/([^/]+)\.json'

PACKS_INDICATOR_TYPES_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{INDICATOR_TYPES_DIR}'
PACKS_INDICATOR_TYPE_JSON_REGEX = fr'{PACKS_INDICATOR_TYPES_DIR_REGEX}\/([^/]+)\.json'

PACKS_INDICATOR_FIELDS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{INDICATOR_FIELDS_DIR}'
PACKS_INDICATOR_FIELD_JSON_REGEX = fr'{PACKS_INDICATOR_FIELDS_DIR_REGEX}\/([^/]+)\.json'

PACKS_GENERIC_TYPES_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{GENERIC_TYPES_DIR}\/([^\\\/]+)'
PACKS_GENERIC_TYPE_JSON_REGEX = fr'{PACKS_GENERIC_TYPES_DIR_REGEX}\/([^/]+)\.json'

PACKS_GENERIC_FIELDS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{GENERIC_FIELDS_DIR}\/([^\\\/]+)'
PACKS_GENERIC_FIELD_JSON_REGEX = fr'{PACKS_GENERIC_FIELDS_DIR_REGEX}\/([^/]+)\.json'

PACKS_GENERIC_MODULES_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{GENERIC_MODULES_DIR}'
PACKS_GENERIC_MODULE_JSON_REGEX = fr'{PACKS_GENERIC_MODULES_DIR_REGEX}\/([^/]+)\.json'

PACKS_GENERIC_DEFINITIONS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{GENERIC_DEFINITIONS_DIR}'
PACKS_GENERIC_DEFINITION_JSON_REGEX = fr'{PACKS_GENERIC_DEFINITIONS_DIR_REGEX}\/([^/]+)\.json'

PACKS_CLASSIFIERS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{CLASSIFIERS_DIR}'

_PACKS_CLASSIFIER_BASE_REGEX = fr'{PACKS_CLASSIFIERS_DIR_REGEX}\/*classifier-(?!mapper).*(?<!5_9_9)'
PACKS_CLASSIFIER_JSON_REGEX = fr'{_PACKS_CLASSIFIER_BASE_REGEX}\.json'

JOBS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{JOBS_DIR}'
JOB_JSON_REGEX = fr'{JOBS_DIR_REGEX}\/job-([^/]+)\.json'

# old classifier structure
_PACKS_CLASSIFIER_BASE_5_9_9_REGEX = fr'{PACKS_CLASSIFIERS_DIR_REGEX}\/*classifier-(?!mapper).*_5_9_9'
PACKS_CLASSIFIER_JSON_5_9_9_REGEX = fr'{_PACKS_CLASSIFIER_BASE_5_9_9_REGEX}\.json'

_PACKS_MAPPER_BASE_REGEX = fr'{PACKS_CLASSIFIERS_DIR_REGEX}\/classifier-(?=mapper).*'
PACKS_MAPPER_JSON_REGEX = fr'{_PACKS_MAPPER_BASE_REGEX}\.json'

PACKS_CONNECTIONS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{CONNECTIONS_DIR}'
PACKS_CONNECTION_JSON_REGEX = fr'{PACKS_CONNECTIONS_DIR_REGEX}\/canvas-context-connections.*\.json$'

PACKS_RELEASE_NOTES_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{RELEASE_NOTES_DIR}'

PLAYBOOKS_DIR_REGEX = fr'{PACK_DIR_REGEX}\/{PLAYBOOKS_DIR}'
PLAYBOOK_BASE_REGEX = fr'{PLAYBOOKS_DIR_REGEX}\/.*'
PLAYBOOK_YML_REGEX = fr'{PLAYBOOK_BASE_REGEX}\.yml'
PLAYBOOK_README_REGEX = fr'{PLAYBOOK_BASE_REGEX}_README\.md$'

TEST_SCRIPT_REGEX = r'{}{}.*script-.*\.yml$'.format(CAN_START_WITH_DOT_SLASH, TEST_PLAYBOOKS_DIR)
TEST_PLAYBOOK_YML_REGEX = fr'{PACK_DIR_REGEX}/{TEST_PLAYBOOKS_DIR}\/(?!script-)([^.]+)\.yml'

PACKS_INDICATOR_TYPES_REPUTATIONS_REGEX = r'{}{}/([^/]+)/{}/reputations.json'.format(CAN_START_WITH_DOT_SLASH,
                                                                                     PACKS_DIR,
                                                                                     INDICATOR_TYPES_DIR)
PACKS_RELEASE_NOTES_REGEX = r'{}{}/([^/]+)/{}/([^/]+)\.md$'.format(CAN_START_WITH_DOT_SLASH, PACKS_DIR,
                                                                   RELEASE_NOTES_DIR)
PACKS_TOOLS_REGEX = r'{}{}/([^/]+)/{}/([^.]+)\.zip'.format(CAN_START_WITH_DOT_SLASH, PACKS_DIR, TOOLS_DIR)

PLAYBOOK_REGEX = r'{}(?!Test){}/playbook-.*\.yml$'.format(CAN_START_WITH_DOT_SLASH, PLAYBOOKS_DIR)

TEST_PLAYBOOK_REGEX = r'{}{}/(?!script-).*\.yml$'.format(CAN_START_WITH_DOT_SLASH, TEST_PLAYBOOKS_DIR)
TEST_NOT_PLAYBOOK_REGEX = r'{}{}/(?!playbook).*-.*\.yml$'.format(CAN_START_WITH_DOT_SLASH, TEST_PLAYBOOKS_DIR)

CONNECTIONS_REGEX = r'{}{}.*canvas-context-connections.*\.json$'.format(CAN_START_WITH_DOT_SLASH, CONNECTIONS_DIR)

INDICATOR_TYPES_REPUTATIONS_REGEX = r'{}{}.reputations\.json$'.format(CAN_START_WITH_DOT_SLASH, INDICATOR_TYPES_DIR)

# deprecated regex
DEPRECATED_DESC_REGEX = r"Deprecated\.\s*(.*?Use .*? instead\.*?)"
DEPRECATED_NO_REPLACE_DESC_REGEX = r"Deprecated\.\s*(.*?No available replacement\.*?)"

DEPRECATED_REGEXES: List[str] = [
    DEPRECATED_DESC_REGEX,
    DEPRECATED_NO_REPLACE_DESC_REGEX
]

PACK_METADATA_NAME = 'name'
PACK_METADATA_DESC = 'description'
PACK_METADATA_SUPPORT = 'support'
PACK_METADATA_MIN_VERSION = 'serverMinVersion'
PACK_METADATA_CURR_VERSION = 'currentVersion'
PACK_METADATA_AUTHOR = 'author'
PACK_METADATA_URL = 'url'
PACK_METADATA_EMAIL = 'email'
PACK_METADATA_CATEGORIES = 'categories'
PACK_METADATA_TAGS = 'tags'
PACK_METADATA_CREATED = 'created'
PACK_METADATA_CERTIFICATION = 'certification'
PACK_METADATA_USE_CASES = 'useCases'
PACK_METADATA_KEYWORDS = 'keywords'
PACK_METADATA_PRICE = 'price'
PACK_METADATA_DEPENDENCIES = 'dependencies'
PACK_METADATA_IRON_BANK_TAG = 'Iron Bank'

PACK_METADATA_FIELDS = (PACK_METADATA_NAME, PACK_METADATA_DESC, PACK_METADATA_SUPPORT,
                        PACK_METADATA_CURR_VERSION, PACK_METADATA_AUTHOR, PACK_METADATA_URL, PACK_METADATA_CATEGORIES,
                        PACK_METADATA_TAGS, PACK_METADATA_USE_CASES, PACK_METADATA_KEYWORDS)
API_MODULES_PACK = 'ApiModules'
API_MODULE_PY_REGEX = r'{}{}/{}/{}/([^/]+)/([^.]+)\.py'.format(
    CAN_START_WITH_DOT_SLASH, PACKS_DIR, API_MODULES_PACK, SCRIPTS_DIR)
API_MODULE_YML_REGEX = r'{}{}/{}/{}/([^/]+)/([^.]+)\.yml'.format(
    CAN_START_WITH_DOT_SLASH, PACKS_DIR, API_MODULES_PACK, SCRIPTS_DIR)
API_MODULE_REGEXES = [
    API_MODULE_PY_REGEX,
    API_MODULE_YML_REGEX
]

ID_IN_COMMONFIELDS = [  # entities in which 'id' key is under 'commonfields'
    'integration',
    'betaintegration',
    'script'
]
ID_IN_ROOT = [  # entities in which 'id' key is in the root
    'playbook',
    'dashboard',
    'incident_type',
    'layoutscontainer',
    'mapper',
    'pre_process_rule',
    'lists',
    JOB
]

INTEGRATION_PREFIX = 'integration'
SCRIPT_PREFIX = 'script'

# Pack Unique Files
PACKS_WHITELIST_FILE_NAME = '.secrets-ignore'
PACKS_PACK_IGNORE_FILE_NAME = '.pack-ignore'
PACKS_PACK_META_FILE_NAME = 'pack_metadata.json'
PACKS_README_FILE_NAME = 'README.md'
PACKS_CONTRIBUTORS_FILE_NAME = 'CONTRIBUTORS.md'
AUTHOR_IMAGE_FILE_NAME = 'Author_image.png'

PYTHON_TEST_REGEXES = [
    PACKS_SCRIPT_TEST_PY_REGEX,
    PACKS_INTEGRATION_TEST_PY_REGEX
]

PYTHON_INTEGRATION_REGEXES = [
    PACKS_INTEGRATION_PY_REGEX
]

PLAYBOOKS_REGEXES_LIST = [
    PLAYBOOK_REGEX,
    TEST_PLAYBOOK_REGEX
]

PYTHON_SCRIPT_REGEXES = [
    PACKS_SCRIPT_PY_REGEX
]

PYTHON_ALL_REGEXES: List[str] = sum(
    [
        PYTHON_SCRIPT_REGEXES,
        PYTHON_INTEGRATION_REGEXES,
        PYTHON_TEST_REGEXES
    ], []
)

INTEGRATION_REGXES: List[str] = [
    PACKS_INTEGRATION_NON_SPLIT_YML_REGEX
]

YML_INTEGRATION_REGEXES: List[str] = [
    PACKS_INTEGRATION_YML_REGEX,
    PACKS_INTEGRATION_NON_SPLIT_YML_REGEX
]

YML_ALL_INTEGRATION_REGEXES: List[str] = sum(
    [
        YML_INTEGRATION_REGEXES,
    ], []
)

YML_SCRIPT_REGEXES: List[str] = [
    PACKS_SCRIPT_YML_REGEX,
    PACKS_SCRIPT_NON_SPLIT_YML_REGEX,
    PACKS_SCRIPT_TEST_PLAYBOOK
]

YML_ALL_SCRIPTS_REGEXES: List[str] = sum(
    [
        YML_SCRIPT_REGEXES
    ], []
)

YML_PLAYBOOKS_NO_TESTS_REGEXES: List[str] = [
    PLAYBOOK_YML_REGEX
]

YML_TEST_PLAYBOOKS_REGEXES: List[str] = [
    TEST_PLAYBOOK_YML_REGEX
]

YML_ALL_PLAYBOOKS_REGEX: List[str] = sum(
    [
        YML_PLAYBOOKS_NO_TESTS_REGEXES,
        YML_TEST_PLAYBOOKS_REGEXES,
    ], []
)

YML_ALL_REGEXES: List[str] = sum(
    [
        YML_INTEGRATION_REGEXES,
        YML_SCRIPT_REGEXES,
        YML_PLAYBOOKS_NO_TESTS_REGEXES,
        YML_TEST_PLAYBOOKS_REGEXES
    ], []
)

JSON_INDICATOR_AND_INCIDENT_FIELDS = [
    PACKS_INCIDENT_FIELD_JSON_REGEX,
    PACKS_INDICATOR_FIELD_JSON_REGEX
]

JSON_ALL_WIDGETS_REGEXES = [
    PACKS_WIDGET_JSON_REGEX,
]

JSON_ALL_DASHBOARDS_REGEXES = [
    PACKS_DASHBOARD_JSON_REGEX,
]

JSON_ALL_CLASSIFIER_REGEXES = [
    PACKS_CLASSIFIER_JSON_REGEX,
]

JSON_ALL_CLASSIFIER_REGEXES_5_9_9 = [
    PACKS_CLASSIFIER_JSON_5_9_9_REGEX,
]

JSON_ALL_MAPPER_REGEXES = [
    PACKS_MAPPER_JSON_REGEX,
]

JSON_ALL_LAYOUT_REGEXES = [
    PACKS_LAYOUT_JSON_REGEX,
]

JSON_ALL_LAYOUTS_CONTAINER_REGEXES = [
    PACKS_LAYOUTS_CONTAINER_JSON_REGEX,
]

JSON_ALL_PRE_PROCESS_RULES_REGEXES = [
    PACKS_PRE_PROCESS_RULES_JSON_REGEX,
]

JSON_ALL_INCIDENT_FIELD_REGEXES = [
    PACKS_INCIDENT_FIELD_JSON_REGEX,
]

JSON_ALL_INCIDENT_TYPES_REGEXES = [
    PACKS_INCIDENT_TYPE_JSON_REGEX,
]

JSON_ALL_INDICATOR_FIELDS_REGEXES = [
    PACKS_INDICATOR_FIELD_JSON_REGEX
]

JSON_ALL_INDICATOR_TYPES_REGEXES = [
    PACKS_INDICATOR_TYPE_JSON_REGEX
]

JSON_ALL_GENERIC_FIELDS_REGEXES = [
    PACKS_GENERIC_FIELD_JSON_REGEX,
]

JSON_ALL_GENERIC_TYPES_REGEXES = [
    PACKS_GENERIC_TYPE_JSON_REGEX,
]

JSON_ALL_GENERIC_MODULES_REGEXES = [
    PACKS_GENERIC_MODULE_JSON_REGEX,
]

JSON_ALL_GENERIC_DEFINITIONS_REGEXES = [
    PACKS_GENERIC_DEFINITION_JSON_REGEX,
]

JSON_ALL_REPUTATIONS_INDICATOR_TYPES_REGEXES = [
    PACKS_INDICATOR_TYPES_REPUTATIONS_REGEX
]

JSON_ALL_CONNECTIONS_REGEXES = [
    CONNECTIONS_REGEX,
]

JSON_ALL_REPORTS_REGEXES = [
    PACKS_REPORT_JSON_REGEX
]

JSON_ALL_JOB_REGEXES = [
    JOB_JSON_REGEX
]

CHECKED_TYPES_REGEXES = [
    # Playbooks
    PLAYBOOK_YML_REGEX,
    TEST_PLAYBOOK_YML_REGEX,

    # Integrations
    PACKS_INTEGRATION_YML_REGEX,
    PACKS_INTEGRATION_PY_REGEX,
    PACKS_INTEGRATION_PS_REGEX,
    PACKS_INTEGRATION_TEST_PY_REGEX,
    PACKS_INTEGRATION_README_REGEX,

    PACKS_INTEGRATION_NON_SPLIT_YML_REGEX,

    # Scripts yaml
    PACKS_SCRIPT_YML_REGEX,
    PACKS_SCRIPT_NON_SPLIT_YML_REGEX,
    PACKS_SCRIPT_PY_REGEX,
    PACKS_SCRIPT_PS_REGEX,
    PACKS_SCRIPT_TEST_PY_REGEX,
    PACKS_SCRIPT_README_REGEX,
    PACKS_SCRIPT_TEST_PLAYBOOK,

    PACKS_CLASSIFIER_JSON_REGEX,
    PACKS_CLASSIFIER_JSON_5_9_9_REGEX,
    PACKS_MAPPER_JSON_REGEX,
    PACKS_DASHBOARD_JSON_REGEX,
    PACKS_INCIDENT_TYPE_JSON_REGEX,
    PACKS_INCIDENT_FIELD_JSON_REGEX,
    PACKS_INDICATOR_FIELD_JSON_REGEX,
    PACKS_INDICATOR_TYPE_JSON_REGEX,
    PACKS_LAYOUT_JSON_REGEX,
    PACKS_LAYOUTS_CONTAINER_JSON_REGEX,
    PACKS_PRE_PROCESS_RULES_JSON_REGEX,
    PACKS_WIDGET_JSON_REGEX,
    PACKS_REPORT_JSON_REGEX,
    PACKS_RELEASE_NOTES_REGEX,
    PACKS_TOOLS_REGEX,
    CONNECTIONS_REGEX,
    JOB_JSON_REGEX,

    # ReleaseNotes
    PACKS_RELEASE_NOTES_REGEX
]

CHECKED_TYPES_NO_REGEX = [item.replace(CAN_START_WITH_DOT_SLASH, "").replace(NOT_TEST, "") for item in
                          CHECKED_TYPES_REGEXES]

PATHS_TO_VALIDATE: List[str] = sum(
    [
        PYTHON_ALL_REGEXES,
        JSON_ALL_REPORTS_REGEXES
    ], []
)

PACKAGE_SCRIPTS_REGEXES = [
    PACKS_SCRIPT_PY_REGEX,
    PACKS_SCRIPT_YML_REGEX
]

PACKAGE_SUPPORTING_DIRECTORIES = [INTEGRATIONS_DIR, SCRIPTS_DIR]

IGNORED_TYPES_REGEXES = [DESCRIPTION_REGEX, IMAGE_REGEX, PIPFILE_REGEX, SCHEMA_REGEX]

IGNORED_PACK_NAMES = ['Legacy', 'NonSupported', 'ApiModules']

PACK_IGNORE_TEST_FLAG = 'auto-test'

PACKAGE_YML_FILE_REGEX = r'(?:\./)?(?:Packs/[^/]+\/)?(?:Integrations|Scripts)\/([^\\/]+)/([^\\/]+)\.yml'

OLD_YML_FORMAT_FILE = [PACKS_INTEGRATION_NON_SPLIT_YML_REGEX, PACKS_SCRIPT_NON_SPLIT_YML_REGEX]

DIR_LIST_FOR_REGULAR_ENTETIES = [
    PLAYBOOKS_DIR,
    REPORTS_DIR,
    DASHBOARDS_DIR,
    WIDGETS_DIR,
    INCIDENT_TYPES_DIR,
    INCIDENT_FIELDS_DIR,
    LAYOUTS_DIR,
    PRE_PROCESS_RULES_DIR,
    CLASSIFIERS_DIR,
    INDICATOR_TYPES_DIR,
    CONNECTIONS_DIR,
    INDICATOR_FIELDS_DIR,
    LISTS_DIR,
    JOBS_DIR
]
PACKS_DIRECTORIES = [
    SCRIPTS_DIR,
    INTEGRATIONS_DIR,
    DASHBOARDS_DIR,
    WIDGETS_DIR,
    INDICATOR_FIELDS_DIR,
    INDICATOR_TYPES_DIR,
    INCIDENT_FIELDS_DIR,
    INCIDENT_TYPES_DIR,
    REPORTS_DIR,
    CONNECTIONS_DIR,
    PLAYBOOKS_DIR,
    JOBS_DIR
]
SPELLCHECK_FILE_TYPES = [
    PACKS_INTEGRATION_YML_REGEX,
    PACKS_SCRIPT_YML_REGEX,
    PLAYBOOK_YML_REGEX
]

KNOWN_FILE_STATUSES = ['a', 'm', 'd', 'r'] + ['r{:03}'.format(i) for i in range(101)]

CODE_FILES_REGEX = [
    PACKS_INTEGRATION_PY_REGEX,
    PACKS_SCRIPT_PY_REGEX,
    PACKS_INTEGRATION_PS_REGEX,
    PACKS_SCRIPT_PS_REGEX
]

SCRIPTS_REGEX_LIST = [PACKS_SCRIPT_YML_REGEX, PACKS_SCRIPT_PY_REGEX, PACKS_SCRIPT_PS_REGEX]

# All files that have related yml file
REQUIRED_YML_FILE_TYPES = [PACKS_INTEGRATION_PY_REGEX,
                           PACKS_INTEGRATION_README_REGEX,
                           PACKS_INTEGRATION_NON_SPLIT_README_REGEX,

                           PACKS_SCRIPT_PY_REGEX,
                           PACKS_SCRIPT_README_REGEX,
                           PACKS_SCRIPT_NON_SPLIT_README_REGEX,

                           PLAYBOOK_README_REGEX]

TYPE_PWSH = 'powershell'
TYPE_PYTHON = 'python'
TYPE_JS = 'javascript'

TYPE_TO_EXTENSION = {
    TYPE_PYTHON: '.py',
    TYPE_JS: '.js',
    TYPE_PWSH: '.ps1'
}

TESTS_AND_DOC_DIRECTORIES = [
    'testdata',
    'test_data',
    'data_test',
    'tests_data',
    'doc_files',
    'doc_imgs',
]

VALIDATION_USING_GIT_IGNORABLE_DATA = (
    'Pipfile',
    'Pipfile.lock',
    'command_examples',
    'pack_metadata.json',
    'testdata',
    'test_data',
    'data_test',
    'tests_data',
    'doc_files',
    'doc_imgs',
    '.secrets-ignore',
    '.pack-ignore'
)

FILE_TYPES_FOR_TESTING = [
    '.py',
    '.js',
    '.yml',
    '.ps1'
]

# python subtypes
PYTHON_SUBTYPES = {'python3', 'python2'}


def urljoin(*args: str):
    """Gets arguments to join as url

    Args:
        *args: args to join

    Returns:
        Joined url

    Examples:
        >>> urljoin('https://www.example.com', 'suffix/', '/suffix2', 'suffix', 'file.json')
        'https://www.example.com/suffix/suffix2/suffix/file.json'
    """
    return reduce(lambda a, b: str(a).rstrip('/') + '/' + str(b).lstrip('/'), args).rstrip("/")


OFFICIAL_CONTENT_ID_SET_PATH = 'https://storage.googleapis.com/marketplace-dist/content/id_set.json'

# Run all test signal
RUN_ALL_TESTS_FORMAT = 'Run all tests'
FILTER_CONF = './artifacts/filter_file.txt'


class PB_Status:
    NOT_SUPPORTED_VERSION = 'Not supported version'
    COMPLETED = 'completed'
    FAILED = 'failed'
    IN_PROGRESS = 'inprogress'
    FAILED_DOCKER_TEST = 'failed_docker_test'


# change log regexes
UNRELEASE_HEADER = '## [Unreleased]\n'  # lgtm[py/regex/duplicate-in-character-class]
CONTENT_RELEASE_TAG_REGEX = r'^\d{2}\.\d{1,2}\.\d'
RELEASE_NOTES_REGEX = re.escape(UNRELEASE_HEADER) + r'([\s\S]+?)## \[\d{2}\.\d{1,2}\.\d\] - \d{4}-\d{2}-\d{2}'

# Beta integration disclaimer
BETA_INTEGRATION_DISCLAIMER = 'Note: This is a beta Integration,' \
                              ' which lets you implement and test pre-release software. ' \
                              'Since the integration is beta, it might contain bugs. ' \
                              'Updates to the integration during the beta phase might include ' \
                              'non-backward compatible features. We appreciate your feedback on ' \
                              'the quality and usability of the integration to help us identify issues, ' \
                              'fix them, and continually improve.'

# Integration categories according to the schema
INTEGRATION_CATEGORIES = ['Analytics & SIEM', 'Utilities', 'Messaging', 'Endpoint', 'Network Security',
                          'Vulnerability Management', 'Case Management', 'Forensics & Malware Analysis',
                          'IT Services', 'Data Enrichment & Threat Intelligence', 'Authentication', 'Database',
                          'Deception', 'Email Gateway', 'Identity and Access Management', 'File Integrity Management']
SCHEMA_TO_REGEX = {
    'integration': YML_INTEGRATION_REGEXES,
    'playbook': YML_ALL_PLAYBOOKS_REGEX,
    'script': YML_SCRIPT_REGEXES,
    'widget': JSON_ALL_WIDGETS_REGEXES,
    'dashboard': JSON_ALL_DASHBOARDS_REGEXES,
    'canvas-context-connections': JSON_ALL_CONNECTIONS_REGEXES,
    'classifier_5_9_9': JSON_ALL_CLASSIFIER_REGEXES_5_9_9,
    'classifier': JSON_ALL_CLASSIFIER_REGEXES,
    'mapper': JSON_ALL_MAPPER_REGEXES,
    'layoutscontainer': JSON_ALL_LAYOUTS_CONTAINER_REGEXES,
    'layout': JSON_ALL_LAYOUT_REGEXES,
    'pre-process-rules': JSON_ALL_PRE_PROCESS_RULES_REGEXES,
    'incidentfield': JSON_ALL_INCIDENT_FIELD_REGEXES + JSON_ALL_INDICATOR_FIELDS_REGEXES,
    'incidenttype': JSON_ALL_INCIDENT_TYPES_REGEXES,
    'image': [IMAGE_REGEX],
    'reputation': JSON_ALL_INDICATOR_TYPES_REGEXES,
    'reputations': JSON_ALL_REPUTATIONS_INDICATOR_TYPES_REGEXES,
    'readme': [PACKS_INTEGRATION_README_REGEX,
               PACKS_INTEGRATION_NON_SPLIT_README_REGEX,
               PLAYBOOK_README_REGEX,
               PACKS_SCRIPT_README_REGEX,
               PACKS_SCRIPT_NON_SPLIT_README_REGEX
               ],

    'report': [PACKS_REPORT_JSON_REGEX],
    'release-notes': [PACKS_RELEASE_NOTES_REGEX],
    'genericfield': JSON_ALL_GENERIC_FIELDS_REGEXES,
    'generictype': JSON_ALL_GENERIC_TYPES_REGEXES,
    'genericmodule': JSON_ALL_GENERIC_MODULES_REGEXES,
    'genericdefinition': JSON_ALL_GENERIC_DEFINITIONS_REGEXES,
    JOB: JSON_ALL_JOB_REGEXES
}

EXTERNAL_PR_REGEX = r'^pull/(\d+)$'

FILE_TYPES_PATHS_TO_VALIDATE = {
    'reports': JSON_ALL_REPORTS_REGEXES
}

DEF_DOCKER = 'demisto/python:1.3-alpine'
DEF_DOCKER_PWSH = 'demisto/powershell:6.2.3.5563'

DIR_TO_PREFIX = {
    'Integrations': INTEGRATION_PREFIX,
    'Scripts': SCRIPT_PREFIX
}

ENTITY_NAME_SEPARATORS = [' ', '_', '-']

DELETED_YML_FIELDS_BY_DEMISTO = ['fromversion', 'toversion', 'alt_dockerimages', 'script.dockerimage45', 'tests',
                                 'defaultclassifier', 'defaultmapperin', 'defaultmapperout']

DELETED_JSON_FIELDS_BY_DEMISTO = ['fromVersion', 'toVersion']

FILE_EXIST_REASON = 'File already exist'
FILE_NOT_IN_CC_REASON = 'File does not exist in Demisto instance'

ACCEPTED_FILE_EXTENSIONS = [
    '.yml', '.json', '.md', '.py', '.js', '.ps1', '.png', '', '.lock'
]
ENDPOINT_COMMAND_NAME = 'endpoint'

REPUTATION_COMMAND_NAMES = {'file', 'email', 'domain', 'url', 'ip', 'cve'}

BANG_COMMAND_NAMES = {'file', 'email', 'domain', 'url', 'ip', 'cve', 'endpoint'}

BANG_COMMAND_ARGS_MAPPING_DICT: Dict[str, dict] = {
    'file': {'default': ['file'], },
    'email': {'default': ['email']},
    'domain': {'default': ['domain']},
    'url': {'default': ['url']},
    'ip': {'default': ['ip']},
    'cve': {'default': ['cve', 'cve_id']},
    'endpoint': {'default': ['ip'], 'required': False}
}

ENDPOINT_FLEXIBLE_REQUIRED_ARGS = ["ip", "id", "hostname"]

GENERIC_COMMANDS_NAMES = BANG_COMMAND_NAMES.union({'send-mail', 'send-notification', 'cve-latest', 'cve-search'})

DBOT_SCORES_DICT = {
    'DBotScore.Indicator': 'The indicator that was tested.',
    'DBotScore.Type': 'The indicator type.',
    'DBotScore.Vendor': 'The vendor used to calculate the score.',
    'DBotScore.Score': 'The actual score.'
}

IOC_OUTPUTS_DICT = {
    'domain': {'Domain.Name'},
    'file': {'File.MD5', 'File.SHA1', 'File.SHA256'},
    'ip': {'IP.Address'},
    'url': {'URL.Data'},
    'endpoint': {'Endpoint.Hostname', 'Endpoint.IPAddress', 'Endpoint.ID'}
}
XSOAR_SUPPORT = "xsoar"
XSOAR_AUTHOR = "Cortex XSOAR"
PACK_INITIAL_VERSION = '1.0.0'
PACK_SUPPORT_OPTIONS = ['xsoar', 'partner', 'developer', 'community']
XSOAR_CONTEXT_STANDARD_URL = "https://xsoar.pan.dev/docs/integrations/context-standards"
XSOAR_SUPPORT_URL = "https://www.paloaltonetworks.com/cortex"
MARKETPLACE_LIVE_DISCUSSIONS = \
    'https://live.paloaltonetworks.com/t5/cortex-xsoar-discussions/bd-p/Cortex_XSOAR_Discussions'
EXCLUDED_DISPLAY_NAME_WORDS = ['partner', 'community']
MARKETPLACES = ['xsoar', 'marketplacev2']

DEFAULT_CONTENT_ITEM_FROM_VERSION = '0.0.0'
DEFAULT_CONTENT_ITEM_TO_VERSION = '99.99.99'
MARKETPLACE_MIN_VERSION = '6.0.0'
DEFAULT_JOB_FROM_VERSION = '6.5.0'
OLDEST_SUPPORTED_VERSION = '5.0.0'
LAYOUTS_CONTAINERS_OLDEST_SUPPORTED_VERSION = '6.0.0'
GENERIC_OBJECTS_OLDEST_SUPPORTED_VERSION = '6.5.0'
FEATURE_BRANCHES = ['v4.5.0']

BASE_PACK = "Base"
NON_SUPPORTED_PACK = "NonSupported"
DEPRECATED_CONTENT_PACK = "DeprecatedContent"
IGNORED_DEPENDENCY_CALCULATION = {BASE_PACK, NON_SUPPORTED_PACK, DEPRECATED_CONTENT_PACK}
COMMON_TYPES_PACK = 'CommonTypes'

FEED_REQUIRED_PARAMS = [
    {
        'name': 'feed',
        'must_equal': {
            'defaultvalue': 'true',
            'display': 'Fetch indicators',
            'type': 8,
            'required': False
        },
        'must_contain': {}
    },
    {
        'name': 'feedReputation',
        'must_equal': {
            'display': 'Indicator Reputation',
            'type': 18,
            'required': False,
            'options': ['None', 'Good', 'Suspicious', 'Bad']
        },
        'must_contain': {
            'additionalinfo': 'Indicators from this integration instance will be marked with this reputation'
        }
    },
    {
        'name': 'feedReliability',
        'must_equal': {
            'display': 'Source Reliability',
            'type': 15,
            'required': True,
            'options': [
                'A - Completely reliable', 'B - Usually reliable', 'C - Fairly reliable', 'D - Not usually reliable',
                'E - Unreliable', 'F - Reliability cannot be judged']
        },
        'must_contain': {
            'additionalinfo': 'Reliability of the source providing the intelligence data'
        }
    },
    {
        'name': 'feedExpirationPolicy',
        'must_equal': {
            'display': "",
            'type': 17,
            'required': False,
            'options': ['never', 'interval', 'indicatorType', 'suddenDeath']
        },
        'must_contain': {}
    },
    {
        'name': 'feedExpirationInterval',
        'must_equal': {
            'display': "",
            'type': 1,
            'required': False
        },
        'must_contain': {}
    },
    {
        'name': 'feedFetchInterval',
        'must_equal': {
            'display': 'Feed Fetch Interval',
            'type': 19,
            'required': False
        },
        'must_contain': {}
    },
    {
        'name': 'feedBypassExclusionList',
        'must_equal': {
            'display': 'Bypass exclusion list',
            'type': 8,
            'required': False
        },
        'must_contain': {
            'additionalinfo': 'When selected, the exclusion list is ignored for indicators from this feed.'
                              ' This means that if an indicator from this feed is on the exclusion list,'
                              ' the indicator might still be added to the system.'
        }
    },
    {
        'name': 'feedTags',
        'must_equal': {
            'display': 'Tags',
            'required': False,
            'type': 0
        },
        'must_contain': {
            'additionalinfo': 'Supports CSV values.'
        }
    },
    {
        'name': 'tlp_color',
        'must_equal': {
            'display': 'Traffic Light Protocol Color',
            'options': ['RED', 'AMBER', 'GREEN', 'WHITE'],
            'required': False,
            'type': 15
        },
        'must_contain': {
            'additionalinfo': 'The Traffic Light Protocol (TLP) designation to apply to indicators fetched from the '
                              'feed'
        }
    }
]

FETCH_REQUIRED_PARAMS = [
    {
        'display': 'Incident type',
        'name': 'incidentType',
        'required': False,
        'type': 13
    },
    {
        'display': 'Fetch incidents',
        'name': 'isFetch',
        'required': False,
        'type': 8
    }
]

MAX_FETCH_PARAM = {
    'name': 'max_fetch',
    'required': False,
    'type': 0,
    'defaultvalue': '50'
}

# for reference, the defaultvalue and display are not enforced.
FIRST_FETCH_PARAM = {
    'defaultvalue': '7 days',
    'display': 'First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)',
    'name': 'first_fetch',
    'required': False,
    'type': 0
}

DOCS_COMMAND_SECTION_REGEX = r'(?:###\s{}).+?(?:(?=(?:\n###\s))|(?=(?:\n##\s))|\Z)'
# Ignore list for all 'run_all_validations_on_file' method
ALL_FILES_VALIDATION_IGNORE_WHITELIST = [
    'pack_metadata.json',  # this file is validated under 'validate_pack_unique_files' method
    'testdata',
    'test_data',
    'data_test',
    'testcommandsfunctions',
    'testhelperfunctions',
    'stixdecodetest',
    'testcommands',
    'setgridfield_test',
    'ipnetwork_test',
    'test-data',
    'testplaybook'
]
VALIDATED_PACK_ITEM_TYPES = [
    'Playbooks',
    'Integration',
    'Script',
    'IncidentFields',
    'IncidentTypes',
    'Classifiers',
    'Layouts',
    'PreProcessRules',
    'Lists',
    'Jobs'
]

FIRST_FETCH = 'first_fetch'

MAX_FETCH = 'max_fetch'

SKIP_RELEASE_NOTES_FOR_TYPES = (FileType.RELEASE_NOTES, FileType.README, FileType.TEST_PLAYBOOK,
                                FileType.TEST_SCRIPT, FileType.DOC_IMAGE, FileType.AUTHOR_IMAGE, None,
                                FileType.RELEASE_NOTES_CONFIG, FileType.CONTRIBUTORS)

LAYOUT_AND_MAPPER_BUILT_IN_FIELDS = ['indicatortype', 'source', 'comment', 'aggregatedreliability', 'detectedips',
                                     'detectedhosts', 'modified', 'expiration', 'timestamp', 'shortdesc',
                                     'short_description', 'description', 'Tags', 'blocked']

UUID_REGEX = r'[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}'

DEFAULT_ID_SET_PATH = "./Tests/id_set.json"
MP_V2_ID_SET_PATH = "./Tests/id_set_mp_v2.json"
METADATA_FILE_NAME = 'pack_metadata.json'

CONTEXT_OUTPUT_README_TABLE_HEADER = '| **Path** | **Type** | **Description** |'

ARGUMENT_FIELDS_TO_CHECK = ['defaultValue', 'required', 'isArray']

PARAM_FIELDS_TO_CHECK = ['defaultvalue', 'type', 'required']

INTEGRATION_ARGUMENT_TYPES = {
    '0': 'ShortText',
    '4': 'Encrypted',
    '8': 'Boolean',
    '9': 'Authentication',
    '12': 'LongText',
    '15': 'SingleSelect',
    '16': 'MultiSelect'
}

BUILD_IN_COMMANDS = ['getIncidents', 'DeleteContext', 'isWhitelisted', 'excludeIndicators',
                     'deleteIndicators', 'extractIndicators']


class ContentItems(Enum):
    # the format is defined in issue #19786, may change in the future
    SCRIPTS = 'automation'
    PLAYBOOKS = 'playbook'
    INTEGRATIONS = 'integration'
    INCIDENT_FIELDS = 'incidentfield'
    INCIDENT_TYPES = 'incidenttype'
    DASHBOARDS = 'dashboard'
    INDICATOR_FIELDS = 'indicatorfield'
    REPORTS = 'report'
    INDICATOR_TYPES = 'reputation'
    LAYOUTS = 'layoutscontainer'
    CLASSIFIERS = 'classifier'
    WIDGETS = 'widget'
    GENERIC_MODULES = 'genericmodule'
    GENERIC_DEFINITIONS = 'genericdefinition'
    GENERIC_FIELDS = 'genericfield'
    GENERIC_TYPES = 'generictype'
    PRE_PROCESS_RULES = 'pre-process-rule'
    LISTS = 'list'
    JOB = 'job'


CONTENT_ITEMS_DISPLAY_FOLDERS = {
    SCRIPTS_DIR,
    DASHBOARDS_DIR,
    INCIDENT_FIELDS_DIR,
    INCIDENT_TYPES_DIR,
    INTEGRATIONS_DIR,
    PLAYBOOKS_DIR,
    INDICATOR_FIELDS_DIR,
    REPORTS_DIR,
    INDICATOR_TYPES_DIR,
    LAYOUTS_DIR,
    PRE_PROCESS_RULES_DIR,
    CLASSIFIERS_DIR,
    WIDGETS_DIR,
    JOBS_DIR,
    LISTS_DIR
}


class PathLevel(Enum):
    PACK = 'Pack',
    CONTENT_ENTITY_DIR = 'ContentDir',
    PACKAGE = 'Package',
    FILE = 'File'
    CONTENT_GENERIC_ENTITY_DIR = 'ContentGenericDir'


class DemistoException(Exception):
    pass


UUID_REGEX = r'([\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{8,12})'


class IronBankDockers:
    API_LINK = 'https://repo1.dso.mil/api/v4/projects/dsop%2Fopensource%2Fpalo-alto-networks%2Fdemisto%2F'


class MarketplaceVersions(Enum):
    XSOAR = 'xsoar'
    MarketplaceV2 = 'marketplacev2'


# demisto_sdk/commands/common/default_additional_info_loader.py

import json
from pathlib import Path

from requests.structures import CaseInsensitiveDict


def load_default_additional_info_dict() -> CaseInsensitiveDict:
    """ returns a CaseInsensitiveDict"""
    with (Path(__file__).parent / 'default_additional_info.json').open() as f:
        # Case insensitive to catch both `API key` and `API Key`, giving both the same value.
        return CaseInsensitiveDict(json.load(f))


# demisto_sdk/commands/common/docker_util.py

import io
import os
import tarfile
from typing import Dict, List, Optional, Union

import docker

from demisto_sdk.commands.common.logger import logger

client = docker.from_env()


class ContainerRunner:
    def __init__(self, image: str, container_name: Optional[str] = None):
        """

        Args:
            image: the image (name or id)
            container_name: you can give your container a meaningful name (optional)
        """
        self._base_image_name = image
        self._image_name = image
        self.container_name = container_name
        self._container_obj: Optional[docker.models.containers.Container] = None

    @property
    def container(self) -> docker.models.containers.Container:
        """
        Notes:
            try to avoid using the container in this way.
        Returns:
            a container object (docker.models.containers.Container).
        """
        if self._container_obj is None:
            self._create_container()
        return self._container_obj

    def _create_container(self, **kwargs):
        if self._container_obj:
            self.remove_container()
        self._container_obj = client.containers.create(image=self._image_name, name=self.container_name, **kwargs)

    def exec(self, command: Union[str, List[str]], **kwargs) -> Dict[str, Union[bytes, Dict, int]]:
        """
        Args:
            command: the command to run inside the docker container
            **kwargs: extra args to pass to the docker container (according to the docker API)

        Returns:
            a dict contains 3 elements:
                1. (Outputs) the container stdout
                2. (StatusCode) the command exit code
                3. (Error) the container stderr
        """
        # one important thing that you need to know about docker is that a dead container is basically an image.
        # and that is the reason why do we need to commit to create the image.
        # so we can build a new container on the basis of this dead container.
        if self._container_obj is not None:
            self._image_name = self.container.commit().id
            self.remove_container()
        kwargs['detach'] = True
        self._create_container(command=command, **kwargs)
        try:
            self.container.start()
        except Exception as error:
            logger.debug(error)
        return {**self.container.wait(), 'Outputs': self.container.logs()}

    def import_file(self, file_bytes: bytes, file_dest: str) -> bool:
        """

        Args:
            file_bytes: the file data in bytes
            file_dest: the file destination (e.g. path in the container)

        Returns:
            True if the operation was successful False otherwise
        """
        tar_byte_file = io.BytesIO()
        dir_dest, file_name = os.path.split(file_dest)
        if not dir_dest:
            dir_dest = '.'
        with tarfile.open(fileobj=tar_byte_file, mode='w') as temp_tar_file:
            temp_tar_file.addfile(**self._create_tar_info(file_name, file_bytes))
        tar_byte_file.seek(0)
        return self.container.put_archive(dir_dest, tar_byte_file.read())

    def export_file(self, file_src: str) -> bytes:
        """

        Args:
            file_src: the path to file in the container

        Returns:
            the file data in bytes
        """
        archive, stat = self.container.get_archive(f'{file_src}')
        byte_file = io.BytesIO(initial_bytes=b''.join(archive))
        byte_file.seek(0)
        with tarfile.open(fileobj=byte_file) as tar_file:
            io_obj = tar_file.extractfile(file_src.split(os.path.sep)[-1])
            return io_obj.read() if io_obj else b''

    def remove_container(self):
        """
        removing the container (e.g. docker rm)
        """
        if self._container_obj:
            self._container_obj.remove()
            self._container_obj = None

    def remove_images(self):
        """
        removing the images created by the commits along the containers live (see the explanation in the exec comment)
        """
        self.remove_container()
        if not (self._image_name == self._base_image_name):
            DockerTools.remove_image(self._image_name)
            self._image_name = self._base_image_name

    def __del__(self):
        self.remove_images()

    @staticmethod
    def _create_tar_info(name: str, file_bytes: bytes):
        file_data = io.BytesIO(initial_bytes=file_bytes)
        file_data.name = name
        file_data.seek(0)
        tar_info = tarfile.TarInfo(name=name)
        tar_info.size = file_data.getbuffer().nbytes
        return {'tarinfo': tar_info, 'fileobj': file_data}


class DockerTools:
    """
    docker extra tools that will be usefully also as stand alone commands
    """
    @staticmethod
    def remove_container(name_or_id: str, ignore_container_not_found: bool = True, force: bool = False):
        """
        Examples
            docker rm MyTestContainer
            can be replaced with
            DockerTools.remove_container("MyTestContainer")
            in your code
        Args:
            name_or_id: the name or id of the container
            ignore_container_not_found: don't raise an exception in case the container already removed
            force: same as the -f option in the "docker rm" command

        Raises:
            docker.errors.NotFound in case that the container doesn't exist
        """
        try:
            client.containers.get(name_or_id).remove(force=force)
        except docker.errors.NotFound as not_found:
            if not ignore_container_not_found:
                raise not_found

    @staticmethod
    def remove_image(name_or_id: str, ignore_image_not_found: bool = True, force: bool = False):
        """
        Examples
            docker rmi MyTestImage
            can be replaced with
            DockerTools.remove_image("MyTestImage")
            in your code
        Args:
            name_or_id: the name or id of the image
            ignore_image_not_found: don't raise an exception in case the image already removed
            force: same as the -f option in the "docker rmi" command

        Raises:
            docker.errors.ImageNotFound in case that the image doesn't exist

        """
        try:
            client.images.remove(name_or_id, force=force)
        except docker.errors.ImageNotFound as not_found:
            if not ignore_image_not_found:
                raise not_found


# demisto_sdk/commands/common/errors.py

from typing import Any, Dict, List, Optional

import decorator

from demisto_sdk.commands.common.constants import (BETA_INTEGRATION_DISCLAIMER,
                                                   CONF_PATH,
                                                   DEFAULT_JOB_FROM_VERSION,
                                                   INTEGRATION_CATEGORIES,
                                                   PACK_METADATA_DESC,
                                                   PACK_METADATA_NAME)

FOUND_FILES_AND_ERRORS: list = []
FOUND_FILES_AND_IGNORED_ERRORS: list = []
ALLOWED_IGNORE_ERRORS = [
    'BA101', 'BA106', 'BA108', 'BA109', 'BA110', 'BA111', 'BA112', 'BA113',
    'DS107',
    'GF102',
    'IF100', 'IF106', 'IF115',
    'IN109', 'IN110', 'IN122', 'IN124', 'IN126', 'IN128', 'IN135', 'IN136', 'IN139', 'IN144', 'IN145',
    'MP106',
    'PA113', 'PA116', 'PA124', 'PA125', 'PA127', 'PA129',
    'PB104', 'PB105', 'PB106', 'PB110', 'PB111', 'PB112', 'PB114', 'PB115', 'PB116', 'PB107',
    'RM100', 'RM102', 'RM104', 'RM106',
    'RP102', 'RP104',
    'SC100', 'SC101', 'SC105', 'SC106',
]

PRESET_ERROR_TO_IGNORE = {
    'community': ['BC', 'CJ', 'DS100', 'DS101', 'DS102', 'DS103', 'DS104', 'IN125', 'IN126', 'IN140'],
    'partner': ['CJ', 'IN140']
}

PRESET_ERROR_TO_CHECK = {
    "deprecated": ['ST', 'BC', 'BA', 'IN127', 'IN128', 'PB104', 'SC101'],
}

ERROR_CODE = {
    # BA - Basic
    "wrong_version": {'code': "BA100", 'ui_applicable': False, 'related_field': 'version'},
    "id_should_equal_name": {'code': "BA101", 'ui_applicable': False, 'related_field': 'id'},
    "file_type_not_supported": {'code': "BA102", 'ui_applicable': False, 'related_field': ''},
    "file_name_include_spaces_error": {'code': "BA103", 'ui_applicable': False, 'related_field': ''},
    "changes_may_fail_validation": {'code': "BA104", 'ui_applicable': False, 'related_field': ''},
    "invalid_id_set": {'code': "BA105", 'ui_applicable': False, 'related_field': ''},
    "no_minimal_fromversion_in_file": {'code': "BA106", 'ui_applicable': False, 'related_field': 'fromversion'},
    "running_on_master_with_git": {'code': "BA107", 'ui_applicable': False, 'related_field': ''},
    "folder_name_has_separators": {'code': "BA108", 'ui_applicable': False, 'related_field': ''},
    "file_name_has_separators": {'code': "BA109", 'ui_applicable': False, 'related_field': ''},
    "field_contain_forbidden_word": {'code': "BA110", 'ui_applicable': False, 'related_field': ''},
    'entity_name_contains_excluded_word': {'code': 'BA111', 'ui_applicable': False, 'related_field': ''},
    "spaces_in_the_end_of_id": {'code': "BA112", 'ui_applicable': False, 'related_field': 'id'},
    "spaces_in_the_end_of_name": {'code': "BA113", 'ui_applicable': False, 'related_field': 'name'},

    # BC - Backward Compatible
    "breaking_backwards_subtype": {'code': "BC100", 'ui_applicable': False, 'related_field': 'subtype'},
    "breaking_backwards_context": {'code': "BC101", 'ui_applicable': False, 'related_field': 'contextPath'},
    "breaking_backwards_command": {'code': "BC102", 'ui_applicable': False, 'related_field': 'contextPath'},
    "breaking_backwards_arg_changed": {'code': "BC103", 'ui_applicable': False, 'related_field': 'name'},
    "breaking_backwards_command_arg_changed": {'code': "BC104", 'ui_applicable': False, 'related_field': 'args'},

    # CJ - conf.json
    "description_missing_from_conf_json": {'code': "CJ100", 'ui_applicable': False, 'related_field': ''},
    "test_not_in_conf_json": {'code': "CJ101", 'ui_applicable': False, 'related_field': ''},
    "integration_not_registered": {'code': "CJ102", 'ui_applicable': False, 'related_field': ''},
    "no_test_playbook": {'code': "CJ103", 'ui_applicable': False, 'related_field': ''},
    "test_playbook_not_configured": {'code': "CJ104", 'ui_applicable': False, 'related_field': ''},
    "all_entity_test_playbooks_are_skipped": {'code': "CJ105", 'ui_applicable': False, 'related_field': ''},

    # CL - Classifiers
    "invalid_to_version_in_new_classifiers": {'code': "CL100", 'ui_applicable': False, 'related_field': 'toVersion'},
    "invalid_to_version_in_old_classifiers": {'code': "CL101", 'ui_applicable': False, 'related_field': 'toVersion'},
    "invalid_from_version_in_new_classifiers": {'code': "CL102", 'ui_applicable': False,
                                                'related_field': 'fromVersion'},
    "invalid_from_version_in_old_classifiers": {'code': "CL103", 'ui_applicable': False,
                                                'related_field': 'fromVersion'},
    "missing_from_version_in_new_classifiers": {'code': "CL104", 'ui_applicable': False,
                                                'related_field': 'fromVersion'},
    "missing_to_version_in_old_classifiers": {'code': "CL105", 'ui_applicable': False, 'related_field': 'toVersion'},
    "from_version_higher_to_version": {'code': "CL106", 'ui_applicable': False, 'related_field': 'fromVersion'},
    "invalid_type_in_new_classifiers": {'code': "CL107", 'ui_applicable': False, 'related_field': 'type'},
    "classifier_non_existent_incident_types": {'code': "CL108", 'ui_applicable': False,
                                               'related_field': 'incident_types'},

    # DA - Dashboards
    "remove_field_from_dashboard": {'code': "DA100", 'ui_applicable': False, 'related_field': ''},
    "include_field_in_dashboard": {'code': "DA101", 'ui_applicable': False, 'related_field': ''},

    # DB - DBot
    "dbot_invalid_output": {'code': "DB100", 'ui_applicable': True, 'related_field': 'contextPath'},
    "dbot_invalid_description": {'code': "DB101", 'ui_applicable': True, 'related_field': 'description'},

    # DO - Docker Images
    "default_docker_error": {'code': "DO100", 'ui_applicable': True, 'related_field': 'dockerimage'},
    "latest_docker_error": {'code': "DO101", 'ui_applicable': True, 'related_field': 'dockerimage'},
    "not_demisto_docker": {'code': "DO102", 'ui_applicable': True, 'related_field': 'dockerimage'},
    "docker_tag_not_fetched": {'code': "DO103", 'ui_applicable': True, 'related_field': 'dockerimage'},
    "no_docker_tag": {'code': "DO104", 'ui_applicable': True, 'related_field': 'dockerimage'},
    "docker_not_formatted_correctly": {'code': "DO105", 'ui_applicable': True, 'related_field': 'dockerimage'},
    "docker_not_on_the_latest_tag": {'code': "DO106", 'ui_applicable': True, 'related_field': 'dockerimage'},
    "non_existing_docker": {'code': "DO107", 'ui_applicable': True, 'related_field': 'dockerimage'},
    "dockerimage_not_in_yml_file": {'code': "DO108", 'ui_applicable': True, 'related_field': 'dockerimage'},

    # DS - Descriptions
    "description_missing_in_beta_integration": {'code': "DS100", 'ui_applicable': False, 'related_field': ''},
    "no_beta_disclaimer_in_description": {'code': "DS101", 'ui_applicable': False, 'related_field': ''},
    "no_beta_disclaimer_in_yml": {'code': "DS102", 'ui_applicable': False, 'related_field': ''},
    "description_in_package_and_yml": {'code': "DS103", 'ui_applicable': False, 'related_field': ''},
    "no_description_file_warning": {'code': "DS104", 'ui_applicable': False, 'related_field': ''},
    "description_contains_contrib_details": {'code': "DS105", 'ui_applicable': False,
                                             'related_field': 'detaileddescription'},
    "invalid_description_name": {'code': "DS106", 'ui_applicable': False, 'related_field': ''},
    "description_contains_demisto_word": {'code': "DS107", 'ui_applicable': True,
                                          'related_field': 'detaileddescription'},

    # GF - Generic Fields
    "invalid_generic_field_group_value": {'code': "GF100", 'ui_applicable': False, 'related_field': 'group'},
    "invalid_generic_field_id": {'code': "GF101", 'ui_applicable': False, 'related_field': 'id'},
    "unsearchable_key_should_be_true_generic_field": {'code': "GF102", 'ui_applicable': False, 'related_field': 'unsearchable'},

    # ID - ID Set
    "id_set_conflicts": {'code': "ID100", 'ui_applicable': False, 'related_field': ''},
    # missing 101
    "duplicated_id": {'code': "ID102", 'ui_applicable': False, 'related_field': ''},
    "no_id_set_file": {'code': "ID103", 'ui_applicable': False, 'related_field': ''},

    # IF - Incident Fields
    "invalid_incident_field_name": {'code': "IF100", 'ui_applicable': True, 'related_field': 'name'},
    "invalid_incident_field_content_key_value": {'code': "IF101", 'ui_applicable': False, 'related_field': 'content'},
    "invalid_incident_field_system_key_value": {'code': "IF102", 'ui_applicable': False, 'related_field': 'system'},
    "invalid_incident_field_type": {'code': "IF103", 'ui_applicable': True, 'related_field': 'type'},
    "invalid_incident_field_group_value": {'code': "IF104", 'ui_applicable': False, 'related_field': 'group'},
    "invalid_incident_field_cli_name_regex": {'code': "IF105", 'ui_applicable': False, 'related_field': 'cliName'},
    "invalid_incident_field_cli_name_value": {'code': "IF106", 'ui_applicable': True, 'related_field': 'cliName'},
    # missing 107
    "invalid_incident_field_or_type_from_version": {'code': "IF108", 'ui_applicable': False,
                                                    'related_field': 'fromVersion'},
    "new_incident_field_required": {'code': "IF109", 'ui_applicable': True, 'related_field': 'required'},
    "from_version_modified_after_rename": {'code': "IF110", 'ui_applicable': False, 'related_field': 'fromVersion'},
    "incident_field_type_change": {'code': "IF111", 'ui_applicable': False, 'related_field': 'type'},
    "indicator_field_type_grid_minimal_version": {'code': "IF112", 'ui_applicable': False,
                                                  'related_field': 'fromVersion'},
    "invalid_incident_field_prefix": {'code': "IF113", 'ui_applicable': False, 'related_field': 'name'},
    "incident_field_non_existent_script_id": {'code': "IF114", 'ui_applicable': False, 'related_field': ''},
    "unsearchable_key_should_be_true_incident_field": {'code': "IF115", 'ui_applicable': False, 'related_field': 'unsearchable'},

    # IM - Images
    "no_image_given": {'code': "IM100", 'ui_applicable': True, 'related_field': 'image'},
    "image_too_large": {'code': "IM101", 'ui_applicable': True, 'related_field': 'image'},
    "image_in_package_and_yml": {'code': "IM102", 'ui_applicable': False, 'related_field': 'image'},
    "not_an_image_file": {'code': "IM103", 'ui_applicable': False, 'related_field': 'image'},
    "no_image_field_in_yml": {'code': "IM104", 'ui_applicable': True, 'related_field': 'image'},
    "image_field_not_in_base64": {'code': "IM105", 'ui_applicable': True, 'related_field': 'image'},
    "default_image_error": {'code': "IM106", 'ui_applicable': True, 'related_field': 'image'},
    "invalid_image_name": {'code': "IM107", 'ui_applicable': False, 'related_field': 'image'},
    "image_is_empty": {'code': "IM108", 'ui_applicable': True, 'related_field': 'image'},
    "author_image_is_missing": {'code': "IM109", 'ui_applicable': True, 'related_field': 'image'},

    # IN - Integrations
    "wrong_display_name": {'code': "IN100", 'ui_applicable': True, 'related_field': '<parameter-name>.display'},
    "wrong_default_parameter_not_empty": {'code': "IN101", 'ui_applicable': True,
                                          'related_field': '<parameter-name>.default'},
    "wrong_required_value": {'code': "IN102", 'ui_applicable': True, 'related_field': '<parameter-name>.required'},
    "wrong_required_type": {'code': "IN103", 'ui_applicable': True, 'related_field': '<parameter-name>.type'},
    "wrong_category": {'code': "IN104", 'ui_applicable': True, 'related_field': 'category'},
    "wrong_default_argument": {'code': "IN105", 'ui_applicable': True, 'related_field': '<argument-name>.default'},
    "no_default_arg": {'code': "IN106", 'ui_applicable': True, 'related_field': '<argument-name>.default'},
    "missing_reputation": {'code': "IN107", 'ui_applicable': True, 'related_field': 'outputs'},
    "wrong_subtype": {'code': "IN108", 'ui_applicable': False, 'related_field': 'subtype'},
    "beta_in_id": {'code': "IN109", 'ui_applicable': False, 'related_field': 'id'},
    "beta_in_name": {'code': "IN110", 'ui_applicable': False, 'related_field': 'name'},
    "beta_field_not_found": {'code': "IN111", 'ui_applicable': False, 'related_field': 'beta'},
    "no_beta_in_display": {'code': "IN112", 'ui_applicable': False, 'related_field': 'display'},
    "duplicate_arg_in_file": {'code': "IN113", 'ui_applicable': True, 'related_field': 'arguments'},
    "duplicate_param": {'code': "IN114", 'ui_applicable': True, 'related_field': 'configuration'},
    "invalid_context_output": {'code': "IN115", 'ui_applicable': True, 'related_field': 'outputs'},
    "added_required_fields": {'code': "IN116", 'ui_applicable': False, 'related_field': '<parameter-name>.required'},
    "not_used_display_name": {'code': "IN117", 'ui_applicable': True, 'related_field': '<parameter-name>.display'},
    "empty_display_configuration": {'code': "IN118", 'ui_applicable': True,
                                    'related_field': '<parameter-name>.display'},
    "feed_wrong_from_version": {'code': "IN119", 'ui_applicable': False, 'related_field': 'fromversion'},
    "pwsh_wrong_version": {'code': "IN120", 'ui_applicable': False, 'related_field': 'fromversion'},
    "parameter_missing_from_yml": {'code': "IN121", 'ui_applicable': True, 'related_field': 'configuration'},
    "parameter_missing_for_feed": {'code': "IN122", 'ui_applicable': True, 'related_field': 'configuration'},
    "invalid_version_integration_name": {'code': "IN123", 'ui_applicable': True, 'related_field': 'display'},
    "found_hidden_param": {'code': "IN124", 'ui_applicable': False, 'related_field': '<parameter-name>.hidden'},
    "no_default_value_in_parameter": {'code': "IN125", 'ui_applicable': False,
                                      'related_field': '<parameter-name>.default'},
    "parameter_missing_from_yml_not_community_contributor": {'code': "IN126", 'ui_applicable': False,
                                                             'related_field': 'configuration'},
    "invalid_deprecated_integration_display_name": {'code': "IN127", 'ui_applicable': False,
                                                    'related_field': 'display'},
    "invalid_deprecated_integration_description": {'code': "IN128", 'ui_applicable': False, 'related_field': ''},
    "removed_integration_parameters": {'code': "IN129", 'ui_applicable': False, 'related_field': 'configuration'},
    "integration_not_runnable": {'code': "IN130", 'ui_applicable': False, 'related_field': 'configuration'},
    "missing_get_mapping_fields_command": {'code': "IN131", 'ui_applicable': False, 'related_field': 'ismappable'},
    "integration_non_existent_classifier": {'code': "IN132", 'ui_applicable': False, 'related_field': 'classifiers'},
    "integration_non_existent_mapper": {'code': "IN133", 'ui_applicable': False, 'related_field': 'mappers'},
    "multiple_default_arg": {'code': "IN134", "ui_applicable": True, 'related_field': "arguments"},
    "invalid_integration_parameters_display_name": {'code': "IN135", 'ui_applicable': True, 'related_field': 'display'},
    "missing_output_context": {'code': "IN136", 'ui_applicable': True, 'related_field': 'contextOutput'},
    "is_valid_integration_file_path_in_folder": {'code': "IN137", 'ui_applicable': False, 'related_field': ''},
    "is_valid_integration_file_path_in_integrations_folder": {'code': "IN138", 'ui_applicable': False,
                                                              'related_field': ''},
    "incident_in_command_name_or_args": {'code': "IN139", "ui_applicable": False,
                                         'related_field': 'script.commands.name'},
    "integration_is_skipped": {'code': "IN140", 'ui_applicable': False, 'related_field': ''},
    "reputation_missing_argument": {'code': "IN141", 'ui_applicable': True, 'related_field': '<argument-name>.default'},
    "non_default_additional_info": {'code': "IN142", 'ui_applicable': True, 'related_field': 'additionalinfo'},
    "missing_default_additional_info": {'code': "IN143", 'ui_applicable': True, 'related_field': 'additionalinfo'},
    "wrong_is_array_argument": {'code': "IN144", 'ui_applicable': True, 'related_field': '<argument-name>.default'},
    "api_token_is_not_in_credential_type": {'code': "IN145", 'ui_applicable': True, 'related_field': '<argument-name>.type'},
    "fromlicense_in_parameters": {'code': "IN146", 'ui_applicable': True,
                                  'related_field': '<parameter-name>.fromlicense'},
    "changed_integration_yml_fields": {'code': "IN147", "ui_applicable": False, 'related_field': 'script'},

    # IT - Incident Types
    "incident_type_integer_field": {'code': "IT100", 'ui_applicable': True, 'related_field': ''},
    "incident_type_invalid_playbook_id_field": {'code': "IT101", 'ui_applicable': False, 'related_field': 'playbookId'},
    "incident_type_auto_extract_fields_invalid": {'code': "IT102", 'ui_applicable': False,
                                                  'related_field': 'extractSettings'},
    "incident_type_invalid_auto_extract_mode": {'code': "IT103", 'ui_applicable': True, 'related_field': 'mode'},
    "incident_type_non_existent_playbook_id": {'code': "IT104", 'ui_applicable': False, 'related_field': ''},

    # LI - Lists
    "invalid_from_server_version_in_lists": {'code': "LI100", 'ui_applicable': False,
                                             'related_field': 'fromVersion'},

    # LO - Layouts
    "invalid_version_in_layout": {'code': "LO100", 'ui_applicable': False, 'related_field': 'version'},
    "invalid_version_in_layoutscontainer": {'code': "LO101", 'ui_applicable': False, 'related_field': 'version'},
    "invalid_file_path_layout": {'code': "LO102", 'ui_applicable': False, 'related_field': ''},
    "invalid_file_path_layoutscontainer": {'code': "LO103", 'ui_applicable': False, 'related_field': ''},
    "invalid_incident_field_in_layout": {'code': "LO104", 'ui_applicable': False, 'related_field': ''},
    "layouts_container_non_existent_script_id": {'code': "LO105", 'ui_applicable': False, 'related_field': ''},
    "layout_non_existent_script_id": {'code': "LO106", 'ui_applicable': False, 'related_field': ''},

    # MP - Mappers
    "invalid_from_version_in_mapper": {'code': "MP100", 'ui_applicable': False, 'related_field': 'fromVersion'},
    "invalid_to_version_in_mapper": {'code': "MP101", 'ui_applicable': False, 'related_field': 'toVersion'},
    "invalid_mapper_file_name": {'code': "MP102", 'ui_applicable': False, 'related_field': ''},
    "missing_from_version_in_mapper": {'code': "MP103", 'ui_applicable': False, 'related_field': 'fromVersion'},
    "invalid_type_in_mapper": {'code': "MP104", 'ui_applicable': False, 'related_field': 'type'},
    "mapper_non_existent_incident_types": {'code': "MP105", 'ui_applicable': False, 'related_field': 'incident_types'},
    "invalid_incident_field_in_mapper": {'code': "MP106", 'ui_applicable': False, 'related_field': 'mapping'},
    "changed_incident_field_in_mapper": {'code': "MP107", 'ui_applicable': True, 'related_field': 'mapping'},
    "removed_incident_types": {'code': "MP108", 'ui_applicable': True, 'related_field': 'mapping'},

    # PA - Packs (unique files)
    "pack_file_does_not_exist": {'code': "PA100", 'ui_applicable': False, 'related_field': ''},
    "cant_open_pack_file": {'code': "PA101", 'ui_applicable': False, 'related_field': ''},
    "cant_read_pack_file": {'code': "PA102", 'ui_applicable': False, 'related_field': ''},
    "cant_parse_pack_file_to_list": {'code': "PA103", 'ui_applicable': False, 'related_field': ''},
    "pack_file_bad_format": {'code': "PA104", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_empty": {'code': "PA105", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_should_be_dict": {'code': "PA106", 'ui_applicable': False, 'related_field': ''},
    "missing_field_iin_pack_metadata": {'code': "PA107", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_name_not_valid": {'code': "PA108", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_field_invalid": {'code': "PA109", 'ui_applicable': False, 'related_field': ''},
    "dependencies_field_should_be_dict": {'code': "PA110", 'ui_applicable': False, 'related_field': ''},
    "empty_field_in_pack_metadata": {'code': "PA111", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_isnt_json": {'code': "PA112", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_missing_url_and_email": {'code': "PA113", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_version_should_be_raised": {'code': "PA114", 'ui_applicable': False, 'related_field': ''},
    "pack_timestamp_field_not_in_iso_format": {'code': "PA115", 'ui_applicable': False, 'related_field': ''},
    "invalid_package_dependencies": {'code': "PA116", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_invalid_support_type": {'code': "PA117", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_certification_is_invalid": {'code': "PA118", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_non_approved_usecases": {'code': "PA119", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_non_approved_tags": {'code': "PA120", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_price_change": {'code': "PA121", 'ui_applicable': False, 'related_field': ''},
    "pack_name_already_exists": {'code': "PA122", 'ui_applicable': False, 'related_field': ''},
    "is_wrong_usage_of_usecase_tag": {'code': "PA123", 'ui_applicable': False, 'related_field': ''},
    "invalid_core_pack_dependencies": {'code': "PA124", 'ui_applicable': True, 'related_field': ''},
    "pack_name_is_not_in_xsoar_standards": {'code': "PA125", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_long_description": {'code': "PA126", 'ui_applicable': False, 'related_field': ''},
    "metadata_url_invalid": {'code': "PA127", 'ui_applicable': False, 'related_field': ''},
    "required_pack_file_does_not_exist": {'code': "PA128", 'ui_applicable': False, 'related_field': ''},
    "pack_metadata_missing_categories": {'code': "PA129", 'ui_applicable': False, 'related_field': ''},

    # PB - Playbooks
    "playbook_cant_have_rolename": {'code': "PB100", 'ui_applicable': True, 'related_field': 'rolename'},
    "playbook_unreachable_condition": {'code': "PB101", 'ui_applicable': True, 'related_field': 'tasks'},
    "playbook_unhandled_condition": {'code': "PB102", 'ui_applicable': True, 'related_field': 'conditions'},
    "playbook_unconnected_tasks": {'code': "PB103", 'ui_applicable': True, 'related_field': 'tasks'},
    "invalid_deprecated_playbook": {'code': "PB104", 'ui_applicable': False, 'related_field': 'description'},
    "playbook_cant_have_deletecontext_all": {'code': "PB105", 'ui_applicable': True, 'related_field': 'tasks'},
    "using_instance_in_playbook": {'code': "PB106", 'ui_applicable': True, 'related_field': 'tasks'},
    "invalid_script_id": {'code': "PB107", 'ui_applicable': False, 'related_field': 'tasks'},
    "invalid_uuid": {'code': "PB108", 'ui_applicable': False, 'related_field': 'taskid'},
    "taskid_different_from_id": {'code': "PB109", 'ui_applicable': False, 'related_field': 'taskid'},
    "content_entity_version_not_match_playbook_version": {'code': "PB110", 'ui_applicable': False,
                                                          'related_field': 'toVersion'},
    "integration_version_not_match_playbook_version": {'code': "PB111", 'ui_applicable': False,
                                                       'related_field': 'toVersion'},
    "playbook_condition_has_no_else_path": {'code': "PB112", 'ui_applicable': False, 'related_field': 'nexttasks'},
    "invalid_subplaybook_name": {'code': "PB113", 'ui_applicable': False, 'related_field': 'tasks'},
    "playbook_not_quiet_mode": {'code': "PB114", 'ui_applicable': False, 'related_field': ''},
    "playbook_tasks_not_quiet_mode": {'code': "PB115", 'ui_applicable': False, 'related_field': 'tasks'},
    "playbook_tasks_continue_on_error": {'code': "PB116", 'ui_applicable': False, 'related_field': 'tasks'},
    "content_entity_is_not_in_id_set": {'code': "PB117", 'ui_applicable': False, 'related_field': ''},

    # PP - Pre-Process Rules
    "invalid_from_server_version_in_pre_process_rules": {'code': "PP100", 'ui_applicable': False, 'related_field': 'fromServerVersion'},
    "invalid_incident_field_in_pre_process_rules": {'code': "PP101", 'ui_applicable': False, 'related_field': ''},

    # RM - READMEs
    "readme_error": {'code': "RM100", 'ui_applicable': False, 'related_field': ''},
    "image_path_error": {'code': "RM101", 'ui_applicable': False, 'related_field': ''},
    "readme_missing_output_context": {'code': "RM102", 'ui_applicable': False, 'related_field': ''},
    "error_starting_mdx_server": {'code': "RM103", 'ui_applicable': False, 'related_field': ''},
    "empty_readme_error": {'code': "RM104", 'ui_applicable': False, 'related_field': ''},
    "readme_equal_description_error": {'code': "RM105", 'ui_applicable': False, 'related_field': ''},
    "readme_contains_demisto_word": {'code': "RM106", 'ui_applicable': False, 'related_field': ''},
    "template_sentence_in_readme": {'code': "RM107", 'ui_applicable': False, 'related_field': ''},
    "invalid_readme_image_error": {'code': "RM108", 'ui_applicable': False, 'related_field': ''},

    # RN - Release Notes
    "missing_release_notes": {'code': "RN100", 'ui_applicable': False, 'related_field': ''},
    "no_new_release_notes": {'code': "RN101", 'ui_applicable': False, 'related_field': ''},
    "release_notes_not_formatted_correctly": {'code': "RN102", 'ui_applicable': False, 'related_field': ''},
    "release_notes_not_finished": {'code': "RN103", 'ui_applicable': False, 'related_field': ''},
    "release_notes_file_empty": {'code': "RN104", 'ui_applicable': False, 'related_field': ''},
    "multiple_release_notes_files": {'code': "RN105", 'ui_applicable': False, 'related_field': ''},
    "missing_release_notes_for_pack": {'code': "RN106", 'ui_applicable': False, 'related_field': ''},
    "missing_release_notes_entry": {'code': "RN107", 'ui_applicable': False, 'related_field': ''},
    "added_release_notes_for_new_pack": {'code': "RN108", 'ui_applicable': False, 'related_field': ''},
    "modified_existing_release_notes": {'code': "RN109", 'ui_applicable': False, 'related_field': ''},
    "release_notes_config_file_missing_release_notes": {'code': "RN110", 'ui_applicable': False, 'related_field': ''},

    # RP - Reputations (Indicator Types)
    "wrong_version_reputations": {'code': "RP100", 'ui_applicable': False, 'related_field': 'version'},
    "reputation_expiration_should_be_numeric": {'code': "RP101", 'ui_applicable': True, 'related_field': 'expiration'},
    "reputation_id_and_details_not_equal": {'code': "RP102", 'ui_applicable': False, 'related_field': 'id'},
    "reputation_invalid_indicator_type_id": {'code': "RP103", 'ui_applicable': False, 'related_field': 'id'},
    "reputation_empty_required_fields": {'code': "RP104", 'ui_applicable': False, 'related_field': 'id'},

    # SC - Scripts
    "invalid_version_script_name": {'code': "SC100", 'ui_applicable': True, 'related_field': 'name'},
    "invalid_deprecated_script": {'code': "SC101", 'ui_applicable': False, 'related_field': 'comment'},
    "invalid_command_name_in_script": {'code': "SC102", 'ui_applicable': False, 'related_field': ''},
    "is_valid_script_file_path_in_folder": {'code': "SC103", 'ui_applicable': False, 'related_field': ''},
    "is_valid_script_file_path_in_scripts_folder": {'code': "SC104", 'ui_applicable': False, 'related_field': ''},
    "incident_in_script_arg": {'code': "SC105", 'ui_applicable': True, 'related_field': 'args.name'},
    "runas_is_dbotrole": {'code': "SC106", 'ui_applicable': False, 'related_field': 'runas'},

    # ST - Structures
    "structure_doesnt_match_scheme": {'code': "ST100", 'ui_applicable': False, 'related_field': ''},
    "file_id_contains_slashes": {'code': "ST101", 'ui_applicable': False, 'related_field': 'id'},
    "file_id_changed": {'code': "ST102", 'ui_applicable': False, 'related_field': 'id'},
    "from_version_modified": {'code': "ST103", 'ui_applicable': False, 'related_field': 'fromversion'},
    "wrong_file_extension": {'code': "ST104", 'ui_applicable': False, 'related_field': ''},
    "invalid_file_path": {'code': "ST105", 'ui_applicable': False, 'related_field': ''},
    "invalid_package_structure": {'code': "ST106", 'ui_applicable': False, 'related_field': ''},
    "pykwalify_missing_parameter": {'code': "ST107", 'ui_applicable': False, 'related_field': ''},
    "pykwalify_field_undefined": {'code': "ST108", 'ui_applicable': False, 'related_field': ''},
    "pykwalify_missing_in_root": {'code': "ST109", 'ui_applicable': False, 'related_field': ''},
    "pykwalify_general_error": {'code': "ST110", 'ui_applicable': False, 'related_field': ''},
    "pykwalify_field_undefined_with_path": {'code': "ST111", 'ui_applicable': False, 'related_field': ''},
    "pykwalify_incorrect_enum": {'code': "ST112", 'ui_applicable': False, 'related_field': ''},
    "invalid_yml_file": {'code': "ST113", 'ui_applicable': False, 'related_field': ''},

    # WD - Widgets
    "remove_field_from_widget": {'code': "WD100", 'ui_applicable': False, 'related_field': ''},
    "include_field_in_widget": {'code': "WD101", 'ui_applicable': False, 'related_field': ''},
    "invalid_fromversion_for_type_metrics": {'code': "WD102", 'ui_applicable': False, 'related_field': ''},

    # XC - XSOAR Config
    "xsoar_config_file_is_not_json": {'code': "XC100", 'ui_applicable': False, 'related_field': ''},
    "xsoar_config_file_malformed": {'code': "XC101", 'ui_applicable': False, 'related_field': ''},

    # JB - Jobs
    "invalid_fromversion_in_job": {
        'code': "JB100", 'ui_applicable': False,
        'related_field': 'fromVersion'
    },
    "invalid_both_selected_and_all_feeds_in_job": {
        'code': "JB101", 'ui_applicable': False,
        'related_field': 'isAllFields'
    },
    "unexpected_field_values_in_non_feed_job": {
        'code': "JB102",
        'ui_applicable': False,
        'related_field': 'isFeed'
    },
    "missing_field_values_in_feed_job": {
        'code': "JB103",
        'ui_applicable': False,
        'related_field': 'isFeed'
    },
    "empty_or_missing_job_name": {
        'code': "JB104",
        'ui_applicable': False,
        'related_field': 'name'
    },
}


def get_all_error_codes() -> List:
    error_codes = []
    for error in ERROR_CODE:
        error_codes.append(ERROR_CODE[error].get('code'))

    return error_codes


def get_error_object(error_code: str) -> Dict:
    for error in ERROR_CODE:
        if error_code == ERROR_CODE[error].get('code'):
            return ERROR_CODE[error]
    return {}


@decorator.decorator
def error_code_decorator(func, *args, **kwargs):
    return func(*args, **kwargs), ERROR_CODE[func.__name__].get('code')


class Errors:
    BACKWARDS = "Possible backwards compatibility break"

    @staticmethod
    def suggest_fix(file_path: str, *args: Any, cmd: str = 'format') -> str:
        return f'To fix the problem, try running `demisto-sdk {cmd} -i {file_path} {" ".join(args)}`'

    @staticmethod
    @error_code_decorator
    def wrong_version(expected="-1"):
        return "The version for our files should always " \
               "be {}, please update the file.".format(expected)

    @staticmethod
    @error_code_decorator
    def id_should_equal_name(name, file_id):
        return "The File's name, which is: '{}', should be equal to its ID, which is: '{}'." \
               " please update the file.".format(name, file_id)

    @staticmethod
    @error_code_decorator
    def file_type_not_supported():
        return "The file type is not supported in the validate command.\n" \
               "The validate command supports: Integrations, Scripts, Playbooks, " \
               "Incident fields, Incident types, Indicator fields, Indicator types, Objects fields, Object types," \
               " Object modules, Images, Release notes, Layouts, Jobs and Descriptions."

    @staticmethod
    @error_code_decorator
    def file_name_include_spaces_error(file_name):
        return "Please remove spaces from the file's name: '{}'.".format(file_name)

    @staticmethod
    @error_code_decorator
    def changes_may_fail_validation():
        return "Warning: The changes may fail validation once submitted via a " \
               "PR. To validate your changes, please make sure you have a git remote setup" \
               " and pointing to github.com/demisto/content.\nYou can do this by running " \
               "the following commands:\n\ngit remote add upstream https://github.com/" \
               "demisto/content.git\ngit fetch upstream\n\nMore info about configuring " \
               "a remote for a fork is available here: https://help.github.com/en/" \
               "github/collaborating-with-issues-and-pull-requests/configuring-a-remote-for-a-fork"

    @staticmethod
    @error_code_decorator
    def invalid_id_set():
        return "id_set.json file is invalid - delete it and re-run `validate`.\n" \
               "From content repository root run the following: `rm -rf Tests/id_set.json`\n" \
               "Then re-run the `validate` command."

    @staticmethod
    @error_code_decorator
    def no_minimal_fromversion_in_file(fromversion, oldest_supported_version):
        if fromversion == 'fromversion':
            return f"{fromversion} field is invalid.\nAdd `{fromversion}: " \
                   f"{oldest_supported_version}` to the file."
        else:
            return f'{fromversion} field is invalid.\nAdd `"{fromversion}": "{oldest_supported_version}"` ' \
                   f'to the file.'

    @staticmethod
    @error_code_decorator
    def running_on_master_with_git():
        return "Running on master branch while using git is ill advised." \
               "\nrun: 'git checkout -b NEW_BRANCH_NAME' and rerun the command."

    @staticmethod
    @error_code_decorator
    def folder_name_has_separators(entity_type, invalid_name, valid_name):
        return f"The {entity_type} folder name '{invalid_name}' should be named '{valid_name}' without any separator."

    @staticmethod
    @error_code_decorator
    def file_name_has_separators(entity_type, invalid_files, valid_files):
        return f"The {entity_type} files {invalid_files} should be named {valid_files} " \
               f"without any separator in the base name."

    @staticmethod
    @error_code_decorator
    def field_contain_forbidden_word(field_names: list, word: str):
        return f"The following fields: {', '.join(field_names)} shouldn't contain the word '{word}'."

    @staticmethod
    @error_code_decorator
    def indicator_field_type_grid_minimal_version(fromversion):
        return f"The indicator field has a fromVersion of: {fromversion} but the minimal fromVersion is 5.5.0."

    @staticmethod
    @error_code_decorator
    def unsearchable_key_should_be_true_incident_field():
        return 'The unsearchable key in indicator and incident fields should be set to true.'

    @staticmethod
    @error_code_decorator
    def unsearchable_key_should_be_true_generic_field():
        return 'The unsearchable key in a generic field should be set to true.'

    @staticmethod
    @error_code_decorator
    def wrong_display_name(param_name, param_display):
        return 'The display name of the {} parameter should be \'{}\''.format(param_name, param_display)

    @staticmethod
    @error_code_decorator
    def wrong_default_parameter_not_empty(param_name, default_value):
        return 'The default value of the {} parameter should be {}'.format(param_name, default_value)

    @staticmethod
    @error_code_decorator
    def no_default_value_in_parameter(param_name):
        return 'The {} parameter should have a default value'.format(param_name)

    @staticmethod
    @error_code_decorator
    def wrong_required_value(param_name):
        return 'The required field of the {} parameter should be False'.format(param_name)

    @staticmethod
    @error_code_decorator
    def wrong_required_type(param_name):
        return 'The type field of the {} parameter should be 8'.format(param_name)

    @staticmethod
    @error_code_decorator
    def api_token_is_not_in_credential_type(param_name):
        return f"In order to allow fetching the {param_name} from an external vault, the type of the {param_name} " \
               f"parameter should be changed from 'Encrypted' (type 4), to 'Credentials' (type 9)'. For more details" \
               f"check the convention for credentials - " \
               f"https://xsoar.pan.dev/docs/integrations/code-conventions#credentials"

    @staticmethod
    @error_code_decorator
    def fromlicense_in_parameters(param_name):
        return 'The "fromlicense" field of the {} parameter is not allowed for contributors'.format(param_name)

    @staticmethod
    @error_code_decorator
    def wrong_category(category):
        return "The category '{}' is not in the integration schemas, the valid options are:\n{}" \
            .format(category, '\n'.join(INTEGRATION_CATEGORIES))

    @staticmethod
    @error_code_decorator
    def reputation_missing_argument(arg_name, command_name, all=False):
        missing_msg = "These" if all else 'At least one of these'
        return "{} arguments '{}' are required in the command '{}' and are not configured in yml." \
            .format(missing_msg, arg_name, command_name)

    @staticmethod
    @error_code_decorator
    def wrong_default_argument(arg_name, command_name):
        return "The argument '{}' of the command '{}' is not configured as default" \
            .format(arg_name, command_name)

    @staticmethod
    @error_code_decorator
    def wrong_is_array_argument(arg_name, command_name):
        return "The argument '{}' of the command '{}' is not configured as array input." \
            .format(arg_name, command_name)

    @staticmethod
    @error_code_decorator
    def no_default_arg(command_name):
        return "Could not find default argument " \
               "{} in command {}".format(command_name, command_name)

    @staticmethod
    @error_code_decorator
    def missing_reputation(command_name, reputation_output, context_standard):
        return "The outputs of the reputation command {} aren't valid. The {} outputs is missing. " \
               "Fix according to context standard {} " \
            .format(command_name, reputation_output, context_standard)

    @staticmethod
    @error_code_decorator
    def wrong_subtype():
        return "The subtype for our yml files should be either python2 or python3, " \
               "please update the file."

    @classmethod
    @error_code_decorator
    def beta_in_id(cls):
        return cls.beta_in_str('id')

    @classmethod
    @error_code_decorator
    def beta_in_name(cls):
        return cls.beta_in_str('name')

    @staticmethod
    @error_code_decorator
    def beta_field_not_found():
        return "Beta integration yml file should have " \
               "the field \"beta: true\", but was not found in the file."

    @staticmethod
    @error_code_decorator
    def no_beta_in_display():
        return "Field 'display' in Beta integration yml file should include the string \"beta\", " \
               "but was not found in the file."

    @staticmethod
    @error_code_decorator
    def duplicate_arg_in_file(arg, command_name=None):
        err_msg = "The argument '{}' is duplicated".format(arg)
        if command_name:
            err_msg += " in '{}'.".format(command_name)
        err_msg += ", please remove one of its appearances."
        return err_msg

    @staticmethod
    @error_code_decorator
    def duplicate_param(param_name):
        return "The parameter '{}' of the " \
               "file is duplicated, please remove one of its appearances.".format(param_name)

    @staticmethod
    @error_code_decorator
    def invalid_context_output(command_name, output):
        return f'Invalid context output for command {command_name}. Output is {output}'

    @staticmethod
    @error_code_decorator
    def added_required_fields(field):
        return "You've added required, the field is '{}'".format(field)

    @staticmethod
    @error_code_decorator
    def removed_integration_parameters(field):
        return "You've removed integration parameters, the removed parameters are '{}'".format(field)

    @staticmethod
    @error_code_decorator
    def changed_integration_yml_fields(removed, changed):
        return f"You've made some changes to some fields in the yml file, \n" \
               f" the changed fields are: {changed} \n" \
               f"the removed fields are: {removed} "

    @staticmethod
    def suggest_server_allowlist_fix(words=None):
        words = words if words else ['incident']
        return f"To fix the problem, remove the words {words}, " \
               f"or add them to the whitelist named argsExceptionsList in:\n" \
               f"https://github.com/demisto/server/blob/57fbe417ae420c41ee12a9beb850ff4672209af8/services/" \
               f"servicemodule_test.go#L8273"

    @staticmethod
    @error_code_decorator
    def incident_in_command_name_or_args(commands, args):
        return f"This is a core pack with an integration that contains the word incident in the following commands'" \
               f" name or argument:\ncommand's name: {commands} \ncommand's argument: {args}"

    @staticmethod
    @error_code_decorator
    def not_used_display_name(field_name):
        return "The display details for {} will not be used " \
               "due to the type of the parameter".format(field_name)

    @staticmethod
    @error_code_decorator
    def empty_display_configuration(field_name):
        return "No display details were entered for the field {}".format(field_name)

    @staticmethod
    @error_code_decorator
    def feed_wrong_from_version(given_fromversion, needed_from_version="5.5.0"):
        return "This is a feed and has wrong fromversion. got `{}` expected `{}`" \
            .format(given_fromversion, needed_from_version)

    @staticmethod
    @error_code_decorator
    def pwsh_wrong_version(given_fromversion, needed_from_version='5.5.0'):
        return f'Detected type: powershell and fromversion less than {needed_from_version}.' \
               f' Found version: {given_fromversion}'

    @staticmethod
    @error_code_decorator
    def parameter_missing_from_yml(name, correct_format):
        return f'A required parameter "{name}" is missing or malformed ' \
               f'in the YAML file.\nThe correct format of the parameter should ' \
               f'be as follows:\n{correct_format}'

    @staticmethod
    @error_code_decorator
    def parameter_missing_from_yml_not_community_contributor(name, correct_format):
        """
            This error is ignored if the contributor is community
        """
        return f'A required parameter "{name}" is missing or malformed ' \
               f'in the YAML file.\nThe correct format of the parameter should ' \
               f'be as follows:\n{correct_format}'

    @staticmethod
    @error_code_decorator
    def parameter_missing_for_feed(name, correct_format):
        return f'Feed Integration was detected A required ' \
               f'parameter "{name}" is missing or malformed in the YAML file.\n' \
               f'The correct format of the parameter should be as follows:\n{correct_format}'

    @staticmethod
    @error_code_decorator
    def missing_get_mapping_fields_command():
        return 'The command "get-mapping-fields" is missing from the YML file and is required as the ismappable ' \
               'field is set to true.'

    @staticmethod
    @error_code_decorator
    def readme_missing_output_context(command, context_paths):
        return f'The Following context paths for command {command} are found in YML file ' \
               f'but are missing from the README file: {context_paths}'

    @staticmethod
    @error_code_decorator
    def error_starting_mdx_server(line):
        return f'Failed starting mdx server. stdout: {line}.\n' \
               f'Try running the following command: `npm install`'

    @staticmethod
    @error_code_decorator
    def missing_output_context(command, context_paths):
        return f'The Following context paths for command {command} are found in the README file ' \
               f'but are missing from the YML file: {context_paths}'

    @staticmethod
    @error_code_decorator
    def integration_non_existent_classifier(integration_classifier):
        return f"The integration has a classifier {integration_classifier} which does not exist."

    @staticmethod
    @error_code_decorator
    def integration_non_existent_mapper(integration_mapper):
        return f"The integration has a mapper {integration_mapper} which does not exist."

    @staticmethod
    @error_code_decorator
    def multiple_default_arg(command_name, default_args):
        return f"The integration command: {command_name} has multiple default arguments: {default_args}."

    @staticmethod
    @error_code_decorator
    def invalid_integration_parameters_display_name(invalid_display_names):
        return f"The integration display names: {invalid_display_names} are invalid, " \
               "Integration parameters display name should be capitalized and spaced using whitespaces " \
               "and not underscores ( _ )."

    @staticmethod
    @error_code_decorator
    def is_valid_integration_file_path_in_folder(integration_file):
        return f"The integration file name: {integration_file} is invalid, " \
               f"The integration file name should be the same as the name of the folder that contains it."

    @staticmethod
    @error_code_decorator
    def is_valid_integration_file_path_in_integrations_folder(integration_file):
        return f"The integration file name: {integration_file} is invalid, " \
               f"The integration file name should start with 'integration-'."

    @staticmethod
    @error_code_decorator
    def invalid_version_integration_name(version_number: str):
        return f"The display name of this v{version_number} integration is incorrect , " \
               f"should be **name** v{version_number}.\n" \
               f"e.g: Kenna v{version_number}, Jira v{version_number}"

    @staticmethod
    @error_code_decorator
    def found_hidden_param(parameter_name):
        return f"Parameter: \"{parameter_name}\" can't be hidden. Please remove this field."

    @staticmethod
    @error_code_decorator
    def invalid_deprecated_integration_display_name():
        return 'The display_name (display) of all deprecated integrations should end with (Deprecated)".'

    @staticmethod
    @error_code_decorator
    def invalid_deprecated_integration_description():
        return 'The description of all deprecated integrations should follow one of the formats:' \
               '1. "Deprecated. Use <INTEGRATION_DISPLAY_NAME> instead."' \
               '2. "Deprecated. <REASON> No available replacement."'

    @staticmethod
    @error_code_decorator
    def invalid_version_script_name(version_number: str):
        return f"The name of this v{version_number} script is incorrect , should be **name**V{version_number}." \
               f" e.g: DBotTrainTextClassifierV{version_number}"

    @staticmethod
    @error_code_decorator
    def invalid_deprecated_script():
        return 'The comment of all deprecated scripts should follow one of the formats:' \
               '1. "Deprecated. Use <SCRIPT_NAME> instead."' \
               '2. "Deprecated. <REASON> No available replacement."'

    @staticmethod
    @error_code_decorator
    def dbot_invalid_output(command_name, missing_outputs, context_standard):
        return "The DBotScore outputs of the reputation command {} aren't valid. Missing: {}. " \
               "Fix according to context standard {} ".format(command_name, missing_outputs,
                                                              context_standard)

    @staticmethod
    @error_code_decorator
    def dbot_invalid_description(command_name, missing_descriptions, context_standard):
        return "The DBotScore description of the reputation command {} aren't valid. Missing: {}. " \
               "Fix according to context standard {} " \
            .format(command_name, missing_descriptions, context_standard)

    @classmethod
    @error_code_decorator
    def breaking_backwards_subtype(cls):
        return "{}, You've changed the subtype, please undo.".format(cls.BACKWARDS)

    @classmethod
    @error_code_decorator
    def breaking_backwards_context(cls):
        return "{}, You've changed the context in the file," \
               " please undo.".format(cls.BACKWARDS)

    @classmethod
    @error_code_decorator
    def breaking_backwards_command(cls, old_command):
        return "{}, You've changed the context in the file,please " \
               "undo. the command is:\n{}".format(cls.BACKWARDS, old_command)

    @classmethod
    @error_code_decorator
    def breaking_backwards_arg_changed(cls):
        return "{}, You've changed the name of an arg in " \
               "the file, please undo.".format(cls.BACKWARDS)

    @classmethod
    @error_code_decorator
    def breaking_backwards_command_arg_changed(cls, command):
        return "{}, You've changed the name of a command or its arg in" \
               " the file, please undo, the command was:\n{}".format(cls.BACKWARDS, command)

    @staticmethod
    @error_code_decorator
    def default_docker_error():
        return 'The current docker image in the yml file is the default one: demisto/python:1.3-alpine,\n' \
               'Please create or use another docker image'

    @staticmethod
    @error_code_decorator
    def latest_docker_error(docker_image_tag, docker_image_name):
        return f'"latest" tag is not allowed,\n' \
               f'Please create or update to an updated versioned image\n' \
               f'You can check for the most updated version of {docker_image_tag} ' \
               f'here: https://hub.docker.com/r/{docker_image_name}/tags'

    @staticmethod
    @error_code_decorator
    def not_demisto_docker():
        return 'docker image must be a demisto docker image. When the docker image is ready, ' \
               'please rename it to: demisto/<image>:<tag>'

    @staticmethod
    @error_code_decorator
    def docker_tag_not_fetched(docker_image_name, exception_msg=None):
        msg = f'Failed getting tag for: {docker_image_name}. Please check it exists and of demisto format.'
        if exception_msg:
            msg = msg + '\n' + exception_msg
        return msg

    @staticmethod
    @error_code_decorator
    def no_docker_tag(docker_image):
        return f'{docker_image} - The docker image in your integration/script does not have a tag.' \
               f' Please create or update to an updated versioned image.'

    @staticmethod
    @error_code_decorator
    def dockerimage_not_in_yml_file(file_path):
        return f'There is no docker image provided in file {file_path}.\nYou can choose one from ' \
               'DockerHub: https://hub.docker.com/u/demisto/, or create your own in the repo: ' \
               ' https://github.com/demisto/dockerfiles'

    @staticmethod
    @error_code_decorator
    def non_existing_docker(docker_image):
        return f'{docker_image} - Could not find the docker image. Check if it exists in ' \
               f'DockerHub: https://hub.docker.com/u/demisto/.'

    @staticmethod
    @error_code_decorator
    def docker_not_formatted_correctly(docker_image):
        return f'The docker image: {docker_image} is not of format - demisto/image_name:X.X'

    @staticmethod
    def suggest_docker_fix(docker_image_name: str, file_path: str, is_iron_bank=False) -> str:
        docker_hub_link = f'https://hub.docker.com/r/{docker_image_name}/tags'
        iron_bank_link = f'https://repo1.dso.mil/dsop/opensource/palo-alto-networks/{docker_image_name}/'
        return f'You can check for the most updated version of {docker_image_name} ' \
               f'here: {iron_bank_link if is_iron_bank else docker_hub_link} \n' \
               f'To update the docker image run:\ndemisto-sdk format -ud -i {file_path}\n'

    @staticmethod
    @error_code_decorator
    def docker_not_on_the_latest_tag(docker_image_tag, docker_image_latest_tag, is_iron_bank=False) -> str:
        return f'The docker image tag is not the latest numeric tag, please update it.\n' \
               f'The docker image tag in the yml file is: {docker_image_tag}\n' \
               f'The latest docker image tag in {"Iron Bank" if is_iron_bank else "docker hub" } ' \
               f'is: {docker_image_latest_tag}\n'

    @staticmethod
    @error_code_decorator
    def id_set_conflicts():
        return "You probably merged from master and your id_set.json has " \
               "conflicts. Run `demisto-sdk create-id-set`, it should reindex your id_set.json"

    @staticmethod
    @error_code_decorator
    def duplicated_id(obj_id):
        return f"The ID {obj_id} already exists, please update the file or update the " \
               f"id_set.json toversion field of this id to match the old occurrence of this id"

    @staticmethod
    @error_code_decorator
    def no_id_set_file():
        return "Unable to find id_set.json file in path - rerun the command with --create-id-set flag"

    @staticmethod
    @error_code_decorator
    def remove_field_from_dashboard(field):
        return f'the field {field} needs to be removed.'

    @staticmethod
    @error_code_decorator
    def include_field_in_dashboard(field):
        return f'The field {field} needs to be included. Please add it.'

    @staticmethod
    @error_code_decorator
    def remove_field_from_widget(field, widget):
        return f'The field {field} needs to be removed from the widget: {widget}.'

    @staticmethod
    @error_code_decorator
    def include_field_in_widget(field, widget_name):
        return f'The field {field} needs to be included in the widget: {widget_name}. Please add it.'

    @staticmethod
    @error_code_decorator
    def invalid_fromversion_for_type_metrics():
        return 'The minimal fromVersion for widget with data type \'metrics\' is \'6.2.0\'.\n'

    @staticmethod
    @error_code_decorator
    def no_image_given():
        return "You've created/modified a yml or package but failed to provide an image as " \
               "a .png file for it, please add an image in order to proceed."

    @staticmethod
    @error_code_decorator
    def image_too_large():
        return "Too large logo, please update the logo to be under 10kB"

    @staticmethod
    @error_code_decorator
    def image_in_package_and_yml():
        return "Image in both yml and package, remove the 'image' " \
               "key from the yml file"

    @staticmethod
    @error_code_decorator
    def not_an_image_file():
        return "This isn't an image file or unified integration file."

    @staticmethod
    @error_code_decorator
    def no_image_field_in_yml():
        return "This is a yml file but has no image field."

    @staticmethod
    @error_code_decorator
    def image_field_not_in_base64():
        return "The image field isn't in base64 encoding."

    @staticmethod
    @error_code_decorator
    def default_image_error():
        return "This is the default image, please change to the integration image."

    @staticmethod
    @error_code_decorator
    def invalid_image_name():
        return "The image's file name is invalid - " \
               "make sure the name looks like the following: <integration_name>_image.png"

    @staticmethod
    @error_code_decorator
    def image_is_empty(image_path: str):
        return f'The author image in path {image_path} should not be empty. ' \
               'Please provide a relevant image.'

    @staticmethod
    @error_code_decorator
    def author_image_is_missing(image_path: str):
        return f'Partners must provide a non-empty author image under the path {image_path}.'

    @staticmethod
    @error_code_decorator
    def description_missing_from_conf_json(problematic_instances):
        return "Those instances don't have description:\n{}".format('\n'.join(problematic_instances))

    @staticmethod
    @error_code_decorator
    def test_not_in_conf_json(file_id):
        return f"You've failed to add the {file_id} to conf.json\n" \
               "see here: https://xsoar.pan.dev/docs/integrations/test-playbooks#adding-tests-to-confjson"

    @staticmethod
    @error_code_decorator
    def integration_not_registered(file_path, missing_test_playbook_configurations, no_tests_key):
        return f'The following integration is not registered in {CONF_PATH} file.\n' \
               f'Please add:\n{missing_test_playbook_configurations}\nto {CONF_PATH} ' \
               f'path under \'tests\' key.\n' \
               f'If you don\'t want to add a test playbook for this integration, please add: \n{no_tests_key}to the ' \
               f'file {file_path} or run \'demisto-sdk format -i {file_path}\''

    @staticmethod
    @error_code_decorator
    def no_test_playbook(file_path, file_type):
        return f'You don\'t have a TestPlaybook for {file_type} {file_path}. ' \
               f'If you have a TestPlaybook for this {file_type}, ' \
               f'please edit the yml file and add the TestPlaybook under the \'tests\' key. ' \
               f'If you don\'t want to create a TestPlaybook for this {file_type}, ' \
               f'edit the yml file and add  \ntests:\n -  No tests\n lines to it or ' \
               f'run \'demisto-sdk format -i {file_path}\''

    @staticmethod
    @error_code_decorator
    def test_playbook_not_configured(content_item_id, missing_test_playbook_configurations,
                                     missing_integration_configurations):
        return f'The TestPlaybook {content_item_id} is not registered in {CONF_PATH} file.\n ' \
               f'Please add\n{missing_test_playbook_configurations}\n ' \
               f'or if this test playbook is for an integration\n{missing_integration_configurations}\n ' \
               f'to {CONF_PATH} path under \'tests\' key.'

    @staticmethod
    @error_code_decorator
    def missing_release_notes(rn_path):
        return 'Missing release notes, Please add it under {}'.format(rn_path)

    @staticmethod
    @error_code_decorator
    def no_new_release_notes(release_notes_path):
        return F'No new comment has been added in the release notes file: {release_notes_path}'

    @staticmethod
    @error_code_decorator
    def release_notes_not_formatted_correctly(link_to_rn_standard):
        return F'Not formatted according to ' \
               F'release notes standards.\nFix according to {link_to_rn_standard}'

    @staticmethod
    @error_code_decorator
    def release_notes_not_finished():
        return "Please finish filling out the release notes. For common troubleshooting steps, please " \
               "review the documentation found here: " \
               "https://xsoar.pan.dev/docs/integrations/changelog#common-troubleshooting-tips"

    @staticmethod
    @error_code_decorator
    def release_notes_file_empty():
        return "Your release notes file is empty, please complete it\nHaving empty release notes " \
               "looks bad in the product UI.\nIf the change you made was minor, please use " \
               "\"Maintenance and stability enhancements.\" for general changes, or use " \
               "\"Documentation and metadata improvements.\" for changes to documentation."

    @staticmethod
    @error_code_decorator
    def multiple_release_notes_files():
        return "More than one release notes file has been found." \
               "Only one release note file is permitted per release. Please delete the extra release notes."

    @staticmethod
    @error_code_decorator
    def missing_release_notes_for_pack(pack):
        return f"Release notes were not found. Please run `demisto-sdk " \
               f"update-release-notes -i Packs/{pack} -u (major|minor|revision|documentation)` to " \
               f"generate release notes according to the new standard. You can refer to the documentation " \
               f"found here: https://xsoar.pan.dev/docs/integrations/changelog for more information."

    @staticmethod
    @error_code_decorator
    def missing_release_notes_entry(file_type, pack_name, entity_name):
        return f"No release note entry was found for the {file_type.value.lower()} \"{entity_name}\" in the " \
               f"{pack_name} pack. Please rerun the update-release-notes command without -u to " \
               f"generate an updated template. If you are trying to exclude an item from the release " \
               f"notes, please refer to the documentation found here - " \
               f"https://xsoar.pan.dev/docs/integrations/changelog#excluding-items"

    @staticmethod
    @error_code_decorator
    def added_release_notes_for_new_pack(pack_name):
        return f"ReleaseNotes were added for the newly created pack \"{pack_name}\" - remove them"

    @staticmethod
    @error_code_decorator
    def modified_existing_release_notes(pack_name):
        return f"Modified existing release notes for \"{pack_name}\" - revert the change and add new release notes " \
               f"if needed by running:\n`demisto-sdk update-release-notes -i Packs/{pack_name} -u " \
               f"(major|minor|revision|documentation)`\n" \
               f"You can refer to the documentation found here: " \
               f"https://xsoar.pan.dev/docs/integrations/changelog for more information."

    @staticmethod
    @error_code_decorator
    def release_notes_config_file_missing_release_notes(config_rn_path: str):
        return f'Release notes config file {config_rn_path} is missing corresponding release notes file.\n' \
               f'''Please add release notes file: {config_rn_path.replace('json', 'md')}'''

    @staticmethod
    @error_code_decorator
    def playbook_cant_have_rolename():
        return "Playbook can not have a rolename."

    @staticmethod
    @error_code_decorator
    def using_instance_in_playbook():
        return "Playbook should not use specific instance."

    @staticmethod
    @error_code_decorator
    def playbook_unreachable_condition(task_id, next_task_branch):
        return f'Playbook conditional task with id:{task_id} has task with unreachable ' \
               f'next task condition "{next_task_branch}". Please remove this task or add ' \
               f'this condition to condition task with id:{task_id}.'

    @staticmethod
    @error_code_decorator
    def playbook_unhandled_condition(task_id, task_condition_labels):
        return f'Playbook conditional task with id:{task_id} has an unhandled ' \
               f'condition: {",".join(map(lambda x: f"{str(x)}", task_condition_labels))}'

    @staticmethod
    @error_code_decorator
    def playbook_unconnected_tasks(orphan_tasks):
        return f'The following tasks ids have no previous tasks: {orphan_tasks}'

    @staticmethod
    @error_code_decorator
    def playbook_cant_have_deletecontext_all():
        return 'Playbook can not have DeleteContext script with arg all set to yes.'

    @staticmethod
    @error_code_decorator
    def invalid_deprecated_playbook():
        return 'The description of all deprecated playbooks should follow one of the formats:\n' \
               '1. "Deprecated. Use <PLAYBOOK_NAME> instead."\n' \
               '2. "Deprecated. <REASON> No available replacement."'

    @staticmethod
    @error_code_decorator
    def invalid_script_id(script_entry_to_check, pb_task):
        return f"in task {pb_task} the script {script_entry_to_check} was not found in the id_set.json file. " \
               f"Please make sure:\n" \
               f"1 - The right script id is set and the spelling is correct.\n" \
               f"2 - The id_set.json file is up to date. Delete the file by running: rm -rf Tests/id_set.json and" \
               f" rerun the command."

    @staticmethod
    @error_code_decorator
    def invalid_subplaybook_name(playbook_entry_to_check, file_path):
        return f"Sub-playbooks {playbook_entry_to_check} in {file_path} not found in the id_set.json file. " \
               f"Please make sure:\n" \
               f"1 - The right playbook name is set and the spelling is correct.\n" \
               f"2 - The id_set.json file is up to date. Delete the file by running: rm -rf Tests/id_set.json and" \
               f" rerun the command."

    @staticmethod
    @error_code_decorator
    def content_entity_version_not_match_playbook_version(main_playbook, entities_names, main_playbook_version):
        return f"Playbook {main_playbook} with version {main_playbook_version} uses {entities_names} " \
               f"with a version that does not match the main playbook version. The from version of" \
               f" {entities_names} should be {main_playbook_version} or lower."

    @staticmethod
    @error_code_decorator
    def integration_version_not_match_playbook_version(main_playbook, command, main_playbook_version):
        return f"Playbook {main_playbook} with version {main_playbook_version} uses the command {command} " \
               f"that not implemented in integration that match the main playbook version. This command should be " \
               f"implemented in an integration with a from version of {main_playbook_version} or lower."

    @staticmethod
    @error_code_decorator
    def invalid_command_name_in_script(script_name, command):
        return f"in script {script_name} the command {command} has an invalid name. " \
               f"Please make sure:\n" \
               f"1 - The right command name is set and the spelling is correct." \
               f" Do not use 'dev' in it or suffix it with 'copy'\n" \
               f"2 - The id_set.json file is up to date. Delete the file by running: rm -rf Tests/id_set.json and" \
               f" rerun the command."

    @staticmethod
    @error_code_decorator
    def is_valid_script_file_path_in_folder(script_file):
        return f"The script file name: {script_file} is invalid, " \
               f"The script file name should be the same as the name of the folder that contains it."

    @staticmethod
    @error_code_decorator
    def is_valid_script_file_path_in_scripts_folder(script_file):
        return f"The script file name: {script_file} is invalid, " \
               f"The script file name should start with 'script-'."

    @staticmethod
    @error_code_decorator
    def incident_in_script_arg(arguments):
        return f"The script is part of a core pack. Therefore, the use of the word `incident` in argument names is" \
               f" forbidden. problematic argument names:\n {arguments}."

    @staticmethod
    @error_code_decorator
    def description_missing_in_beta_integration():
        return f"No detailed description file (<integration_name>_description.md) was found in the package." \
               f" Please add one, and make sure it includes the beta disclaimer note." \
               f" Add the following to the detailed description:\n{BETA_INTEGRATION_DISCLAIMER}"

    @staticmethod
    @error_code_decorator
    def description_contains_contrib_details():
        return "Description file contains contribution/partner details that will be generated automatically " \
               "when the upload command is performed.\nDelete any details related to contribution/partner "

    @staticmethod
    @error_code_decorator
    def invalid_description_name():
        return "The description's file name is invalid - " \
               "make sure the name looks like the following: <integration_name>_description.md"

    @staticmethod
    @error_code_decorator
    def description_contains_demisto_word(line_nums, yml_or_file):
        return f'Found the word \'Demisto\' in the description content {yml_or_file} in lines: {line_nums}.'

    @staticmethod
    @error_code_decorator
    def no_beta_disclaimer_in_description():
        return f"The detailed description in beta integration package " \
               f"does not contain the beta disclaimer note. Add the following to the description:\n" \
               f"{BETA_INTEGRATION_DISCLAIMER}"

    @staticmethod
    @error_code_decorator
    def no_beta_disclaimer_in_yml():
        return f"The detailed description field in beta integration " \
               f"does not contain the beta disclaimer note. Add the following to the detailed description:\n" \
               f"{BETA_INTEGRATION_DISCLAIMER}"

    @staticmethod
    @error_code_decorator
    def description_in_package_and_yml():
        return "A description was found both in the " \
               "package and in the yml, please update the package."

    @staticmethod
    @error_code_decorator
    def no_description_file_warning():
        return "No detailed description file was found. Consider adding one."

    @staticmethod
    @error_code_decorator
    def invalid_incident_field_name(words):
        return f"The words: {words} cannot be used as a name."

    @staticmethod
    @error_code_decorator
    def invalid_incident_field_content_key_value(content_value):
        return f"The content key must be set to {content_value}."

    @staticmethod
    @error_code_decorator
    def invalid_incident_field_system_key_value(system_value):
        return f"The system key must be set to {system_value}"

    @staticmethod
    @error_code_decorator
    def invalid_incident_field_type(file_type, type_fields):
        return f"Type: `{file_type}` is not one of available types.\n" \
               f"available types: {[value.value for value in type_fields]}"

    @staticmethod
    @error_code_decorator
    def invalid_incident_field_group_value(group):
        return f"Group {group} is not a group field."

    @staticmethod
    @error_code_decorator
    def invalid_incident_field_cli_name_regex(cli_regex):
        return f"Field `cliName` contains non-alphanumeric letters. " \
               f"must match regex: {cli_regex}"

    @staticmethod
    @error_code_decorator
    def invalid_incident_field_cli_name_value(cli_name):
        return f"cliName field can not be {cli_name} as it's a builtin key."

    @staticmethod
    @error_code_decorator
    def invalid_incident_field_or_type_from_version():
        return '"fromVersion" has an invalid value.'

    @staticmethod
    @error_code_decorator
    def new_incident_field_required():
        return 'New incident fields can not be required. change to:\nrequired: false.'

    @staticmethod
    @error_code_decorator
    def from_version_modified_after_rename():
        return "fromversion might have been modified, please make sure it hasn't changed."

    @staticmethod
    @error_code_decorator
    def incident_field_type_change():
        return 'Changing incident field type is not allowed.'

    @staticmethod
    @error_code_decorator
    def incident_type_integer_field(field):
        return f'The field {field} needs to be a positive integer. Please add it.\n'

    @staticmethod
    @error_code_decorator
    def incident_type_invalid_playbook_id_field():
        return 'The "playbookId" field is not valid - please enter a non-UUID playbook ID.'

    @staticmethod
    @error_code_decorator
    def incident_type_auto_extract_fields_invalid(incident_fields):
        return f"The following incident fields are not formatted correctly under " \
               f"`fieldCliNameToExtractSettings`: {incident_fields}\n" \
               f"Please format them in one of the following ways:\n" \
               f"1. To extract all indicators from the field: \n" \
               f"isExtractingAllIndicatorTypes: true, extractAsIsIndicatorTypeId: \"\", " \
               f"extractIndicatorTypesIDs: []\n" \
               f"2. To extract the incident field to a specific indicator without using regex: \n" \
               f"isExtractingAllIndicatorTypes: false, extractAsIsIndicatorTypeId: \"<INDICATOR_TYPE>\", " \
               f"extractIndicatorTypesIDs: []\n" \
               f"3. To extract indicators from the field using regex: \n" \
               f"isExtractingAllIndicatorTypes: false, extractAsIsIndicatorTypeId: \"\", " \
               f"extractIndicatorTypesIDs: [\"<INDICATOR_TYPE1>\", \"<INDICATOR_TYPE2>\"]"

    @staticmethod
    @error_code_decorator
    def incident_type_invalid_auto_extract_mode():
        return 'The `mode` field under `extractSettings` should be one of the following:\n' \
               ' - \"All\" - To extract all indicator types regardless of auto-extraction settings.\n' \
               ' - \"Specific\" - To extract only the specific indicator types set in the auto-extraction settings.'

    @staticmethod
    @error_code_decorator
    def incident_type_non_existent_playbook_id(incident_type, playbook):
        return f"in incident type {incident_type} the playbook {playbook} was not found in the id_set.json file. " \
               f"Please make sure:\n" \
               f"1 - The right playbook name is set and the spelling is correct.\n" \
               f"2 - The id_set.json file is up to date. Delete the file by running: rm -rf Tests/id_set.json and" \
               f" rerun the command."

    @staticmethod
    @error_code_decorator
    def incident_field_non_existent_script_id(incident_field, scripts):
        return f"In incident field {incident_field} the following scripts were not found in the id_set.json file:" \
               f" {scripts}"

    @staticmethod
    @error_code_decorator
    def layouts_container_non_existent_script_id(layouts_container, scripts):
        return f"In layouts container {layouts_container} the following scripts were not found in the id_set.json " \
               f"file: {scripts}"

    @staticmethod
    @error_code_decorator
    def layout_non_existent_script_id(layout, scripts):
        return f"In layout {layout} the following scripts were not found in the id_set.json file: {scripts}"

    @staticmethod
    def suggest_fix_non_existent_script_id() -> str:
        return "Please make sure:\n" \
               "1 - The right script name is set and the spelling is correct.\n" \
               "2 - The id_set.json file is up to date. Delete the file by running: rm -rf Tests/id_set.json and" \
               " rerun the command with the --create-id-set option."

    @staticmethod
    @error_code_decorator
    def invalid_generic_field_group_value(group, generic_field_group):
        return f"Group {group} is not a valid generic field group. Please set group = {generic_field_group} instead."

    @staticmethod
    @error_code_decorator
    def invalid_generic_field_id(generic_id, generic_id_prefix):
        return f"ID {generic_id} is not a valid generic field ID - it should start with the prefix {generic_id_prefix}."

    @staticmethod
    @error_code_decorator
    def pack_file_does_not_exist(file_name):
        return f'"{file_name}" file does not exist, create one in the root of the pack'

    @staticmethod
    @error_code_decorator
    def required_pack_file_does_not_exist(file_name):
        return f'The required "{file_name}" file does not exist in the pack root.\n ' \
               f'Its absence may prevent other tests from being run! Create it and run validate again.'

    @staticmethod
    @error_code_decorator
    def cant_open_pack_file(file_name):
        return f'Could not open "{file_name}" file'

    @staticmethod
    @error_code_decorator
    def cant_read_pack_file(file_name):
        return f'Could not read the contents of "{file_name}" file'

    @staticmethod
    @error_code_decorator
    def cant_parse_pack_file_to_list(file_name):
        return f'Could not parse the contents of "{file_name}" file into a list'

    @staticmethod
    @error_code_decorator
    def pack_file_bad_format(file_name):
        return f'Detected invalid {file_name} file'

    @staticmethod
    @error_code_decorator
    def pack_metadata_empty():
        return 'Pack metadata is empty.'

    @staticmethod
    @error_code_decorator
    def pack_metadata_should_be_dict(pack_meta_file):
        return f'Pack metadata {pack_meta_file} should be a dictionary.'

    @staticmethod
    @error_code_decorator
    def pack_metadata_certification_is_invalid(pack_meta_file):
        return f'Pack metadata {pack_meta_file} - certification field should be \'certified\' or \'verified\'.'

    @staticmethod
    @error_code_decorator
    def missing_field_iin_pack_metadata(pack_meta_file, missing_fields):
        return f'{pack_meta_file} - Missing fields in the pack metadata: {missing_fields}'

    @staticmethod
    @error_code_decorator
    def pack_metadata_name_not_valid():
        return f'Pack metadata {PACK_METADATA_NAME} field is not valid. Please fill valid pack name.'

    @staticmethod
    @error_code_decorator
    def pack_metadata_field_invalid():
        return f'Pack metadata {PACK_METADATA_DESC} field is not valid. Please fill valid pack description.'

    @staticmethod
    @error_code_decorator
    def dependencies_field_should_be_dict(pack_meta_file):
        return f'{pack_meta_file} - The dependencies field in the pack must be a dictionary.'

    @staticmethod
    @error_code_decorator
    def empty_field_in_pack_metadata(pack_meta_file, list_field):
        return f'{pack_meta_file} - Empty value in the {list_field} field.'

    @staticmethod
    @error_code_decorator
    def pack_metadata_isnt_json(pack_meta_file):
        return f'Could not parse {pack_meta_file} file contents to json format'

    @staticmethod
    @error_code_decorator
    def pack_metadata_missing_url_and_email():
        return 'Contributed packs must include email or url.'

    @staticmethod
    @error_code_decorator
    def pack_metadata_invalid_support_type():
        return 'Support field should be one of the following: xsoar, partner, developer or community.'

    @staticmethod
    @error_code_decorator
    def pack_metadata_version_should_be_raised(pack, old_version):
        return f"The pack version (currently: {old_version}) needs to be raised - " \
               f"make sure you are merged from master and " \
               f"update the \"currentVersion\" field in the " \
               f"pack_metadata.json or in case release notes are required run:\n" \
               f"`demisto-sdk update-release-notes -i Packs/{pack} -u " \
               f"(major|minor|revision|documentation)` to " \
               f"generate them according to the new standard."

    @staticmethod
    @error_code_decorator
    def pack_metadata_non_approved_usecases(non_approved_usecases: set) -> str:
        return f'The pack metadata contains non approved usecases: {", ".join(non_approved_usecases)}'

    @staticmethod
    @error_code_decorator
    def pack_metadata_non_approved_tags(non_approved_tags: set) -> str:
        return f'The pack metadata contains non approved tags: {", ".join(non_approved_tags)}'

    @staticmethod
    @error_code_decorator
    def pack_metadata_price_change(old_price, new_price) -> str:
        return f"The pack price was changed from {old_price} to {new_price} - revert the change"

    @staticmethod
    @error_code_decorator
    def pack_metadata_missing_categories(pack_meta_file) -> str:
        return f'{pack_meta_file} - Missing categories.\nPlease supply at least one category, ' \
               f'for example: {INTEGRATION_CATEGORIES}'

    @staticmethod
    @error_code_decorator
    def pack_name_already_exists(new_pack_name) -> str:
        return f"A pack named: {new_pack_name} already exists in content repository, " \
               f"change the pack's name in the metadata file."

    @staticmethod
    @error_code_decorator
    def is_wrong_usage_of_usecase_tag():
        return "pack_metadata.json file contains the Use Case tag, without having any PB, incidents Types or Layouts"

    @staticmethod
    @error_code_decorator
    def pack_name_is_not_in_xsoar_standards(reason, excluded_words: Optional[List[str]] = None):
        if reason == "short":
            return f'Pack metadata {PACK_METADATA_NAME} field is not valid. The pack name must be at least 3' \
                   f' characters long.'
        if reason == "capital":
            return f'Pack metadata {PACK_METADATA_NAME} field is not valid. The pack name must start with a capital' \
                   f' letter.'
        if reason == "wrong_word":
            return f'Pack metadata {PACK_METADATA_NAME} field is not valid. The pack name must not contain the words:' \
                   f' ["Pack", "Playbook", "Integration", "Script"]'
        if reason == 'excluded_word':
            return f'Pack metadata {PACK_METADATA_NAME} field is not valid. The pack name must not contain the words:' \
                   f' {excluded_words}'

    @staticmethod
    @error_code_decorator
    def pack_metadata_long_description():
        return "The description field of the pack_metadata.json file is longer than 130 characters." \
               " Consider modifying it."

    @staticmethod
    @error_code_decorator
    def pack_timestamp_field_not_in_iso_format(field_name, value, changed_value):
        return f"The field \"{field_name}\" should be in the following format: YYYY-MM-DDThh:mm:ssZ, found {value}.\n" \
               f"Suggested change: {changed_value}"

    @staticmethod
    @error_code_decorator
    def readme_error(stderr):
        return f'Failed verifying README.md Error Message is: {stderr}'

    @staticmethod
    @error_code_decorator
    def empty_readme_error():
        return 'README.md is empty'

    @staticmethod
    @error_code_decorator
    def readme_equal_description_error():
        return 'README.md content is equal to pack description. ' \
               'Please remove the duplicate description from README.md file.'

    @staticmethod
    @error_code_decorator
    def metadata_url_invalid():
        return 'The metadata URL leads to a GitHub repo instead of a support page. ' \
               'Please provide a URL for a support page as detailed in:\n ' \
               'https://xsoar.pan.dev/docs/packs/packs-format#pack_metadatajson\n ' \
               'Note that GitHub URLs that lead to a /issues page are also acceptable. ' \
               '(e.g. https://github.com/some_monitored_repo/issues)'

    @staticmethod
    @error_code_decorator
    def readme_contains_demisto_word(line_nums):
        return f'Found the word \'Demisto\' in the readme content in lines: {line_nums}.'

    @staticmethod
    @error_code_decorator
    def template_sentence_in_readme(line_nums):
        return f"Please update the integration version differences section in lines: {line_nums}."

    @staticmethod
    @error_code_decorator
    def image_path_error(path, alternative_path):
        return f'Detected following image url:\n{path}\n' \
               f'Which is not the raw link. You probably want to use the following raw image url:\n{alternative_path}'

    @staticmethod
    def pack_readme_image_relative_path_error(path):
        return f'Detected the following image relative path: {path}.\nRelative paths are not supported in pack README files. See ' \
               f'https://xsoar.pan.dev/docs/integrations/integration-docs#images for further info on how to ' \
               f'add images to pack README files.'

    @staticmethod
    def invalid_readme_image_relative_path_error(path):
        return f'The following image relative path is not valid, please recheck it:\n{path}.'

    @staticmethod
    def invalid_readme_image_absolute_path_error(path):
        return f'The following image link seems to be broken, please repair it:\n{path}'

    @staticmethod
    def branch_name_in_readme_image_absolute_path_error(path):
        return f'Branch name was found in the URL, please change it to the commit hash:\n{path}'

    @staticmethod
    def invalid_readme_insert_image_link_error(path):
        return f'Image link was not found, either insert it or remove it:\n{path}'

    @staticmethod
    @error_code_decorator
    def invalid_readme_image_error(path, error_type):
        return 'Error in readme image:\n' + {
            'pack_readme_relative_error': Errors.pack_readme_image_relative_path_error,
            'general_readme_relative_error': Errors.invalid_readme_image_relative_path_error,
            'general_readme_absolute_error': Errors.invalid_readme_image_absolute_path_error,
            'branch_name_readme_absolute_error': Errors.branch_name_in_readme_image_absolute_path_error,
            'insert_image_link_error': Errors.invalid_readme_insert_image_link_error
        }.get(error_type, lambda x: f'Something went wrong when testing {x}')(path)

    @staticmethod
    @error_code_decorator
    def wrong_version_reputations(object_id, version):
        return "Reputation object with id {} must have version {}".format(object_id, version)

    @staticmethod
    @error_code_decorator
    def reputation_expiration_should_be_numeric():
        return 'Expiration field should have a positive numeric value.'

    @staticmethod
    @error_code_decorator
    def reputation_id_and_details_not_equal():
        return 'id and details fields are not equal.'

    @staticmethod
    @error_code_decorator
    def reputation_invalid_indicator_type_id():
        return 'Indicator type "id" field can not include spaces or special characters.'

    @staticmethod
    @error_code_decorator
    def reputation_empty_required_fields():
        return 'id and details fields can not be empty.'

    @staticmethod
    @error_code_decorator
    def structure_doesnt_match_scheme(pretty_formatted_string_of_regexes):
        return f"The file does not match any scheme we have, please refer to the following list " \
               f"for the various file name options we have in our repo {pretty_formatted_string_of_regexes}"

    @staticmethod
    @error_code_decorator
    def file_id_contains_slashes():
        return "File's ID contains slashes - please remove."

    @staticmethod
    @error_code_decorator
    def file_id_changed(old_version_id, new_file_id):
        return f"The file id has changed from {old_version_id} to {new_file_id}"

    @staticmethod
    @error_code_decorator
    def from_version_modified():
        return "You've added fromversion to an existing " \
               "file in the system, this is not allowed, please undo."

    @staticmethod
    @error_code_decorator
    def wrong_file_extension(file_extension, accepted_extensions):
        return "File extension {} is not valid. accepted {}".format(file_extension, accepted_extensions)

    @staticmethod
    @error_code_decorator
    def invalid_file_path():
        return "Found incompatible file path."

    @staticmethod
    @error_code_decorator
    def invalid_package_structure():
        return 'You should update the following file to the package format, for further details please visit ' \
               'https://xsoar.pan.dev/docs/integrations/package-dir.'

    @staticmethod
    @error_code_decorator
    def invalid_package_dependencies(pack_name):
        return f'{pack_name} depends on NonSupported / DeprecatedContent packs.'

    @staticmethod
    @error_code_decorator
    def invalid_core_pack_dependencies(core_pack, dependencies_packs):
        return f'The core pack {core_pack} cannot depend on non-core packs: {dependencies_packs} - ' \
               f'revert this change.'

    @staticmethod
    @error_code_decorator
    def pykwalify_missing_parameter(key_from_error, path):
        return f'Missing the field "{key_from_error}" in Path: {path}'

    @staticmethod
    @error_code_decorator
    def pykwalify_field_undefined(key_from_error):
        return f'The field "{key_from_error}" was not defined in the scheme'

    @staticmethod
    @error_code_decorator
    def pykwalify_field_undefined_with_path(key_from_error, path):
        return f'The field "{key_from_error}" in path {path} was not defined in the scheme'

    @staticmethod
    @error_code_decorator
    def pykwalify_missing_in_root(key_from_error):
        return f'Missing the field "{key_from_error}" in root'

    @staticmethod
    @error_code_decorator
    def pykwalify_general_error(error):
        return f'in {error}'

    @staticmethod
    @error_code_decorator
    def pykwalify_incorrect_enum(path_to_wrong_enum, wrong_enum, enum_values):
        return f'The value "{wrong_enum}" in {path_to_wrong_enum} is invalid - legal values include: {enum_values}'

    @staticmethod
    @error_code_decorator
    def invalid_version_in_layout(version_field):
        return f'{version_field} field in layout needs to be lower than 6.0.0'

    @staticmethod
    @error_code_decorator
    def invalid_version_in_layoutscontainer(version_field):
        return f'{version_field} field in layoutscontainer needs to be higher or equal to 6.0.0'

    @staticmethod
    @error_code_decorator
    def invalid_file_path_layout(file_name):
        return f'Invalid file name - {file_name}. layout file name should start with "layout-" prefix.'

    @staticmethod
    @error_code_decorator
    def invalid_file_path_layoutscontainer(file_name):
        return f'Invalid file name - {file_name}. layoutscontainer file name should start with ' \
               '"layoutscontainer-" prefix.'

    @staticmethod
    @error_code_decorator
    def invalid_fromversion_in_job(version):
        return f'fromVersion field in Job needs to be at least {DEFAULT_JOB_FROM_VERSION} (found {version})'

    @staticmethod
    @error_code_decorator
    def invalid_both_selected_and_all_feeds_in_job():
        return 'Job cannot have non-empty selectedFeeds values when isAllFields is set to true.'

    @staticmethod
    @error_code_decorator
    def unexpected_field_values_in_non_feed_job(found_selected_fields: bool, found_is_all_fields: bool):
        found: List[str] = []
        for key, value in {found_selected_fields: 'selectedFeeds',
                           found_is_all_fields: 'isAllFields'}.items():
            if key:
                found.append(value)
        return f'Job objects cannot have non-empty {" or ".join(found)} when isFeed is set to false.'

    @staticmethod
    @error_code_decorator
    def empty_or_missing_job_name():
        return 'Job objects must have a non-empty name.'

    @staticmethod
    @error_code_decorator
    def missing_field_values_in_feed_job():
        return 'Job must either have non-empty selectedFeeds OR have isAllFields set to true ' \
               'when isFeed is set to true.'

    @staticmethod
    @error_code_decorator
    def invalid_from_server_version_in_pre_process_rules(version_field):
        return f'{version_field} field in Pre Process Rule needs to be at least 6.5.0'

    @staticmethod
    @error_code_decorator
    def unknown_fields_in_pre_process_rules(fields_names: str):
        return f'Unknown field(s) in Pre Process Rule: {fields_names}'

    @staticmethod
    @error_code_decorator
    def invalid_from_server_version_in_lists(version_field):
        return f'{version_field} field in a list item needs to be at least 6.5.0'

    @staticmethod
    @error_code_decorator
    def invalid_incident_field_in_pre_process_rules(invalid_inc_fields_list):
        return f"The Pre Process Rules contains incident fields that do not exist in the content: {invalid_inc_fields_list}.\n" \
               "Please make sure:\n" \
               "1 - The right incident field is set and the spelling is correct.\n" \
               "2 - The id_set.json file is up to date. Delete the file by running: rm -rf Tests/id_set.json and" \
               " rerun the command."

    @staticmethod
    @error_code_decorator
    def invalid_incident_field_in_layout(invalid_inc_fields_list):
        return f"The layout contains incident fields that do not exist in the content: {invalid_inc_fields_list}.\n" \
               "Please make sure:\n" \
               "1 - The right incident field is set and the spelling is correct.\n" \
               "2 - The id_set.json file is up to date. Delete the file by running: rm -rf Tests/id_set.json and" \
               " rerun the command."

    @staticmethod
    @error_code_decorator
    def invalid_to_version_in_new_classifiers():
        return 'toVersion field in new classifiers needs to be higher than 6.0.0'

    @staticmethod
    @error_code_decorator
    def invalid_to_version_in_old_classifiers():
        return 'toVersion field in old classifiers needs to be lower than 6.0.0'

    @staticmethod
    @error_code_decorator
    def invalid_from_version_in_new_classifiers():
        return 'fromVersion field in new classifiers needs to be higher or equal to 6.0.0'

    @staticmethod
    @error_code_decorator
    def invalid_from_version_in_old_classifiers():
        return 'fromVersion field in old classifiers needs to be lower than 6.0.0'

    @staticmethod
    @error_code_decorator
    def missing_from_version_in_new_classifiers():
        return 'Must have fromVersion field in new classifiers'

    @staticmethod
    @error_code_decorator
    def missing_to_version_in_old_classifiers():
        return 'Must have toVersion field in old classifiers'

    @staticmethod
    @error_code_decorator
    def from_version_higher_to_version():
        return 'The `fromVersion` field cannot be higher or equal to the `toVersion` field.'

    @staticmethod
    @error_code_decorator
    def invalid_type_in_new_classifiers():
        return 'Classifiers type must be classification'

    @staticmethod
    @error_code_decorator
    def classifier_non_existent_incident_types(incident_types):
        return f"The Classifiers related incident types: {incident_types} where not found."

    @staticmethod
    @error_code_decorator
    def invalid_from_version_in_mapper():
        return 'fromVersion field in mapper needs to be higher or equal to 6.0.0'

    @staticmethod
    @error_code_decorator
    def invalid_to_version_in_mapper():
        return 'toVersion field in mapper needs to be higher than 6.0.0'

    @staticmethod
    @error_code_decorator
    def invalid_mapper_file_name():
        return 'Invalid file name for mapper. Need to change to classifier-mapper-NAME.json'

    @staticmethod
    @error_code_decorator
    def missing_from_version_in_mapper():
        return 'Must have fromVersion field in mapper'

    @staticmethod
    @error_code_decorator
    def invalid_type_in_mapper():
        return 'Mappers type must be mapping-incoming or mapping-outgoing'

    @staticmethod
    @error_code_decorator
    def mapper_non_existent_incident_types(incident_types):
        return f"The Mapper related incident types: {incident_types} where not found."

    @staticmethod
    @error_code_decorator
    def invalid_incident_field_in_mapper(invalid_inc_fields_list):
        return f"Your mapper contains incident fields that do not exist in the content: {invalid_inc_fields_list}.\n" \
               "Please make sure:\n" \
               "1 - The right incident field is set and the spelling is correct.\n" \
               "2 - The id_set.json file is up to date. Delete the file by running: rm -rf Tests/id_set.json and" \
               " rerun the command."

    @staticmethod
    @error_code_decorator
    def changed_incident_field_in_mapper(changed_inc_fields):
        return f"Some incident fields were removed from the mapper, The removed fields: {changed_inc_fields}."

    @staticmethod
    @error_code_decorator
    def removed_incident_types(removed_inc_types):
        return f"Some Incidents types were removed from the mapper, the removed types are: {removed_inc_types}."

    @staticmethod
    @error_code_decorator
    def integration_not_runnable():
        return "Could not find any runnable command in the integration." \
               "Must have at least one command, `isFetch: true`, `feed: true`, `longRunning: true`"

    @staticmethod
    @error_code_decorator
    def invalid_uuid(task_key, id_, taskid):
        return f"On task: {task_key},  the field 'taskid': {taskid} and the 'id' under the 'task' field: {id_}, " \
               f"must be from uuid format."

    @staticmethod
    @error_code_decorator
    def taskid_different_from_id(task_key, id_, taskid):
        return f"On task: {task_key},  the field 'taskid': {taskid} and the 'id' under the 'task' field: {id_}, " \
               f"must be with equal value. "

    @staticmethod
    @error_code_decorator
    def integration_is_skipped(integration_id, skip_comment: Optional[str] = None):
        message = f"The integration {integration_id} is currently in skipped. Please add working tests and unskip."
        if skip_comment:
            message += f" Skip comment: {skip_comment}"
        return message

    @staticmethod
    @error_code_decorator
    def all_entity_test_playbooks_are_skipped(entity_id):
        return f"Either {entity_id} does not have any test playbooks or that all test playbooks in this " \
               f"pack are currently skipped, and there is no unittests file to be found.\n" \
               f"Please create a test playbook or un-skip at least one of the relevant test playbooks.\n " \
               f"You can un-skip a playbook by deleting the line relevant to one of the test playbooks from " \
               f"the 'skipped_tests' section inside the conf.json file and deal " \
               f"with the matching issue,\n  or create a new active test playbook " \
               f"and add the id to the 'tests' field in the yml."

    @staticmethod
    def wrong_filename(file_type):
        return 'This is not a valid {} filename.'.format(file_type)

    @staticmethod
    def wrong_path():
        return "This is not a valid filepath."

    @staticmethod
    def beta_in_str(field):
        return "Field '{}' should NOT contain the substring \"beta\" in a new beta integration. " \
               "please change the id in the file.".format(field)

    @classmethod
    def breaking_backwards_no_old_script(cls, e):
        return "{}\n{}, Could not find the old file.".format(cls.BACKWARDS, str(e))

    @staticmethod
    def id_might_changed():
        return "ID may have changed, please make sure to check you have the correct one."

    @staticmethod
    def id_changed():
        return "You've changed the ID of the file, please undo this change."

    @staticmethod
    def might_need_release_notes():
        return "You may need RN in this file, please verify if they are required."

    @staticmethod
    def unknown_file():
        return "File type is unknown, check it out."

    @staticmethod
    def no_common_server_python(path):
        return "Could not get CommonServerPythonScript.py file. Please download it manually from {} and " \
               "add it to the root of the repository.".format(path)

    @staticmethod
    def no_yml_file(file_path):
        return "No yml files were found in {} directory.".format(file_path)

    @staticmethod
    @error_code_decorator
    def playbook_condition_has_no_else_path(tasks_ids):
        return f'Playbook conditional tasks with ids: {" ".join([str(id) for id in tasks_ids])} have no else path'

    @staticmethod
    @error_code_decorator
    def xsoar_config_file_is_not_json(file_path):
        return f"Could not load {file_path} as a JSON XSOAR configuration file."

    @staticmethod
    @error_code_decorator
    def xsoar_config_file_malformed(configuration_file_path, schema_file_path, errors_table):
        return f'Errors were found in the configuration file: "{configuration_file_path}" ' \
               f'with schema "{schema_file_path}":\n {errors_table}'

    @staticmethod
    @error_code_decorator
    def playbook_not_quiet_mode():
        return "The playbook's quiet mode is off, it should be on, if it's done on purpose, then add this error to " \
               "the pack's 'pack ignore' file"

    @staticmethod
    @error_code_decorator
    def playbook_tasks_not_quiet_mode(tasks):
        return f"The following tasks of the playbook have the quiet mode turned off:\n{tasks}\n"

    @staticmethod
    @error_code_decorator
    def playbook_tasks_continue_on_error(tasks):
        return f"The following tasks of the playbook do not stop on error:\n{tasks}"

    @staticmethod
    @error_code_decorator
    def invalid_incident_field_prefix(field_name):
        return f"Field name: {field_name} is invalid. Field name must start with the relevant pack name."

    @staticmethod
    def suggest_fix_field_name(field_name, pack_prefix):
        return f"To fix the problem, add pack name prefix to the field name. " \
               f"You can use the pack name or one of the prefixes found in the itemPrefix field in the pack_metadata. " \
               f"Example: {pack_prefix} {field_name}.\n" \
               f"Also make sure to update the field id and cliName accordingly. " \
               f"Example: cliName: {pack_prefix.replace(' ', '')}{field_name.replace(' ', '')}, "

    @staticmethod
    @error_code_decorator
    def entity_name_contains_excluded_word(entity_name: str, excluded_words: List[str]):
        return f'Entity {entity_name} should not contain one of {excluded_words} in its name. Please remove.'

    @staticmethod
    @error_code_decorator
    def content_entity_is_not_in_id_set(main_playbook, entities_names):
        return f"Playbook {main_playbook} uses {entities_names}, which do not exist in the id_set.\n" \
               f"Possible reason for such an error, would be that the name of the entity in the yml file of " \
               f"{main_playbook} is not identical to its name in its own yml file. Or the id_set is not up to date"

    @staticmethod
    @error_code_decorator
    def spaces_in_the_end_of_id(item_id: str):
        return f'Content item id "{item_id}" should not have trailing spaces. Please remove.'

    @staticmethod
    @error_code_decorator
    def spaces_in_the_end_of_name(name: str):
        return f'Content item name "{name}" should not have trailing spaces. Please remove.'

    @staticmethod
    @error_code_decorator
    def non_default_additional_info(params: List[str]):
        return f'The additionalinfo of params {params} is not the default value, please consider changing it.'

    @staticmethod
    @error_code_decorator
    def missing_default_additional_info(params: List[str]):
        return f'The additionalinfo of params {params} is empty.'

    @staticmethod
    @error_code_decorator
    def runas_is_dbotrole():
        return 'The runas value is DBotRole, it may cause access and exposure of sensitive data. ' \
               'Please consider changing it.'

    @staticmethod
    @error_code_decorator
    def invalid_yml_file(error):
        return f'There is problem with the yml file. The error: {error}'


# demisto_sdk/commands/common/git_util.py

import os
import re
from pathlib import Path
from typing import Set, Tuple, Union

import click
import gitdb
from git import InvalidGitRepositoryError, Repo


class GitUtil:
    repo: Repo

    def __init__(self, repo: Repo = None):
        if not repo:
            try:
                self.repo = Repo(Path.cwd(), search_parent_directories=True)
            except InvalidGitRepositoryError:
                raise InvalidGitRepositoryError("Unable to find Repository from current working directory - aborting")
        else:
            self.repo = repo

    def modified_files(self, prev_ver: str = 'master', committed_only: bool = False,
                       staged_only: bool = False, debug: bool = False, include_untracked: bool = False) -> Set[Path]:
        """Gets all the files that are recognized by git as modified against the prev_ver.
        Args:
            prev_ver (str): The base branch against which the comparison is made.
            committed_only (bool): Whether to return only committed files.
            staged_only (bool): Whether to return only staged files.
            debug (bool): Whether to print the debug logs.
            include_untracked (bool): Whether to include untracked files.
        Returns:
            Set: A set of Paths to the modified files.
        """
        remote, branch = self._handle_prev_ver(prev_ver)
        current_branch_or_hash = self.get_current_git_branch_or_hash()

        # when checking branch against itself only return the last commit.
        last_commit = self._only_last_commit(prev_ver, requested_status='M')
        if last_commit:
            self.debug_print(debug=debug, status='Modified', staged=set(), committed=last_commit)
            return last_commit

        # get all renamed files - some of these can be identified as modified by git,
        # but we want to identify them as renamed - so will remove them from the returned files.
        renamed = {item[0] for item in self.renamed_files(prev_ver, committed_only, staged_only)}  # type: ignore[index]

        # handle a case where a file is wrongly recognized as renamed (not 100% score) and
        # is actually of modified status
        untrue_rename_staged = self.handle_wrong_renamed_status(status='M', remote=remote, branch=branch,
                                                                staged_only=True)
        untrue_rename_committed = self.handle_wrong_renamed_status(status='M', remote=remote, branch=branch,
                                                                   staged_only=False)

        deleted = self.deleted_files(prev_ver, committed_only, staged_only)

        committed = set()

        if not staged_only:
            # get all committed files identified as modified which are changed from prev_ver.
            # this can result in extra files identified which were not touched on this branch.
            if remote:
                committed = {Path(os.path.join(item.a_path)) for item
                             in self.repo.remote(name=remote).refs[branch].commit.diff(
                    current_branch_or_hash).iter_change_type('M')}.union(untrue_rename_committed)

            # if remote does not exist we are checking against the commit sha1
            else:
                committed = {Path(os.path.join(item.a_path)) for item in self.repo.commit(rev=prev_ver).diff(
                    current_branch_or_hash).iter_change_type('M')}.union(untrue_rename_committed)

            # identify all files that were touched on this branch regardless of status
            # intersect these with all the committed files to identify the committed modified files.
            all_branch_changed_files = self._get_all_changed_files(prev_ver)
            committed = committed.intersection(all_branch_changed_files)

        # remove the renamed and deleted files from the committed
        committed = committed - renamed - deleted

        if committed_only:
            self.debug_print(debug=debug, status='Modified', staged=set(), committed=committed)
            return committed

        untracked = set()  # type: Set
        if include_untracked:
            # get all untracked modified files
            untracked = self._get_untracked_files('M')

        # get all the files that are staged on the branch and identified as modified.
        staged = {Path(os.path.join(item.a_path)) for item
                  in self.repo.head.commit.diff().iter_change_type('M')}.union(untracked).union(untrue_rename_staged)

        # If a file is Added in regards to prev_ver
        # and is then modified locally after being committed - it is identified as modified
        # but we want to identify the file as Added (its actual status against prev_ver) -
        # so will remove it from the staged modified files.
        # also remove the deleted and renamed files as well.
        if remote:
            committed_added = {Path(os.path.join(item.a_path)) for item in
                               self.repo.remote(name=remote).refs[branch].commit.
                               diff(current_branch_or_hash).iter_change_type('A')}

        # if remote does not exist we are checking against the commit sha1
        else:
            committed_added = {Path(os.path.join(item.a_path)) for item in
                               self.repo.commit(rev=prev_ver).diff(current_branch_or_hash).iter_change_type('A')}

        staged = staged - committed_added - renamed - deleted

        if staged_only:
            self.debug_print(debug=debug, status='Modified', staged=staged, committed=set())
            return staged

        self.debug_print(debug=debug, status='Modified', staged=staged, committed=committed)

        return staged.union(committed)

    def added_files(self, prev_ver: str = 'master', committed_only: bool = False,
                    staged_only: bool = False, debug: bool = False, include_untracked: bool = False) -> Set[Path]:
        """Gets all the files that are recognized by git as added against the prev_ver.
        Args:
            prev_ver (str): The base branch against which the comparison is made.
            committed_only (bool): Whether to return only committed files.
            staged_only (bool): Whether to return only staged files.
            debug (bool): Whether to print the debug logs.
            include_untracked (bool): Whether to include untracked files.
        Returns:
            Set: A set of Paths to the added files.
        """
        remote, branch = self._handle_prev_ver(prev_ver)
        current_branch_or_hash = self.get_current_git_branch_or_hash()

        # when checking branch against itself only return the last commit.
        last_commit = self._only_last_commit(prev_ver, requested_status='A')
        if last_commit:
            self.debug_print(debug=debug, status='Added', staged=set(), committed=last_commit)
            return last_commit

        deleted = self.deleted_files(prev_ver, committed_only, staged_only)

        # handle a case where a file is wrongly recognized as renamed (not 100% score) and is actually of added status
        untrue_rename_staged = self.handle_wrong_renamed_status(status='A', remote=remote, branch=branch,
                                                                staged_only=True)
        untrue_rename_committed = self.handle_wrong_renamed_status(status='A', remote=remote, branch=branch,
                                                                   staged_only=False)

        # get all committed files identified as added which are changed from prev_ver.
        # this can result in extra files identified which were not touched on this branch.
        if remote:
            committed = {Path(os.path.join(item.a_path)) for item
                         in self.repo.remote(name=remote).refs[branch].commit.diff(
                current_branch_or_hash).iter_change_type('A')}.union(untrue_rename_committed)

        # if remote does not exist we are checking against the commit sha1
        else:
            committed = {Path(os.path.join(item.a_path)) for item
                         in self.repo.commit(rev=prev_ver).diff(
                current_branch_or_hash).iter_change_type('A')}.union(untrue_rename_committed)

        # identify all files that were touched on this branch regardless of status
        # intersect these with all the committed files to identify the committed added files.
        all_branch_changed_files = self._get_all_changed_files(prev_ver)
        committed = committed.intersection(all_branch_changed_files)

        # remove deleted files
        committed = committed - deleted

        if committed_only:
            self.debug_print(debug=debug, status='Added', staged=set(), committed=committed)
            return committed

        untracked_added = set()  # type: Set
        untracked_modified = set()  # type: Set
        if include_untracked:
            # get all untracked added files
            untracked_added = self._get_untracked_files('A')

            # get all untracked modified files
            untracked_modified = self._get_untracked_files('M')

        # get all the files that are staged on the branch and identified as added.
        staged = {Path(os.path.join(item.a_path)) for item in
                  self.repo.head.commit.diff().iter_change_type('A')}.union(untrue_rename_staged)

        # If a file is Added in regards to prev_ver
        # and is then modified locally after being committed - it is identified as modified
        # but we want to identify the file as Added (its actual status against prev_ver) -
        # so will added it from the staged added files.
        # same goes to untracked files - can be identified as modified but are actually added against prev_ver
        committed_added_locally_modified = {Path(os.path.join(item.a_path)) for item in
                                            self.repo.head.commit.diff().iter_change_type('M')}.intersection(committed)
        untracked = untracked_added.union(untracked_modified.intersection(committed))

        staged = staged.union(committed_added_locally_modified).union(untracked)

        # remove deleted files.
        staged = staged - deleted

        if staged_only:
            self.debug_print(debug=debug, status='Added', staged=staged, committed=set())
            return staged

        self.debug_print(debug=debug, status='Added', staged=staged, committed=committed)

        return staged.union(committed)

    def deleted_files(self, prev_ver: str = 'master', committed_only: bool = False,
                      staged_only: bool = False, include_untracked: bool = False) -> Set[Path]:
        """Gets all the files that are recognized by git as deleted against the prev_ver.
        Args:
            prev_ver (str): The base branch against which the comparison is made.
            committed_only (bool): Whether to return only committed files.
            staged_only (bool): Whether to return only staged files.
            include_untracked (bool): Whether to include untracked files.
        Returns:
            Set: A set of Paths to the deleted files.
        """
        remote, branch = self._handle_prev_ver(prev_ver)
        current_branch_or_hash = self.get_current_git_branch_or_hash()

        # when checking branch against itself only return the last commit.
        last_commit = self._only_last_commit(prev_ver, requested_status='D')
        if last_commit:
            return last_commit

        committed = set()

        if not staged_only:
            # get all committed files identified as added which are changed from prev_ver.
            # this can result in extra files identified which were not touched on this branch.
            if remote:
                committed = {Path(os.path.join(item.a_path)) for item
                             in self.repo.remote(name=remote).refs[branch].commit.diff(
                    current_branch_or_hash).iter_change_type('D')}

            # if remote does not exist we are checking against the commit sha1
            else:
                committed = {Path(os.path.join(item.a_path)) for item
                             in self.repo.commit(rev=prev_ver).diff(
                    current_branch_or_hash).iter_change_type('D')}

            # identify all files that were touched on this branch regardless of status
            # intersect these with all the committed files to identify the committed added files.
            all_branch_changed_files = self._get_all_changed_files(prev_ver)
            committed = committed.intersection(all_branch_changed_files)

        if committed_only:
            return committed

        untracked = set()  # type: Set
        if include_untracked:
            # get all untracked deleted files
            untracked = self._get_untracked_files('D')

        # get all the files that are staged on the branch and identified as added.
        staged = {Path(os.path.join(item.a_path)) for item in
                  self.repo.head.commit.diff().iter_change_type('D')}.union(untracked)

        if staged_only:
            return staged

        return staged.union(committed)

    def renamed_files(self, prev_ver: str = 'master', committed_only: bool = False,
                      staged_only: bool = False, debug: bool = False,
                      include_untracked: bool = False,
                      get_only_current_file_names: bool = False) -> Union[Set[Tuple[Path, Path]], Set[Path]]:
        """Gets all the files that are recognized by git as renamed against the prev_ver.
        Args:
            prev_ver (str): The base branch against which the comparison is made.
            committed_only (bool): Whether to return only committed files.
            staged_only (bool): Whether to return only staged files.
            debug (bool): Whether to print the debug logs.
            include_untracked (bool): Whether to include untracked files.
            get_only_current_file_names (bool): Whether to get only the current file names and not the old file names.
        Returns:
            Set: A set of Tuples of Paths to the renamed files -
            first element being the old file path and the second is the new.
        """
        remote, branch = self._handle_prev_ver(prev_ver)
        current_branch_or_hash = self.get_current_git_branch_or_hash()

        # when checking branch against itself only return the last commit.
        last_commit = self._only_last_commit(prev_ver, requested_status='R')
        if last_commit:
            self.debug_print(debug=debug, status='Renamed', staged=set(), committed=last_commit)
            return last_commit

        deleted = self.deleted_files(prev_ver, committed_only, staged_only)
        committed = set()

        if not staged_only:
            # get all committed files identified as renamed which are changed from prev_ver and are with 100% score.
            # this can result in extra files identified which were not touched on this branch.
            if remote:
                committed = {(Path(item.a_path), Path(item.b_path)) for item
                             in self.repo.remote(name=remote).refs[branch].commit.diff(
                    current_branch_or_hash).iter_change_type('R') if item.score == 100}

            # if remote does not exist we are checking against the commit sha1
            else:
                committed = {(Path(item.a_path), Path(item.b_path)) for item
                             in self.repo.commit(rev=prev_ver).diff(
                    current_branch_or_hash).iter_change_type('R') if item.score == 100}

            # identify all files that were touched on this branch regardless of status
            # intersect these with all the committed files to identify the committed added files.
            all_branch_changed_files = self._get_all_changed_files(prev_ver)
            committed = {tuple_item for tuple_item in committed
                         if (tuple_item[1] in all_branch_changed_files and tuple_item[1] not in deleted)}

        if committed_only:
            self.debug_print(debug=debug, status='Renamed', staged=set(), committed=committed)
            if get_only_current_file_names:
                committed_only_new = {file[1] for file in committed}
                return committed_only_new

            return committed

        untracked = set()  # type:Set
        if include_untracked:
            # get all untracked renamed files
            untracked = self._get_untracked_files('R')

        # get all the files that are staged on the branch and identified as renamed and are with 100% score.
        staged = {(Path(item.a_path), Path(item.b_path)) for item
                  in self.repo.head.commit.diff().iter_change_type('R') if item.score == 100}.union(untracked)

        if staged_only:
            self.debug_print(debug=debug, status='Renamed', staged=staged, committed=set())
            if get_only_current_file_names:
                staged_only_new = {file[1] for file in staged}
                return staged_only_new

            return staged

        self.debug_print(debug=debug, status='Renamed', staged=staged, committed=committed)

        all_renamed_files = staged.union(committed)
        if get_only_current_file_names:
            all_renamed_files_only_new = {file[1] for file in all_renamed_files}
            return all_renamed_files_only_new

        return all_renamed_files

    def _get_untracked_files(self, requested_status: str) -> set:
        """return all untracked files of the given requested status.
        Args:
            requested_status (str): M, A, R, D - the git status to return
        Returns:
            Set: of path strings which include the untracked files of a certain status.
        """
        git_status = self.repo.git.status('--short', '-u').split('\n')

        # in case there are no local changes - return
        if git_status == ['']:
            return set()

        extracted_paths = set()
        for line in git_status:
            line = line.strip()
            file_status = line.split()[0].upper() if not line.startswith('?') else 'A'
            if file_status.startswith(requested_status):
                if requested_status == 'R':
                    if file_status == 'R100':
                        extracted_paths.add((Path(line.split()[-2]), Path(line.split()[-1])))
                else:
                    extracted_paths.add(Path(line.split()[-1]))  # type: ignore

        return extracted_paths

    def _get_all_changed_files(self, prev_ver: str) -> Set[Path]:
        """Get all the files changed in the current branch without status distinction.
        Args:
            prev_ver (str): The base branch against which the comparison is made.
        Returns:
            Set: of Paths to files changed in the current branch.
        """
        remote, branch = self._handle_prev_ver(prev_ver)
        current_branch_or_hash = self.get_current_git_branch_or_hash()

        if remote:
            return {Path(os.path.join(item)) for item
                    in self.repo.git.diff('--name-only',
                                          f'{remote}/{branch}...{current_branch_or_hash}').split('\n')}

        # if remote does not exist we are checking against the commit sha1
        else:
            return {Path(os.path.join(item)) for item
                    in self.repo.git.diff('--name-only',
                                          f'{prev_ver}...{current_branch_or_hash}').split('\n')}

    def _only_last_commit(self, prev_ver: str, requested_status: str) -> Set:
        """Get all the files that were changed in the last commit of a given type when checking a branch against itself.
        Args:
            prev_ver (str): The base branch against which the comparison is made.
            requested_status (str): M, A, R, D - the git status to return
        Returns:
            Set: of Paths to files changed in the the last commit or an empty set if not
            running on master against master.
        """
        # when checking branch against itself only return the last commit.
        if self.get_current_working_branch() != prev_ver:
            return set()

        try:
            if requested_status != 'R':
                return {Path(os.path.join(item.a_path)) for item in
                        self.repo.commit('HEAD~1').diff().iter_change_type(requested_status) if item.score == 100}
            else:
                return {(Path(item.a_path), Path(item.b_path)) for item in
                        self.repo.commit('HEAD~1').diff().iter_change_type(requested_status)}
        except gitdb.exc.BadName:
            # in case no last commit exists - just pass
            pass

        return set()

    def check_if_remote_exists(self, remote):
        if '/' in remote:
            remote = remote.split('/')[0]

        return remote in self.repo.remotes

    def _handle_prev_ver(self, prev_ver):
        # check for sha1 in regex
        sha1_pattern = re.compile(r'\b[0-9a-f]{40}\b', flags=re.IGNORECASE)
        if sha1_pattern.match(prev_ver):
            return None, prev_ver

        if '/' in prev_ver:
            remote = prev_ver.split('/')[0]
            remote = remote if self.check_if_remote_exists(remote) else str(self.repo.remote())
            branch = prev_ver.split('/')[1]

        else:
            remote = str(self.repo.remote())
            branch = prev_ver

        return remote, branch

    def get_current_git_branch_or_hash(self) -> str:
        try:
            return self.get_current_working_branch()
        except TypeError:
            return self.get_current_commit_hash()

    def get_current_working_branch(self) -> str:
        return str(self.repo.active_branch)

    def get_current_commit_hash(self) -> str:
        return str(self.repo.head.object.hexsha)

    def git_path(self) -> str:
        git_path = self.repo.git.rev_parse('--show-toplevel')
        return git_path.replace('\n', '')

    def debug_print(self, debug: bool, status: str, staged: Set, committed: Set) -> None:
        if debug:
            click.echo(f'######## - {status} staged:')
            click.echo(staged)
            click.echo('\n')
            click.echo(f'######## - {status} committed:')
            click.echo(committed)
            click.echo('\n')

    def handle_wrong_renamed_status(self, status: str, remote: str, branch: str, staged_only: bool) -> Set[Path]:
        """Get all the files that are recognized as non-100% rename in a given file status.
        Args:
            status (str): the requested file status
            remote (str): the used git remote
            branch (str): the used git branch
            staged_only (bool): whether to bring only staged files
        Returns:
            Set: of Paths to non 100% renamed files which are of a given status.
        """
        current_branch_or_hash = self.get_current_git_branch_or_hash()

        if staged_only:
            return {Path(item.b_path) for item
                    in self.repo.head.commit.diff().iter_change_type('R') if item.score < 100 and
                    self._check_file_status(file_path=str(item.b_path), remote=remote, branch=branch) == status}

        if remote:
            return {Path(item.b_path) for item in self.repo.remote(name=remote).refs[branch].commit.diff(
                current_branch_or_hash).iter_change_type('R') if item.score < 100 and
                self._check_file_status(file_path=str(item.b_path), remote=remote, branch=branch) == status}

        # if remote does not exist we are checking against the commit sha1
        return {Path(item.b_path) for item in self.repo.commit(rev=branch).diff(
            current_branch_or_hash).iter_change_type('R') if item.score < 100 and
            self._check_file_status(file_path=str(item.b_path), remote=remote, branch=branch) == status}

    def _check_file_status(self, file_path: str, remote: str, branch: str) -> str:
        """Get the git status of a given file path
        Args:
            file_path (str): the file path to check
            remote (str): the used git remote
            branch (str): the used git branch
        Returns:
            str: the git status of the file (M, A, R, D).
        """
        current_branch_or_hash = self.get_current_git_branch_or_hash()

        if remote:
            diff_line = self.repo.git.diff('--name-status',
                                           f'{remote}/{branch}...{current_branch_or_hash}',
                                           '--', file_path)

        # if remote does not exist we are checking against the commit sha1
        else:
            diff_line = self.repo.git.diff('--name-status',
                                           f'{branch}...{current_branch_or_hash}',
                                           '--', file_path)

        if not diff_line:
            return ''

        return diff_line.split()[0].upper() if not diff_line.startswith('?') else 'A'

    def get_local_remote_file_content(self, git_file_path: str) -> str:
        """Get local file content from remote branch. For example get origin/master:README.md

        Args:
            git_file_path: The git file path. For example get origin/master:README.md

        Returns:
            The fetched file content.
        """
        file_content = self.repo.git.show(git_file_path)
        return file_content

    def get_local_remote_file_path(self, full_file_path: str, tag: str) -> str:
        """Get local file path of remote branch. For example get origin/master:README.md

        Args:
            full_file_path: The file path to fetch. For example 'content/README.md'
            tag: The tag of the branch. For example 'master'

        Returns:
            The git file path. For example get origin/master:README.md
        """
        relative_file_path = os.path.relpath(full_file_path, self.git_path())
        try:
            remote_name = self.repo.remote()
        except ValueError as exc:
            if "Remote named 'origin' didn't exist" in str(exc):
                remote_name = 'origin'
            else:
                raise exc
        return f'{remote_name}/{tag}:{relative_file_path}'

    def get_all_changed_files(self, prev_ver: str = 'master', committed_only: bool = False,
                              staged_only: bool = False, debug: bool = False,
                              include_untracked: bool = False) -> Set[Path]:
        """Get a set of all changed files in the branch (modified, added and renamed)

        Args:
            prev_ver (str): The base branch against which the comparison is made.
            committed_only (bool): Whether to return only committed files.
            staged_only (bool): Whether to return only staged files.
            debug (bool): Whether to print the debug logs.
            include_untracked (bool): Whether to include untracked files.
        Returns:
            Set. A set of all the changed files in the given branch when comparing to prev_ver
        """
        modified_files: Set[Path] = self.modified_files(prev_ver=prev_ver, committed_only=committed_only,
                                                        staged_only=staged_only, debug=debug,
                                                        include_untracked=include_untracked)
        added_files: Set[Path] = self.added_files(prev_ver=prev_ver, committed_only=committed_only,
                                                  staged_only=staged_only, debug=debug,
                                                  include_untracked=include_untracked)
        renamed_files: Set[Path] = self.renamed_files(prev_ver=prev_ver, committed_only=committed_only,  # type: ignore[assignment]
                                                      staged_only=staged_only, debug=debug,
                                                      include_untracked=include_untracked,
                                                      get_only_current_file_names=True)
        return modified_files.union(added_files).union(renamed_files)


# demisto_sdk/commands/common/legacy_git_tools.py

import os
import re
from typing import Callable, List

import click

from demisto_sdk.commands.common.constants import (KNOWN_FILE_STATUSES,
                                                   PACKS_PACK_META_FILE_NAME,
                                                   TESTS_AND_DOC_DIRECTORIES,
                                                   FileType)
from demisto_sdk.commands.common.errors import Errors
from demisto_sdk.commands.common.hook_validations.base_validator import \
    BaseValidator
from demisto_sdk.commands.common.tools import (filter_packagify_changes,
                                               find_type, get_pack_name,
                                               has_remote_configured,
                                               is_origin_content_repo,
                                               run_command)
from demisto_sdk.commands.validate.validate_manager import ValidateManager


def git_path() -> str:
    git_path = run_command('git rev-parse --show-toplevel')
    return git_path.replace('\n', '')


def get_current_working_branch() -> str:
    branches = run_command('git branch')
    branch_name_reg = re.search(r'\* (.*)', branches)
    if branch_name_reg:
        return branch_name_reg.group(1)

    return ''


def get_changed_files(from_branch: str = 'master', filter_results: Callable = None):
    temp_files = run_command(f'git diff --name-status {from_branch}').split('\n')
    files: List = []
    for file in temp_files:
        if file:
            temp_file_data = {
                'status': file[0]
            }
            if file.lower().startswith('r'):
                file = file.split('\t')
                temp_file_data['name'] = file[2]
            else:
                temp_file_data['name'] = file[2:]
            files.append(temp_file_data)

    if filter_results:
        filter(filter_results, files)

    return files


# """ ################################### Validate Git Tools and filtering #################################### """

def add_origin(branch_name, prev_ver):
    # If git base not provided - check against origin/prev_ver unless using release branch
    if '/' not in prev_ver and not (branch_name.startswith('20.') or branch_name.startswith('21.')):
        prev_ver = 'origin/' + prev_ver
    return prev_ver


def filter_staged_only(modified_files, added_files, old_format_files, changed_meta_files):
    """The function gets sets of files which were changed in the current branch and filters
    out only the files that were changed in the current commit"""
    all_changed_files = run_command('git diff --name-only --staged').split()
    formatted_changed_files = set()

    for changed_file in all_changed_files:
        if find_type(changed_file) in [FileType.POWERSHELL_FILE, FileType.PYTHON_FILE]:
            changed_file = os.path.splitext(changed_file)[0] + '.yml'
        formatted_changed_files.add(changed_file)

    modified_files = modified_files.intersection(formatted_changed_files)
    added_files = added_files.intersection(formatted_changed_files)
    old_format_files = old_format_files.intersection(formatted_changed_files)
    changed_meta_files = changed_meta_files.intersection(formatted_changed_files)
    return modified_files, added_files, old_format_files, changed_meta_files


def get_modified_and_added_files(compare_type,
                                 prev_ver,
                                 ignored_errors=dict(),
                                 no_configuration_prints=False,
                                 staged=False, print_ignored_files=False, is_circle=False, branch_name=None):
    """Get the modified and added files from a specific branch

    Args:
        is_circle (bool): Whether the code runs on circle build.
        print_ignored_files (bool): Whether to print ignored files.
        staged (bool): Whether to return only staged files
        no_configuration_prints (bool): Whether to print additional config prints
        ignored_errors (dict): A dict of ignored errors per file.
        branch_name (str): the branch name
        compare_type (str): whether to run diff with two dots (..) or three (...)
        prev_ver (str): Against which branch to run the comparision - master/last release

    Returns:
        tuple. 3 sets representing modified files, added files and files of old format who have changed.
    """
    if not branch_name:
        branch_name = get_current_working_branch()
    base_validator = BaseValidator(ignored_errors=ignored_errors)
    if not no_configuration_prints:
        if staged:
            click.echo("Collecting staged files only")
        else:
            click.echo("Collecting all committed files")

    prev_ver = add_origin(branch_name, prev_ver)
    # all committed changes of the current branch vs the prev_ver
    all_committed_files_string = run_command(
        f'git diff --name-status {prev_ver}{compare_type}refs/heads/{branch_name}')

    modified_files, added_files, _, old_format_files, changed_meta_files, ignored_files, new_packs = \
        filter_changed_files(all_committed_files_string, prev_ver, print_ignored_files=print_ignored_files)

    if not is_circle:
        remote_configured = has_remote_configured()
        is_origin_demisto = is_origin_content_repo()
        if remote_configured and not is_origin_demisto:
            if not no_configuration_prints:
                click.echo("Collecting all local changed files from fork against the content master")

            # only changes against prev_ver (without local changes)

            all_changed_files_string = run_command(
                'git diff --name-status upstream/master...HEAD')
            modified_files_from_tag, added_files_from_tag, _, _, changed_meta_files_from_tag, \
                ignored_files_from_tag, new_packs_from_tag = \
                filter_changed_files(all_changed_files_string, print_ignored_files=print_ignored_files)

            # all local non-committed changes and changes against prev_ver
            outer_changes_files_string = run_command('git diff --name-status --no-merges upstream/master...HEAD')
            nc_modified_files, nc_added_files, nc_deleted_files, nc_old_format_files, nc_changed_meta_files, \
                nc_ignored_files, nc_new_packs = \
                filter_changed_files(outer_changes_files_string, print_ignored_files=print_ignored_files)

        else:
            if (not is_origin_demisto and not remote_configured) and not no_configuration_prints:
                error_message, error_code = Errors.changes_may_fail_validation()
                base_validator.handle_error(error_message, error_code, file_path="General-Error", warning=True,
                                            drop_line=True)

            if not no_configuration_prints and not staged:
                click.echo("Collecting all local changed files against the content master")

            # only changes against prev_ver (without local changes)
            all_changed_files_string = run_command('git diff --name-status {}'.format(prev_ver))
            modified_files_from_tag, added_files_from_tag, _, _, changed_meta_files_from_tag, \
                ignored_files_from_tag, new_packs_from_tag = \
                filter_changed_files(all_changed_files_string, print_ignored_files=print_ignored_files)

            # all local non-committed changes and changes against prev_ver
            outer_changes_files_string = run_command('git diff --name-status --no-merges HEAD')
            nc_modified_files, nc_added_files, nc_deleted_files, nc_old_format_files, nc_changed_meta_files, \
                nc_ignored_files, nc_new_packs = \
                filter_changed_files(outer_changes_files_string, print_ignored_files=print_ignored_files)

        old_format_files = old_format_files.union(nc_old_format_files)
        modified_files = modified_files.union(
            modified_files_from_tag.intersection(nc_modified_files))

        added_files = added_files.union(
            added_files_from_tag.intersection(nc_added_files))

        changed_meta_files = changed_meta_files.union(
            changed_meta_files_from_tag.intersection(nc_changed_meta_files))

        ignored_files = ignored_files.union(ignored_files_from_tag.intersection(nc_ignored_files))

        new_packs = new_packs.union(new_packs_from_tag.intersection(nc_new_packs))

        modified_files = modified_files - set(nc_deleted_files)
        added_files = added_files - set(nc_deleted_files)
        changed_meta_files = changed_meta_files - set(nc_deleted_files)

    if staged:
        modified_files, added_files, old_format_files, changed_meta_files = \
            filter_staged_only(modified_files, added_files, old_format_files, changed_meta_files)

    modified_packs = get_packs(modified_files).union(get_packs(old_format_files)).union(
        get_packs(added_files))
    return modified_files, added_files, old_format_files, changed_meta_files, \
        modified_packs, ignored_files, new_packs


# flake8: noqa: C901
def filter_changed_files(files_string, tag='master', print_ignored_files=False):
    """Get lists of the modified files in your branch according to the files string.

    Args:
        files_string (string): String that was calculated by git using `git diff` command.
        tag (string): String of git tag used to update modified files.
        print_ignored_files (bool): should print ignored files.

    Returns:
        Tuple of sets.
    """
    all_files = files_string.split('\n')
    deleted_files = set()
    added_files_list = set()
    modified_files_list = set()
    old_format_files = set()
    changed_meta_files = set()
    ignored_files = set()
    new_packs = set()
    for f in all_files:
        file_data: list = list(filter(None, f.split('\t')))

        if not file_data:
            continue

        file_status = file_data[0]
        file_path = file_data[1]

        if file_status.lower().startswith('r'):
            file_status = 'r'
            file_path = file_data[2]
        try:
            file_type = find_type(file_path)
            # if the file is a code file - change path to
            # the associated yml path to trigger release notes validation.
            if file_status.lower() != 'd' and \
                    file_type in [FileType.POWERSHELL_FILE, FileType.PYTHON_FILE] and \
                    not (file_path.endswith('_test.py') or file_path.endswith('.Tests.ps1')):
                # naming convention - code file and yml file in packages must have same name.
                file_path = os.path.splitext(file_path)[0] + '.yml'

            # ignore changes in JS files and unit test files.
            elif file_path.endswith('.js') or file_path.endswith('.py') or file_path.endswith('.ps1'):
                if file_path not in ignored_files:
                    ignored_files.add(file_path)
                    if print_ignored_files:
                        click.secho('Ignoring file path: {} - code file'.format(file_path), fg="yellow")
                continue

            # ignore changes in TESTS_DIRECTORIES files.
            elif any(test_dir in file_path for test_dir in TESTS_AND_DOC_DIRECTORIES):
                if file_path not in ignored_files:
                    ignored_files.add(file_path)
                    if print_ignored_files:
                        click.secho('Ignoring file path: {} - test file'.format(file_path), fg="yellow")
                continue

            # identify deleted files
            if file_status.lower() == 'd' and not file_path.startswith('.'):
                deleted_files.add(file_path)

            # ignore directories
            elif not os.path.isfile(file_path):
                if print_ignored_files:
                    click.secho('Ignoring file path: {} - directory'.format(file_path), fg="yellow")
                continue

            # changes in old scripts and integrations - unified python scripts/integrations
            elif file_status.lower() in ['m', 'a', 'r'] and \
                    file_type in [FileType.INTEGRATION, FileType.SCRIPT] and \
                    ValidateManager.is_old_file_format(file_path, file_type):
                old_format_files.add(file_path)
            # identify modified files
            elif file_status.lower() == 'm' and file_type and not file_path.startswith('.'):
                modified_files_list.add(file_path)
            # identify added files
            elif file_status.lower() == 'a' and file_type and not file_path.startswith('.'):
                added_files_list.add(file_path)
            # identify renamed files
            elif file_status.lower().startswith('r') and file_type:
                # if a code file changed, take the associated yml file.
                if file_type in [FileType.POWERSHELL_FILE, FileType.PYTHON_FILE]:
                    modified_files_list.add(file_path)

                else:
                    # file_data[1] = old name, file_data[2] = new name
                    modified_files_list.add((file_data[1], file_data[2]))
            elif file_status.lower() not in KNOWN_FILE_STATUSES:
                click.secho('{} file status is an unknown one, please check. File status was: {}'
                            .format(file_path, file_status), fg="bright_red")
            # handle meta data file changes
            elif file_path.endswith(PACKS_PACK_META_FILE_NAME):
                if file_status.lower() == 'a':
                    new_packs.add(get_pack_name(file_path))
                elif file_status.lower() == 'm':
                    changed_meta_files.add(file_path)
            else:
                # pipefile and pipelock files should not enter to ignore_files
                if 'Pipfile' not in file_path:
                    if file_path not in ignored_files:
                        ignored_files.add(file_path)
                        if print_ignored_files:
                            click.secho('Ignoring file path: {} - system file'.format(file_path), fg="yellow")
                    else:
                        if print_ignored_files:
                            click.secho('Ignoring file path: {} - system file'.format(file_path), fg="yellow")

        # handle a case where a file was deleted locally though recognised as added against master.
        except FileNotFoundError:
            if file_path not in ignored_files:
                ignored_files.add(file_path)
                if print_ignored_files:
                    click.secho('Ignoring file path: {} - File not found'.format(file_path), fg="yellow")

    modified_files_list, added_files_list, deleted_files = filter_packagify_changes(
        modified_files_list,
        added_files_list,
        deleted_files,
        tag)

    return modified_files_list, added_files_list, deleted_files, old_format_files, \
        changed_meta_files, ignored_files, new_packs


def get_packs(changed_files):
    packs = set()
    for changed_file in changed_files:
        if isinstance(changed_file, tuple):
            changed_file = changed_file[1]
        pack = get_pack_name(changed_file)
        if pack:
            packs.add(pack)

    return packs


# demisto_sdk/commands/common/logger.py

import logging
import os
import sys
from typing import Optional


def logging_setup(verbose: int, quiet: Optional[bool] = False,
                  log_path: Optional[str] = None) -> logging.Logger:
    """ Init logger object for logging in demisto-sdk
        For more info - https://docs.python.org/3/library/logging.html

    Args:
        verbose(int) verosity level - 1-3
        quiet(bool): Whether to output a quiet response.
        log_path(str): Path to save log of all levels

    Returns:
        logging.Logger: logger object
    """
    if quiet:
        verbose = 0
    l: logging.Logger = logging.getLogger('demisto-sdk')
    l.setLevel(logging.DEBUG)

    log_level = logging.getLevelName((6 - 2 * verbose) * 10)

    fmt = logging.Formatter('[%(levelname)s] %(message)s')
    console_handler_index = -1
    file_handler_index = -1

    if l.hasHandlers():
        for i, h in enumerate(l.handlers):
            if h.name == 'console-handler':
                console_handler_index = i
            elif h.name == 'file-handler':
                file_handler_index = i

    if verbose:
        console_handler = logging.StreamHandler(sys.stdout)
        console_handler.name = 'console-handler'
        console_handler.setFormatter(fmt)
        console_handler.setLevel(log_level)

        if console_handler_index == -1:
            l.addHandler(console_handler)
        else:
            l.handlers[console_handler_index] = console_handler

    # Setting debug log file if in circleci
    if log_path:
        file_handler = logging.FileHandler(filename=os.path.join(log_path, 'lint_debug_log.log'))
        file_handler.setFormatter(
            logging.Formatter('[%(asctime)s] - [%(threadName)s] - [%(levelname)s] - %(message)s'))
        file_handler.name = 'file-handler'
        file_handler.setLevel(level=logging.DEBUG)

        if file_handler_index == -1:
            l.addHandler(file_handler)
        else:
            l.handlers[file_handler_index] = file_handler

    l.propagate = False

    return l


logger: logging.Logger = logging_setup(verbose=2,
                                       quiet=False)


# Python program to print
# colored text and background
class Colors:
    """Colors class:reset all colors with colors.reset; two
    sub classes fg for foreground
    and bg for background; use as colors.subclass.colorname.
    i.e. colors.fg.red or colors.bg.greenalso, the generic bold, disable,
    underline, reverse, strike through,
    and invisible work with the main class i.e. colors.bold"""
    reset = '\033[0m'
    bold = '\033[01m'
    disable = '\033[02m'
    underline = '\033[04m'
    reverse = '\033[07m'
    strikethrough = '\033[09m'
    invisible = '\033[08m'

    class Fg:
        """Forgrownd"""
        black = '\033[30m'
        red = '\033[31m'
        green = '\033[32m'
        orange = '\033[33m'
        blue = '\033[34m'
        purple = '\033[35m'
        cyan = '\033[36m'
        lightgrey = '\033[37m'
        darkgrey = '\033[90m'
        lightred = '\033[91m'
        lightgreen = '\033[92m'
        yellow = '\033[93m'
        lightblue = '\033[94m'
        pink = '\033[95m'
        lightcyan = '\033[96m'

    class Bg:
        """Backgrownd"""
        black = '\033[40m'
        red = '\033[41m'
        green = '\033[42m'
        orange = '\033[43m'
        blue = '\033[44m'
        purple = '\033[45m'
        cyan = '\033[46m'


# demisto_sdk/commands/common/tools.py

import argparse
import glob
import io
import json
import os
import re
import shlex
import sys
import urllib.parse
from configparser import ConfigParser, MissingSectionHeaderError
from contextlib import contextmanager
from distutils.version import LooseVersion
from enum import Enum
from functools import lru_cache, partial
from pathlib import Path
from subprocess import DEVNULL, PIPE, Popen, check_output
from typing import Callable, Dict, List, Match, Optional, Tuple, Type, Union

import click
import colorama
import demisto_client
import git
import giturlparse
import requests
import urllib3
import yaml
from packaging.version import parse
from ruamel.yaml import YAML

from demisto_sdk.commands.common.constants import (
    ALL_FILES_VALIDATION_IGNORE_WHITELIST, API_MODULES_PACK, CLASSIFIERS_DIR,
    DASHBOARDS_DIR, DEF_DOCKER, DEF_DOCKER_PWSH,
    DEFAULT_CONTENT_ITEM_FROM_VERSION, DEFAULT_CONTENT_ITEM_TO_VERSION,
    DOC_FILES_DIR, ID_IN_COMMONFIELDS, ID_IN_ROOT, INCIDENT_FIELDS_DIR,
    INCIDENT_TYPES_DIR, INDICATOR_FIELDS_DIR, INDICATOR_TYPES_DIR,
    INTEGRATIONS_DIR, JOBS_DIR, LAYOUTS_DIR, LISTS_DIR,
    MARKETPLACE_KEY_PACK_METADATA, METADATA_FILE_NAME,
    OFFICIAL_CONTENT_ID_SET_PATH, PACK_METADATA_IRON_BANK_TAG,
    PACKAGE_SUPPORTING_DIRECTORIES, PACKAGE_YML_FILE_REGEX, PACKS_DIR,
    PACKS_DIR_REGEX, PACKS_PACK_IGNORE_FILE_NAME, PACKS_PACK_META_FILE_NAME,
    PACKS_README_FILE_NAME, PLAYBOOKS_DIR, PRE_PROCESS_RULES_DIR,
    RELEASE_NOTES_DIR, RELEASE_NOTES_REGEX, REPORTS_DIR, SCRIPTS_DIR,
    TEST_PLAYBOOKS_DIR, TYPE_PWSH, UNRELEASE_HEADER, UUID_REGEX, WIDGETS_DIR,
    XSOAR_CONFIG_FILE, FileType, GitContentConfig, MarketplaceVersions,
    urljoin)
from demisto_sdk.commands.common.git_util import GitUtil

urllib3.disable_warnings()

# inialize color palette
colorama.init()

ryaml = YAML()
ryaml.preserve_quotes = True
ryaml.allow_duplicate_keys = True


class LOG_COLORS:
    NATIVE = colorama.Style.RESET_ALL
    RED = colorama.Fore.RED
    GREEN = colorama.Fore.GREEN
    YELLOW = colorama.Fore.YELLOW
    WHITE = colorama.Fore.WHITE


LOG_VERBOSE = False

LAYOUT_CONTAINER_FIELDS = {'details', 'detailsV2', 'edit', 'close', 'mobile', 'quickView', 'indicatorsQuickView',
                           'indicatorsDetails'}
SDK_PYPI_VERSION = r'https://pypi.org/pypi/demisto-sdk/json'


class XsoarLoader(yaml.SafeLoader):
    """
    New yaml loader based on SafeLoader which can handle the XSOAR related changes in yml.
    """

    def reference(self, node):
        """
        !reference - found in gitlab ci files.
        handle !reference tag by turning its line into a string.
        """
        build_string = '!reference ' + str(self.construct_sequence(node))
        return self.construct_yaml_str(yaml.ScalarNode(tag='!reference', value=build_string))


XsoarLoader.add_constructor('!reference', XsoarLoader.reference)


def set_log_verbose(verbose: bool):
    global LOG_VERBOSE
    LOG_VERBOSE = verbose


def get_log_verbose() -> bool:
    return LOG_VERBOSE


def get_yml_paths_in_dir(project_dir: str, error_msg: str = '') -> Tuple[list, str]:
    """
    Gets the project directory and returns the path of the first yml file in that directory
    :param project_dir: string path to the project_dir
    :param error_msg: the error msg to show to the user in case not yml files found in the directory
    :return: first returned argument is the list of all yml files paths in the directory, second returned argument is a
    string path to the first yml file in project_dir
    """
    yml_files = glob.glob(os.path.join(project_dir, '*.yml'))
    if not yml_files:
        if error_msg:
            print(error_msg)
        return [], ''
    return yml_files, yml_files[0]


# print srt in the given color
def print_color(obj, color):
    print(u'{}{}{}'.format(color, obj, LOG_COLORS.NATIVE))


def get_files_in_dir(project_dir: str, file_endings: list, recursive: bool = True) -> list:
    """
    Gets the project directory and returns the path of all yml, json and py files in it
    Args:
        project_dir: String path to the project_dir
        file_endings: List of file endings to search for in a given directory
        recursive: Indicates whether search should be recursive or not
    :return: The path of files with file_endings in the current dir
    """
    files = []
    project_path = Path(project_dir)
    glob_function = project_path.rglob if recursive else project_path.glob
    for file_type in file_endings:
        if project_dir.endswith(file_type):
            return [project_dir]
        files.extend([str(f) for f in glob_function(f'*.{file_type}')])
    return files


def src_root() -> Path:
    """ Demisto-sdk absolute path from src root.

    Returns:
        Path: src root path.
    """
    git_dir = git.Repo(Path.cwd(),
                       search_parent_directories=True).working_tree_dir

    return Path(git_dir) / 'demisto_sdk'


def print_error(error_str):
    print_color(error_str, LOG_COLORS.RED)


def print_warning(warning_str):
    print_color(warning_str, LOG_COLORS.YELLOW)


def print_success(success_str):
    print_color(success_str, LOG_COLORS.GREEN)


def run_command(command, is_silenced=True, exit_on_error=True, cwd=None):
    """Run a bash command in the shell.

    Args:
        command (string): The string of the command you want to execute.
        is_silenced (bool): Whether to print command output.
        exit_on_error (bool): Whether to exit on command error.
        cwd (str): the path to the current working directory.

    Returns:
        string. The output of the command you are trying to execute.
    """
    if is_silenced:
        p = Popen(command.split(), stdout=PIPE, stderr=PIPE, universal_newlines=True, cwd=cwd)
    else:
        p = Popen(command.split(), cwd=cwd)  # type: ignore

    output, err = p.communicate()
    if err:
        if exit_on_error:
            print_error('Failed to run command {}\nerror details:\n{}'.format(command, err))
            sys.exit(1)
        else:
            raise RuntimeError('Failed to run command {}\nerror details:\n{}'.format(command, err))

    return output


core_pack_list: Optional[
    list] = None  # Initiated in get_core_pack_list function. Here to create a "cached" core_pack_list


def get_core_pack_list() -> list:
    """Getting the core pack list from Github content

    Returns:
        Core pack list
    """
    global core_pack_list
    if isinstance(core_pack_list, list):
        return core_pack_list
    if not is_external_repository():
        core_pack_list = get_remote_file(
            'Tests/Marketplace/core_packs_list.json', github_repo=GitContentConfig.OFFICIAL_CONTENT_REPO_NAME
        ) or []
    else:
        # no core packs in external repos.
        core_pack_list = []
    return core_pack_list


# @lru_cache(maxsize=64)
def get_remote_file(
        full_file_path: str,
        tag: str = 'master',
        return_content: bool = False,
        suppress_print: bool = False,
        github_repo: Optional[str] = None
):
    """
    Args:
        full_file_path:The full path of the file.
        tag: The branch name. default is 'master'
        return_content: Determines whether to return the file's raw content or the dict representation of it.
        suppress_print: whether to suppress the warning message in case the file was not found.
        github_repo: The repository to grab the file from
    Returns:
        The file content in the required format.

    """
    git_config = GitContentConfig(github_repo)
    if git_config.GITLAB_ID:
        full_file_path_quote_plus = urllib.parse.quote_plus(full_file_path)
        git_path = urljoin(git_config.BASE_RAW_GITLAB_LINK, 'files', full_file_path_quote_plus, 'raw')
        tag = tag.replace('origin/', '')
    else:  # github
        # 'origin/' prefix is used to compared with remote branches but it is not a part of the github url.
        tag = tag.replace('origin/', '').replace('demisto/', '')
        git_path = urljoin(git_config.CONTENT_GITHUB_LINK, tag, full_file_path)

    local_content = '{}'

    github_token: Optional[str] = None
    gitlab_token: Optional[str] = None
    try:
        external_repo = is_external_repository()
        if external_repo:
            github_token = git_config.Credentials.GITHUB_TOKEN
            gitlab_token = git_config.Credentials.GITLAB_TOKEN
            if gitlab_token and git_config.GITLAB_ID:
                res = requests.get(git_path,
                                   params={'ref': tag},
                                   headers={'PRIVATE-TOKEN': gitlab_token},
                                   verify=False)
                res.raise_for_status()
            elif github_token:
                res = requests.get(git_path, verify=False, timeout=10, headers={
                    'Authorization': f"Bearer {github_token}",
                    'Accept': f'application/vnd.github.VERSION.raw',
                })  # Sometime we need headers
                if not res.ok:  # sometime we need param token
                    res = requests.get(
                        git_path,
                        verify=False,
                        timeout=10,
                        params={'token': github_token}
                    )
                res.raise_for_status()
            else:
                # If no token defined, maybe it's a open repo. 🤷‍♀️
                res = requests.get(git_path, verify=False, timeout=10)
                # And maybe it's just not defined. 😢
                if not res.ok:
                    if not suppress_print:
                        click.secho(
                            f'You are working in a private repository: "{git_config.CURRENT_REPOSITORY}".\n'
                            f'The github token in your environment is undefined.\n'
                            f'Getting file from local repository instead. \n'
                            f'If you wish to get the file from the remote repository, \n'
                            f'Please define your github or gitlab token in your environment.\n'
                            f'`export {git_config.Credentials.ENV_GITHUB_TOKEN_NAME}=<TOKEN> or`\n'
                            f'export {git_config.Credentials.ENV_GITLAB_TOKEN_NAME}=<TOKEN>', fg='yellow'
                        )
                        click.echo("Getting file from local environment")
                    # Get from local git origin/master instead
                    repo = git.Repo(os.path.dirname(full_file_path), search_parent_directories=True)
                    repo_git_util = GitUtil(repo)
                    git_path = repo_git_util.get_local_remote_file_path(full_file_path, tag)
                    local_content = repo_git_util.get_local_remote_file_content(git_path)
        else:
            res = requests.get(git_path, verify=False, timeout=10)
            res.raise_for_status()
    except Exception as exc:
        # Replace token secret if needed
        err_msg: str = str(exc).replace(github_token, 'XXX') if github_token else str(exc)
        err_msg = err_msg.replace(gitlab_token, 'XXX') if gitlab_token else err_msg
        if not suppress_print:
            click.secho(
                f'Could not find the old entity file under "{git_path}".\n'
                'please make sure that you did not break backward compatibility.\n'
                f'Reason: {err_msg}', fg='yellow'
            )
        return {}
    file_content = res.content if res.ok else local_content
    if return_content:
        return file_content
    if full_file_path.endswith('json'):
        details = res.json() if res.ok else json.loads(local_content)
    elif full_file_path.endswith('yml'):
        details = yaml.safe_load(file_content)  # type: ignore[arg-type]
    # if neither yml nor json then probably a CHANGELOG or README file.
    else:
        details = {}
    return details


def filter_files_on_pack(pack: str, file_paths_list=str()) -> set:
    """
    filter_files_changes_on_pack.

    :param file_paths_list: list of content files
    :param pack: pack to filter

    :return: files_paths_on_pack: set of file paths contains only files located in the given pack
    """
    files_paths_on_pack = set()
    for file in file_paths_list:
        if get_pack_name(file) == pack:
            files_paths_on_pack.add(file)

    return files_paths_on_pack


def filter_packagify_changes(modified_files, added_files, removed_files, tag='master'):
    """
    Mark scripts/integrations that were removed and added as modified.

    :param modified_files: list of modified files in branch
    :param added_files: list of new files in branch
    :param removed_files: list of removed files in branch
    :param tag: tag of compared revision

    :return: tuple of updated lists: (modified_files, updated_added_files, removed_files)
    """
    # map IDs to removed files
    packagify_diff = {}  # type: dict
    for file_path in removed_files:
        if file_path.split("/")[0] in PACKAGE_SUPPORTING_DIRECTORIES:
            if PACKS_README_FILE_NAME in file_path:
                continue
            details = get_remote_file(file_path, tag)
            if details:
                uniq_identifier = '_'.join([
                    details['name'],
                    details.get('fromversion', DEFAULT_CONTENT_ITEM_FROM_VERSION),
                    details.get('toversion', DEFAULT_CONTENT_ITEM_TO_VERSION)
                ])
                packagify_diff[uniq_identifier] = file_path

    updated_added_files = set()
    for file_path in added_files:
        if file_path.split("/")[0] in PACKAGE_SUPPORTING_DIRECTORIES:
            if PACKS_README_FILE_NAME in file_path:
                updated_added_files.add(file_path)
                continue
            with open(file_path) as f:
                details = yaml.safe_load(f.read())

            uniq_identifier = '_'.join([
                details['name'],
                details.get('fromversion', DEFAULT_CONTENT_ITEM_FROM_VERSION),
                details.get('toversion', DEFAULT_CONTENT_ITEM_TO_VERSION)
            ])
            if uniq_identifier in packagify_diff:
                # if name appears as added and removed, this is packagify process - treat as modified.
                removed_files.remove(packagify_diff[uniq_identifier])
                modified_files.add((packagify_diff[uniq_identifier], file_path))
                continue

        updated_added_files.add(file_path)

    # remove files that are marked as both "added" and "modified"
    for file_path in modified_files:
        if isinstance(file_path, tuple):
            updated_added_files -= {file_path[1]}
        else:
            updated_added_files -= {file_path}

    return modified_files, updated_added_files, removed_files


def get_child_directories(directory):
    """Return a list of paths of immediate child directories of the 'directory' argument"""
    if not os.path.isdir(directory):
        return []
    child_directories = [
        os.path.join(directory, path) for
        path in os.listdir(directory) if os.path.isdir(os.path.join(directory, path))
    ]
    return child_directories


def get_child_files(directory):
    """Return a list of paths of immediate child files of the 'directory' argument"""
    if not os.path.isdir(directory):
        return []
    child_files = [
        os.path.join(directory, path) for
        path in os.listdir(directory) if os.path.isfile(os.path.join(directory, path))
    ]
    return child_files


def has_remote_configured():
    """
    Checks to see if a remote named "upstream" is configured. This is important for forked
    repositories as it will allow validation against the demisto/content master branch as
    opposed to the master branch of the fork.
    :return: bool : True if remote is configured, False if not.
    """
    remotes = run_command('git remote -v')
    if re.search(GitContentConfig.CONTENT_GITHUB_UPSTREAM, remotes):
        return True
    else:
        return False


def is_origin_content_repo():
    """
    Checks to see if a remote named "origin" is configured. This check helps to determine if
    validation needs to be ran against the origin master branch or the upstream master branch
    :return: bool : True if remote is configured, False if not.
    """
    remotes = run_command('git remote -v')
    if re.search(GitContentConfig.CONTENT_GITHUB_ORIGIN, remotes):
        return True
    else:
        return False


def get_last_remote_release_version():
    """
    Get latest release tag from PYPI.

    :return: tag
    """
    if not os.environ.get(
            'CI'):  # Check only when no on CI. If you want to disable it - use `DEMISTO_SDK_SKIP_VERSION_CHECK` environment variable
        try:
            pypi_request = requests.get(SDK_PYPI_VERSION, verify=False, timeout=5)
            pypi_request.raise_for_status()
            pypi_json = pypi_request.json()
            version = pypi_json.get('info', {}).get('version', '')
            return version
        except Exception as exc:
            exc_msg = str(exc)
            if isinstance(exc, requests.exceptions.ConnectionError):
                exc_msg = f'{exc_msg[exc_msg.find(">") + 3:-3]}.\n' \
                          f'This may happen if you are not connected to the internet.'
            print_warning(f'Could not get latest demisto-sdk version.\nEncountered error: {exc_msg}')

    return ''


def get_file(file_path, type_of_file):
    data_dictionary = None
    with open(os.path.expanduser(file_path), mode="r", encoding="utf8") as f:
        if file_path.endswith(type_of_file):
            read_file = f.read()
            replaced = read_file.replace("simple: =", "simple: '='")
            # revert str to stream for loader
            stream = io.StringIO(replaced)
            try:
                if type_of_file in ('yml', '.yml'):
                    data_dictionary = yaml.load(stream, Loader=XsoarLoader)

                else:
                    data_dictionary = json.load(stream)

            except Exception as e:
                raise ValueError(
                    "{} has a structure issue of file type {}. Error was: {}".format(file_path, type_of_file, str(e)))
    if isinstance(data_dictionary, (dict, list)):
        return data_dictionary
    return {}


def get_yaml(file_path):
    return get_file(file_path, 'yml')


def get_ryaml(file_path: str) -> dict:
    """
    Get yml file contents using ruaml

    Args:
        file_path (string): The file path

    Returns:
        dict. The yml contents
    """
    try:
        with open(os.path.expanduser(file_path), 'r') as yf:
            data = ryaml.load(yf)
    except FileNotFoundError as e:
        click.echo(f'File {file_path} not found. Error was: {str(e)}', nl=True)
    except Exception as e:
        click.echo(
            "{} has a structure issue of file type yml. Error was: {}".format(file_path, str(e)), nl=True)
    return data


def get_json(file_path):
    return get_file(file_path, 'json')


def get_script_or_integration_id(file_path):
    data_dictionary = get_yaml(file_path)

    if data_dictionary:
        commonfields = data_dictionary.get('commonfields', {})
        return commonfields.get('id', ['-', ])


def get_api_module_integrations_set(changed_api_modules, integration_set):
    integrations_set = list()
    for integration in integration_set:
        integration_data = list(integration.values())[0]
        if integration_data.get('api_modules', '') in changed_api_modules:
            integrations_set.append(integration_data)
    return integrations_set


def get_api_module_ids(file_list):
    """Extracts APIModule IDs from the file list"""
    api_module_set = set()
    if file_list:
        for pf in file_list:
            parent = pf
            while f'/{API_MODULES_PACK}/Scripts/' in parent:
                parent = get_parent_directory_name(parent, abs_path=True)
                if f'/{API_MODULES_PACK}/Scripts/' in parent:
                    pf = parent
            if parent != pf:
                api_module_set.add(os.path.basename(pf))
    return api_module_set


def get_entity_id_by_entity_type(data: dict, content_entity: str):
    """
    Returns the id of the content entity given its entity type
    :param data: The data of the file
    :param content_entity: The content entity type
    :return: The file id
    """
    try:
        if content_entity in (INTEGRATIONS_DIR, SCRIPTS_DIR):
            return data.get('commonfields', {}).get('id', '')
        elif content_entity == LAYOUTS_DIR:
            return data.get('typeId', '')
        else:
            return data.get('id', '')

    except AttributeError:
        raise ValueError(f"Could not retrieve id from file of type {content_entity} - make sure the file structure is "
                         f"valid")


def get_entity_name_by_entity_type(data: dict, content_entity: str):
    """
    Returns the name of the content entity given its entity type
    :param data: The data of the file
    :param content_entity: The content entity type
    :return: The file name
    """
    try:
        if content_entity == LAYOUTS_DIR:
            if 'typeId' in data:
                return data.get('typeId', '')
            return data.get('name', '')  # for layoutscontainer
        return data.get('name', '')

    except AttributeError:
        raise ValueError(
            f"Could not retrieve name from file of type {content_entity} - make sure the file structure is "
            f"valid")


def collect_ids(file_path):
    """Collect id mentioned in file_path"""
    data_dictionary = get_yaml(file_path)

    if data_dictionary:
        return data_dictionary.get('id', '-')


def get_from_version(file_path):
    data_dictionary = get_yaml(file_path) if file_path.endswith('yml') else get_json(file_path)

    if data_dictionary:
        from_version = data_dictionary.get('fromversion') if 'fromversion' in data_dictionary \
            else data_dictionary.get('fromVersion', DEFAULT_CONTENT_ITEM_FROM_VERSION)
        if from_version == '':
            return DEFAULT_CONTENT_ITEM_FROM_VERSION

        if not re.match(r'^\d{1,2}\.\d{1,2}\.\d{1,2}$', from_version):
            raise ValueError(f'{file_path} fromversion is invalid "{from_version}". '
                             'Should be of format: "x.x.x". for example: "4.5.0"')

        return from_version

    return DEFAULT_CONTENT_ITEM_FROM_VERSION


def get_to_version(file_path):
    data_dictionary = get_yaml(file_path)

    if data_dictionary:
        to_version = data_dictionary.get('toversion', DEFAULT_CONTENT_ITEM_TO_VERSION)
        if not re.match(r'^\d{1,2}\.\d{1,2}\.\d{1,2}$', to_version):
            raise ValueError(f'{file_path} toversion is invalid "{to_version}". '
                             'Should be of format: "x.x.x". for example: "4.5.0"')

        return to_version

    return DEFAULT_CONTENT_ITEM_TO_VERSION


def str2bool(v):
    if v.lower() in ('yes', 'true', 't', 'y', '1'):
        return True

    if v.lower() in ('no', 'false', 'f', 'n', '0'):
        return False

    raise argparse.ArgumentTypeError('Boolean value expected.')


def to_dict(obj):
    if isinstance(obj, Enum):
        return obj.name

    if not hasattr(obj, '__dict__'):
        return obj

    result = {}
    for key, val in obj.__dict__.items():
        if key.startswith("_"):
            continue

        element = []
        if isinstance(val, list):
            for item in val:
                element.append(to_dict(item))
        else:
            element = to_dict(val)
        result[key] = element

    return result


def old_get_release_notes_file_path(file_path):
    dir_name = os.path.dirname(file_path)

    # CHANGELOG in pack sub dirs
    if re.match(PACKAGE_YML_FILE_REGEX, file_path):
        return os.path.join(dir_name, 'CHANGELOG.md')

    # We got the CHANGELOG file to get its release notes
    if file_path.endswith('CHANGELOG.md'):
        return file_path

    # outside of packages, change log file will include the original file name.
    file_name = os.path.basename(file_path)
    return os.path.join(dir_name, os.path.splitext(file_name)[0] + '_CHANGELOG.md')


def old_get_latest_release_notes_text(rn_path):
    if not os.path.isfile(rn_path):
        # releaseNotes were not provided
        return None

    with open(rn_path) as f:
        rn = f.read()

    if not rn:
        # empty releaseNotes is not supported
        return None

    new_rn = re.findall(RELEASE_NOTES_REGEX, rn)
    if new_rn:
        # get release notes up to release header
        new_rn = new_rn[0].rstrip()
    else:
        new_rn = rn.replace(UNRELEASE_HEADER, '')  # type: ignore

    return new_rn if new_rn else None


def get_release_notes_file_path(file_path):
    """
    Accepts file path which is alleged to contain release notes. Validates that the naming convention
    is followed. If the file identified does not match the naming convention, error is returned.
    :param file_path: str - File path of the suspected release note.
    :return: file_path: str - Validated release notes path.
    """
    if file_path is None:
        print_warning("Release notes were not found.")
        return None
    else:
        if bool(re.search(r'\d{1,2}_\d{1,2}_\d{1,2}\.md', file_path)):
            return file_path
        else:
            print_warning(f'Unsupported file type found in ReleaseNotes directory - {file_path}')
            return None


def get_latest_release_notes_text(rn_path):
    if rn_path is None:
        print_warning('Path to release notes not found.')
        rn = None
    else:
        with open(rn_path) as f:
            rn = f.read()

        if not rn:
            print_error(f'Release Notes may not be empty. Please fill out correctly. - {rn_path}')
            return None

    return rn if rn else None


def format_version(version):
    """format server version to form X.X.X

    Args:
        version (string): string representing Demisto version

    Returns:
        string.
        The formatted server version.
    """
    formatted_version = version
    if len(version.split('.')) == 1:
        formatted_version = f'{version}.0.0'
    elif len(version.split('.')) == 2:
        formatted_version = f'{version}.0'

    return formatted_version


def server_version_compare(v1, v2):
    """compare Demisto versions

    Args:
        v1 (string): string representing Demisto version (first comparable)
        v2 (string): string representing Demisto version (second comparable)


    Returns:
        int.
        0 for equal versions.
        positive if v1 later version than v2.
        negative if v2 later version than v1.
    """

    v1 = format_version(v1)
    v2 = format_version(v2)

    _v1, _v2 = LooseVersion(v1), LooseVersion(v2)
    if _v1 == _v2:
        return 0
    if _v1 > _v2:
        return 1
    return -1


def run_threads_list(threads_list):
    """
    Start a list of threads and wait for completion (join)

    Arguments:
        threads_list (list of threads) -- list of threads to start and wait for join
    """
    # run each command in a separate thread
    for t in threads_list:
        t.start()
    # wait for the commands to complete
    for t in threads_list:
        t.join()


def is_file_path_in_pack(file_path):
    return bool(re.findall(PACKS_DIR_REGEX, file_path))


def get_pack_name(file_path):
    """
    extract pack name (folder name) from file path

    Arguments:
        file_path (str): path of a file inside the pack

    Returns:
        pack name (str)
    """
    if isinstance(file_path, Path):
        file_path = str(file_path)
    # the regex extracts pack name from relative paths, for example: Packs/EWSv2 -> EWSv2
    match = re.search(rf'{PACKS_DIR_REGEX}[/\\]([^/\\]+)[/\\]?', file_path)
    return match.group(1) if match else None


def get_pack_names_from_files(file_paths, skip_file_types=None):
    if skip_file_types is None:
        skip_file_types = set()

    packs = set()
    for path in file_paths:
        # renamed files are in a tuples - the second element is the new file name
        if isinstance(path, tuple):
            path = path[1]

        file_type = find_type(path)
        if file_type not in skip_file_types:
            pack = get_pack_name(path)
            if pack and is_file_path_in_pack(path):
                packs.add(pack)
    return packs


def filter_files_by_type(file_paths=None, skip_file_types=None) -> set:
    """get set of files and return the set whiteout the types to skip

    Args:
    - file_paths (set): set of content files.
    - skip_file_types List[str]: list of file types to skip.

    Returns:
    files (set): list of files whiteout the types to skip
    """
    if file_paths is None:
        file_paths = set()
    files = set()
    for path in file_paths:
        # renamed files are in a tuples - the second element is the new file name
        if isinstance(path, tuple):
            path = path[1]
        file_type = find_type(path)
        if file_type not in skip_file_types and is_file_path_in_pack(path):
            files.add(path)
    return files


def pack_name_to_path(pack_name):
    return os.path.join(PACKS_DIR, pack_name)


def get_pack_ignore_file_path(pack_name):
    return os.path.join(PACKS_DIR, pack_name, PACKS_PACK_IGNORE_FILE_NAME)


def get_test_playbook_id(test_playbooks_list: list, tpb_path: str) -> Tuple:  # type: ignore
    """

    Args:
        test_playbooks_list: The test playbook list from id_set
        tpb_path: test playbook path.

    Returns (Tuple): test playbook name and pack.

    """
    for test_playbook_dict in test_playbooks_list:
        test_playbook_id = list(test_playbook_dict.keys())[0]
        test_playbook_path = test_playbook_dict[test_playbook_id].get('file_path')
        test_playbook_pack = test_playbook_dict[test_playbook_id].get('pack')
        if not test_playbook_path or not test_playbook_pack:
            continue

        if tpb_path in test_playbook_path:
            return test_playbook_id, test_playbook_pack
    return None, None


def get_ignore_pack_skipped_tests(pack_name: str, modified_packs: set, id_set: dict) -> set:
    """
    Retrieve the skipped tests of a given pack, as detailed in the .pack-ignore file

    expected ignored tests structure in .pack-ignore:
        [file:playbook-Not-To-Run-Directly.yml]
        ignore=auto-test

    Arguments:
        pack_name (str): name of the pack
        modified_packs (set): Set of modified packs
        id_set (dict): ID set

    Returns:
        ignored_tests_set (set[str]): set of ignored test ids

    """
    if not modified_packs:
        modified_packs = {pack_name}
    ignored_tests_set = set()
    file_name_to_ignore_dict: Dict[str, List[str]] = {}
    test_playbooks = id_set.get('TestPlaybooks', {})

    pack_ignore_path = get_pack_ignore_file_path(pack_name)
    if pack_name in modified_packs:
        if os.path.isfile(pack_ignore_path):
            try:
                # read pack_ignore using ConfigParser
                config = ConfigParser(allow_no_value=True)
                config.read(pack_ignore_path)

                # go over every file in the config
                for section in config.sections():
                    if section.startswith("file:"):
                        # given section is of type file
                        file_name: str = section[5:]
                        for key in config[section]:
                            if key == 'ignore':
                                # group ignore codes to a list
                                file_name_to_ignore_dict[file_name] = str(config[section][key]).split(',')
            except MissingSectionHeaderError:
                pass

    for file_name, ignore_list in file_name_to_ignore_dict.items():
        if any(ignore_code == 'auto-test' for ignore_code in ignore_list):
            test_id, test_pack = get_test_playbook_id(test_playbooks, file_name)
            if test_id:
                ignored_tests_set.add(test_id)
    return ignored_tests_set


def get_all_docker_images(script_obj) -> List[str]:
    """Gets a yml as dict and returns a list of all 'dockerimage' values in the yml.

    Args:
        script_obj (dict): A yml dict.

    Returns:
        List. A list of all docker images.
    """
    # this makes sure the first docker in the list is the main docker image.
    def_docker_image = DEF_DOCKER
    if script_obj.get('type') == TYPE_PWSH:
        def_docker_image = DEF_DOCKER_PWSH
    imgs = [script_obj.get('dockerimage') or def_docker_image]

    # get additional docker images
    for key in script_obj.keys():
        if 'dockerimage' in key and key != 'dockerimage':
            if isinstance(script_obj.get(key), str):
                imgs.append(script_obj.get(key))

            elif isinstance(script_obj.get(key), list):
                imgs.extend(script_obj.get(key))

    return imgs


def get_python_version(docker_image, log_verbose=None, no_prints=False):
    """
    Get the python version of a docker image
    Arguments:
        docker_image {string} -- Docker image being used by the project
    Return:
        python version as a float (2.7, 3.7)
    Raises:
        ValueError -- if version is not supported
    """
    if log_verbose is None:
        log_verbose = LOG_VERBOSE
    stderr_out = None if log_verbose else DEVNULL
    py_ver = check_output(["docker", "run", "--rm", docker_image,
                           "python", "-c",
                           "import sys;print('{}.{}'.format(sys.version_info[0], sys.version_info[1]))"],
                          universal_newlines=True, stderr=stderr_out).strip()
    if not no_prints:
        print("Detected python version: [{}] for docker image: {}".format(py_ver, docker_image))

    py_num = float(py_ver)
    if py_num < 2.7 or (3 < py_num < 3.4):  # pylint can only work on python 3.4 and up
        raise ValueError("Python vesion for docker image: {} is not supported: {}. "
                         "We only support python 2.7.* and python3 >= 3.4.".format(docker_image, py_num))
    return py_num


def get_pipenv_dir(py_version, envs_dirs_base):
    """
    Get the direcotry holding pipenv files for the specified python version
    Arguments:
        py_version {float} -- python version as 2.7 or 3.7
    Returns:
        string -- full path to the pipenv dir
    """
    return "{}{}".format(envs_dirs_base, int(py_version))


def print_v(msg, log_verbose=None):
    if log_verbose is None:
        log_verbose = LOG_VERBOSE
    if log_verbose:
        print(msg)


def get_dev_requirements(py_version, envs_dirs_base):
    """
    Get the requirements for the specified py version.

    Arguments:
        py_version {float} -- python version as float (2.7, 3.7)

    Raises:
        ValueError -- If can't detect python version

    Returns:
        string -- requirement required for the project
    """
    env_dir = get_pipenv_dir(py_version, envs_dirs_base)
    stderr_out = None if LOG_VERBOSE else DEVNULL
    requirements = check_output(['pipenv', 'lock', '-r', '-d'], cwd=env_dir, universal_newlines=True,
                                stderr=stderr_out)
    print_v("dev requirements:\n{}".format(requirements))
    return requirements


def get_dict_from_file(path: str, use_ryaml: bool = False,
                       raises_error: bool = True) -> Tuple[Dict, Union[str, None]]:
    """
    Get a dict representing the file

    Arguments:
        path - a path to the file
        use_ryaml - Whether to use ryaml for file loading or not
        raises_error - Whether to raise a FileNotFound error if `path` is not a valid file.

    Returns:
        dict representation of the file, and the file_type, either .yml or .json
    """
    try:
        if path:
            if path.endswith('.yml'):
                if use_ryaml:
                    return get_ryaml(path), 'yml'
                return get_yaml(path), 'yml'
            elif path.endswith('.json'):
                return get_json(path), 'json'
            elif path.endswith('.py'):
                return {}, 'py'
    except FileNotFoundError as e:
        if raises_error:
            raise

    return {}, None


@lru_cache()
def find_type_by_path(path: Union[str, Path] = '') -> Optional[FileType]:
    """Find docstring by file path only
    This function is here as we want to implement lru_cache and we can do it on `find_type`
    as dict is not hashable.

    Args:
        path: Path to find its file type. Defaults to ''.

    Returns:
        FileType: The file type if found. else None;
    """
    path = Path(path)
    if path.suffix == '.md':
        if 'README' in path.name:
            return FileType.README

        if RELEASE_NOTES_DIR in path.parts:
            return FileType.RELEASE_NOTES

        if 'description' in path.name:
            return FileType.DESCRIPTION

        if 'CONTRIBUTORS' in path.name:
            return FileType.CONTRIBUTORS

        return FileType.CHANGELOG

    if path.suffix == '.json':
        if RELEASE_NOTES_DIR in path.parts:
            return FileType.RELEASE_NOTES_CONFIG
        elif LISTS_DIR in os.path.dirname(path):
            return FileType.LISTS
        elif JOBS_DIR in path.parts:
            return FileType.JOB
        elif INDICATOR_TYPES_DIR in path.parts:
            return FileType.REPUTATION

    # integration image
    if path.name.endswith('_image.png'):
        if path.name.endswith("Author_image.png"):
            return FileType.AUTHOR_IMAGE
        return FileType.IMAGE

    # doc files images
    if path.suffix == ".png" and DOC_FILES_DIR in path.parts:
        return FileType.DOC_IMAGE

    if path.suffix == '.ps1':
        return FileType.POWERSHELL_FILE

    if path.suffix == '.py':
        return FileType.PYTHON_FILE

    if path.suffix == '.js':
        return FileType.JAVASCRIPT_FILE

    if path.name.endswith(XSOAR_CONFIG_FILE):
        return FileType.XSOAR_CONFIG

    return None


# flake8: noqa: C901


def find_type(path: str = '', _dict=None, file_type: Optional[str] = None, ignore_sub_categories: bool = False):
    """
    returns the content file type

    Arguments:
        path - a path to the file

    Returns:
        string representing the content file type
    """
    type_by_path = find_type_by_path(path)
    if type_by_path:
        return type_by_path
    try:
        if not _dict and not file_type:
            _dict, file_type = get_dict_from_file(path)

    except FileNotFoundError:
        # unable to find the file - hence can't identify it
        return None

    if file_type == 'yml':
        if 'category' in _dict:
            if _dict.get('beta') and not ignore_sub_categories:
                return FileType.BETA_INTEGRATION

            return FileType.INTEGRATION

        if 'script' in _dict:
            if TEST_PLAYBOOKS_DIR in Path(path).parts and not ignore_sub_categories:
                return FileType.TEST_SCRIPT

            return FileType.SCRIPT

        if 'tasks' in _dict:
            if TEST_PLAYBOOKS_DIR in Path(path).parts:
                return FileType.TEST_PLAYBOOK

            return FileType.PLAYBOOK

    if file_type == 'json':
        if 'widgetType' in _dict:
            return FileType.WIDGET

        if 'orientation' in _dict:
            return FileType.REPORT

        if 'color' in _dict and 'cliName' not in _dict:
            if 'definitionId' in _dict and _dict['definitionId'] and \
                    _dict['definitionId'].lower() not in ['incident', 'indicator']:
                return FileType.GENERIC_TYPE
            return FileType.INCIDENT_TYPE

        # 'regex' key can be found in new reputations files while 'reputations' key is for the old reputations
        # located in reputations.json file.
        if 'regex' in _dict or 'reputations' in _dict:
            return FileType.REPUTATION

        if 'brandName' in _dict and 'transformer' in _dict:
            return FileType.OLD_CLASSIFIER

        if ('transformer' in _dict and 'keyTypeMap' in _dict) or 'mapping' in _dict:
            if _dict.get('type') and _dict.get('type') == 'classification':
                return FileType.CLASSIFIER
            elif _dict.get('type') and 'mapping' in _dict.get('type'):
                return FileType.MAPPER
            return None

        if 'canvasContextConnections' in _dict:
            return FileType.CONNECTION

        if 'layout' in _dict or 'kind' in _dict:  # it's a Layout or Dashboard but not a Generic Object
            if 'kind' in _dict or 'typeId' in _dict:
                return FileType.LAYOUT

            return FileType.DASHBOARD

        if 'group' in _dict and LAYOUT_CONTAINER_FIELDS.intersection(_dict):
            return FileType.LAYOUTS_CONTAINER

        if 'scriptName' in _dict and 'existingEventsFilters' in _dict and 'readyExistingEventsFilters' in _dict and \
                'newEventFilters' in _dict and 'readyNewEventFilters' in _dict:
            return FileType.PRE_PROCESS_RULES

        if 'allRead' in _dict and 'truncated' in _dict:
            return FileType.LISTS

        if 'definitionIds' in _dict and 'views' in _dict:
            return FileType.GENERIC_MODULE

        if 'auditable' in _dict:
            return FileType.GENERIC_DEFINITION

        if isinstance(_dict, dict) and {'isAllFeeds', 'selectedFeeds', 'isFeed'}.issubset(_dict.keys()):
            return FileType.JOB

        # When using it for all files validation- sometimes 'id' can be integer
        if 'id' in _dict:
            if isinstance(_dict['id'], str):
                if 'definitionId' in _dict and _dict['definitionId'] and \
                        _dict['definitionId'].lower() not in ['incident', 'indicator']:
                    return FileType.GENERIC_FIELD
                _id = _dict['id'].lower()
                if _id.startswith('incident'):
                    return FileType.INCIDENT_FIELD
                if _id.startswith('indicator'):
                    return FileType.INDICATOR_FIELD
            else:
                print(f'The file {path} could not be recognized, please update the "id" to be a string')

    return None


def get_common_server_path(env_dir):
    common_server_dir = get_common_server_dir(env_dir)
    return os.path.join(common_server_dir, 'CommonServerPython.py')


def get_common_server_path_pwsh(env_dir):
    common_server_dir = get_common_server_dir_pwsh(env_dir)
    return os.path.join(common_server_dir, 'CommonServerPowerShell.ps1')


def _get_common_server_dir_general(env_dir, name):
    common_server_pack_path = os.path.join(env_dir, 'Packs', 'Base', 'Scripts', name)

    return common_server_pack_path


def get_common_server_dir(env_dir):
    return _get_common_server_dir_general(env_dir, 'CommonServerPython')


def get_common_server_dir_pwsh(env_dir):
    return _get_common_server_dir_general(env_dir, 'CommonServerPowerShell')


def is_external_repository() -> bool:
    """
    Returns True if script executed from private repository

    """
    try:
        git_repo = git.Repo(os.getcwd(), search_parent_directories=True)
        private_settings_path = os.path.join(git_repo.working_dir, '.private-repo-settings')
        return os.path.exists(private_settings_path)
    except git.InvalidGitRepositoryError:
        return True


def get_content_id_set() -> dict:
    """Getting the ID Set from official content's bucket"""
    return requests.get(OFFICIAL_CONTENT_ID_SET_PATH).json()


def get_content_path() -> str:
    """ Get abs content path, from any CWD
    Returns:
        str: Absolute content path
    """
    try:
        git_repo = git.Repo(os.getcwd(), search_parent_directories=True)
        remote_url = git_repo.remote().urls.__next__()
        is_fork_repo = 'content' in remote_url
        is_external_repo = is_external_repository()

        if not is_fork_repo and not is_external_repo:
            raise git.InvalidGitRepositoryError
        return git_repo.working_dir
    except (git.InvalidGitRepositoryError, git.NoSuchPathError):
        print_error("Please run demisto-sdk in content repository - Aborting!")
    return ''


def run_command_os(command: str, cwd: Union[Path, str], env: Union[os._Environ, dict] = os.environ) -> \
        Tuple[str, str, int]:
    """ Run command in subprocess tty
    Args:
        command(str): Command to be executed.
        cwd(Path): Path from pathlib object to be executed
        env: Environment variables for the execution
    Returns:
        str: Stdout of the command
        str: Stderr of the command
        int: exit code of command
    """
    if isinstance(cwd, str):
        cwd = Path(cwd)
    try:
        process = Popen(
            shlex.split(command),
            cwd=cwd,
            env=env,
            stdout=PIPE,
            stderr=PIPE,
            universal_newlines=True
        )
        stdout, stderr = process.communicate()
    except OSError as e:
        return '', str(e), 1

    return stdout, stderr, process.returncode


def pascal_case(st: str) -> str:
    """Convert a string to pascal case. Will simply remove spaces and make sure the first
    character is capitalized

    Arguments:
        st {str} -- string to convert

    Returns:
        str -- converted string
    """
    words = re.findall(r'[a-zA-Z0-9]+', st)
    return ''.join(''.join([w[0].upper(), w[1:]]) for w in words)


def capital_case(st: str) -> str:
    """Capitalize the first letter of each word of a string. The remaining characters are untouched.

    Arguments:
        st {str} -- string to convert

    Returns:
        str -- converted string
    """
    if len(st) >= 1:
        words = st.split()
        return ' '.join([f'{s[:1].upper()}{s[1:]}' for s in words if len(s) >= 1])
    else:
        return ''


def get_last_release_version():
    """
    Get latest release tag (xx.xx.xx)

    :return: tag
    """
    tags = run_command('git tag').split('\n')
    tags = [tag for tag in tags if re.match(r'\d+\.\d+\.\d+', tag) is not None]
    tags.sort(key=LooseVersion, reverse=True)

    return tags[0]


def is_file_from_content_repo(file_path: str) -> Tuple[bool, str]:
    """ Check if an absolute file_path is part of content repo.
    Args:
        file_path (str): The file path which is checked.
    Returns:
        bool: if file is part of content repo.
        str: relative path of file in content repo.
    """
    try:
        git_repo = git.Repo(os.getcwd(),
                            search_parent_directories=True)
        remote_url = git_repo.remote().urls.__next__()
        is_fork_repo = 'content' in remote_url
        is_external_repo = is_external_repository()

        if not is_fork_repo and not is_external_repo:
            return False, ''
        content_path_parts = Path(git_repo.working_dir).parts
        input_path_parts = Path(file_path).parts
        input_path_parts_prefix = input_path_parts[:len(content_path_parts)]
        if content_path_parts == input_path_parts_prefix:
            return True, '/'.join(input_path_parts[len(content_path_parts):])
        else:
            return False, ''

    except Exception as e:
        click.secho(f"Unable to identify the repository: {e}")
        return False, ''


def should_file_skip_validation(file_path: str) -> bool:
    """Check if the file cannot be validated under 'run_all_validations_on_file' method for various reasons,
        either if it's a test file, or if it's a file that's been validated somewhere else
        Args:
            file_path (str): The file path which is checked.
        Returns:
            bool: True if the file's validation should be skipped, False otherwise.
        """
    file_extension = os.path.splitext(file_path)[-1]
    # We validate only yml json and .md files
    if file_extension not in ['.yml', '.json', '.md']:
        return True
    if any(ignore_pattern in file_path.lower() for ignore_pattern in ALL_FILES_VALIDATION_IGNORE_WHITELIST):
        return True
    # Ignoring changelog and description files since these are checked on the integration validation
    if 'changelog' in file_path.lower() or 'description' in file_path.lower():
        return True
    # unified files should not be validated
    if file_path.endswith('_unified.yml'):
        return True
    return False


def retrieve_file_ending(file_path: str) -> str:
    """
    Retrieves the file ending (without the dot)
    :param file_path: The file path
    :return: The file ending
    """
    os_split: tuple = os.path.splitext(file_path)
    if os_split:
        file_ending: str = os_split[1]
        if file_ending and '.' in file_ending:
            return file_ending[1:]
    return ''


def is_test_config_match(test_config: dict, test_playbook_id: str = '', integration_id: str = '') -> bool:
    """
    Given a test configuration from conf.json file, this method checks if the configuration is configured for the
    test playbook or for integration_id.
    Since in conf.json there could be test configurations with 'integrations' as strings or list of strings
    the type of test_configurations['integrations'] is checked in first and the match according to the type.
    If file type is not an integration- will return True if the test_playbook id matches playbookID.
    Args:
        test_config: A test configuration from conf.json file under 'tests' key.
        test_playbook_id: A test playbook ID.
        integration_id: An integration ID.
    If both test_playbook_id and integration_id are given will look for a match of both, else will look for match
    of either test playbook id or integration id
    Returns:
        True if the test configuration contains the test playbook and the content item or False if not
    """
    test_playbook_match = test_playbook_id == test_config.get('playbookID')
    test_integrations = test_config.get('integrations')
    if isinstance(test_integrations, list):
        integration_match = any(
            test_integration for test_integration in test_integrations if test_integration == integration_id)
    else:
        integration_match = test_integrations == integration_id
    # If both playbook id and integration id are given
    if integration_id and test_playbook_id:
        return test_playbook_match and integration_match

    # If only integration id is given
    if integration_id:
        return integration_match

    # If only test playbook is given
    if test_playbook_id:
        return test_playbook_match

    return False


def get_not_registered_tests(conf_json_tests: list, content_item_id: str, file_type: str, test_playbooks: list) -> list:
    """
    Return all test playbooks that are not configured in conf.json file
    Args:
        conf_json_tests: the 'tests' value of 'conf.json file
        content_item_id: A content item ID, could be a script, an integration or a playbook.
        file_type: The file type, could be an integration or a playbook.
        test_playbooks: The yml file's list of test playbooks

    Returns:
        A list of TestPlaybooks not configured
    """
    not_registered_tests = []
    for test in test_playbooks:
        if file_type == 'playbook':
            test_registered_in_conf_json = any(
                test_config for test_config in conf_json_tests if is_test_config_match(test_config,
                                                                                       test_playbook_id=test)
            )
        else:
            test_registered_in_conf_json = any(
                test_config for test_config in conf_json_tests if is_test_config_match(test_config,
                                                                                       integration_id=content_item_id)
            )
        if not test_registered_in_conf_json:
            not_registered_tests.append(test)
    return not_registered_tests


def _get_file_id(file_type: str, file_content: Dict):
    """
    Gets the ID of a content item according to it's type
    Args:
        file_type: The type of the content item
        file_content: The content of the content item

    Returns:
        The file's content ID
    """
    file_id = ''
    if file_type in ID_IN_ROOT:
        file_id = file_content.get('id', '')
    elif file_type in ID_IN_COMMONFIELDS:
        file_id = file_content.get('commonfields', {}).get('id')
    return file_id


def is_path_of_integration_directory(path: str) -> bool:
    """Returns true if directory is integration directory false if not.
    """
    return os.path.basename(path) == INTEGRATIONS_DIR


def is_path_of_script_directory(path: str) -> bool:
    """Returns true if directory is script directory false if not.
    """
    return os.path.basename(path) == SCRIPTS_DIR


def is_path_of_playbook_directory(path: str) -> bool:
    """Returns true if directory is playbook directory false if not.
    """
    return os.path.basename(path) == PLAYBOOKS_DIR


def is_path_of_test_playbook_directory(path: str) -> bool:
    """Returns true if directory is test_playbook directory false if not.
    """
    return os.path.basename(path) == TEST_PLAYBOOKS_DIR


def is_path_of_report_directory(path: str) -> bool:
    """Returns true if directory is report directory false if not.
    """
    return os.path.basename(path) == REPORTS_DIR


def is_path_of_dashboard_directory(path: str) -> bool:
    """Returns true if directory is integration directory false if not.
    """
    return os.path.basename(path) == DASHBOARDS_DIR


def is_path_of_widget_directory(path: str) -> bool:
    """Returns true if directory is integration directory false if not.
    """
    return os.path.basename(path) == WIDGETS_DIR


def is_path_of_incident_field_directory(path: str) -> bool:
    """Returns true if directory is integration directory false if not.
    """
    return os.path.basename(path) == INCIDENT_FIELDS_DIR


def is_path_of_incident_type_directory(path: str) -> bool:
    """Returns true if directory is integration directory false if not.
    """
    return os.path.basename(path) == INCIDENT_TYPES_DIR


def is_path_of_indicator_field_directory(path: str) -> bool:
    """Returns true if directory is integration directory false if not.
    """
    return os.path.basename(path) == INDICATOR_FIELDS_DIR


def is_path_of_layout_directory(path: str) -> bool:
    """Returns true if directory is integration directory false if not.
    """
    return os.path.basename(path) == LAYOUTS_DIR


def is_path_of_pre_process_rules_directory(path: str) -> bool:
    """Returns true if directory is pre-processing rules directory, false if not.
    """
    return os.path.basename(path) == PRE_PROCESS_RULES_DIR


def is_path_of_lists_directory(path: str) -> bool:
    return os.path.basename(path) == LISTS_DIR


def is_path_of_classifier_directory(path: str) -> bool:
    """Returns true if directory is integration directory false if not.
    """
    return os.path.basename(path) == CLASSIFIERS_DIR


def get_parent_directory_name(path: str, abs_path: bool = False) -> str:
    """
    Retrieves the parent directory name
    :param path: path to get the parent dir name
    :param abs_path: when set to true, will return absolute path
    :return: parent directory name
    """
    parent_dir_name = os.path.dirname(os.path.abspath(path))
    if abs_path:
        return parent_dir_name
    return os.path.basename(parent_dir_name)


def get_content_file_type_dump(file_path: str) -> Callable[[str], str]:
    """
    Return a method with which 'curr' (the current key the lies in the path of the error) should be printed with
    If the file is a yml file:
        will return a yaml.dump function
    If the file is a json file:
        will return a json.dumps function configured with indent=4
    In any other case- will just print the string representation of the key.

    The file type is checked according to the file extension

    Args:
        file_path: The file path whose type is determined in this method

    Returns:
        A function that returns string representation of 'curr'
    """
    # Setting the method that should the curr path
    file_extension = os.path.splitext(file_path)[-1]
    curr_string_transformer: Union[partial[str], Type[str], Callable] = str
    if file_extension in ['.yml', '.yaml']:
        curr_string_transformer = yaml.dump
    elif file_extension == '.json':
        curr_string_transformer = partial(json.dumps, indent=4)
    return curr_string_transformer


def get_code_lang(file_data: dict, file_entity: str) -> str:
    """
    Returns the code language by the file entity
    :param file_data: The file data
    :param file_entity: The file entity
    :return: The code language
    """
    if file_entity == INTEGRATIONS_DIR:
        return file_data.get('script', {}).get('type', '')
    elif file_entity == SCRIPTS_DIR:
        return file_data.get('type', {})
    return ''


def camel_to_snake(camel: str) -> str:
    """
    Converts camel case (CamelCase) strings to snake case (snake_case) strings.
    Args:
        camel (str): The camel case string.

    Returns:
        str: The snake case string.
    """
    camel_to_snake_pattern = re.compile(r'(?<!^)(?=[A-Z][a-z])')
    snake = camel_to_snake_pattern.sub('_', camel).lower()
    return snake


def open_id_set_file(id_set_path):
    id_set = None
    try:
        with open(id_set_path, 'r') as id_set_file:
            id_set = json.load(id_set_file)
    except IOError:
        print_warning("Could not open id_set file")
    finally:
        return id_set


def get_demisto_version(demisto_client: demisto_client) -> str:
    """
    Args:
        demisto_client: A configured demisto_client instance

    Returns:
        the server version of the Demisto instance.
    """
    try:
        resp = demisto_client.generic_request('/about', 'GET')
        about_data = json.loads(resp[0].replace("'", '"'))
        return parse(about_data.get('demistoVersion'))  # type: ignore
    except Exception:
        return "0"


def arg_to_list(arg: Union[str, List[str]], separator: str = ",") -> List[str]:
    """
       Converts a string representation of lists to a python list
       Args:
              arg: string or list of string.
              separator: A string separator to separate the strings, the default is a comma.
       Returns:
             list, contains strings.

    """
    if not arg:
        return []
    if isinstance(arg, list):
        return arg
    if isinstance(arg, str):
        if arg[0] == '[' and arg[-1] == ']':
            return json.loads(arg)
        return [s.strip() for s in arg.split(separator)]
    return [arg]


def get_file_version_suffix_if_exists(current_file: Dict, check_in_display: bool = False) -> Optional[str]:
    """
    Checks if current YML file name is versioned or no, e.g, ends with v<number>.
    Args:
        current_file (Dict): Dict representing YML data of an integration or script.
        check_in_display (bool): Whether to get name by 'display' field or not (by 'name' field).

    Returns:
        (Optional[str]): Number of the version as a string, if the file ends with version suffix. None otherwise.
    """
    versioned_file_regex = r'v([0-9]+)$'
    name = current_file.get('display') if check_in_display else current_file.get('name')
    if not name:
        return None
    matching_regex = re.findall(versioned_file_regex, name.lower())
    if matching_regex:
        return matching_regex[-1]
    return None


def get_all_incident_and_indicator_fields_from_id_set(id_set_file, entity_type):
    fields_list = []
    for item in ['IncidentFields', 'IndicatorFields']:
        all_item_fields = id_set_file.get(item)
        for item_field in all_item_fields:
            for field, field_info in item_field.items():
                if entity_type == 'mapper' or entity_type == 'old classifier':
                    fields_list.append(field_info.get('name', ''))
                    fields_list.append(field.replace('incident_', '').replace('indicator_', ''))
                elif entity_type == 'layout':
                    fields_list.append(field.replace('incident_', '').replace('indicator_', ''))
    return fields_list


def is_string_uuid(string_to_check: str):
    """
    Check if a given string is from uuid type
    Args:
        string_to_check: string

    Returns:
        bool. True if the string match uuid type, else False

    """
    return bool(re.fullmatch(UUID_REGEX, string_to_check))


def extract_multiple_keys_from_dict(key: str, var: dict):
    """
    Args:
        key: string representing a re-occurring field in dictionary
        var: nested dictionary (can contain both nested lists and nested dictionary)

    Returns: A generator that generates value in an occurrence of the nested key in var.
    """
    if hasattr(var, 'items'):
        for k, v in var.items():
            if k == key:
                yield v
            if isinstance(v, dict):
                for result in extract_multiple_keys_from_dict(key, v):
                    yield result
            elif isinstance(v, list):
                for d in v:
                    for result in extract_multiple_keys_from_dict(key, d):
                        yield result


def find_file(root_path, file_name):
    """Find a file with a given file name under a given root path.
    Returns:
        str: The full file path from root path if exists, else return empty string.
    """
    for file in os.listdir(root_path):
        file_path = os.path.join(root_path, file)
        if file_path.endswith(file_name):
            return file_path
        elif os.path.isdir(file_path):
            found_file = find_file(file_path, file_name)
            if found_file:
                return found_file
    return ''


@lru_cache()
def get_file_displayed_name(file_path):
    """Gets the file name that is displayed in the UI by the file's path.
    If there is no displayed name - returns the file name"""
    file_type = find_type(file_path)
    if FileType.INTEGRATION == file_type:
        return get_yaml(file_path).get('display')
    elif file_type in [FileType.SCRIPT, FileType.TEST_SCRIPT, FileType.PLAYBOOK, FileType.TEST_PLAYBOOK]:
        return get_yaml(file_path).get('name')
    elif file_type in [FileType.MAPPER, FileType.CLASSIFIER, FileType.INCIDENT_FIELD, FileType.INCIDENT_TYPE,
                       FileType.INDICATOR_FIELD, FileType.LAYOUTS_CONTAINER, FileType.PRE_PROCESS_RULES,
                       FileType.DASHBOARD, FileType.WIDGET,
                       FileType.REPORT, FileType.JOB]:
        return get_json(file_path).get('name')
    elif file_type == FileType.OLD_CLASSIFIER:
        return get_json(file_path).get('brandName')
    elif file_type == FileType.LAYOUT:
        return get_json(file_path).get('TypeName')
    elif file_type == FileType.REPUTATION:
        return get_json(file_path).get('id')
    else:
        return os.path.basename(file_path)


def compare_context_path_in_yml_and_readme(yml_dict, readme_content):
    """
    Gets both README and YML file of Integration and compares the context path between them.
    Scripts are not being checked.
    Args:
        yml_dict: a dictionary representing YML content.
        readme_content: the content string of the readme file.
    Returns: A dictionary as following: {<command_name>:{'only in yml': <set of context paths found only in yml>,
                                                        'only in readme': <set of context paths found only in readme>}}
    """
    different_contexts: dict = {}

    # Gets the data from the README
    # the pattern to get the context part out of command section:
    context_section_pattern = r"\| *\*\*Path\*\* *\| *\*\*Type\*\* *\| *\*\*Description\*\* *\|.(.*?)#{3,5}"
    # the pattern to get the value in the first column under the outputs table:
    context_path_pattern = r"\| *(\S.*?\S) *\| *[^\|]* *\| *[^\|]* *\|"
    readme_content += "### "  # mark end of file so last pattern of regex will be recognized.
    commands = yml_dict.get("script", {})

    # handles scripts
    if not commands:
        return different_contexts
    commands = commands.get('commands', [])
    for command in commands:
        command_name = command.get('name')

        # Gets all context path in the relevant command section from README file
        command_section_pattern = fr" Base Command..`{command_name}`.(.*?)\n### "  # pattern to get command section
        command_section = re.findall(command_section_pattern, readme_content, re.DOTALL)
        if not command_section:
            continue
        if not command_section[0].endswith('###'):
            command_section[0] += '###'  # mark end of file so last pattern of regex will be recognized.
        context_section = re.findall(context_section_pattern, command_section[0], re.DOTALL)
        if not context_section:
            context_path_in_command = set()
        else:
            context_path_in_command = set(re.findall(context_path_pattern, context_section[0], re.DOTALL))

            # remove the header line ---- (could be of any length)
            for path in context_path_in_command:
                if not path.replace('-', ''):
                    context_path_in_command.remove(path)
                    break

        # handles cases of old integrations with context in 'important' section
        if 'important' in command:
            command.pop('important')

        # Gets all context path in the relevant command section from YML file
        existing_context_in_yml = set(extract_multiple_keys_from_dict("contextPath", command))

        # finds diff between YML and README
        only_in_yml_paths = existing_context_in_yml - context_path_in_command
        only_in_readme_paths = context_path_in_command - existing_context_in_yml
        if only_in_yml_paths or only_in_readme_paths:
            different_contexts[command_name] = {"only in yml": only_in_yml_paths,
                                                "only in readme": only_in_readme_paths}

    return different_contexts


def write_yml(yml_path: str, yml_data: Dict):
    ryaml = YAML()
    ryaml.allow_duplicate_keys = True
    ryaml.preserve_quotes = True
    with open(yml_path, 'w') as f:
        ryaml.dump(yml_data, f)  # ruamel preservers multilines


def to_kebab_case(s: str):
    """
    Scan File => scan-file
    Scan File- => scan-file
    *scan,file => scan-file
    Scan     File => scan-file

    """
    if s:
        new_s = s.lower()
        new_s = re.sub('[ ,.-]+', '-', new_s)
        new_s = re.sub('[^A-Za-z0-9-]+', '', new_s)
        m = re.search('[a-z0-9]+(-[a-z]+)*', new_s)
        if m:
            return m.group(0)
        else:
            return new_s

    return s


def to_pascal_case(s: str):
    """
    Scan File => ScanFile
    Scan File- => ScanFile
    *scan,file => ScanFile
    Scan     File => ScanFile
    scan-file => ScanFile
    scan.file => ScanFile

    """
    if s:
        if re.search(r'^[A-Z][a-z]+(?:[A-Z][a-z]+)*$', s):
            return s

        new_s = s.lower()
        new_s = re.sub(r'[ -\.]+', '-', new_s)
        new_s = ''.join([t.title() for t in new_s.split('-')])
        new_s = re.sub(r'[^A-Za-z0-9]+', '', new_s)

        return new_s

    return s


def get_approved_usecases() -> list:
    """Gets approved list of usecases from content master

    Returns:
        List of approved usecases
    """
    return get_remote_file(
        'Tests/Marketplace/approved_usecases.json',
        github_repo=GitContentConfig.OFFICIAL_CONTENT_REPO_NAME
    ).get('approved_list', [])


def get_approved_tags() -> list:
    """Gets approved list of tags from content master

    Returns:
        List of approved tags
    """
    return get_remote_file(
        'Tests/Marketplace/approved_tags.json',
        github_repo=GitContentConfig.OFFICIAL_CONTENT_REPO_NAME
    ).get('approved_list', [])


def get_pack_metadata(file_path: str) -> dict:
    """ Get the pack_metadata dict, of the pack containing the given file path.

    Args:
        file_path(str): file path

    Returns: pack_metadata of the pack, that source_file related to,
        on failure returns {}

    """
    pack_path = file_path if PACKS_DIR in file_path else os.path.realpath(__file__)
    match = re.search(rf".*{PACKS_DIR}[/\\]([^/\\]+)[/\\]?", pack_path)
    directory = match.group() if match else ''

    try:
        metadata_path = os.path.join(directory, PACKS_PACK_META_FILE_NAME)
        pack_metadata, _ = get_dict_from_file(metadata_path)
        return pack_metadata
    except Exception:
        return {}


def is_pack_path(input_path: str) -> bool:
    """
    Checks whether pack given in input path is for a pack.
    Args:
        input_path (str): Input path.
    Examples
        - input_path = 'Packs/BitcoinAbuse
          Returns: True
        - input_path = 'Packs/BitcoinAbuse/Layouts'
          Returns: False
    Returns:
        (bool):
        - True if the input path is for a given pack.
        - False if the input path is not for a given pack.
    """
    return os.path.basename(os.path.dirname(input_path)) == PACKS_DIR


def get_relative_path_from_packs_dir(file_path: str) -> str:
    """Get the relative path for a given file_path starting in the Packs directory"""
    if PACKS_DIR not in file_path or file_path.startswith(PACKS_DIR):
        return file_path

    return file_path[file_path.find(PACKS_DIR):]


def is_uuid(s: str) -> Optional[Match]:
    """Checks whether given string is a UUID

    Args:
         s (str): The string to check if it is a UUID

    Returns:
        Match: Returns the match if given string is a UUID, otherwise None
    """
    return re.match(UUID_REGEX, s)


def get_release_note_entries(version='') -> list:
    """
    Gets the release notes entries for the current version.

    Args:
        version: The current demisto-sdk version.

    Return:
        list: A list of the release notes given from the CHANGELOG file.
    """

    changelog_file_content = get_remote_file(full_file_path='CHANGELOG.md',
                                             return_content=True,
                                             github_repo='demisto/demisto-sdk').decode('utf-8').split('\n')

    if not version or 'dev' in version:
        version = 'Changelog'

    if f'# {version}' not in changelog_file_content:
        return []

    result = changelog_file_content[changelog_file_content.index(f'# {version}') + 1:]
    result = result[:result.index('')]

    return result


def get_current_usecases() -> list:
    """Gets approved list of usecases from current branch (only in content repo).

    Returns:
        List of approved usecases from current branch
    """
    if not is_external_repository():
        approved_usecases_json, _ = get_dict_from_file('Tests/Marketplace/approved_usecases.json')
        return approved_usecases_json.get('approved_list', [])
    return []


def get_current_tags() -> list:
    """Gets approved list of tags from current branch (only in content repo).

    Returns:
        List of approved tags from current branch
    """
    if not is_external_repository():
        approved_tags_json, _ = get_dict_from_file('Tests/Marketplace/approved_tags.json')
        return approved_tags_json.get('approved_list', [])
    return []


@contextmanager
def suppress_stdout():
    """
        Temporarily suppress console output without effecting error outputs.
        Example of use:

            with suppress_stdout():
                print('This message will not be printed')
            print('This message will be printed')
    """
    with open(os.devnull, "w") as devnull:
        try:
            old_stdout = sys.stdout
            sys.stdout = devnull
            yield
        finally:
            sys.stdout = old_stdout


def get_definition_name(path: str, pack_path: str) -> Optional[str]:
    r"""
        param:
            path (str): path to the file which needs a definition name (generic field\generic type file)
            pack_path (str): relevant pack path

        :rtype: ``str``
        :return:
            for generic type and generic field return associated generic definition name folder

    """

    try:
        file_dictionary = get_json(path)
        definition_id = file_dictionary['definitionId']
        generic_def_path = os.path.join(pack_path, 'GenericDefinitions')
        file_names_lst = os.listdir(generic_def_path)
        for file in file_names_lst:
            if str.find(file, definition_id):
                def_file_path = os.path.join(generic_def_path, file)
                def_file_dictionary = get_json(def_file_path)
                cur_id = def_file_dictionary["id"]
                if cur_id == definition_id:
                    return def_file_dictionary["name"]

        print("Was unable to find the file for definitionId " + definition_id)
        return None

    except FileNotFoundError or AttributeError:
        print("Error while retrieving definition name for definitionId " + definition_id +
              "\n Check file structure and make sure all relevant fields are entered properly")
        return None


def is_iron_bank_pack(file_path):
    metadata = get_pack_metadata(file_path)
    return PACK_METADATA_IRON_BANK_TAG in metadata.get('tags', [])


def get_script_or_sub_playbook_tasks_from_playbook(searched_entity_name: str, main_playbook_data: Dict) -> List[Dict]:
    """Get the tasks data for a task running the searched_entity_name (script/playbook).

    Returns:
        List. A list of dicts representing tasks running the searched_entity_name.
    """
    searched_tasks: List = []
    tasks = main_playbook_data.get('tasks', {})
    if not tasks:
        return searched_tasks

    for task_data in tasks.values():
        task_details = task_data.get('task', {})
        found_entity = searched_entity_name in {task_details.get('scriptName'), task_details.get('playbookName')}

        if found_entity:
            searched_tasks.append(task_data)

    return searched_tasks


def get_current_repo() -> Tuple[str, str, str]:
    try:
        git_repo = git.Repo(os.getcwd(), search_parent_directories=True)
        parsed_git = giturlparse.parse(git_repo.remotes.origin.url)
        host = parsed_git.host
        if '@' in host:
            host = host.split('@')[1]
        return host, parsed_git.owner, parsed_git.repo
    except git.InvalidGitRepositoryError:
        print_warning('git repo is not found')
        return "Unknown source", '', ''


def get_mp_types_from_metadata_by_item(file_path):
    """
    Get the supporting marketplaces for the given content item, defined by the mp field in the metadata.
    If the field doesnt exist in the pack's metadata, consider as xsoar only.
    Args:
        file_path: path to content item in content repo

    Returns:
        list of names of supporting marketplaces (current options are marketplacev2 and xsoar)
    """
    if METADATA_FILE_NAME in Path(file_path).parts:  # for when the type is pack, the item we get is the metadata path
        metadata_path = file_path
    else:
        metadata_path_parts = get_pack_dir(file_path)
        metadata_path = Path(*metadata_path_parts) / METADATA_FILE_NAME

    try:
        with open(metadata_path, 'r') as metadata_file:
            metadata = json.load(metadata_file)
            marketplaces = metadata.get(MARKETPLACE_KEY_PACK_METADATA)
            if not marketplaces:
                return [MarketplaceVersions.XSOAR.value]
            return marketplaces
    except FileNotFoundError:
        return []


def get_pack_dir(path):
    """
    Used for testing packs where the location of the "Packs" dir is not constant.
    Args:
        path: path of current file

    Returns:
        the path starting from Packs dir

    """
    parts = Path(path).parts
    for index in range(len(parts)):
        if parts[index] == 'Packs':
            return parts[:index + 2]
    return []


# demisto_sdk/commands/common/update_id_set.py

import copy
import glob
import itertools
import json
import os
import re
import time
from collections import OrderedDict
from datetime import datetime
from distutils.version import LooseVersion
from enum import Enum
from functools import partial
from multiprocessing import Pool, cpu_count
from pathlib import Path
from typing import Callable, Dict, List, Optional, Tuple

import click
import networkx

from demisto_sdk.commands.common.constants import (
    CLASSIFIERS_DIR, COMMON_TYPES_PACK, DASHBOARDS_DIR,
    DEFAULT_CONTENT_ITEM_FROM_VERSION, DEFAULT_CONTENT_ITEM_TO_VERSION,
    DEFAULT_ID_SET_PATH, GENERIC_DEFINITIONS_DIR, GENERIC_FIELDS_DIR,
    GENERIC_MODULES_DIR, GENERIC_TYPES_DIR, INCIDENT_FIELDS_DIR,
    INCIDENT_TYPES_DIR, INDICATOR_FIELDS_DIR, INDICATOR_TYPES_DIR, JOBS_DIR,
    LAYOUTS_DIR, LISTS_DIR, MAPPERS_DIR, MP_V2_ID_SET_PATH, REPORTS_DIR,
    SCRIPTS_DIR, TEST_PLAYBOOKS_DIR, WIDGETS_DIR, FileType,
    MarketplaceVersions)
from demisto_sdk.commands.common.tools import (
    LOG_COLORS, find_type, get_current_repo, get_file, get_json,
    get_mp_types_from_metadata_by_item, get_pack_name, get_yaml, print_color,
    print_error, print_warning)
from demisto_sdk.commands.unify.yml_unifier import YmlUnifier

CONTENT_ENTITIES = ['Packs', 'Integrations', 'Scripts', 'Playbooks', 'TestPlaybooks', 'Classifiers',
                    'Dashboards', 'IncidentFields', 'IncidentTypes', 'IndicatorFields', 'IndicatorTypes',
                    'Layouts', 'Reports', 'Widgets', 'Mappers', 'GenericTypes',
                    'GenericFields', 'GenericModules', 'GenericDefinitions', 'Lists', 'Jobs']

ID_SET_ENTITIES = ['integrations', 'scripts', 'playbooks', 'TestPlaybooks', 'Classifiers',
                   'Dashboards', 'IncidentFields', 'IncidentTypes', 'IndicatorFields', 'IndicatorTypes',
                   'Layouts', 'Reports', 'Widgets', 'Mappers', 'GenericTypes', 'GenericFields', 'GenericModules',
                   'GenericDefinitions', 'Lists', 'Jobs']

CONTENT_MP_V2_ENTITIES = ['Integrations', 'Scripts', 'Playbooks', 'TestPlaybooks', 'Classifiers',
                          'IncidentFields', 'IncidentTypes', 'IndicatorFields', 'IndicatorTypes',
                          'Mappers', 'Packs', 'Lists', 'Jobs']

ID_SET_MP_V2_ENTITIES = ['integrations', 'scripts', 'playbooks', 'TestPlaybooks', 'Classifiers',
                         'IncidentFields', 'IncidentTypes', 'IndicatorFields', 'IndicatorTypes',
                         'Mappers', 'Lists', 'Jobs']


BUILT_IN_FIELDS = [
    "name",
    "details",
    "severity",
    "owner",
    "dbotCreatedBy",
    "type",
    "dbotSource",
    "category",
    "dbotStatus",
    "playbookId",
    "dbotCreated",
    "dbotClosed",
    "occurred",
    "dbotDueDate",
    "dbotModified",
    "dbotTotalTime",
    "reason",
    "closeReason",
    "closeNotes",
    "closingUserId",
    "reminder",
    "phase",
    "roles",
    "labels",
    "attachment",
    "runStatus",
    "sourceBrand",
    "sourceInstance",
    "CustomFields",
    "droppedCount",
    "linkedCount",
    "feedBased",
    "id",
    "xsoarReadOnlyRoles",
    "dbotMirrorId",
    "dbotMirrorInstance",
    "dbotMirrorDirection",
    "dbotMirrorTags",
    "dbotMirrorLastSync"
]


def should_skip_item_by_mp(file_path: str, marketplace: str, print_logs: bool = False):
    """
    Checks if the item given (as path) should be part of the current generated id set.
     The checks are in this order:
     1. Check if the given item has the right marketplaces under the 'marketplaces' in the item's file,
     2. Otherwise, we check if the item is inside a pack that it's pack metadata 'marketplaces' field does not include
     the current marketplace we are creating this id set for.
     If there are no 'marketplaces' fields in the the item's file and the metadata, the item is set as xsoar only.

    Args:
        file_path: path to content item
        marketplace: the marketplace this current generated id set is for

    Returns: True if should be skipped, else False

    """

    # first check, check field 'marketplaces' in the item's file
    file_type = Path(file_path).suffix
    try:
        item_data = get_file(file_path, file_type)
        if item_data.get('marketplaces'):
            if marketplace not in item_data.get('marketplaces'):
                if print_logs:
                    print(f'Skipping {file_path} due to mismatch with the given marketplace')
                return True
    except (ValueError, FileNotFoundError, IsADirectoryError):
        return True

    # second check, check the metadata of the pack
    if marketplace not in get_mp_types_from_metadata_by_item(file_path):
        if print_logs:
            print(f'Skipping {file_path} due to mismatch with the given marketplace')
        return True


def build_tasks_graph(playbook_data):
    """
    Builds tasks flow graph.

    Args:
        playbook_data (dict): playbook yml data.

    Returns:
        DiGraph: all tasks of given playbook.
    """
    initial_task = playbook_data.get('starttaskid', '')
    tasks = playbook_data.get('tasks', {})

    graph = networkx.DiGraph()
    graph.add_node(initial_task, mandatory=True)  # add starting task as root of the direct graph

    found_new_tasks = True
    while found_new_tasks:
        current_number_of_nodes = graph.number_of_nodes()
        leaf_nodes = {node for node in graph.nodes() if graph.out_degree(node) == 0}

        for leaf in leaf_nodes:
            leaf_task = tasks.get(leaf)
            leaf_mandatory = graph.nodes[leaf]['mandatory']

            # In this case the playbook is invalid, starttaskid contains invalid task id.
            if not leaf_task:
                print_warning(f'{playbook_data.get("id")}: No such task {leaf} in playbook')
                continue

            leaf_next_tasks = sum(leaf_task.get('nexttasks', {}).values(), [])  # type: ignore

            for task_id in leaf_next_tasks:
                task = tasks.get(task_id)
                if not task:
                    print_warning(f'{playbook_data.get("id")}: No such task {leaf} in playbook')
                    continue

                # If task can't be skipped and predecessor task is mandatory - set as mandatory.
                mandatory = leaf_mandatory and not task.get('skipunavailable', False)
                if task_id not in graph.nodes():
                    graph.add_node(task_id, mandatory=mandatory)
                else:
                    # If task already in graph, update mandatory field.
                    # If one of the paths to the task is mandatory - set as mandatory.
                    graph.nodes[task_id]['mandatory'] = graph.nodes[task_id]['mandatory'] or mandatory
                graph.add_edge(leaf, task_id)

        found_new_tasks = graph.number_of_nodes() > current_number_of_nodes

    return graph


def get_lists_names_from_playbook(data_dictionary: dict, graph: networkx.DiGraph) -> tuple:
    lists_names = set()
    lists_names_skippable = set()
    tasks = data_dictionary.get('tasks', {})
    lists_tasks_scripts = ['Builtin|||setList', 'Builtin|||getList']
    for task_id, task in tasks.items():
        script = task.get('task', {}).get('script')
        if script in lists_tasks_scripts:
            list_name = task.get('scriptarguments', {}).get('listName', {}).get('simple')

            try:
                skippable = not graph.nodes[task_id]['mandatory']
            except KeyError:
                # if task id not in the graph - the task is unreachable.
                print_error(f'{data_dictionary["id"]}: task {task_id} is not connected')
                continue
            if list_name:
                lists_names.add(list_name)
                if skippable:
                    lists_names_skippable.add(list_name)

    return list(lists_names), list(lists_names_skippable)


def get_task_ids_from_playbook(param_to_enrich_by: str, data_dict: dict, graph: networkx.DiGraph) -> tuple:
    implementing_ids = set()
    implementing_ids_skippable = set()
    tasks = data_dict.get('tasks', {})

    for task_id, task in tasks.items():
        task_details = task.get('task', {})

        enriched_id = task_details.get(param_to_enrich_by)
        try:
            skippable = not graph.nodes[task_id]['mandatory']
        except KeyError:
            # if task id not in the graph - the task is unreachable.
            print_error(f'{data_dict["id"]}: task {task_id} is not connected')
            continue
        if enriched_id:
            implementing_ids.add(enriched_id)
            if skippable:
                implementing_ids_skippable.add(enriched_id)

    return list(implementing_ids), list(implementing_ids_skippable)


def get_commands_from_playbook(data_dict: dict) -> tuple:
    command_to_integration = {}
    command_to_integration_skippable = set()
    tasks = data_dict.get('tasks', {})

    for task in tasks.values():
        task_details = task.get('task', {})

        command = task_details.get('script')
        skippable = task.get('skipunavailable', False)
        if command:
            splitted_cmd = command.split('|')

            if 'Builtin' not in command:
                command_to_integration[splitted_cmd[-1]] = splitted_cmd[0]
                if skippable:
                    command_to_integration_skippable.add(splitted_cmd[-1])

    return command_to_integration, list(command_to_integration_skippable)


def get_filters_and_transformers_from_complex_value(complex_value: dict) -> Tuple[list, list]:
    all_filters = set()
    all_transformers = set()

    # add the filters to all_filters set
    filters = complex_value.get('filters', [])
    for tmp_filter in filters:
        if tmp_filter:
            operator = tmp_filter[0].get('operator')
            all_filters.add(operator)

    # add the transformers to all_transformers set
    transformers = complex_value.get('transformers', [])
    for tmp_transformer in transformers:
        if tmp_transformer:
            operator = tmp_transformer.get('operator')
            all_transformers.add(operator)

    return list(all_transformers), list(all_filters)


def get_filters_and_transformers_from_playbook(data_dict: dict) -> Tuple[list, list]:
    all_filters = set()
    all_transformers = set()

    # collect complex values from playbook inputs
    inputs = data_dict.get('inputs', [])
    complex_values = [_input.get('value', {}).get('complex', {}) for _input in inputs]

    # gets the playbook tasks
    tasks = data_dict.get('tasks', {})

    # collect complex values from playbook tasks
    for task in tasks.values():
        # gets the task value
        if task.get('type') == 'condition':
            for condition_entry in task.get('conditions', []):
                for inner_condition in condition_entry.get('condition', []):
                    if inner_condition:
                        for condition in inner_condition:
                            complex_values.append(condition.get('left', {}).get('value', {}).get('complex', {}))
                            complex_values.append(condition.get('right', {}).get('value', {}).get('complex', {}))
        else:
            complex_values.append(task.get('scriptarguments', {}).get('value', {}).get('complex', {}))

    # get transformers and filters from the values
    for complex_value in complex_values:
        if complex_value:
            transformers, filters = get_filters_and_transformers_from_complex_value(complex_value)
            all_transformers.update(transformers)
            all_filters.update(filters)

    return list(all_transformers), list(all_filters)


def get_integration_api_modules(file_path, data_dictionary, is_unified_integration):
    unifier = YmlUnifier(os.path.dirname(file_path))
    if is_unified_integration:
        integration_script_code = data_dictionary.get('script', {}).get('script', '')
    else:
        _, integration_script_code = unifier.get_script_or_integration_package_data()

    return unifier.check_api_module_imports(integration_script_code)[1]


def get_integration_data(file_path):
    data_dictionary = get_yaml(file_path)

    is_unified_integration = data_dictionary.get('script', {}).get('script', '') not in ['-', '']

    id_ = data_dictionary.get('commonfields', {}).get('id', '-')
    name = data_dictionary.get('name', '-')

    deprecated = data_dictionary.get('deprecated', False)
    tests = data_dictionary.get('tests')
    toversion = data_dictionary.get('toversion')
    fromversion = data_dictionary.get('fromversion')
    docker_image = data_dictionary.get('script', {}).get('dockerimage')
    commands = data_dictionary.get('script', {}).get('commands', [])
    cmd_list = [command.get('name') for command in commands]
    pack = get_pack_name(file_path)
    integration_api_modules = get_integration_api_modules(file_path, data_dictionary, is_unified_integration)
    default_classifier = data_dictionary.get('defaultclassifier')
    default_incident_type = data_dictionary.get('defaultIncidentType')
    is_feed = data_dictionary.get('script', {}).get('feed', False)
    mappers = set()

    deprecated_commands = []
    for command in commands:
        if command.get('deprecated', False):
            deprecated_commands.append(command.get('name'))

    for mapper in ['defaultmapperin', 'defaultmapperout']:
        if data_dictionary.get(mapper):
            mappers.add(data_dictionary.get(mapper))
    integration_data = create_common_entity_data(path=file_path,
                                                 name=name,
                                                 to_version=toversion,
                                                 from_version=fromversion,
                                                 pack=pack)
    if docker_image:
        integration_data['docker_image'] = docker_image
    if cmd_list:
        integration_data['commands'] = cmd_list
    if tests:
        integration_data['tests'] = tests
    if deprecated:
        integration_data['deprecated'] = deprecated
    if deprecated_commands:
        integration_data['deprecated_commands'] = deprecated_commands
    if integration_api_modules:
        integration_data['api_modules'] = integration_api_modules
    if default_classifier and default_classifier != '':
        integration_data['classifiers'] = default_classifier
    if mappers:
        integration_data['mappers'] = list(mappers)
    if default_incident_type and default_incident_type != '':
        integration_data['incident_types'] = default_incident_type
    if is_feed:
        # if the integration is a feed it should be dependent on CommonTypes
        integration_data['indicator_fields'] = COMMON_TYPES_PACK
        integration_data['indicator_types'] = COMMON_TYPES_PACK

    return {id_: integration_data}


def get_fields_by_script_argument(task):
    """Iterates over the task script arguments and search for non empty fields

    Args:
        task (dict): A task of the playbook with `script: Builtin|||setIncident`

    Returns:
        set. set of incident fields related to this task

    Example:
        for the task:
            {
                task:
                  id: e80e3f5a-a74f-44a8-8e83-8eee96def1d0
                  name: Save authenticity check result to incident field
                  script: Builtin|||setIncident

                scriptarguments:
                  emailaddress:
                    complex:
                      root: ActiveDirectory
                      accessor: Users.mail
                  duration: {}
            }

        we will return the 'emailaddress` incident field
    """

    dependent_incident_fields = set()
    for field_name, field_value in task.get('scriptarguments', {}).items():
        if field_value and field_name not in BUILT_IN_FIELDS:
            if field_name != "customFields":
                dependent_incident_fields.add(field_name)
            else:
                # the value should be a list of dicts in str format
                custom_field_value = list(field_value.values())[0]
                if isinstance(custom_field_value, str):
                    custom_fields_list = json.loads(custom_field_value)
                    for custom_field in custom_fields_list:
                        field_name = list(custom_field.keys())[0]
                        if field_name not in BUILT_IN_FIELDS:
                            dependent_incident_fields.add(field_name)
    return dependent_incident_fields


def get_incident_fields_by_playbook_input(playbook_input):
    """Searches for incident fields in a playbook input.

    Args:
        playbook_input (dict): An input of the playbook

    Returns:
        set. set of incident fields related to this task
    """
    dependent_incident_fields = set()

    input_type = list(playbook_input.keys())[0]  # type can be `simple` or `complex`
    input_value = list(playbook_input.values())[0]

    # check if it is in the form 'simple: ${incident.field_name}'
    if input_type == 'simple' and str(input_value).startswith('${incident.'):
        field_name = input_value.split('.')[1][:-1]
        if field_name not in BUILT_IN_FIELDS:
            dependent_incident_fields.add(field_name)

    elif input_type == 'complex':
        root_value = str(input_value.get('root', ''))
        accessor_value = str(input_value.get('accessor'))
        combined_value = root_value + '.' + accessor_value  # concatenate the strings

        field_name = re.match(r'incident\.([^.]+)', combined_value)
        if field_name:
            field_name = field_name.groups()[0]
            if field_name not in BUILT_IN_FIELDS:
                dependent_incident_fields.add(field_name)

    return dependent_incident_fields


def get_dependent_incident_and_indicator_fields(data_dictionary):
    """Finds the incident fields and indicator fields dependent on this playbook

    Args:
        data_dictionary (dict): The playbook data dict

    Returns:
        set. set of incident fields related to this playbook
    """
    dependent_incident_fields = set()
    dependent_indicator_fields = set()
    for task in data_dictionary.get('tasks', {}).values():
        # incident fields dependent by field mapping
        related_incident_fields = task.get('fieldMapping')
        if related_incident_fields:
            for incident_field in related_incident_fields:
                if incident_field not in BUILT_IN_FIELDS:
                    dependent_incident_fields.add(incident_field.get('incidentfield'))

        # incident fields dependent by scripts arguments
        if 'setIncident' in task.get('task', {}).get('script', ''):
            dependent_incident_fields.update(get_fields_by_script_argument(task))
            # incident fields dependent by scripts arguments
        if 'setIndicator' in task.get('task', {}).get('script', ''):
            dependent_indicator_fields.update(get_fields_by_script_argument(task))

    # incident fields by playbook inputs
    for playbook_input in data_dictionary.get('inputs', []):
        input_value_dict = playbook_input.get('value', {})
        if input_value_dict and isinstance(input_value_dict, dict):  # deprecated playbooks bug
            dependent_incident_fields.update(get_incident_fields_by_playbook_input(input_value_dict))

    return dependent_incident_fields, dependent_indicator_fields


def get_playbook_data(file_path: str) -> dict:
    data_dictionary = get_yaml(file_path)
    graph = build_tasks_graph(data_dictionary)

    id_ = data_dictionary.get('id', '-')
    name = data_dictionary.get('name', '-')
    deprecated = data_dictionary.get('hidden', False)
    tests = data_dictionary.get('tests')
    toversion = data_dictionary.get('toversion')
    fromversion = data_dictionary.get('fromversion')

    implementing_scripts, implementing_scripts_skippable = get_task_ids_from_playbook('scriptName',
                                                                                      data_dictionary,
                                                                                      graph
                                                                                      )
    implementing_playbooks, implementing_playbooks_skippable = get_task_ids_from_playbook('playbookName',
                                                                                          data_dictionary,
                                                                                          graph
                                                                                          )
    implementing_lists, implementing_lists_skippable = get_lists_names_from_playbook(data_dictionary, graph)
    command_to_integration, command_to_integration_skippable = get_commands_from_playbook(data_dictionary)
    skippable_tasks = (implementing_scripts_skippable + implementing_playbooks_skippable +
                       command_to_integration_skippable + implementing_lists_skippable)
    pack = get_pack_name(file_path)
    dependent_incident_fields, dependent_indicator_fields = get_dependent_incident_and_indicator_fields(data_dictionary)

    playbook_data = create_common_entity_data(path=file_path, name=name, to_version=toversion,
                                              from_version=fromversion, pack=pack)

    transformers, filters = get_filters_and_transformers_from_playbook(data_dictionary)

    if implementing_scripts:
        playbook_data['implementing_scripts'] = implementing_scripts
    if implementing_playbooks:
        playbook_data['implementing_playbooks'] = implementing_playbooks
    if command_to_integration:
        playbook_data['command_to_integration'] = command_to_integration
    if tests:
        playbook_data['tests'] = tests
    if deprecated:
        playbook_data['deprecated'] = deprecated
    if skippable_tasks:
        playbook_data['skippable_tasks'] = skippable_tasks
    if dependent_incident_fields:
        playbook_data['incident_fields'] = list(dependent_incident_fields)
    if dependent_indicator_fields:
        playbook_data['indicator_fields'] = list(dependent_indicator_fields)
    if filters:
        playbook_data['filters'] = filters
    if transformers:
        playbook_data['transformers'] = transformers
    if implementing_lists:
        playbook_data['lists'] = implementing_lists
    return {id_: playbook_data}


def get_script_data(file_path, script_code=None):
    data_dictionary = get_yaml(file_path)
    id_ = data_dictionary.get('commonfields', {}).get('id', '-')
    if script_code is None:
        script_code = data_dictionary.get('script', '')

    name = data_dictionary.get('name', '-')

    tests = data_dictionary.get('tests')
    toversion = data_dictionary.get('toversion')
    deprecated = data_dictionary.get('deprecated', False)
    fromversion = data_dictionary.get('fromversion')
    docker_image = data_dictionary.get('dockerimage')
    depends_on, command_to_integration = get_depends_on(data_dictionary)
    script_executions = sorted(list(set(re.findall(r"demisto.executeCommand\(['\"](\w+)['\"].*", script_code))))
    pack = get_pack_name(file_path)

    script_data = create_common_entity_data(path=file_path, name=name, to_version=toversion, from_version=fromversion,
                                            pack=pack)
    if deprecated:
        script_data['deprecated'] = deprecated
    if depends_on:
        script_data['depends_on'] = depends_on
    if script_executions:
        script_data['script_executions'] = script_executions
    if command_to_integration:
        script_data['command_to_integration'] = command_to_integration
    if docker_image:
        script_data['docker_image'] = docker_image
    if tests:
        script_data['tests'] = tests

    return {id_: script_data}


def get_values_for_keys_recursively(json_object: dict, keys_to_search: list) -> dict:
    """Recursively iterates over a dictionary to extract values for a list of keys.

    Args:
        json_object (dict): The dict to iterate on.
        keys_to_search (list): The list of keys to extract values for .

    Returns:
        dict. list of extracted values for each of the keys_to_search.

    Notes:
        only primitive values will be extracted (str/int/float/bool).

    Example:
        for the dict:
            {
                'id': 1,
                'nested': {
                    'x1': 1,
                    'x2': 'x2',
                    'x3': False
                },
                'x2': 4.0
            }

        and the list of keys
            [x1, x2, x3, x4]

        we will get the following dict:
            {
                'x1': [1],
                'x2': ['x2', 4.0],
                'x3': [False]
            }
    """
    values = {key: [] for key in keys_to_search}  # type: dict

    def get_values(current_object):
        if not current_object or not isinstance(current_object, (dict, list)):
            return

        if current_object and isinstance(current_object, list):
            if isinstance(current_object[0], dict):
                for item in current_object:
                    get_values(item)
            return

        if isinstance(current_object, dict):
            for key, value in current_object.items():
                if isinstance(value, (dict, list)):
                    get_values(value)
                elif key in keys_to_search:
                    if isinstance(value, (str, int, float, bool)):
                        values[key].append(value)

    get_values(json_object)
    return values


def get_layout_data(path):
    json_data = get_json(path)

    layout = json_data.get('layout', {})
    name = layout.get('name', '-')
    id_ = json_data.get('id', layout.get('id', '-'))
    type_ = json_data.get('typeId')
    type_name = json_data.get('TypeName')
    fromversion = json_data.get('fromVersion')
    toversion = json_data.get('toVersion')
    kind = json_data.get('kind')
    pack = get_pack_name(path)
    incident_indicator_types_dependency = {id_}
    incident_indicator_fields_dependency = get_values_for_keys_recursively(json_data, ['fieldId'])
    definition_id = json_data.get('definitionId')
    tabs = layout.get('tabs', [])
    scripts = get_layouts_scripts_ids(tabs)

    data = create_common_entity_data(path=path, name=name, to_version=toversion, from_version=fromversion, pack=pack)
    if type_:
        data['typeID'] = type_
    if type_name:
        data['typename'] = type_name
        incident_indicator_types_dependency.add(type_name)
    if kind:
        data['kind'] = kind
    data['incident_and_indicator_types'] = list(incident_indicator_types_dependency)
    if incident_indicator_fields_dependency['fieldId']:
        data['incident_and_indicator_fields'] = incident_indicator_fields_dependency['fieldId']
    if definition_id:
        data['definitionId'] = definition_id
    if scripts:
        data['scripts'] = scripts

    return {id_: data}


def get_layouts_scripts_ids(layout_tabs):
    """
    Finds all scripts IDs of a certain layout or layouts container.

    Args:
        layout_tabs: (List) Tabs list of a layout or a layouts container

    Returns:
        A list of all scripts IDs in a certain layout or layouts container
    """
    scripts = []

    for tab in layout_tabs:
        if isinstance(tab, dict):
            tab_sections = tab.get('sections', [])
            for section in tab_sections:

                # Find dynamic sections scripts:
                query_type = section.get('queryType')
                if query_type == 'script':
                    script_id = section.get('query')
                    if script_id:
                        scripts.append(script_id)

                # Find Buttons scripts:
                items = section.get('items', [])
                if items:
                    for item in items:
                        script_id = item.get('scriptId')
                        if script_id:
                            scripts.append(script_id)

    return scripts


def get_layoutscontainer_data(path):
    json_data = get_json(path)
    layouts_container_fields = ["group", "edit", "indicatorsDetails", "indicatorsQuickView", "quickView", "close",
                                "details", "detailsV2", "mobile"]
    pack = get_pack_name(path)
    data = create_common_entity_data(path=path, name=json_data.get('name'),
                                     to_version=json_data.get('toVersion'),
                                     from_version=json_data.get('fromVersion'),
                                     pack=pack)
    data.update(OrderedDict({field: json_data[field] for field in layouts_container_fields if json_data.get(field)}))

    id_ = json_data.get('id')
    incident_indicator_types_dependency = {id_}
    incident_indicator_fields_dependency = get_values_for_keys_recursively(json_data, ['fieldId'])
    definition_id = json_data.get('definitionId')
    if data.get('name'):
        incident_indicator_types_dependency.add(data['name'])
    data['incident_and_indicator_types'] = list(incident_indicator_types_dependency)
    if incident_indicator_fields_dependency['fieldId']:
        data['incident_and_indicator_fields'] = incident_indicator_fields_dependency['fieldId']
    if definition_id:
        data['definitionId'] = definition_id

    return {id_: data}


def get_incident_field_data(path, incidents_types_list):
    json_data = get_json(path)

    id_ = json_data.get('id')
    name = json_data.get('name', '')
    fromversion = json_data.get('fromVersion')
    toversion = json_data.get('toVersion')
    pack = get_pack_name(path)
    all_associated_types: set = set()
    all_scripts = set()

    associated_types = json_data.get('associatedTypes')
    if associated_types:
        all_associated_types = set(associated_types)

    system_associated_types = json_data.get('systemAssociatedTypes')
    if system_associated_types:
        all_associated_types = all_associated_types.union(set(system_associated_types))

    if 'all' in all_associated_types:
        all_associated_types = {list(incident_type.keys())[0] for incident_type in incidents_types_list}

    scripts = json_data.get('script')
    if scripts:
        all_scripts = {scripts}

    field_calculations_scripts = json_data.get('fieldCalcScript')
    if field_calculations_scripts:
        all_scripts = all_scripts.union({field_calculations_scripts})

    data = create_common_entity_data(path=path, name=name, to_version=toversion, from_version=fromversion, pack=pack)

    if all_associated_types:
        data['incident_types'] = list(all_associated_types)
    if all_scripts:
        data['scripts'] = list(all_scripts)

    return {id_: data}


def get_indicator_type_data(path, all_integrations):
    json_data = get_json(path)

    id_ = json_data.get('id')
    name = json_data.get('details', '')
    fromversion = json_data.get('fromVersion')
    toversion = json_data.get('toVersion')
    reputation_command = json_data.get('reputationCommand')
    pack = get_pack_name(path)
    all_scripts: set = set()
    associated_integrations = set()

    for field in ['reputationScriptName', 'enhancementScriptNames']:
        associated_scripts = json_data.get(field)
        if not associated_scripts or associated_scripts == 'null':
            continue

        associated_scripts = [associated_scripts] if not isinstance(associated_scripts, list) else associated_scripts
        if associated_scripts:
            all_scripts = all_scripts.union(set(associated_scripts))

    for integration in all_integrations:
        integration_name = next(iter(integration))
        integration_commands = integration.get(integration_name).get('commands')
        if integration_commands and reputation_command in integration_commands:
            associated_integrations.add(integration_name)

    data = create_common_entity_data(path=path, name=name, to_version=toversion, from_version=fromversion, pack=pack)
    if associated_integrations:
        data['integrations'] = list(associated_integrations)
    if all_scripts:
        data['scripts'] = list(all_scripts)

    return {id_: data}


def get_incident_type_data(path):
    json_data = get_json(path)

    id_ = json_data.get('id')
    name = json_data.get('name', '')
    fromversion = json_data.get('fromVersion')
    toversion = json_data.get('toVersion')
    playbook_id = json_data.get('playbookId')
    pre_processing_script = json_data.get('preProcessingScript')
    pack = get_pack_name(path)

    data = create_common_entity_data(path=path, name=name, to_version=toversion, from_version=fromversion, pack=pack)
    if playbook_id and playbook_id != '':
        data['playbooks'] = playbook_id
    if pre_processing_script and pre_processing_script != '':
        data['scripts'] = pre_processing_script

    return {id_: data}


def get_classifier_data(path):
    json_data = get_json(path)

    id_ = json_data.get('id')
    name = json_data.get('name', '')
    fromversion = json_data.get('fromVersion')
    toversion = json_data.get('toVersion')
    pack = get_pack_name(path)
    incidents_types = set()
    transformers: List[str] = []
    filters: List[str] = []
    definition_id = json_data.get('definitionId')

    default_incident_type = json_data.get('defaultIncidentType')
    if default_incident_type and default_incident_type != '':
        incidents_types.add(default_incident_type)
    key_type_map = json_data.get('keyTypeMap', {})
    for key, value in key_type_map.items():
        incidents_types.add(value)

    transformer = json_data.get('transformer', {})
    if transformer is dict:
        complex_value = transformer.get('complex', {})
        if complex_value:
            transformers, filters = get_filters_and_transformers_from_complex_value(complex_value)

    data = create_common_entity_data(path=path, name=name, to_version=toversion, from_version=fromversion, pack=pack)
    if incidents_types:
        data['incident_types'] = list(incidents_types)
    if filters:
        data['filters'] = filters
    if transformers:
        data['transformers'] = transformers
    if definition_id:
        data['definitionId'] = definition_id

    return {id_: data}


def create_common_entity_data(path, name, to_version, from_version, pack):
    data = OrderedDict()
    if name:
        data['name'] = name
    data['file_path'] = path
    data['source'] = list(get_current_repo())
    if to_version:
        data['toversion'] = to_version
    if from_version:
        data['fromversion'] = from_version
    if pack:
        data['pack'] = pack
    return data


def get_pack_metadata_data(file_path, print_logs: bool, marketplace: str = 'xsoar'):
    try:
        if print_logs:
            print(f'adding {file_path} to id_set')

        if should_skip_item_by_mp(file_path, marketplace, print_logs):
            return {}

        if print_logs:
            print(f'adding {file_path} to id_set')
        json_data = get_json(file_path)
        pack_data = {
            "name": json_data.get('name'),
            "current_version": json_data.get('currentVersion'),
            'source': get_current_repo(),
            "author": json_data.get('author', ''),
            'certification': 'certified' if json_data.get('support', '').lower() in ['xsoar', 'partner'] else '',
            "tags": json_data.get('tags', []),
            "use_cases": json_data.get('useCases', []),
            "categories": json_data.get('categories', []),
            "marketplaces": json_data.get('marketplaces', []),
        }

        pack_id = get_pack_name(file_path)
        return {pack_id: pack_data}

    except Exception as exp:  # noqa
        print_error(f'Failed to process {file_path}, Error: {str(exp)}')
        raise


def get_mapper_data(path):
    json_data = get_json(path)

    id_ = json_data.get('id')
    name = json_data.get('name', '')
    type_ = json_data.get('type', '')  # can be 'mapping-outgoing' or 'mapping-incoming'
    fromversion = json_data.get('fromVersion')
    toversion = json_data.get('toVersion')
    pack = get_pack_name(path)
    incidents_types = set()
    incidents_fields: set = set()
    all_transformers = set()
    all_filters = set()
    definition_id = json_data.get('definitionId')

    default_incident_type = json_data.get('defaultIncidentType')
    if default_incident_type and default_incident_type != '':
        incidents_types.add(default_incident_type)
    mapping = json_data.get('mapping', {})
    for key, value in mapping.items():
        incidents_types.add(key)
        internal_mapping = value.get('internalMapping')  # get the mapping
        if type_ == 'mapping-outgoing':
            incident_fields_set = set()
            # incident fields are in the simple key or in complex.root key of each key
            for internal_mapping_key in internal_mapping.keys():
                fields_mapper = internal_mapping.get(internal_mapping_key, {})
                if isinstance(fields_mapper, dict):
                    incident_field_simple = fields_mapper.get('simple')
                    if incident_field_simple:
                        incident_fields_set.add(incident_field_simple)
                    else:
                        incident_field_complex = fields_mapper.get('complex', {})
                        if incident_field_complex and 'root' in incident_field_complex:
                            incident_fields_set.add(incident_field_complex.get('root'))
            incidents_fields = incidents_fields.union(incident_fields_set)
        elif type_ == 'mapping-incoming':
            # all the incident fields are the keys of the mapping
            incidents_fields = incidents_fields.union(set(internal_mapping.keys()))

        # get_filters_and_transformers_from_complex_value(list(value.get('internalMapping', {}).values())[0]['complex'])
        for internal_mapping in internal_mapping.values():
            incident_field_complex = internal_mapping.get('complex', {})
            if incident_field_complex:
                transformers, filters = get_filters_and_transformers_from_complex_value(incident_field_complex)
                all_transformers.update(transformers)
                all_filters.update(filters)

    incidents_fields = {incident_field for incident_field in incidents_fields if incident_field not in BUILT_IN_FIELDS}
    data = create_common_entity_data(path=path, name=name, to_version=toversion, from_version=fromversion, pack=pack)
    if incidents_types:
        data['incident_types'] = list(incidents_types)
    if incidents_fields:
        data['incident_fields'] = list(incidents_fields)
    if all_filters:
        data['filters'] = list(all_filters)
    if all_transformers:
        data['transformers'] = list(all_transformers)
    if definition_id:
        data['definitionId'] = definition_id

    return {id_: data}


def get_widget_data(path):
    json_data = get_json(path)

    id_ = json_data.get('id')
    name = json_data.get('name', '')
    fromversion = json_data.get('fromVersion')
    toversion = json_data.get('toVersion')
    pack = get_pack_name(path)
    scripts = ''

    # if the widget is script based - add it to the dependencies of the widget
    if json_data.get('dataType') == 'scripts':
        scripts = json_data.get('query')

    data = create_common_entity_data(path=path, name=name, to_version=toversion, from_version=fromversion, pack=pack)
    if scripts:
        data['scripts'] = [scripts]

    return {id_: data}


def get_dashboard_data(path):
    dashboard_data = get_json(path)
    layouts = dashboard_data.get('layout', {})
    return parse_dashboard_or_report_data(layouts, dashboard_data, path)


def get_report_data(path):
    report_data = get_json(path)
    layouts = report_data.get('dashboard', {}).get('layout')
    return parse_dashboard_or_report_data(layouts, report_data, path)


def parse_dashboard_or_report_data(all_layouts, data_file_json, path):
    id_ = data_file_json.get('id')
    name = data_file_json.get('name', '')
    fromversion = data_file_json.get('fromVersion')
    toversion = data_file_json.get('toVersion')
    pack = get_pack_name(path)
    scripts = set()
    if all_layouts:
        for layout in all_layouts:
            widget_data = layout.get('widget')
            if widget_data.get('dataType') == 'scripts':
                scripts.add(widget_data.get('query'))

    data = create_common_entity_data(path=path, name=name, to_version=toversion, from_version=fromversion, pack=pack)
    if scripts:
        data['scripts'] = list(scripts)

    return {id_: data}


def get_general_data(path):
    json_data = get_json(path)
    id_ = json_data.get('id')
    brandname = json_data.get('brandName', '')
    name = json_data.get('name', '')
    fromversion = json_data.get('fromVersion')
    toversion = json_data.get('toVersion')
    pack = get_pack_name(path)

    data = create_common_entity_data(path=path, name=name, to_version=toversion, from_version=fromversion, pack=pack)
    if brandname:  # for classifiers
        data['name'] = brandname
    return {id_: data}


def get_depends_on(data_dict):
    depends_on = data_dict.get('dependson', {}).get('must', [])
    depends_on_list = list({cmd.split('|')[-1] for cmd in depends_on})
    command_to_integration = {}
    for cmd in depends_on:
        splitted_cmd = cmd.split('|')
        if splitted_cmd[0] and '|' in cmd:
            command_to_integration[splitted_cmd[-1]] = splitted_cmd[0]

    return depends_on_list, command_to_integration


def process_integration(file_path: str, print_logs: bool, marketplace: str = 'xsoar') -> list:
    """
    Process integration dir or file

    Arguments:
        file_path (str): file path to integration file
        print_logs (bool): whether to print logs to stdout
        marketplace (str): the marketplace this id set is designated for (xsoar or )

    Returns:
        list: integration data list (may be empty)
    """
    res = []
    try:
        if os.path.isfile(file_path):
            if should_skip_item_by_mp(file_path, marketplace, print_logs):
                return []
            if find_type(file_path) in (FileType.INTEGRATION, FileType.BETA_INTEGRATION):
                if print_logs:
                    print(f'adding {file_path} to id_set')
                res.append(get_integration_data(file_path))
        else:
            # package integration
            package_name = os.path.basename(file_path)
            file_path = os.path.join(file_path, '{}.yml'.format(package_name))
            if should_skip_item_by_mp(file_path, marketplace, print_logs):
                return []
            if os.path.isfile(file_path):
                # locally, might have leftover dirs without committed files
                if print_logs:
                    print(f'adding {file_path} to id_set')
                res.append(get_integration_data(file_path))
    except Exception as exp:  # noqa
        print_error(f'failed to process {file_path}, Error: {str(exp)}')
        raise

    return res


def process_script(file_path: str, print_logs: bool, marketplace: str = 'xsoar') -> list:
    res = []
    try:
        if os.path.isfile(file_path):
            if should_skip_item_by_mp(file_path, marketplace, print_logs):
                return []
            if find_type(file_path) == FileType.SCRIPT:
                if print_logs:
                    print(f'adding {file_path} to id_set')
                res.append(get_script_data(file_path))
        else:
            # package script
            unifier = YmlUnifier(file_path)
            yml_path, code = unifier.get_script_or_integration_package_data()
            if should_skip_item_by_mp(yml_path, marketplace, print_logs):
                return []
            if print_logs:
                print(f'adding {file_path} to id_set')
            res.append(get_script_data(yml_path, script_code=code))
    except Exception as exp:  # noqa
        print_error(f'failed to process {file_path}, Error: {str(exp)}')
        raise

    return res


def process_incident_fields(file_path: str, print_logs: bool, incidents_types_list: list, marketplace: str = 'xsoar') -> list:
    """
    Process a incident_fields JSON file
    Args:
        file_path: The file path from incident field folder
        print_logs: Whether to print logs to stdout.
        incidents_types_list: List of all the incident types in the system.

    Returns:
        a list of incident field data.
    """
    res = []
    try:
        if should_skip_item_by_mp(file_path, marketplace, print_logs):
            return []
        if find_type(file_path) == FileType.INCIDENT_FIELD:
            if print_logs:
                print(f'adding {file_path} to id_set')
            res.append(get_incident_field_data(file_path, incidents_types_list))
    except Exception as exp:  # noqa
        print_error(f'failed to process {file_path}, Error: {str(exp)}')
        raise
    return res


def process_indicator_types(file_path: str, print_logs: bool, all_integrations: list, marketplace: str = 'xsoar') -> list:
    """
    Process a indicator types JSON file
    Args:
        file_path: The file path from indicator type folder
        print_logs: Whether to print logs to stdout
        all_integrations: The integrations section in the id-set

    Returns:
        a list of indicator type data.
    """
    res = []
    try:
        if should_skip_item_by_mp(file_path, marketplace, print_logs):
            if print_logs:
                print(f'Skipping {file_path} due to mismatch with the marketplace this id set is generated for.')
            return []
        # ignore old reputations.json files
        if not os.path.basename(file_path) == 'reputations.json' and find_type(file_path) == FileType.REPUTATION:
            if print_logs:
                print(f'adding {file_path} to id_set')
            res.append(get_indicator_type_data(file_path, all_integrations))
    except Exception as exp:  # noqa
        print_error(f'failed to process {file_path}, Error: {str(exp)}')
        raise

    return res


def process_generic_items(file_path: str, print_logs: bool, marketplace: str = 'xsoar',
                          generic_types_list: list = None) -> list:
    """
    Process a generic field JSON file
    Args:
        file_path: The file path from object field folder
        print_logs: Whether to print logs to stdout.
        generic_types_list: List of all the generic types in the system.
        generic_modules_list: List of all the generic modules in the system.

    Returns:
        a list of generic items data: fields or types.
    """
    res = []
    try:
        if should_skip_item_by_mp(file_path, marketplace, print_logs):
            return []
        if find_type(file_path) == FileType.GENERIC_FIELD:
            if print_logs:
                print(f'adding {file_path} to id_set')
            res.append(get_generic_field_data(file_path, generic_types_list))
        elif find_type(file_path) == FileType.GENERIC_TYPE:
            if print_logs:
                print(f'adding {file_path} to id_set')
            res.append(get_generic_type_data(file_path))
    except Exception as exp:  # noqa
        print_error(f'failed to process {file_path}, Error: {str(exp)}')
        raise
    return res


def process_jobs(file_path: str, print_logs: bool, marketplace: str = 'xsoar') -> list:
    """
    Process a JSON file representing a Job object.
    Args:
        file_path: The file path from object field folder
        print_logs: Whether to print logs to stdout.

    Returns:
        a list of Job data.
    """
    result: List = []
    try:
        if should_skip_item_by_mp(file_path, marketplace, print_logs):
            return []
        if find_type(file_path) == FileType.JOB:
            if print_logs:
                print(f'adding {file_path} to id_set')
            result.append(get_job_data(file_path, print_logs))
    except Exception as exp:  # noqa
        print_error(f'failed to process job {file_path}, Error: {str(exp)}')
        raise
    return result


def process_general_items(file_path: str, print_logs: bool, expected_file_types: Tuple[FileType],
                          data_extraction_func: Callable, marketplace: str = 'xsoar') -> list:
    """
    Process a general item file.
    expected file in one of the following:
    * classifier
    * incident type
    * indicator field
    * layout
    * layoutscontainer
    * mapper
    * playbook
    * report
    * widget
    * list

    Args:
        file_path: The file path from an item folder
        print_logs: Whether to print logs to stdout
        expected_file_types: specific file type to parse, will ignore the rest
        data_extraction_func: a function that given a file path will return an id-set data dict.
        marketplace: the marketplace this id set is generated for
    Returns:
        a list of item data.
    """
    res = []
    try:
        if find_type(file_path) in expected_file_types:
            if should_skip_item_by_mp(file_path, marketplace, print_logs):
                return []
            if print_logs:
                print(f'adding {file_path} to id_set')
            res.append(data_extraction_func(file_path))
    except Exception as exp:  # noqa
        print_error(f'failed to process {file_path}, Error: {str(exp)}')
        raise

    return res


def process_test_playbook_path(file_path: str, print_logs: bool, marketplace: str = 'xsoar') -> tuple:
    """
    Process a yml file in the test playbook dir. Maybe either a script or playbook

    Arguments:
        file_path {string} -- path to yaml file
        print_logs {bool} -- whether to print logs to stdout

    Returns:
        pair -- first element is a playbook second is a script. each may be None
    """
    script = None
    playbook = None
    try:
        if print_logs:
            print(f'adding {file_path} to id_set')
        if should_skip_item_by_mp(file_path, marketplace, print_logs):
            return None, None
        if find_type(file_path) == FileType.TEST_SCRIPT:
            script = get_script_data(file_path)
        if find_type(file_path) == FileType.TEST_PLAYBOOK:
            playbook = get_playbook_data(file_path)
    except Exception as exp:  # noqa
        print_error(f'failed to process {file_path}, Error: {str(exp)}')
        raise

    return playbook, script


def get_integrations_paths(pack_to_create):
    if pack_to_create:
        path_list = [
            [pack_to_create, 'Integrations', '*']
        ]

    else:
        path_list = [
            ['Packs', '*', 'Integrations', '*']
        ]

    integration_files = list()
    for path in path_list:
        integration_files.extend(glob.glob(os.path.join(*path)))

    return integration_files


def get_playbooks_paths(pack_to_create):
    if pack_to_create:
        path_list = [
            [pack_to_create, 'Playbooks', '*.yml']
        ]

    else:
        path_list = [
            ['Packs', '*', 'Playbooks', '*.yml']
        ]

    playbook_files = list(pack_to_create) if pack_to_create else []
    for path in path_list:
        playbook_files.extend(glob.glob(os.path.join(*path)))

    return playbook_files


def get_pack_metadata_paths(pack_to_create):
    if pack_to_create:
        path_list = [pack_to_create, 'pack_metadata.json']

    else:
        path_list = ['Packs', '*', 'pack_metadata.json']

    return glob.glob(os.path.join(*path_list))


def get_general_paths(path, pack_to_create):
    if pack_to_create:
        path_list = [
            [pack_to_create, path, '*']
        ]

    else:
        path_list = [
            [path, '*'],
            ['Packs', '*', path, '*']
        ]

    files = list()
    for path in path_list:
        files.extend(glob.glob(os.path.join(*path)))

    return files


def get_generic_entities_paths(path, pack_to_create):
    """
    get paths of genericTypes, genericFields

    """
    if pack_to_create:
        path_list = [
            [pack_to_create, path, '*', '*.json']
        ]

    else:
        path_list = [
            [path, '*'],
            ['Packs', '*', path, '*', '*.json']
        ]

    files = list()
    for path in path_list:
        files.extend(glob.glob(os.path.join(*path)))

    return files


def get_generic_type_data(path):
    json_data = get_json(path)

    id_ = json_data.get('id')
    name = json_data.get('name', '')
    fromversion = json_data.get('fromVersion')
    toversion = json_data.get('toVersion')
    playbook_id = json_data.get('playbookId')
    pack = get_pack_name(path)
    definitionId = json_data.get('definitionId')
    layout = json_data.get('layout')

    data = create_common_entity_data(path=path, name=name, to_version=toversion, from_version=fromversion, pack=pack)
    if playbook_id and playbook_id != '':
        data['playbooks'] = playbook_id
    if definitionId:
        data['definitionId'] = definitionId
    if layout:
        data['layout'] = layout
    return {id_: data}


def get_module_id_from_definition_id(definition_id: str, generic_modules_list: list):
    for module in generic_modules_list:
        module_id = list(module.keys())[0]
        if definition_id in module.get(module_id, {}).get('definitionIds', []):
            return module_id


def get_generic_field_data(path, generic_types_list):
    json_data = get_json(path)

    id_ = json_data.get('id')
    name = json_data.get('name', '')
    fromversion = json_data.get('fromVersion')
    toversion = json_data.get('toVersion')
    pack = get_pack_name(path)
    all_associated_types: set = set()
    all_scripts = set()
    definitionId = json_data.get('definitionId')

    associated_types = json_data.get('associatedTypes')
    if associated_types:
        all_associated_types = set(associated_types)

    system_associated_types = json_data.get('systemAssociatedTypes')
    if system_associated_types:
        all_associated_types = all_associated_types.union(set(system_associated_types))

    if 'all' in all_associated_types:
        all_associated_types = {list(generic_type.keys())[0] for generic_type in generic_types_list}

    scripts = json_data.get('script')
    if scripts:
        all_scripts = {scripts}

    field_calculations_scripts = json_data.get('fieldCalcScript')
    if field_calculations_scripts:
        all_scripts = all_scripts.union({field_calculations_scripts})

    data = create_common_entity_data(path=path, name=name, to_version=toversion, from_version=fromversion, pack=pack)

    if all_associated_types:
        data['generic_types'] = list(all_associated_types)
    if all_scripts:
        data['scripts'] = list(all_scripts)
    if definitionId:
        data['definitionId'] = definitionId

    return {id_: data}


def get_job_data(path: str, print_logs: bool):
    json_data = get_json(path)

    data = create_common_entity_data(path=path,
                                     name=json_data.get('name'),
                                     to_version=json_data.get('toVersion'),
                                     from_version=json_data.get('fromVersion'),
                                     pack=get_pack_name(path))
    data['playbookId'] = json_data.get('playbookId')
    data['selectedFeeds'] = json_data.get('selectedFeeds', [])
    data['details'] = json_data.get('details', [])

    if print_logs:
        print(f'adding {path} to id_set')

    return {json_data.get('id'): data}


def get_generic_module_data(path):
    json_data = get_json(path)
    id_ = json_data.get('id')
    name = json_data.get('name', '')
    pack = get_pack_name(path)
    fromversion = json_data.get('fromVersion')
    toversion = json_data.get('toVersion')
    definitionIds = json_data.get('definitionIds', [])
    views = json_data.get('views', [])
    views = {view.get('name'): {
        'title': view.get('title'),
        'dashboards': [tab.get('dashboard', {}).get('id') for tab in view.get('tabs', [])]} for view in views}

    data = create_common_entity_data(path=path, name=name, to_version=toversion, from_version=fromversion, pack=pack)
    if definitionIds:
        data['definitionIds'] = definitionIds
    if views:
        data['views'] = views

    return {id_: data}


def get_list_data(path: str):
    json_data = get_json(path)
    data = create_common_entity_data(path=path,
                                     name=json_data.get('name'),
                                     to_version=json_data.get('toVersion'),
                                     from_version=json_data.get('fromVersion'),
                                     pack=get_pack_name(path))

    return {json_data.get('id'): data}


class IDSetType(Enum):
    PLAYBOOK = 'playbooks'
    INTEGRATION = 'integrations'
    SCRIPT = 'scripts'
    TEST_PLAYBOOK = 'TestPlaybooks'
    WIDGET = 'Widgets'
    CLASSIFIER = 'Classifiers'
    MAPPER = 'Mappers'
    REPORT = 'Reports'
    DASHBOARD = 'Dashboards'
    INCIDENT_FIELD = 'IncidentFields'
    INCIDENT_TYPE = 'IncidentTypes'
    INDICATOR_FIELD = 'IndicatorFields'
    INDICATOR_TYPE = 'IndicatorTypes'
    LAYOUTS = 'Layouts'
    PACKS = 'Packs'
    GENERIC_TYPE = 'GenericTypes'
    GENERIC_FIELD = 'GenericFields'
    GENERIC_MODULE = 'GenericModules'
    GENERIC_DEFINITION = 'GenericDefinitions'
    JOBS = 'Jobs'
    LISTS = 'Lists'

    @classmethod
    def has_value(cls, value):
        return value in cls._value2member_map_  # type: ignore


class IDSet:
    def __init__(self, id_set_dict=None):
        self._id_set_dict = id_set_dict if id_set_dict else {}

    def get_dict(self):
        return self._id_set_dict

    def get_list(self, item_type):
        return self._id_set_dict.get(item_type, [])

    def add_to_list(self, object_type: IDSetType, obj):
        if not IDSetType.has_value(object_type):
            raise ValueError(f'Invalid IDSetType {object_type}')

        self._id_set_dict.setdefault(object_type, []).append(obj) if obj not in self._id_set_dict[object_type] else None

    def add_pack_to_id_set_packs(self, object_type: IDSetType, obj_name, obj_value):
        self._id_set_dict.setdefault(object_type, {}).update({obj_name: obj_value})


def merge_id_sets_from_files(first_id_set_path, second_id_set_path, output_id_set_path, print_logs: bool = True):
    """
    Merges two id-sets. Loads them from files and saves the merged unified id_set into output_id_set_path.
    """
    with open(first_id_set_path, mode='r') as f1:
        first_id_set = json.load(f1)

    with open(second_id_set_path, mode='r') as f2:
        second_id_set = json.load(f2)

    unified_id_set, duplicates = merge_id_sets(first_id_set, second_id_set, print_logs)

    if unified_id_set:
        with open(output_id_set_path, mode='w', encoding='utf-8') as f:
            json.dump(unified_id_set.get_dict(), f, indent=4)

    return unified_id_set, duplicates


def merge_id_sets(first_id_set_dict: dict, second_id_set_dict: dict, print_logs: bool = True):
    """
    Merged two id_set dictionaries into single id_set. Returns the unified id_set dict.
    """
    duplicates = []
    united_id_set = IDSet(copy.deepcopy(first_id_set_dict))

    first_id_set = IDSet(first_id_set_dict)
    second_id_set = IDSet(second_id_set_dict)

    for object_type, object_list in second_id_set.get_dict().items():
        subset = first_id_set.get_list(object_type)

        if object_type != "Packs":
            for obj in object_list:
                obj_id = list(obj.keys())[0]
                is_duplicate = has_duplicate(subset, obj_id, object_type, print_logs,
                                             external_object=obj, is_create_new=False)
                if is_duplicate:
                    duplicates.append(obj_id)
                else:
                    united_id_set.add_to_list(object_type, obj)

        else:
            for obj_name, obj_value in object_list.items():
                united_id_set.add_pack_to_id_set_packs(object_type, obj_name, obj_value)

    if duplicates:
        return None, duplicates

    return united_id_set, []


def re_create_id_set(id_set_path: Optional[str] = DEFAULT_ID_SET_PATH, pack_to_create=None,  # noqa : C901
                     objects_to_create: list = None, print_logs: bool = True, fail_on_duplicates: bool = False,
                     marketplace: str = 'xsoar'):
    """Re create the id-set

    Args:
        id_set_path (str, optional): If passed an empty string will use default path (dependeing on mp type).
        Pass in None to avoid saving the id-set.
        pack_to_create: The input path. the default is the content repo.
        objects_to_create (list, optional): The content items this id set will contain. Defaults are set
        depending on the mp type.
        fail_on_duplicates: If value is True an error will be raised if duplicates are found
        marketplace: The marketplace the id set is created for.
        print_logs: Whether to print logs or not

    Returns: id-set object
    """
    if id_set_path == "":
        if marketplace == MarketplaceVersions.MarketplaceV2.value:
            id_set_path = MP_V2_ID_SET_PATH
        else:
            id_set_path = DEFAULT_ID_SET_PATH

    if not objects_to_create:
        if marketplace == MarketplaceVersions.MarketplaceV2.value:
            objects_to_create = CONTENT_MP_V2_ENTITIES
        else:
            objects_to_create = CONTENT_ENTITIES

    if id_set_path and os.path.exists(id_set_path):
        try:
            refresh_interval = int(os.getenv('DEMISTO_SDK_ID_SET_REFRESH_INTERVAL', -1))
        except ValueError:
            refresh_interval = -1
            print_color(
                "Re-creating id_set.\n"
                "DEMISTO_SDK_ID_SET_REFRESH_INTERVAL env var is set with value: "
                f"{os.getenv('DEMISTO_SDK_ID_SET_REFRESH_INTERVAL')} which is an illegal integer."
                "\nPlease modify or unset env var.", LOG_COLORS.YELLOW
            )
        if refresh_interval > 0:  # if the file is newer than the refresh interval, use it as is
            mtime = os.path.getmtime(id_set_path)
            mtime_dt = datetime.fromtimestamp(mtime)
            target_time = time.time() - (refresh_interval * 60)
            if mtime >= target_time:
                print_color(
                    f"DEMISTO_SDK_ID_SET_REFRESH_INTERVAL env var is set and detected that current id_set: {id_set_path}"
                    f" modify time: {mtime_dt} "
                    "doesn't require a refresh. Will use current id-set. "
                    "If you rather force an id-set refresh, unset DEMISTO_SDK_ID_SET_REFRESH_INTERVAL or set it to -1.",
                    LOG_COLORS.GREEN)
                with open(id_set_path, mode="r") as f:
                    return json.load(f)
            else:
                print_color(
                    f"The DEMISTO_SDK_ID_SET_REFRESH_INTERVAL env var is set, but current id_set: {id_set_path} "
                    f"modify time: {mtime_dt} is older than the refresh interval. "
                    "Re-generating id-set.", LOG_COLORS.GREEN)
        else:
            print_color("Note: DEMISTO_SDK_ID_SET_REFRESH_INTERVAL env var is not enabled. "
                        f"Will re-generate the id-set and overwrite the existing file: {id_set_path}. "
                        "To avoid re-generating the id-set on every run, you can set the "
                        "DEMISTO_SDK_ID_SET_REFRESH_INTERVAL env var to any refresh interval (in minutes).",
                        LOG_COLORS.GREEN)
        print("")  # add an empty line for clarity

    start_time = time.time()
    scripts_list = []
    playbooks_list = []
    integration_list = []
    testplaybooks_list = []

    classifiers_list = []
    dashboards_list = []
    incident_fields_list = []
    incident_type_list = []
    indicator_fields_list = []
    indicator_types_list = []
    layouts_list = []
    reports_list = []
    widgets_list = []
    mappers_list = []
    generic_types_list = []
    generic_fields_list = []
    generic_modules_list = []
    generic_definitions_list = []
    lists_list = []
    jobs_list = []
    packs_dict: Dict[str, Dict] = {}

    pool = Pool(processes=int(cpu_count()))

    print_color("Starting the creation of the id_set", LOG_COLORS.GREEN)

    with click.progressbar(length=len(objects_to_create), label="Creating id-set") as progress_bar:

        if 'Packs' in objects_to_create:
            print_color("\nStarting iteration over Packs", LOG_COLORS.GREEN)
            for pack_data in pool.map(partial(get_pack_metadata_data,
                                              print_logs=print_logs,
                                              marketplace=marketplace,
                                              ),
                                      get_pack_metadata_paths(pack_to_create)):
                packs_dict.update(pack_data)

        progress_bar.update(1)

        if 'Integrations' in objects_to_create:
            print_color("\nStarting iteration over Integrations", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_integration,
                                        print_logs=print_logs,
                                        marketplace=marketplace,
                                        ),
                                get_integrations_paths(pack_to_create)):

                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('integrations', []).append(_id)
                integration_list.extend(arr)

        progress_bar.update(1)

        if 'Playbooks' in objects_to_create:
            print_color("\nStarting iteration over Playbooks", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_general_items,
                                        print_logs=print_logs,
                                        expected_file_types=(FileType.PLAYBOOK,),
                                        data_extraction_func=get_playbook_data,
                                        marketplace=marketplace,
                                        ),
                                get_playbooks_paths(pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('playbooks', []).append(_id)
                playbooks_list.extend(arr)

        progress_bar.update(1)

        if 'Scripts' in objects_to_create:
            print_color("\nStarting iteration over Scripts", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_script,
                                        print_logs=print_logs,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(SCRIPTS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('scripts', []).append(_id)
                scripts_list.extend(arr)

        progress_bar.update(1)

        if 'TestPlaybooks' in objects_to_create:
            print_color("\nStarting iteration over TestPlaybooks", LOG_COLORS.GREEN)
            for pair in pool.map(partial(process_test_playbook_path,
                                         print_logs=print_logs,
                                         marketplace=marketplace,
                                         ),
                                 get_general_paths(TEST_PLAYBOOKS_DIR, pack_to_create)):
                if pair[0]:
                    testplaybooks_list.append(pair[0])
                if pair[1]:
                    scripts_list.append(pair[1])

        progress_bar.update(1)

        if 'Classifiers' in objects_to_create:
            print_color("\nStarting iteration over Classifiers", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_general_items,
                                        print_logs=print_logs,
                                        expected_file_types=(FileType.CLASSIFIER, FileType.OLD_CLASSIFIER),
                                        data_extraction_func=get_classifier_data,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(CLASSIFIERS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('classifiers', []).append(_id)
                classifiers_list.extend(arr)

        progress_bar.update(1)

        if 'Dashboards' in objects_to_create:
            print_color("\nStarting iteration over Dashboards", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_general_items,
                                        print_logs=print_logs,
                                        expected_file_types=(FileType.DASHBOARD,),
                                        data_extraction_func=get_dashboard_data,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(DASHBOARDS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('dashboards', []).append(_id)
                dashboards_list.extend(arr)

        progress_bar.update(1)

        if 'IncidentTypes' in objects_to_create:
            print_color("\nStarting iteration over Incident Types", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_general_items,
                                        print_logs=print_logs,
                                        expected_file_types=(FileType.INCIDENT_TYPE,),
                                        data_extraction_func=get_incident_type_data,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(INCIDENT_TYPES_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('incidentTypes', []).append(_id)
                incident_type_list.extend(arr)

        progress_bar.update(1)

        # Has to be called after 'IncidentTypes' is called
        if 'IncidentFields' in objects_to_create:
            print_color("\nStarting iteration over Incident Fields", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_incident_fields,
                                        print_logs=print_logs,
                                        incidents_types_list=incident_type_list,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(INCIDENT_FIELDS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('incidentFields', []).append(_id)
                incident_fields_list.extend(arr)

        progress_bar.update(1)

        if 'IndicatorFields' in objects_to_create:
            print_color("\nStarting iteration over Indicator Fields", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_general_items,
                                        print_logs=print_logs,
                                        expected_file_types=(FileType.INDICATOR_FIELD,),
                                        data_extraction_func=get_general_data,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(INDICATOR_FIELDS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('indicatorFields', []).append(_id)
                indicator_fields_list.extend(arr)

        progress_bar.update(1)

        # Has to be called after 'Integrations' is called
        if 'IndicatorTypes' in objects_to_create:
            print_color("\nStarting iteration over Indicator Types", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_indicator_types,
                                        print_logs=print_logs,
                                        all_integrations=integration_list,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(INDICATOR_TYPES_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('indicatorTypes', []).append(_id)
                indicator_types_list.extend(arr)

        progress_bar.update(1)

        if 'Layouts' in objects_to_create:
            print_color("\nStarting iteration over Layouts", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_general_items,
                                        print_logs=print_logs,
                                        expected_file_types=(FileType.LAYOUT,),
                                        data_extraction_func=get_layout_data,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(LAYOUTS_DIR, pack_to_create)):
                layouts_list.extend(arr)
            for arr in pool.map(partial(process_general_items,
                                        print_logs=print_logs,
                                        expected_file_types=(FileType.LAYOUTS_CONTAINER,),
                                        data_extraction_func=get_layoutscontainer_data,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(LAYOUTS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('layouts', []).append(_id)
                layouts_list.extend(arr)

        progress_bar.update(1)

        if 'Reports' in objects_to_create:
            print_color("\nStarting iteration over Reports", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_general_items,
                                        print_logs=print_logs,
                                        expected_file_types=(FileType.REPORT,),
                                        data_extraction_func=get_report_data,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(REPORTS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('reports', []).append(_id)
                reports_list.extend(arr)

        progress_bar.update(1)

        if 'Widgets' in objects_to_create:
            print_color("\nStarting iteration over Widgets", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_general_items,
                                        print_logs=print_logs,
                                        expected_file_types=(FileType.WIDGET,),
                                        data_extraction_func=get_widget_data,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(WIDGETS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('widgets', []).append(_id)
                widgets_list.extend(arr)

        progress_bar.update(1)

        if 'Mappers' in objects_to_create:
            print_color("\nStarting iteration over Mappers", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_general_items,
                                        print_logs=print_logs,
                                        expected_file_types=(FileType.MAPPER,),
                                        data_extraction_func=get_mapper_data,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(MAPPERS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('mappers', []).append(_id)
                mappers_list.extend(arr)

        progress_bar.update(1)

        if 'Lists' in objects_to_create:
            print_color("\nStarting iteration over Lists", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_general_items,
                                        print_logs=print_logs,
                                        expected_file_types=(FileType.LISTS,),
                                        data_extraction_func=get_list_data,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(LISTS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('lists', []).append(_id)
                lists_list.extend(arr)

        progress_bar.update(1)

        if 'GenericDefinitions' in objects_to_create:
            print_color("\nStarting iteration over Generic Definitions", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_general_items,
                                        expected_file_types=(FileType.GENERIC_DEFINITION,),
                                        data_extraction_func=get_general_data,
                                        print_logs=print_logs,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(GENERIC_DEFINITIONS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('genericDefinitions', []).append(_id)
                generic_definitions_list.extend(arr)

        progress_bar.update(1)

        if 'GenericModules' in objects_to_create:
            print_color("\nStarting iteration over Generic Modules", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_general_items,
                                        print_logs=print_logs,
                                        expected_file_types=(FileType.GENERIC_MODULE,),
                                        data_extraction_func=get_generic_module_data,
                                        marketplace=marketplace,
                                        ),
                                get_general_paths(GENERIC_MODULES_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('genericModules', []).append(_id)
                generic_modules_list.extend(arr)

        progress_bar.update(1)

        if 'GenericTypes' in objects_to_create:
            print_color("\nStarting iteration over Generic Types", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_generic_items,
                                        print_logs=print_logs,
                                        marketplace=marketplace,
                                        ),
                                get_generic_entities_paths(GENERIC_TYPES_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('genericTypes', []).append(_id)
                generic_types_list.extend(arr)

        progress_bar.update(1)

        # Has to be called after 'GenericTypes' is called
        if 'GenericFields' in objects_to_create:
            print_color("\nStarting iteration over Generic Fields", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_generic_items,
                                        print_logs=print_logs,
                                        generic_types_list=generic_types_list,
                                        marketplace=marketplace,
                                        ),
                                get_generic_entities_paths(GENERIC_FIELDS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('genericFields', []).append(_id)
                generic_fields_list.extend(arr)

        progress_bar.update(1)

        if 'Jobs' in objects_to_create:
            print_color("\nStarting iteration over Jobs", LOG_COLORS.GREEN)
            for arr in pool.map(partial(process_jobs, print_logs=print_logs, marketplace=marketplace, ),
                                get_general_paths(JOBS_DIR, pack_to_create)):
                for _id, data in (arr[0].items() if arr and isinstance(arr, list) else {}):
                    if data.get('pack'):
                        packs_dict[data.get('pack')].setdefault('ContentItems', {}).setdefault('jobs', []).append(_id)
                jobs_list.extend(arr)

        progress_bar.update(1)

    new_ids_dict = OrderedDict()
    # we sort each time the whole set in case someone manually changed something
    # it shouldn't take too much time
    new_ids_dict['scripts'] = sort(scripts_list)
    new_ids_dict['playbooks'] = sort(playbooks_list)
    new_ids_dict['integrations'] = sort(integration_list)
    new_ids_dict['TestPlaybooks'] = sort(testplaybooks_list)
    new_ids_dict['Classifiers'] = sort(classifiers_list)
    new_ids_dict['IncidentFields'] = sort(incident_fields_list)
    new_ids_dict['IncidentTypes'] = sort(incident_type_list)
    new_ids_dict['IndicatorFields'] = sort(indicator_fields_list)
    new_ids_dict['IndicatorTypes'] = sort(indicator_types_list)
    new_ids_dict['Lists'] = sort(lists_list)
    new_ids_dict['Jobs'] = sort(jobs_list)
    new_ids_dict['Mappers'] = sort(mappers_list)
    new_ids_dict['Packs'] = packs_dict

    if marketplace == MarketplaceVersions.XSOAR.value:
        new_ids_dict['GenericTypes'] = sort(generic_types_list)
        new_ids_dict['GenericFields'] = sort(generic_fields_list)
        new_ids_dict['GenericModules'] = sort(generic_modules_list)
        new_ids_dict['GenericDefinitions'] = sort(generic_definitions_list)
        new_ids_dict['Layouts'] = sort(layouts_list)
        new_ids_dict['Reports'] = sort(reports_list)
        new_ids_dict['Widgets'] = sort(widgets_list)
        new_ids_dict['Dashboards'] = sort(dashboards_list)
    else:
        # a workaround for find-dependencies check (PackDependencies._collect_pack_items)
        new_ids_dict['GenericTypes'] = []
        new_ids_dict['GenericFields'] = []
        new_ids_dict['GenericModules'] = []
        new_ids_dict['GenericDefinitions'] = []
        new_ids_dict['Layouts'] = []
        new_ids_dict['Reports'] = []
        new_ids_dict['Widgets'] = []
        new_ids_dict['Dashboards'] = []

    exec_time = time.time() - start_time
    print_color("Finished the creation of the id_set. Total time: {} seconds".format(exec_time), LOG_COLORS.GREEN)
    duplicates = find_duplicates(new_ids_dict, print_logs, marketplace)
    if any(duplicates) and fail_on_duplicates:
        raise Exception(f'The following ids were found duplicates\n{json.dumps(duplicates, indent=4)}\n')

    return new_ids_dict


def find_duplicates(id_set, print_logs, marketplace):
    lists_to_return = []

    entities = ID_SET_ENTITIES if marketplace == 'xsoar' else ID_SET_MP_V2_ENTITIES

    for object_type in entities:
        if print_logs:
            print_color("Checking diff for {}".format(object_type), LOG_COLORS.GREEN)
        objects = id_set.get(object_type)
        ids = {list(specific_item.keys())[0] for specific_item in objects}

        dup_list = []
        for id_to_check in ids:
            if has_duplicate(objects, id_to_check, object_type, print_logs, is_create_new=True):
                dup_list.append(id_to_check)
        lists_to_return.append(dup_list)
    if print_logs:
        print_color("Checking diff for Incident and Indicator Fields", LOG_COLORS.GREEN)

    fields = id_set['IncidentFields'] + id_set['IndicatorFields']
    field_ids = {list(field.keys())[0] for field in fields}

    field_list = []
    for field_to_check in field_ids:
        if has_duplicate(fields, field_to_check, 'Indicator and Incident Fields', print_logs, is_create_new=True):
            field_list.append(field_to_check)
    lists_to_return.append(field_list)

    return lists_to_return


def has_duplicate(id_set_subset_list, id_to_check, object_type=None, print_logs=True, external_object=None, is_create_new=False):
    """
    Finds if id_set_subset_list contains a duplicate items with the same id_to_check.

    Pass `external_object` to check if it exists in `id_set_subset_list`.
    Otherwise the function will check if `id_set_subset_list` contains 2 or more items with the id of `id_to_check`

    Pass `is_create_new` if searching for duplicate while creating a new id-set.

    """
    duplicates = [duplicate for duplicate in id_set_subset_list if duplicate.get(id_to_check)]

    if external_object and len(duplicates) == 0:
        return False

    if not external_object and len(duplicates) == 1:
        return False

    if external_object:
        duplicates.append(external_object)

    for dup1, dup2 in itertools.combinations(duplicates, 2):
        dict1 = list(dup1.values())[0]
        dict2 = list(dup2.values())[0]
        dict1_from_version = LooseVersion(dict1.get('fromversion', DEFAULT_CONTENT_ITEM_FROM_VERSION))
        dict2_from_version = LooseVersion(dict2.get('fromversion', DEFAULT_CONTENT_ITEM_FROM_VERSION))
        dict1_to_version = LooseVersion(dict1.get('toversion', DEFAULT_CONTENT_ITEM_TO_VERSION))
        dict2_to_version = LooseVersion(dict2.get('toversion', DEFAULT_CONTENT_ITEM_TO_VERSION))

        # Checks if the Layouts kind is different then they are not duplicates
        if object_type == 'Layouts':
            if dict1.get('kind', '') != dict2.get('kind', ''):
                return False
            if dict1.get('typeID', '') != dict2.get('typeID', ''):
                return False

        # If they have the same pack name and the same source they actually the same entity.
        # Added to support merge between two id-sets that contain the same pack.
        if not is_create_new and \
                dict1.get('pack') == dict2.get('pack') and\
                is_same_source(dict1.get('source'), dict2.get('source')):
            return False

        # A: 3.0.0 - 3.6.0
        # B: 3.5.0 - 4.5.0
        # C: 3.5.2 - 3.5.4
        # D: 4.5.0 - 99.99.99
        if any([
            dict1_from_version <= dict2_from_version < dict1_to_version,  # will catch (B, C), (A, B), (A, C)
            dict1_from_version < dict2_to_version <= dict1_to_version,  # will catch (B, C), (A, C)
            dict2_from_version <= dict1_from_version < dict2_to_version,  # will catch (C, B), (B, A), (C, A)
            dict2_from_version < dict1_to_version <= dict2_to_version,  # will catch (C, B), (C, A)
        ]):
            print_warning(f'The following {object_type} have the same ID ({id_to_check}) and their versions overlap: '
                          f'1) "{dict1_from_version}-{dict1_to_version}", '
                          f'2) "{dict2_from_version}-{dict2_to_version}".')
            return True

        if print_logs and dict1.get('name') != dict2.get('name'):
            print_warning('The following {} have the same ID ({}) but different names: '
                          '"{}", "{}".'.format(object_type, id_to_check, dict1.get('name'), dict2.get('name')))

    return False


def is_same_source(source1, source2) -> bool:
    """
    Two sources will considered the same if they are exactly the same repo, or they are the XSOAR repos, one in github
    and another in gitlab
    github.com, demisto, content == code,pan.run, xsoar, content
    """
    if source1 == source2:
        return True
    host1, owner1, repo1 = source1
    host2, owner2, repo2 = source2
    if host1 in {'github.com', 'code.pan.run'} and owner1 in {'demisto', 'xsoar'} \
            and host2 in {'github.com', 'code.pan.run'} and owner2 in {'demisto', 'xsoar'}:
        return repo1 == repo2
    return False


def sort(data):
    data.sort(key=lambda r: list(r.keys())[0].lower())  # Sort data by key value
    return data


# demisto_sdk/commands/common/tests/author_image_validator_test.py

import os

import pytest

from demisto_sdk.commands.common.hook_validations.author_image import \
    AuthorImageValidator
from demisto_sdk.commands.common.legacy_git_tools import git_path


class TestAuthorImageValidator:
    AUTHOR_IMAGE_FILES_PATH = os.path.normpath(os.path.join(f'{git_path()}/demisto_sdk/tests', 'test_files',
                                                            'AuthorImageValidator'))
    VALID_AUTHOR_IMAGE = os.path.join(AUTHOR_IMAGE_FILES_PATH, 'valid_author_image.png')
    EMPTY_AUTHOR_IMAGE = os.path.join(AUTHOR_IMAGE_FILES_PATH, 'empty_author_image.png')

    IS_VALID_INPUTS = [('path_does_not_exist', 'xsoar', AuthorImageValidator.IMAGE_MAX_SIZE, True),
                       ('path_does_not_exist', 'partner', AuthorImageValidator.IMAGE_MAX_SIZE, False),
                       (VALID_AUTHOR_IMAGE, 'xsoar', AuthorImageValidator.IMAGE_MAX_SIZE, True),
                       (EMPTY_AUTHOR_IMAGE, 'xsoar', AuthorImageValidator.IMAGE_MAX_SIZE, False),
                       (VALID_AUTHOR_IMAGE, 'partner', AuthorImageValidator.IMAGE_MAX_SIZE, True),
                       (EMPTY_AUTHOR_IMAGE, 'partner', AuthorImageValidator.IMAGE_MAX_SIZE, False),
                       (VALID_AUTHOR_IMAGE, 'xsoar', 100, False),
                       (VALID_AUTHOR_IMAGE, 'partner', 100, False),
                       ]

    @pytest.mark.parametrize('author_image_path, support_level, max_image_size, expected', IS_VALID_INPUTS)
    def test_is_valid(self, mocker, repo, author_image_path: str, support_level: str, max_image_size: int, expected: bool):
        """
        Given:
        - 'author_image_path': path to where author image should be found.

        When:
        - Performing validations of author image if needed.
        Case a: XSOAR pack, image does not exist.
        Case b: Partner pack, image does not exist.
        Case c: XSOAR pack, valid image exists.
        Case d: XSOAR pack, empty image exists.
        Case e: Partner pack, valid image exists.
        Case f: Partner pack, empty image exists.
        Case g: XSOAR pack, image exists and is bigger than maximum size.
        Case h: Partner pack, image exists and is bigger than maximum size.

        Then:
        - Ensure expected validation status is made.
        Case a: Ensure true is returned.
        Case b: Ensure false is returned.
        Case c: Ensure true is returned.
        Case d: Ensure false is returned.
        Case e: Ensure true is returned.
        Case f: Ensure false is returned.
        Case g: Ensure false is returned.
        Case h: Ensure false is returned.

        """
        # setup test pack and write the author image to test if exists
        pack = repo.create_pack('my_pack')
        if os.path.exists(author_image_path):
            with open(author_image_path, 'rb') as f:
                pack.author_image.write_bytes(f.read())

        author_image_validator: AuthorImageValidator = AuthorImageValidator(pack.author_image.path,
                                                                            maximum_image_size=max_image_size)
        mocker.patch.object(author_image_validator, 'handle_error')
        mocker.patch.object(author_image_validator, 'get_support_level', return_value=support_level)
        author_image_validator.file_path = author_image_path
        assert author_image_validator.is_valid() == expected


# demisto_sdk/commands/common/tests/base_validator_test.py

import json
import os
from os.path import join

from demisto_sdk.commands.common.constants import PACK_METADATA_SUPPORT
from demisto_sdk.commands.common.errors import (FOUND_FILES_AND_ERRORS,
                                                FOUND_FILES_AND_IGNORED_ERRORS,
                                                PRESET_ERROR_TO_CHECK,
                                                PRESET_ERROR_TO_IGNORE, Errors)
from demisto_sdk.commands.common.hook_validations.base_validator import \
    BaseValidator
from demisto_sdk.commands.common.legacy_git_tools import git_path
from demisto_sdk.commands.common.tools import get_yaml
from TestSuite.test_tools import ChangeCWD

DEPRECATED_IGNORE_ERRORS_DEFAULT_LIST = BaseValidator.create_reverse_ignored_errors_list(
    PRESET_ERROR_TO_CHECK['deprecated'])


def test_handle_error(mocker):
    """
    Given
    - An ignore errors list associated with a file.
    - An error, message, code and file paths.

    When
    - Running handle_error method.

    Then
    - Ensure the resulting error messages are correctly formatted.
    - Ensure ignored error codes return None.
    - Ensure non ignored errors are in FOUND_FILES_AND_ERRORS list.
    - Ensure ignored error are not in FOUND_FILES_AND_ERRORS and in FOUND_FILES_AND_IGNORED_ERRORS
    """
    import click
    base_validator = BaseValidator(ignored_errors={"file_name": ["BA101"]}, print_as_warnings=True)

    # passing the flag checks - checked separately
    base_validator.checked_files.union({'PATH', "file_name"})

    formatted_error = base_validator.handle_error("Error-message", "SC102", "PATH")
    assert formatted_error == '[ERROR]: PATH: [SC102] - Error-message\n'
    assert 'PATH - [SC102]' in FOUND_FILES_AND_ERRORS

    formatted_error = base_validator.handle_error("another-error-message", "IN101", "path/to/file_name")
    assert formatted_error == '[ERROR]: path/to/file_name: [IN101] - another-error-message\n'
    assert 'path/to/file_name - [IN101]' in FOUND_FILES_AND_ERRORS

    click_mock = mocker.patch.object(click, 'secho')
    formatted_error = base_validator.handle_error("ignore-file-specific", "BA101", "path/to/file_name")
    assert formatted_error is None
    assert 'path/to/file_name - [BA101]' not in FOUND_FILES_AND_ERRORS
    assert 'path/to/file_name - [BA101]' in FOUND_FILES_AND_IGNORED_ERRORS
    assert click_mock.call_args_list[0][0][0] == "[WARNING]: path/to/file_name: [BA101] - ignore-file-specific\n"

    formatted_error = base_validator.handle_error("Error-message", "ST109", "path/to/file_name")
    assert formatted_error == '[ERROR]: path/to/file_name: [ST109] - Error-message\n'
    assert 'path/to/file_name - [ST109]' in FOUND_FILES_AND_ERRORS


def test_handle_error_file_with_path(pack):
    """
    Given
    - An ignore errors list associated with a file_path.
    - An error, message, code and file paths.

    When
    - Running handle_error method.

    Then
    _ Ensure ignoring right file when full path mentioned in .pack-ignore.
    - Ensure the resulting error messages are correctly formatted.
    - Ensure ignored error codes return None.
    - Ensure non ignored errors are in FOUND_FILES_AND_ERRORS list.
    - Ensure ignored error are not in FOUND_FILES_AND_ERRORS and in FOUND_FILES_AND_IGNORED_ERRORS
    """
    integration = pack.create_integration("TestIntegration")
    rel_path_integration_readme = integration.readme.path[integration.readme.path.find("Packs"):]
    rel_path_pack_readme = pack.readme.path[pack.readme.path.find("Packs"):]

    pack_ignore_text = f"""[file:{rel_path_integration_readme}]
    ignore=ST109

    [file:{rel_path_pack_readme}]
    ignore=BA101"""
    pack.pack_ignore.write_text(pack_ignore_text)

    base_validator = BaseValidator(ignored_errors={rel_path_pack_readme: ["BA101"],
                                                   rel_path_integration_readme: ["ST109"]},
                                   print_as_warnings=True)

    formatted_error = base_validator.handle_error("Error-message", "BA101", integration.readme.path)
    assert formatted_error == f'[ERROR]: {integration.readme.path}: [BA101] - Error-message\n'
    assert f'{integration.readme.path} - [BA101]' in FOUND_FILES_AND_ERRORS

    formatted_error = base_validator.handle_error("Error-message", "ST109", integration.readme.path)
    assert formatted_error is None
    assert f'{integration.readme.path} - [ST109]' not in FOUND_FILES_AND_ERRORS
    assert f'{integration.readme.path} - [ST109]' in FOUND_FILES_AND_IGNORED_ERRORS

    formatted_error = base_validator.handle_error("Error-message", "ST109", pack.readme.path)
    assert formatted_error == f'[ERROR]: {pack.readme.path}: [ST109] - Error-message\n'
    assert f'{pack.readme.path} - [ST109]' in FOUND_FILES_AND_ERRORS

    formatted_error = base_validator.handle_error("Error-message", "BA101", pack.readme.path)
    assert formatted_error is None
    assert f'{pack.readme.path} - [BA101]' not in FOUND_FILES_AND_ERRORS
    assert f'{pack.readme.path} - [BA101]' in FOUND_FILES_AND_IGNORED_ERRORS


def test_check_deprecated_where_ignored_list_exists(repo):
    """
    Given
    - An deprecated integration yml.
    - A pre-existing ignored errors list for the integration.

    When
    - Running check_deprecated method.

    Then
    - Ensure the resulting ignored errors list included the existing errors as well as the deprecated default error list.
    """
    pack = repo.create_pack('pack')
    integration = pack.create_integration('integration')
    integration.yml.write_dict({'deprecated': True})
    files_path = integration.yml.path
    with ChangeCWD(repo.path):
        base_validator = BaseValidator(ignored_errors={'integration.yml': ['BA101']})
        base_validator.check_deprecated(files_path)
    assert base_validator.ignored_errors['integration.yml'] == ["BA101"] + DEPRECATED_IGNORE_ERRORS_DEFAULT_LIST


def test_check_deprecated_where_ignored_list_does_not_exist(repo):
    """
    Given
    - An deprecated integration yml.
    - No pre-existing ignored errors list for the integration.

    When
    - Running check_deprecated method.

    Then
    - Ensure the resulting ignored errors list included the deprecated default error list only.
    """
    pack = repo.create_pack('pack')
    integration = pack.create_integration('integration')
    integration.yml.write_dict({'deprecated': True})
    files_path = integration.yml.path
    with ChangeCWD(repo.path):
        base_validator = BaseValidator(ignored_errors={})
        base_validator.check_deprecated(files_path)
    assert base_validator.ignored_errors['integration.yml'] == DEPRECATED_IGNORE_ERRORS_DEFAULT_LIST


def test_check_deprecated_non_deprecated_integration_no_ignored_errors(repo):
    """
    Given
    - An non-deprecated integration yml.
    - No pre-existing ignored errors list for the integration.

    When
    - Running check_deprecated method.

    Then
    - Ensure there is no resulting ignored errors list.
    """
    pack = repo.create_pack('pack')
    integration = pack.create_integration('integration')
    integration.yml.write_dict({'deprecated': False})
    files_path = integration.yml.path
    with ChangeCWD(repo.path):
        base_validator = BaseValidator(ignored_errors={})
        base_validator.check_deprecated(files_path)
    assert 'integration' not in base_validator.ignored_errors


def test_check_deprecated_non_deprecated_integration_with_ignored_errors(repo):
    """
    Given
    - An non-deprecated integration yml.
    - A pre-existing ignored errors list for the integration.

    When
    - Running check_deprecated method.

    Then
    - Ensure the resulting ignored errors list is the pre-existing one.
    """
    pack = repo.create_pack('pack')
    integration = pack.create_integration('integration')
    integration.yml.write_dict({'deprecated': False})
    files_path = integration.yml.path
    with ChangeCWD(repo.path):
        base_validator = BaseValidator(ignored_errors={'integration.yml': ["BA101"]})
        base_validator.check_deprecated(files_path)
    assert base_validator.ignored_errors['integration.yml'] == ['BA101']


def test_check_deprecated_playbook(repo):
    """
    Given
    - An non-deprecated playbook yml.

    When
    - Running check_deprecated method.

    Then
    - Ensure the resulting ignored errors list included the deprecated default error list only.
    """
    pack = repo.create_pack('pack')
    playbook = pack.create_integration('playbook-somePlaybook')
    test_file_path = join(git_path(), 'demisto_sdk', 'tests', 'test_files')
    valid_deprecated_playbook_file_path = join(test_file_path, 'Packs', 'CortexXDR', 'Playbooks',
                                               'Valid_Deprecated_Playbook.yml')
    playbook.yml.write_dict(get_yaml(valid_deprecated_playbook_file_path))
    files_path = playbook.yml.path
    with ChangeCWD(repo.path):
        base_validator = BaseValidator(ignored_errors={})
        base_validator.check_deprecated(files_path)
    assert base_validator.ignored_errors['playbook-somePlaybook.yml'] == DEPRECATED_IGNORE_ERRORS_DEFAULT_LIST


def test_check_support_status_xsoar_file(repo, mocker):
    """
    Given
    - An xsoar supported integration yml.

    When
    - Running check_support_status method.

    Then
    - Ensure the resulting ignored errors list does not include the integration file name.
    """
    pack = repo.create_pack('pack')
    integration = pack.create_integration('integration')
    meta_json = {
        PACK_METADATA_SUPPORT: "xsoar"
    }
    mocker.patch.object(BaseValidator, 'get_metadata_file_content', return_value=meta_json)
    pack.pack_metadata.write_json(meta_json)
    with ChangeCWD(repo.path):
        base_validator = BaseValidator(ignored_errors={})
        base_validator.update_checked_flags_by_support_level(integration.yml.rel_path)

        assert 'integration.yml' not in base_validator.ignored_errors


def test_check_support_status_partner_file(repo, mocker):
    """
    Given
    - An partner supported integration yml.

    When
    - Running check_support_status method.

    Then
    - Ensure the resulting ignored errors list includes the partner ignore-list.
    """
    pack = repo.create_pack('pack')
    integration = pack.create_integration('integration')
    meta_json = {
        PACK_METADATA_SUPPORT: "partner"
    }
    mocker.patch.object(BaseValidator, 'get_metadata_file_content', return_value=meta_json)
    pack.pack_metadata.write_json(meta_json)
    with ChangeCWD(repo.path):
        base_validator = BaseValidator(ignored_errors={})
        base_validator.update_checked_flags_by_support_level(integration.yml.rel_path)

        assert base_validator.ignored_errors['integration.yml'] == PRESET_ERROR_TO_IGNORE['partner']


def test_check_support_status_community_file(repo, mocker):
    """
    Given
    - An community supported integration yml.

    When
    - Running check_support_status method.

    Then
    - Ensure the resulting ignored errors list does not include the integration file name.
    """
    pack = repo.create_pack('pack')
    integration = pack.create_integration('integration')
    meta_json = {
        PACK_METADATA_SUPPORT: "community"
    }
    mocker.patch.object(BaseValidator, 'get_metadata_file_content', return_value=meta_json)
    pack.pack_metadata.write_json(meta_json)
    with ChangeCWD(repo.path):
        base_validator = BaseValidator(ignored_errors={})
        base_validator.update_checked_flags_by_support_level(integration.yml.rel_path)

        assert base_validator.ignored_errors['integration.yml'] == PRESET_ERROR_TO_IGNORE['community']


class TestJsonOutput:
    def test_json_output(self, repo):
        """
        Given
        - Scenario 1:
        - A ui applicable error.
        - No pre existing json_outputs file.

        - Scenario 2:
        - A non ui applicable warning.
        - A pre existing json_outputs file.

        When
        - Running json_output method.

        Then
        - Scenario 1:
        - Ensure the json outputs file is created and it hold the json error in the `outputs` field.
        - Scenario 2:
        - Ensure the json outputs file is modified and holds the json warning in the `outputs` field.
        """
        pack = repo.create_pack('PackName')
        integration = pack.create_integration('MyInt')
        integration.create_default_integration()
        json_path = os.path.join(repo.path, 'valid_json.json')
        base = BaseValidator(json_file_path=json_path)
        ui_applicable_error_message, ui_applicable_error_code = Errors.wrong_display_name('param1', 'param2')
        non_ui_applicable_error_message, non_ui_applicable_error_code = Errors.wrong_subtype()
        expected_json_1 = [
            {
                'filePath': integration.yml.path,
                'fileType': 'yml',
                'entityType': 'integration',
                'errorType': 'Settings',
                'name': 'Sample',
                'severity': 'error',
                'errorCode': ui_applicable_error_code,
                'message': ui_applicable_error_message,
                'ui': True,
                'relatedField': '<parameter-name>.display'
            }
        ]

        expected_json_2 = [
            {
                'filePath': integration.yml.path,
                'fileType': 'yml',
                'entityType': 'integration',
                'errorType': 'Settings',
                'name': 'Sample',
                'severity': 'error',
                'errorCode': ui_applicable_error_code,
                'message': ui_applicable_error_message,
                'ui': True,
                'relatedField': '<parameter-name>.display',
                'linter': 'validate'
            },
            {
                'filePath': integration.yml.path,
                'fileType': 'yml',
                'entityType': 'integration',
                'errorType': 'Settings',
                'name': 'Sample',
                'severity': 'warning',
                'errorCode': non_ui_applicable_error_code,
                'message': non_ui_applicable_error_message,
                'ui': False,
                'relatedField': 'subtype',
                'linter': 'validate'
            }
        ]

        with ChangeCWD(repo.path):
            # create new file
            base.json_output(integration.yml.path, ui_applicable_error_code, ui_applicable_error_message, False)
            with open(base.json_file_path) as f:
                json_output = json.load(f)

            assert json_output.sort() == expected_json_1.sort()

            # update existing file
            base.json_output(integration.yml.path, non_ui_applicable_error_code, non_ui_applicable_error_message, True)
            with open(base.json_file_path) as f:
                json_output = json.load(f)

            assert json_output == expected_json_2

    def test_json_output_with_json_file(self, repo):
        """
        Given
        - A ui applicable error.
        - An existing and an empty json_outputs file.

        When
        - Running json_output method.

        Then
        - Ensure the json outputs file is created and it hold the json error in the `outputs` field.
        - Ensure it's not failing because the file is empty.
        """
        pack = repo.create_pack('PackName')
        integration = pack.create_integration('MyInt')
        integration.create_default_integration()
        json_path = os.path.join(repo.path, 'valid_json.json')
        open(json_path, "x")
        base = BaseValidator(json_file_path=json_path)
        ui_applicable_error_message, ui_applicable_error_code = Errors.wrong_display_name('param1', 'param2')
        expected_json_1 = [
            {
                'filePath': integration.yml.path,
                'fileType': 'yml',
                'entityType': 'integration',
                'errorType': 'Settings',
                'name': 'Sample',
                'severity': 'error',
                'errorCode': ui_applicable_error_code,
                'message': ui_applicable_error_message,
                'ui': True,
                'relatedField': '<parameter-name>.display',
                'linter': 'validate'
            }
        ]

        with ChangeCWD(repo.path):
            # create new file
            base.json_output(integration.yml.path, ui_applicable_error_code, ui_applicable_error_message, False)
            with open(base.json_file_path, 'r') as f:
                json_output = json.load(f)

            assert json_output.sort() == expected_json_1.sort()

    def test_json_output_with_unified_yml_image_error(self, repo):
        """
        Given
        - A ui applicable image error that occurred in a unified yml.
        - An existing and an empty json_outputs file.

        When
        - Running json_output method.

        Then
        - Ensure the json outputs file is created and it hold the json error in the `outputs` field.
        - Ensure the entityType is 'image'.
        """
        pack = repo.create_pack('PackName')
        integration = pack.create_integration('MyInt')
        integration.create_default_integration()
        json_path = os.path.join(repo.path, 'valid_json.json')
        open(json_path, "x")
        base = BaseValidator(json_file_path=json_path)
        ui_applicable_error_message, ui_applicable_error_code = Errors.image_too_large()
        expected_json_1 = [
            {
                'filePath': integration.yml.path,
                'fileType': 'yml',
                'entityType': 'image',
                'errorType': 'Settings',
                'name': 'Sample',
                'severity': 'error',
                'errorCode': ui_applicable_error_code,
                'message': ui_applicable_error_message,
                'ui': True,
                'relatedField': 'image',
                'validate': 'linter'
            }
        ]

        with ChangeCWD(repo.path):
            # create new file
            base.json_output(integration.yml.path, ui_applicable_error_code, ui_applicable_error_message, False)
            with open(base.json_file_path, 'r') as f:
                json_output = json.load(f)

            assert json_output.sort() == expected_json_1.sort()


# demisto_sdk/commands/common/tests/classifier_test.py

import pytest
from mock import patch

from demisto_sdk.commands.common.hook_validations.classifier import \
    ClassifierValidator
from demisto_sdk.commands.common.hook_validations.mapper import MapperValidator
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator


def mock_structure(file_path=None, current_file=None, old_file=None):
    with patch.object(StructureValidator, '__init__', lambda a, b: None):
        structure = StructureValidator(file_path)
        structure.is_valid = True
        structure.scheme_name = 'classifier'
        structure.file_path = file_path
        structure.current_file = current_file
        structure.old_file = old_file
        structure.prev_ver = 'master'
        structure.branch_name = ''
        structure.quite_bc = False
        return structure


class TestClassifierValidator:
    CLASSIFIER_WITH_VALID_INCIDENT_FIELD = {"mapping": {"0": {"internalMapping": {"Incident Field": "incident field"}}}}

    ID_SET_WITH_INCIDENT_FIELD = {"IncidentFields": [{"name": {"name": "Incident Field"}}],
                                  "IndicatorFields": [{"name": {"name": "Incident Field"}}]}

    ID_SET_WITHOUT_INCIDENT_FIELD = {"IncidentFields": [{"name": {"name": "name"}}],
                                     "IndicatorFields": [{"name": {"name": "name"}}]}

    IS_INCIDENT_FIELD_EXIST = [
        (CLASSIFIER_WITH_VALID_INCIDENT_FIELD, ID_SET_WITH_INCIDENT_FIELD, True, True),
        (CLASSIFIER_WITH_VALID_INCIDENT_FIELD, ID_SET_WITHOUT_INCIDENT_FIELD, True, False)
    ]

    @pytest.mark.parametrize("classifier_json, id_set_json, is_circle, expected_result", IS_INCIDENT_FIELD_EXIST)
    def test_is_incident_field_exist(self, repo, classifier_json, id_set_json, is_circle, expected_result):
        """
        Given
        - A mapper with incident fields
        - An id_set file.
        When
        - validating mapper
        Then
        - validating that incident fields exist in id_set.
        """
        repo.id_set.write_json(id_set_json)
        structure = mock_structure("", classifier_json)
        validator = ClassifierValidator(structure)
        assert validator.is_incident_field_exist(id_set_json, is_circle) == expected_result

    OLD_MAPPER = {"mapping": {"1": {"internalMapping": {"field1": {"data1"}, "field2": {"data2"}}},
                              "2": {"internalMapping": {"field1": {"data1"}, "field2": {"data2"}}}}}
    NEW_MAPPER_WITH_DELETED_TYPES = {"mapping": {"1": {"internalMapping": {"field1": {"data1"}, "field2": {"data2"}}}}}
    NEW_MAPPER_WITH_DELETED_FIELDS = {"mapping": {"1": {"internalMapping": {"field1": {"data1"}, "field2": {"data2"}}},
                                                  "2": {"internalMapping": {"field1": {"data1"}}}}}
    NEW_VALID_MAPPER = {"mapping": {"1": {"internalMapping": {"field1": {"data1"}, "field2": {"data2"}}},
                                    "2": {"internalMapping": {"field1": {"new_data1"}, "field2": {"data2"}}}}}

    IS_CHANGED_INCIDENTS_FIELDS_INPUT = [
        (OLD_MAPPER, NEW_MAPPER_WITH_DELETED_FIELDS, True),
        (OLD_MAPPER, NEW_MAPPER_WITH_DELETED_TYPES, True),
        (OLD_MAPPER, NEW_VALID_MAPPER, False)
    ]

    @pytest.mark.parametrize("old_file, current_file, answer", IS_CHANGED_INCIDENTS_FIELDS_INPUT)
    def test_is_changed_removed_yml_fields(self, old_file, current_file, answer):
        """
        Given
        - A mapper with incident fields
        When
        - running is_changed_incidents_fields
        Then
        - checks that incident fields or incidents types were not removed.
        """
        structure = mock_structure("", current_file, old_file)
        validator = MapperValidator(structure)

        assert validator.is_field_mapping_removed() == answer
        assert validator.is_valid != answer
        structure.quite_bc = True
        assert validator.is_field_mapping_removed() is False


# demisto_sdk/commands/common/tests/conf_test.py

import pytest

from demisto_sdk.commands.common.constants import FileType
from demisto_sdk.commands.common.hook_validations.conf_json import \
    ConfJsonValidator

WITH_DESCRIPTION = {
    "test": "description"
}
MISSING_DESCRIPTION = {
    "test": "",
    "test2": "description"
}
TESTS_SECTION = [
    {
        "playbookID": "siri"
    },
    {
        "playbookID": "alexa"
    }
]


def test_conf_json_description(mocker):
    mocker.patch('demisto_sdk.commands.common.hook_validations.conf_json.ConfJsonValidator.load_conf_file',
                 return_value=None)
    validator = ConfJsonValidator()

    assert validator.is_valid_description_in_conf_dict(checked_dict=WITH_DESCRIPTION), \
        "The conf validator couldn't find the description in the dictionary"


def test_conf_json_description_not_given(mocker):
    mocker.patch('demisto_sdk.commands.common.hook_validations.conf_json.ConfJsonValidator.load_conf_file',
                 return_value=None)
    validator = ConfJsonValidator()

    assert validator.is_valid_description_in_conf_dict(checked_dict=MISSING_DESCRIPTION) is False, \
        "The conf validator couldn't find the missing description in the dictionary"


def test_the_missing_existence_of_added_test_in_conf_json(mocker):
    mocker.patch('demisto_sdk.commands.common.hook_validations.conf_json.ConfJsonValidator.load_conf_file',
                 return_value={"tests": TESTS_SECTION})
    validator = ConfJsonValidator()

    assert validator.is_test_in_conf_json(file_id="cortana") is False, \
        "The conf validator didn't catch that the test is missing"


def test_the_existence_of_added_test_in_conf_json(mocker):
    mocker.patch('demisto_sdk.commands.common.hook_validations.conf_json.ConfJsonValidator.load_conf_file',
                 return_value={"tests": TESTS_SECTION})
    validator = ConfJsonValidator()

    assert validator.is_test_in_conf_json(file_id="alexa"), \
        "The conf validator didn't catch the test although it exists in the test list"


def test_is_valid_conf_json_sanity_check(mocker):
    mocker.patch('demisto_sdk.commands.common.hook_validations.conf_json.ConfJsonValidator.load_conf_file',
                 return_value={"skipped_tests": WITH_DESCRIPTION, "skipped_integrations": WITH_DESCRIPTION,
                               "unmockable_integrations": WITH_DESCRIPTION})
    validator = ConfJsonValidator()

    assert validator.is_valid_conf_json(), \
        "The conf validator didn't find the description sections although they exist"


def test_is_valid_conf_json_negative_sanity_check(mocker):
    mocker.patch('demisto_sdk.commands.common.hook_validations.conf_json.ConfJsonValidator.load_conf_file',
                 return_value={"skipped_tests": WITH_DESCRIPTION, "skipped_integrations": MISSING_DESCRIPTION,
                               "unmockable_integrations": MISSING_DESCRIPTION})
    validator = ConfJsonValidator()

    assert validator.is_valid_conf_json() is False, \
        "The conf validator didn't find the missing description sections although they don't exist"


INTEGRATION_IS_SKIPPED_TEST_INPUTS = [
    ({"tests": [{"integrations": "SomeIntegration", "playbookID": "SomeTestPlaybook"}],
      "skipped_tests": {"SomeTestPlaybook": "Some Issue"}},
     False),
    ({"tests": [{"integrations": "SomeIntegration", "playbookID": "SomeTestPlaybook"}],
      "skipped_tests": {"SomeOtherTestPlaybook": "Some Issue"}},
     True),
    ({"tests": [{"integrations": "SomeIntegration", "playbookID": "SomeTestPlaybook"},
                {"integrations": ["SomeIntegration", "SomeOtherIntegration"],
                 "playbookID": "SomeCombinedTestPlaybook"}],
      "skipped_tests": {"SomeTestPlaybook": "Some Issue"}},
     True)
]


@pytest.mark.parametrize("conf_dict, answer", INTEGRATION_IS_SKIPPED_TEST_INPUTS)
def test_integration_has_unskipped_test_playbook(mocker, conf_dict, answer):
    """
    Given:
        - An integration.
        - conf file with configurations for the integration.

    When: running is_valid_file_in_conf_json specifically on integration.

    Then: Validate the integration has at least one unskipped test playbook.
    """
    mocker.patch.object(ConfJsonValidator, 'load_conf_file', return_value=conf_dict)

    validator = ConfJsonValidator()
    current = {"commonfields": {"id": "SomeIntegration"}, "tests": ["SomeTestPlaybook"]}

    assert validator.is_valid_file_in_conf_json(current_file=current,
                                                file_type=FileType.INTEGRATION,
                                                file_path="SomeFilePath") is answer


SCRIPT_IS_SKIPPED_TEST_INPUTS = [
    (["SomeTestPlaybook"],
     {"skipped_tests": {"SomeTestPlaybook": "Some Issue"}},
     False),
    (["SomeTestPlaybook"],
     {"skipped_tests": {"SomeOtherTestPlaybook": "Some Issue"}},
     True),
    (["SomeTestPlaybook", "SomeSecondTestPlaybook"],
     {"skipped_tests": {"SomeTestPlaybook": "Some Issue"}},
     True)
]


@pytest.mark.parametrize("test_playbooks, conf_dict, answer", SCRIPT_IS_SKIPPED_TEST_INPUTS)
def test_script_has_unskipped_test_playbook(mocker, test_playbooks, conf_dict, answer):
    """
    Given:
        - A Script.
        - conf file with skipped tests.

    When: running is_valid_file_in_conf_json specifically on the script.

    Then: Validate the script has at least one unskipped test playbook.
    """
    mocker.patch.object(ConfJsonValidator, 'load_conf_file', return_value=conf_dict)

    validator = ConfJsonValidator()

    current = {"commonfields": {"id": "SomeScript"}, "tests": test_playbooks}
    assert validator.is_valid_file_in_conf_json(current_file=current,
                                                file_type=FileType.SCRIPT,
                                                file_path="SomeFilePath") is answer


def test_non_testable_entity_is_vaild_in_conf(mocker):
    """
    Given:
        - A content entity that cant have test playbooks, specifically a test playbook
        - Some conf file.

    When: running is_valid_file_in_conf_json specifically on the content entity.

    Then: Validate the content entity is valid in conf file.
    """
    mocker.patch.object(ConfJsonValidator, 'load_conf_file',
                        return_value={"skipped_tests": {"SomeTestPlaybook": "Some Issue"}})

    validator = ConfJsonValidator()

    current = {"id": "TheTestPlaybook"}
    assert validator.is_valid_file_in_conf_json(current_file=current,
                                                file_type=FileType.TEST_PLAYBOOK,
                                                file_path="SomeFilePath")


NOT_SKIPPED_DYNAMIC_SECTION_INPUTS = [
    ({"skipped_tests": {"SomeTestPlaybook": "Some Issue"}}, {'tests': ['TestPlaybook'], 'tags': ['dynamic-section']}),
    ({"skipped_tests": {"TestPlaybook": "Some Issue"}}, {'tests': ['TestPlaybook'], 'tags': ['dynamic-section']}),
    ({"skipped_tests": {"SomeTestPlaybook": "Some Issue"}}, {'tags': ['dynamic-section']})]


@pytest.mark.parametrize('conf_data, yml_data', NOT_SKIPPED_DYNAMIC_SECTION_INPUTS)
def test_not_skipped_test_playbook_for_dynamic_section(mocker, conf_data, yml_data):
    """
    Given:
    - Script data.
    - Conf JSON file.

    When:
    - running is_valid_file_in_conf_json specifically on the content entity with dynamic section tag.
    Cases:
        Case 1) Has not skipped TPB.
        Case 2) Has skipped TPB.
        Case 3) Does not have TPB.

    Then:
    - Ensure True is returned.
    """
    mocker.patch.object(ConfJsonValidator, 'load_conf_file',
                        return_value=conf_data)

    validator = ConfJsonValidator()

    assert validator.is_valid_file_in_conf_json(current_file=yml_data,
                                                file_type=FileType.SCRIPT,
                                                file_path="SomeFilePath")


@pytest.mark.parametrize('has_tests', (True, False))
def test_has_unittests(mocker, integration, has_tests):
    from pathlib import Path

    mocker.patch.object(ConfJsonValidator, 'load_conf_file', return_value={})
    validator = ConfJsonValidator()
    test_file = None
    try:
        if has_tests:
            test_file: Path = Path(integration.path) / (integration.name + '_test.py')
            test_file.touch()
        res = validator.has_unittest(integration.yml.path)
        assert res == has_tests
    finally:
        if test_file:
            test_file.unlink()  # Remove the file


def test_get_test_path(mocker, integration):
    mocker.patch.object(ConfJsonValidator, 'load_conf_file', return_value={})
    validator = ConfJsonValidator()
    res = validator.get_test_path(integration.yml.path)
    assert res.parts[-1] == integration.name + '_test.py'


# demisto_sdk/commands/common/tests/content_entity_validator_test.py

import pytest

from demisto_sdk.commands.common.constants import EXCLUDED_DISPLAY_NAME_WORDS
from demisto_sdk.commands.common.hook_validations.content_entity_validator import \
    ContentEntityValidator
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator
from demisto_sdk.commands.common.tools import (get_not_registered_tests,
                                               is_test_config_match)
from demisto_sdk.tests.constants_test import (
    INVALID_INTEGRATION_WITH_NO_TEST_PLAYBOOK, VALID_INTEGRATION_TEST_PATH,
    VALID_TEST_PLAYBOOK_PATH)

HAS_TESTS_KEY_UNPUTS = [
    (VALID_INTEGRATION_TEST_PATH, 'integration', True),
    (INVALID_INTEGRATION_WITH_NO_TEST_PLAYBOOK, 'integration', False)
]


@pytest.mark.parametrize('file_path, schema, expected', HAS_TESTS_KEY_UNPUTS)
def test_yml_has_test_key(file_path, schema, expected):
    # type: (str, str, bool) -> None
    """
        Given
        - A yml file test playbook list and the yml file type

        When
        - Checking if file has test playbook exists

        Then
        -  Ensure the method 'yml_has_test_key' return answer accordingly
    """
    structure_validator = StructureValidator(file_path, predefined_scheme=schema)
    validator = ContentEntityValidator(structure_validator)
    tests = structure_validator.current_file.get('tests')
    assert validator.yml_has_test_key(tests, schema) == expected


FIND_TEST_MATCH_INPUT = [
    (
        {'integrations': 'integration1', 'playbookID': 'playbook1'},
        'integration1',
        'playbook1',
        'integration',
        True
    ),
    (
        {'integrations': 'integration1', 'playbookID': 'playbook1'},
        'integration2',
        'playbook1',
        'integration',
        False
    ),
    (
        {'integrations': ['integration1', 'integration2'], 'playbookID': 'playbook1'},
        'integration1',
        'playbook1',
        'integration',
        True
    ),
    (
        {'integrations': ['integration1', 'integration2'], 'playbookID': 'playbook1'},
        'integration3',
        'playbook1',
        'integration',
        False
    ),
    (
        {'playbookID': 'playbook1'},
        '',
        'playbook1',
        'playbook',
        True
    ),

]


@pytest.mark.parametrize('test_config, integration_id, test_playbook_id, file_type, expected', FIND_TEST_MATCH_INPUT)
def test_find_test_match(test_config, integration_id, test_playbook_id, expected, file_type):
    # type: (dict, str, str, bool, str) -> None
    """
        Given
        - A test configuration from 'conf.json' file. test-playbook id and a content item id

        When
        - checking if the test configuration matches the content item and the test-playbook

        Then
        -  Ensure the method 'find_test_match' return answer accordingly
    """
    assert is_test_config_match(test_config, test_playbook_id, integration_id) == expected


NOT_REGISTERED_TESTS_INPUT = [
    (
        VALID_INTEGRATION_TEST_PATH,
        'integration',
        [{'integrations': 'PagerDuty v2', 'playbookID': 'PagerDuty Test'}],
        'PagerDuty v2',
        []
    ),
    (
        VALID_INTEGRATION_TEST_PATH,
        'integration',
        [{'integrations': 'test', 'playbookID': 'PagerDuty Test'}],
        'PagerDuty v2',
        ['PagerDuty Test']
    ),
    (
        VALID_INTEGRATION_TEST_PATH,
        'integration',
        [{'integrations': 'PagerDuty v2', 'playbookID': 'Playbook'}],
        'PagerDuty v3',
        ['PagerDuty Test']
    ),
    (
        VALID_TEST_PLAYBOOK_PATH,
        'playbook',
        [{'integrations': 'Account Enrichment', 'playbookID': 'PagerDuty Test'}],
        'Account Enrichment',
        []
    ),
    (
        VALID_TEST_PLAYBOOK_PATH,
        'playbook',
        [{'integrations': 'Account Enrichment', 'playbookID': 'Playbook'}],
        'Account Enrichment',
        ['PagerDuty Test']
    ),
]


@pytest.mark.parametrize('file_path, schema, conf_json_data, content_item_id, expected', NOT_REGISTERED_TESTS_INPUT)
def test_get_not_registered_tests(file_path, schema, conf_json_data, content_item_id, expected):
    # type: (str, str, list, str, list) -> None
    """
        Given
        - A content item with test playbooks configured on it

        When
        - Checking if the test playbooks are configured in 'conf.json' file

        Then
        -  Ensure the method 'get_not_registered_tests' return all test playbooks that are not configured
    """
    structure_validator = StructureValidator(file_path, predefined_scheme=schema)
    tests = structure_validator.current_file.get('tests')
    assert get_not_registered_tests(conf_json_data, content_item_id, schema, tests) == expected


def test_entity_valid_name_valid(repo, mocker):
    """
    Given:
    - Entity name that does not contain excluded words.

    When:
    - Checking whether entity name contains excluded word.

    Then:
    - Ensure true is returned.
    """
    pack = repo.create_pack('TestPack')
    integration = pack.create_integration(name='BitcoinAbuse')
    integration.create_default_integration(name='BitcoinAbuse')
    integration.yml.update({'display': 'BitcoinAbuse'})
    integration_structure_validator = StructureValidator(integration.yml.path)
    integration_content_entity_validator = ContentEntityValidator(integration_structure_validator)
    assert integration_content_entity_validator.name_does_not_contain_excluded_word()


def test_entity_valid_name_invalid(repo, mocker):
    """
    Given:
    - Entity name with excluded word.

    When:
    - Checking whether entity name contains excluded word.

    Then:
    - Ensure false is returned.
    """
    pack = repo.create_pack('TestPack')
    script = pack.create_script(name='QRadar')
    script.create_default_script(name='QRadar')
    excluded_word = EXCLUDED_DISPLAY_NAME_WORDS[0]
    script.yml.update({'name': f'QRadar ({excluded_word})'})
    script_structure_validator = StructureValidator(script.yml.path)
    script_content_entity_validator = ContentEntityValidator(script_structure_validator)
    mocker.patch.object(script_content_entity_validator, 'handle_error')
    assert not script_content_entity_validator.name_does_not_contain_excluded_word()


# demisto_sdk/commands/common/tests/dashboard_test.py

from typing import Optional

import pytest
from mock import patch

from demisto_sdk.commands.common.hook_validations.dashboard import \
    DashboardValidator
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator


def mock_structure(file_path=None, current_file=None, old_file=None):
    # type: (Optional[str], Optional[dict], Optional[dict]) -> StructureValidator
    with patch.object(StructureValidator, '__init__', lambda a, b: None):
        structure = StructureValidator(file_path)
        structure.is_valid = True
        structure.scheme_name = 'dashboard'
        structure.file_path = file_path
        structure.current_file = current_file
        structure.old_file = old_file
        structure.prev_ver = 'master'
        structure.branch_name = ''
        return structure


data_is_valid_version = [
    (-1, True),
    (0, False),
    (1, False),
]


@pytest.mark.parametrize('version, is_valid', data_is_valid_version)
def test_is_valid_version(version, is_valid):
    structure = StructureValidator("")
    structure.current_file = {"version": version}
    validator = DashboardValidator(structure)
    assert validator.is_valid_version() == is_valid, f'is_valid_version({version}) returns {not is_valid}.'


data_is_id_equal_name = [
    ('aa', 'aa', True),
    ('aa', 'ab', False),
    ('my-home-dashboard', 'My Dashboard', False)
]


@pytest.mark.parametrize('id_, name, is_valid', data_is_id_equal_name)
def test_is_id_equal_name(id_, name, is_valid):
    structure = StructureValidator("")
    structure.current_file = {"id": id_, "name": name}
    validator = DashboardValidator(structure)
    assert validator.is_id_equals_name() == is_valid, f'is_id_equal_name returns {not is_valid}.'


data_contains_forbidden_fields = [
    ({"system": False}, False),
    ({"isCommon": False}, False),
    ({"shared": False}, False),
    ({"owner": "Admin"}, False),
    ({"layout": [{"widget": {"owner": "Admin"}}]}, False),
    ({"layout": [{"widget": {"shared": "False"}}]}, False),
    ({"layout": [{"widget": {"shared4": "False"}}]}, True)
]


@pytest.mark.parametrize('current_file, is_valid', data_contains_forbidden_fields)
def test_contains_forbidden_fields(current_file, is_valid):
    structure = mock_structure("", current_file)
    validator = DashboardValidator(structure)
    assert validator.contains_forbidden_fields() == is_valid, f'is_excluding_fields returns {not is_valid}.'


data_is_including_fields = [
    ({"fromDate": "1", "toDate": "2", "fromDateLicense": "3"}, True),
    ({"fromDate": "1", "toDate": "2"}, False),
    ({"fromDate": "1", "toDate": "2", "fromDateLicense": "3",
      "layout": [{"widget": {"fromDate": "1", "toDate": "2", "fromDateLicense": "3"}}]}, True),
    ({"fromDate": "1", "toDate": "2", "fromDateLicense": "3",
      "layout": [{"widget": {"name": "bla", "fromDate": "1", "fromDateLicense": "3"}}]}, False)
]


@pytest.mark.parametrize('current_file, is_valid', data_is_including_fields)
def test_is_including_fields(current_file, is_valid):
    structure = mock_structure("", current_file)
    validator = DashboardValidator(structure)
    assert validator.is_including_fields() == is_valid, f'is_including_fields returns {not is_valid}.'


# demisto_sdk/commands/common/tests/dependencies_test.py

import inspect
import os
import random
from typing import List

import pytest

from demisto_sdk.commands.create_id_set.create_id_set import IDSetCreator
from demisto_sdk.commands.find_dependencies.find_dependencies import \
    PackDependencies
from TestSuite.integration import Integration
from TestSuite.json_based import JSONBased
from TestSuite.playbook import Playbook
from TestSuite.script import Script
from TestSuite.test_tools import ChangeCWD
from TestSuite.utils import IsEqualFunctions


def update_id_set(repo):
    with ChangeCWD(repo.path):
        id_set_creator = IDSetCreator(repo.id_set.path, print_logs=False)
        id_set_creator.create_id_set()


class IntegrationDependencies:
    @staticmethod
    def make_integration_depend_on_classifier(integration: Integration, classifier: JSONBased):
        classifier_id = classifier.read_json_as_dict().get('id')
        integration.yml.update({'defaultclassifier': classifier_id})

    @staticmethod
    def make_integration_depend_on_mapper_in(integration: Integration, mapper: JSONBased):
        mapper_id = mapper.read_json_as_dict().get('id')
        integration.yml.update({'defaultmapperin': mapper_id})

    @staticmethod
    def make_integration_depend_on_mapper_out(integration: Integration, mapper: JSONBased):
        mapper_id = mapper.read_json_as_dict().get('id')
        integration.yml.update({'defaultmapperout': mapper_id})

    @staticmethod
    def make_integration_depend_on_incident_type(integration: Integration, incident_type: JSONBased):
        incident_type_id = incident_type.read_json_as_dict().get('id')
        integration.yml.update({'defaultIncidentType': incident_type_id})

    @staticmethod
    def make_integration_feed(integration: Integration):
        yml_dict = integration.yml.read_dict()
        if 'script' not in yml_dict:
            yml_dict['script'] = {}
        yml_dict['script']['feed'] = True
        integration.yml.write_dict(yml_dict)


class WidgetDependencies:
    @staticmethod
    def make_widget_depend_on_script(widget: JSONBased, script: Script):
        script_id = script.yml.read_dict().get('commonfields').get('id')
        widget.update({'dataType': 'scripts', 'query': script_id})


# Playbook class helper function
def get_new_task_number(playbook: Playbook):
    try:
        playbook_tasks = list(playbook.yml.read_dict().get('tasks').keys())

        if playbook_tasks:
            return max(int(task_num) for task_num in playbook_tasks) + 1

        playbook.yml.update({'starttaskid': '0'})
        return 0

    except AttributeError:
        playbook.yml.update({'starttaskid': '0'})
        return 0


def update_tasks_in_playbook(playbook: Playbook, task_num: int, task: dict):
    tasks = playbook.yml.read_dict().get('tasks', {})

    # Connects the new task added to the last task in the playbook, so the whole playbook will be connected
    if task_num > 0:
        try:
            tasks.get(str(task_num - 1)).get('nexttasks').get('#none#').append(str(task_num))
        except AttributeError:
            tasks.get(str(task_num - 1)).update({
                'nexttasks': {
                    '#none#': [str(task_num)]
                }
            })

    tasks.update(task)

    playbook.yml.update({'tasks': tasks})


class PlaybookDependencies:
    @staticmethod
    def make_playbook_depend_on_script_skippable(playbook: Playbook, script: Script):
        script_name = script.yml.read_dict().get('name')
        task_num = get_new_task_number(playbook)

        task = {
            str(task_num): {
                'id': str(task_num),
                'taskid': 'cfcc9ea0-eb0e-4efa-80ad-606909350e2a',
                'type': 'regular',
                'task': {
                    'id': 'cfcc9ea0-eb0e-4efa-80ad-606909350e2a',
                    'version': -1,
                    'name': script_name,
                    'description': 'Description',
                    'scriptName': script_name,
                    'type': 'regular',
                    'iscommand': False,
                    'brand': '',
                },
                'scriptarguments': {
                    'entryID': {
                        'complex': {
                            'root': 'InfoFile',
                            'accessor': 'EntryID'
                        }
                    },
                    'fileName': {},
                    'lastZipFileInWarroom': {},
                    'password': {}
                },
                'separatecontext': False,
                'view': '''| -
                {
                    "position": {
                        "x": 450,
                        "y": 350
                    }
                }''',
                'note': False,
                'timertriggers': [],
                'ignoreworker': False,
                'skipunavailable': True
            }
        }

        update_tasks_in_playbook(playbook, task_num, task)

    @staticmethod
    def make_playbook_depend_on_script_not_skippable(playbook: Playbook, script: Script):
        script_name = script.yml.read_dict().get('name')
        task_num = get_new_task_number(playbook)

        task = {
            str(task_num): {
                'id': str(task_num),
                'taskid': 'cfcc9ea0-eb0e-4efa-80ad-606909350e2a',
                'type': 'regular',
                'task': {
                    'id': 'cfcc9ea0-eb0e-4efa-80ad-606909350e2a',
                    'version': -1,
                    'name': script_name,
                    'description': 'Description',
                    'scriptName': script_name,
                    'type': 'regular',
                    'iscommand': False,
                    'brand': '',
                },
                'scriptarguments': {
                    'entryID': {
                        'complex': {
                            'root': 'InfoFile',
                            'accessor': 'EntryID'
                        }
                    },
                    'fileName': {},
                    'lastZipFileInWarroom': {},
                    'password': {}
                },
                'separatecontext': False,
                'view': '''| -
                {
                    "position": {
                        "x": 450,
                        "y": 350
                    }
                }''',
                'note': False,
                'timertriggers': [],
                'ignoreworker': False,
            }
        }

        update_tasks_in_playbook(playbook, task_num, task)

    @staticmethod
    def make_playbook_depend_on_playbook_skippable(playbook: Playbook, playbook__1: Playbook):
        other_playbook_name = playbook__1.yml.read_dict().get('name')
        task_num = get_new_task_number(playbook)

        task = {
            str(task_num): {
                'id': str(task_num),
                'taskid': 'fa3391b8-020e-4f53-8576-7445bf741452',
                'type': 'playbook',
                'task': {
                    'id': 'fa3391b8-020e-4f53-8576-7445bf741452',
                    'version': -1,
                    'name': other_playbook_name,
                    'playbookName': other_playbook_name,
                    'type': 'playbook',
                    'iscommand': False,
                    'brand': '',
                    'description': '',
                },
                'separatecontext': True,
                'loop': {
                    'iscommand': False,
                    'exitCondition': '',
                    'wait': 1,
                    'max': 0
                },
                'view': '''| -
                            {
                                "position": {
                                    "x": -800,
                                    "y": 980
                                }
                            }''',
                'note': False,
                'timertriggers': [],
                'ignoreworker': False,
                'skipunavailable': True,
                'quietmode': 0
            }
        }

        update_tasks_in_playbook(playbook, task_num, task)

    @staticmethod
    def make_playbook_depend_on_playbook_not_skippable(playbook: Playbook, playbook__1: Playbook):
        other_playbook_name = playbook__1.yml.read_dict().get('name')
        task_num = get_new_task_number(playbook)

        task = {
            str(task_num): {
                'id': str(task_num),
                'taskid': 'fa3391b8-020e-4f53-8576-7445bf741452',
                'type': 'regular',
                'task': {
                    'id': 'fa3391b8-020e-4f53-8576-7445bf741452',
                    'version': -1,
                    'name': other_playbook_name,
                    'playbookName': other_playbook_name,
                    'type': 'regular',
                    'iscommand': False,
                    'brand': '',
                    'description': '',
                },
                'separatecontext': True,
                'loop': {
                    'iscommand': False,
                    'exitCondition': '',
                    'wait': 1,
                    'max': 0
                },
                'view': '''| -
                            {
                                "position": {
                                    "x": -800,
                                    "y": 980
                                }
                            }''',
                'note': False,
                'timertriggers': [],
                'ignoreworker': False,
                'quietmode': 0
            }
        }

        update_tasks_in_playbook(playbook, task_num, task)

    @staticmethod
    def make_playbook_depend_on_integration_skippable(playbook: Playbook, integration: Integration):
        integration_name = integration.yml.read_dict().get('name')
        task_num = get_new_task_number(playbook)

        task = {
            str(task_num): {
                'id': str(task_num),
                'taskid': 'fa3391b8-020e-4f53-8576-7445bf741452',
                'type': 'regular',
                'task': {
                    'id': 'fa3391b8-020e-4f53-8576-7445bf741452',
                    'version': -1,
                    'name': integration_name,
                    'script': f'{integration_name}|||command_{integration_name}',
                    'type': 'regular',
                    'iscommand': True,
                    'brand': integration_name,
                    'description': ''
                },
                'scriptarguments': {
                    'env_bitness': {},
                    'env_type': {},
                    'env_version': {},
                    'file': {},
                    'obj_type': {
                        'simple': 'download'
                    },
                    'obj_url': {
                        'complex': {
                            'root': 'inputs.URL',
                            'accessor': 'Data'
                        }
                    },
                    'opt_kernel_heavyevasion': {},
                    'opt_network_connect': {},
                    'opt_privacy_type': {},
                },
                'separatecontext': True,
                'view': '''| -
                            {
                                "position": {
                                    "x": -800,
                                    "y": 980
                                }
                            }''',
                'note': False,
                'timertriggers': [],
                'ignoreworker': False,
                'skipunavailable': True
            }
        }

        update_tasks_in_playbook(playbook, task_num, task)

    @staticmethod
    def make_playbook_depend_on_integration_not_skippable(playbook: Playbook, integration: Integration):
        integration_name = integration.yml.read_dict().get('name')
        task_num = get_new_task_number(playbook)

        task = {
            str(task_num): {
                'id': str(task_num),
                'taskid': 'fa3391b8-020e-4f53-8576-7445bf741452',
                'type': 'regular',
                'task': {
                    'id': 'fa3391b8-020e-4f53-8576-7445bf741452',
                    'version': -1,
                    'name': integration_name,
                    'script': f'{integration_name}|||command_{integration_name}',
                    'type': 'regular',
                    'iscommand': True,
                    'brand': integration_name,
                    'description': ''
                },
                'scriptarguments': {
                    'env_bitness': {},
                    'env_type': {},
                    'env_version': {},
                    'file': {},
                    'obj_type': {
                        'simple': 'download'
                    },
                    'obj_url': {
                        'complex': {
                            'root': 'inputs.URL',
                            'accessor': 'Data'
                        }
                    },
                    'opt_kernel_heavyevasion': {},
                    'opt_network_connect': {},
                    'opt_privacy_type': {},
                },
                'separatecontext': True,
                'view': '''| -
                                {
                                    "position": {
                                        "x": -800,
                                        "y": 980
                                    }
                                }''',
                'note': False,
                'timertriggers': [],
                'ignoreworker': False,
            }
        }

        update_tasks_in_playbook(playbook, task_num, task)

    @staticmethod
    def make_playbook_depend_on_incident_field(playbook: Playbook, incident_field: JSONBased):
        incident_field_name = incident_field.read_json_as_dict().get('name')

        mapping = {
            'fieldMapping': [
                {
                    'incidentfield': incident_field_name,
                    'output': {
                        'complex': {
                            'root': 'root',
                            'filters': {
                                'operator': 'lessThan',
                                'left': {
                                    'value': {
                                        'simple': 'context.path',
                                        'iscontext': True
                                    }
                                },
                                'right': {
                                    'value': {
                                        'simple': "49151",
                                    },
                                    'accessor': 'DestPort'
                                }
                            }
                        }
                    }
                }
            ]
        }

        task_num = get_new_task_number(playbook)
        task_num_to_associate = random.choice(range(task_num - 1))

        task = playbook.yml.read_dict().get('tasks').get(str(task_num_to_associate))
        task.update(mapping)

        task.update({'id': str(task_num)})
        new_task = {
            str(task_num): task
        }

        update_tasks_in_playbook(playbook, task_num, new_task)

    @staticmethod
    def make_playbook_depend_on_incident_field_builtin_command(playbook: Playbook, incident_field: JSONBased):
        incident_field_name = incident_field.read_json_as_dict().get('name')
        task_num = get_new_task_number(playbook)

        task = {
            str(task_num): {
                'id': str(task_num),
                'taskid': 'fa3391b8-020e-4f53-8576-7445bf741452',
                'type': 'regular',
                'task': {
                    'id': 'fa3391b8-020e-4f53-8576-7445bf741452',
                    'version': -1,
                    'name': 'Set incident field',
                    'script': 'Builtin|||setIncident',
                    'type': 'regular',
                    'iscommand': True,
                    'brand': 'Builtin',
                    'description': ''
                },
                'scriptarguments': {
                    incident_field_name: 'value'
                },
                'separatecontext': False,
                'view': '''| -
                            {
                                "position": {
                                    "x": -800,
                                    "y": 980
                                }
                            }''',
                'note': False,
                'timertriggers': [],
                'ignoreworker': False,
            }
        }

        update_tasks_in_playbook(playbook, task_num, task)

    @staticmethod
    def make_playbook_depend_on_incident_field_input_simple(playbook: Playbook, incident_field: JSONBased):
        incident_field_name = incident_field.read_json_as_dict().get('name')

        new_input = {
            'key': 'input',
            'value': {
                'simple': '${incident.' + incident_field_name + '}'
            },
            'required': False,
            'description': 'description',
            'playbookInputQuery': None
        }

        playbook_content = playbook.yml.read_dict()

        if 'inputs' in playbook_content:
            playbook_content.get('inputs').append(new_input)
        else:
            playbook_content.update({'inputs': [new_input]})

        playbook.yml.write_dict(playbook_content)

    @staticmethod
    def make_playbook_depend_on_incident_field_input_complex(playbook: Playbook, incident_field: JSONBased):
        incident_field_name = incident_field.read_json_as_dict().get('name')

        new_input = {
            'key': 'input',
            'value': {
                'complex': {
                    'root': 'incident',
                    'accessor': incident_field_name
                }
            },
            'required': False,
            'description': 'description',
            'playbookInputQuery': None
        }

        playbook_content = playbook.yml.read_dict()

        if 'inputs' in playbook_content:
            playbook_content.get('inputs').append(new_input)
        else:
            playbook_content.update({'inputs': [new_input]})

        playbook.yml.write_dict(playbook_content)

    @staticmethod
    def make_playbook_depend_on_indicator_field_builtin_command(playbook: Playbook, indicator_field: JSONBased):
        indicator_field_name = indicator_field.read_json_as_dict().get('name')
        task_num = get_new_task_number(playbook)

        task = {
            str(task_num): {
                'id': str(task_num),
                'taskid': 'fa3391b8-020e-4f53-8576-7445bf741452',
                'type': 'regular',
                'task': {
                    'id': 'fa3391b8-020e-4f53-8576-7445bf741452',
                    'version': -1,
                    'name': 'Set incident field',
                    'script': 'Builtin|||setIndicator',
                    'type': 'regular',
                    'iscommand': True,
                    'brand': 'Builtin',
                    'description': ''
                },
                'scriptarguments': {
                    indicator_field_name: 'value'
                },
                'separatecontext': False,
                'view': '''| -
                                {
                                    "position": {
                                        "x": -800,
                                        "y": 980
                                    }
                                }''',
                'note': False,
                'timertriggers': [],
                'ignoreworker': False,
            }
        }

        update_tasks_in_playbook(playbook, task_num, task)


class ScriptDependencies:
    @staticmethod
    def make_script_depend_on_integration(script: Script, integration: Integration):
        integration_name = integration.yml.read_dict().get('name')
        script_content = script.yml.read_dict()

        if script_content.get('dependson'):
            script_content['dependson']['must'].append(f'{integration_name}|||command_{integration_name}')
            script.yml.write_dict(script_content)
        else:
            script.yml.update(
                {
                    'dependson': {
                        'must': [
                            f'{integration_name}|||command_{integration_name}'
                        ]
                    }
                }
            )

    @staticmethod
    def make_script_depend_on_script(script: Script, script__1: Script):
        other_script_name = script__1.yml.read_dict().get('name')
        script_content = script.yml.read_dict()

        if script_content.get('dependson'):
            script_content['dependson']['must'].append(other_script_name)
            script.yml.write_dict(script_content)
        else:
            script.yml.update(
                {
                    'dependson': {
                        'must': [
                            other_script_name
                        ]
                    }
                }
            )


class ClassifierDependencies:
    @staticmethod
    def make_classifier_depend_on_incident_type_default(classifier: JSONBased, incident_type: JSONBased):
        incident_type_id = incident_type.read_json_as_dict().get('id')
        classifier.update({'defaultIncidentType': incident_type_id})

    @staticmethod
    def make_classifier_depend_on_incident_type_key_type_map(classifier: JSONBased, incident_type: JSONBased):
        incident_type_id = incident_type.read_json_as_dict().get('id')
        key_type_map = classifier.read_json_as_dict().get('keyTypeMap', {})
        key_type_map.update({'key': incident_type_id})
        classifier.update({'keyTypeMap': key_type_map})


class MapperDependencies:
    @staticmethod
    def make_mapper_depend_on_incident_type_default(mapper: JSONBased, incident_type: JSONBased):
        incident_type_id = incident_type.read_json_as_dict().get('id')
        mapper.update({'defaultIncidentType': incident_type_id})

    @staticmethod
    def make_classifier_depend_on_incident_type_and_fields(mapper: JSONBased, incident_type: JSONBased,
                                                           incidents_fields: List[JSONBased]):
        incident_type_id = incident_type.read_json_as_dict().get('id')
        incidents_fields_ids = [incident_field.read_json_as_dict().get('id') for incident_field in incidents_fields]
        mapping = mapper.read_json_as_dict().get('mapping', {})

        updates_to_map = {
            incident_type_id: {
                'internalMapping':
                    {
                        incident_field_id: {
                            "simple": "simple"
                        } for incident_field_id in incidents_fields_ids
                    }
            }
        }

        mapping.update(updates_to_map)
        mapper.update({'mapping': mapping})


class IncidentTypeDependencies:
    @staticmethod
    def make_incident_type_depend_on_playbook(incident_type: JSONBased, playbook: Playbook):
        playbook_id = playbook.yml.read_dict().get('id')
        incident_type.update({'playbookId': playbook_id})

    @staticmethod
    def make_incident_type_depend_on_script_pre_processing(incident_type: JSONBased, script: Script):
        script_id = script.yml.read_dict().get('commonfields').get('id')
        incident_type.update({'preProcessingScript': script_id})


class IndicatorTypeDependencies:
    @staticmethod
    def make_indicator_type_depend_on_script_reputation(indicator_type: JSONBased, script: Script):
        script_id = script.yml.read_dict().get('commonfields').get('id')
        indicator_type.update({'reputationScriptName': script_id})

    @staticmethod
    def make_indicator_type_depend_on_script_enhancement(indicator_type: JSONBased, script: Script):
        script_id = script.yml.read_dict().get('commonfields').get('id')
        indicator_type.update({'enhancementScriptNames': [script_id]})


class LayoutDependencies:
    @staticmethod
    def make_layout_depend_on_incident_indicator_type(layout: JSONBased, incident_type: JSONBased):
        incident_type_id = incident_type.read_json_as_dict().get('id')
        layout.update({'TypeName': incident_type_id})

    @staticmethod
    def make_layout_depend_on_incident_indicator_field(layout: JSONBased, indicators_fields: List[JSONBased],
                                                       incidents_fields: List[JSONBased]):
        indicators_fields_ids = [indicator_field.read_json_as_dict().get('id') for indicator_field in indicators_fields]
        incidents_fields_ids = [incident_field.read_json_as_dict().get('id') for incident_field in incidents_fields]

        layout_data = layout.read_json_as_dict().get('layout', {})
        updates_to_layout = {
            'tabs': {
                'sections': [
                    {
                        'displayType': 'ROW',
                        'h': 2,
                        'i': 'uuid',
                        'isVisible': True,
                        'items': [
                            {
                                'endCol': 2,
                                'fieldId': indicator_field_id,
                                'height': 24,
                                'id': 'id',
                                'index': 0,
                                'startCol': 0
                            } for indicator_field_id in indicators_fields_ids
                        ]
                    },
                    {
                        'displayType': 'ROW',
                        'h': 2,
                        'i': 'uuid',
                        'isVisible': True,
                        'items': [
                            {
                                'endCol': 2,
                                'fieldId': incident_field_id,
                                'height': 24,
                                'id': 'id',
                                'index': 0,
                                'startCol': 0
                            } for incident_field_id in incidents_fields_ids
                        ]
                    }
                ]
            }
        }

        layout_data.update(updates_to_layout)
        layout.update({'layout': layout_data})


class LayoutcontainerDependencies:
    @staticmethod
    def make_layoutcontainer_depend_on_incident_indicator_type(layoutcontainer: JSONBased, incident_type: JSONBased):
        incident_type_id = incident_type.read_json_as_dict().get('id')
        layoutcontainer.update({'name': incident_type_id})

    @staticmethod
    def make_layout_depend_on_incident_indicator_field(layout: JSONBased, indicators_fields: List[JSONBased],
                                                       incidents_fields: List[JSONBased]):
        indicators_fields_ids = [indicator_field.read_json_as_dict().get('id') for indicator_field in indicators_fields]
        incidents_fields_ids = [incident_field.read_json_as_dict().get('id') for incident_field in incidents_fields]

        layout_data = layout.read_json_as_dict().get('layout', {})
        updates_to_layout = {
            'tabs': {
                'sections': [
                    {
                        'displayType': 'ROW',
                        'h': 2,
                        'i': 'uuid',
                        'isVisible': True,
                        'items': [
                            {
                                'endCol': 2,
                                'fieldId': indicator_field_id,
                                'height': 24,
                                'id': 'id',
                                'index': 0,
                                'startCol': 0
                            } for indicator_field_id in indicators_fields_ids
                        ]
                    },
                    {
                        'displayType': 'ROW',
                        'h': 2,
                        'i': 'uuid',
                        'isVisible': True,
                        'items': [
                            {
                                'endCol': 2,
                                'fieldId': incident_field_id,
                                'height': 24,
                                'id': 'id',
                                'index': 0,
                                'startCol': 0
                            } for incident_field_id in incidents_fields_ids
                        ]
                    }
                ]
            }
        }

        layout_data.update(updates_to_layout)
        layout.update({'detailsV2': layout_data})


class IncidentFieldDependencies:
    # Ignored by yaakovi
    # @staticmethod
    # def make_incident_field_depend_on_incident_type_associated(incident_field: JSONBased, incident_type: JSONBased):
    #     incident_type_id = incident_type.read_json_as_dict().get('id')
    #     incident_field.update({'associatedTypes': [incident_type_id]})
    #
    # @staticmethod
    # def make_incident_field_depend_on_incident_type_system_associated(incident_field: JSONBased,
    #                                                                   incident_type: JSONBased):
    #     incident_type_id = incident_type.read_json_as_dict().get('id')
    #     incident_field.update({'systemAssociatedTypes': [incident_type_id]})

    @staticmethod
    def make_incident_field_depend_on_script(incident_field: JSONBased, script: Script):
        script_id = script.yml.read_dict().get('commonfields').get('id')
        incident_field.update({'script': script_id})

    @staticmethod
    def make_incident_field_depend_on_script_field_calc(incident_field: JSONBased, script: Script):
        script_id = script.yml.read_dict().get('commonfields').get('id')
        incident_field.update({'fieldCalcScript': script_id})


CLASSES = [IntegrationDependencies, PlaybookDependencies, ScriptDependencies, ClassifierDependencies,
           MapperDependencies, IncidentTypeDependencies, WidgetDependencies,
           IndicatorTypeDependencies, LayoutDependencies, LayoutcontainerDependencies, IncidentFieldDependencies]
METHODS_POOL: list = \
    [(method_name, entity_class) for entity_class in CLASSES for method_name in list(entity_class.__dict__.keys())
     if '_' != method_name[0]]


def get_entity_by_pack_number_and_entity_type(repo, pack_number, entity_type):
    if entity_type == 'integration':
        return repo.packs[pack_number].integrations[0]

    if entity_type == 'script':
        return repo.packs[pack_number].scripts[0]

    if entity_type == 'playbook':
        return repo.packs[pack_number].playbooks[0]

    if entity_type == 'classifier':
        return repo.packs[pack_number].classifiers[0]

    if entity_type == 'mapper':
        return repo.packs[pack_number].mappers[0]

    if entity_type == 'layout':
        return repo.packs[pack_number].layouts[0]

    if entity_type == 'layoutcontainer':
        return repo.packs[pack_number].layoutcontainers[0]

    if entity_type == 'incident_type':
        return repo.packs[pack_number].incident_types[0]

    if entity_type == 'incident_field':
        return repo.packs[pack_number].incident_fields[0]

    if entity_type == 'indicator_type':
        return repo.packs[pack_number].indicator_types[0]

    if entity_type == 'indicator_field':
        return repo.packs[pack_number].indicator_fields[0]

    if entity_type == 'widget':
        return repo.packs[pack_number].widgets[0]


LIST_ARGUMENTS_TO_METHODS = {
    'indicators_fields': 'indicator_field',
    'incidents_fields': 'incident_field'
}


def create_inputs_for_method(repo, current_pack, inputs_arguments):
    """Creates an `argument: object` dict of inputs for a method according to inputs_arguments.

    Args:
        repo (Repo): Content repo object.
        current_pack (int): ID of the pack that its objects will depend on other packs.
        inputs_arguments (list): List of entity types that  are needed as arguments to the method.

    Returns:
        Tuple(Dict, set):
            inputs_values (Dict): An `argument: object` dict of inputs for a method.
            dependencies (Set): All the packs' names that `current_pack` should depend on.
    """
    dependencies = set()

    inputs_values = {inputs_arguments[0]:
                     get_entity_by_pack_number_and_entity_type(repo, current_pack, inputs_arguments[0])}

    inputs_arguments = inputs_arguments[1:]
    # Ignores the `CommonTypes` pack in the flow, so only numeric packs will be chosen
    number_of_packs = len(repo.packs) - 1

    for arg in inputs_arguments:
        arg_type = arg.split('__')[0]
        if arg_type in LIST_ARGUMENTS_TO_METHODS.keys():
            number_of_items_in_list = random.randint(1, 5)

            input_argument = []
            for i in range(number_of_items_in_list):
                pack_to_take_entity_from = random.choice(range(1, number_of_packs))
                input_argument.append(get_entity_by_pack_number_and_entity_type(repo, pack_to_take_entity_from,
                                                                                LIST_ARGUMENTS_TO_METHODS[arg_type]))

                # The pack is not depend on packs with indicator_field because the Layout is of type incident
                if arg_type == 'indicators_fields':
                    continue
                dependencies.add(f'pack_{pack_to_take_entity_from}')

        else:
            pack_to_take_entity_from = random.choice(range(1, number_of_packs))
            input_argument = get_entity_by_pack_number_and_entity_type(repo, pack_to_take_entity_from, arg_type)

            # The pack is not depend on packs with indicator_type because the Layout is of type incident
            if arg_type == 'indicator_type':
                continue
            dependencies.add(f'pack_{pack_to_take_entity_from}')

        inputs_values[arg] = input_argument

    return inputs_values, dependencies


def run_random_methods(repo, current_pack, current_methods_pool, number_of_methods_to_choose):
    """ Runs random set of methods with size number_of_methods_to_choose
        out of the current_methods_pool.

    Args:
        repo (Repo): Content repo object.
        current_pack (int): ID of the pack that its objects will depend on other packs.
        current_methods_pool (list): The pool of methods to choose from.
        number_of_methods_to_choose (int): Amount of methods to choose.

    Returns:
        Set. All the packs' names that `current_pack` should depend on.
    """
    all_dependencies = set()

    for i in range(number_of_methods_to_choose):
        chosen_method = random.choice(current_methods_pool)
        current_methods_pool.remove(chosen_method)

        method = getattr(chosen_method[1], chosen_method[0])
        inputs_arguments = inspect.getfullargspec(method)[0]

        args, dependencies = create_inputs_for_method(repo, current_pack, inputs_arguments)

        if chosen_method[0] == 'make_integration_feed':
            all_dependencies.add('CommonTypes')

        all_dependencies = all_dependencies.union(dependencies)
        method(**args)

    return all_dependencies


def run_find_dependencies(mocker, repo_path, pack_name):
    with ChangeCWD(repo_path):
        # Circle froze on 3.7 dut to high usage of processing power.
        # pool = Pool(processes=cpu_count() * 2) is the line that in charge of the multiprocessing initiation,
        # so changing `cpu_count` return value to 1 still gives you multiprocessing but with only 2 processors,
        # and not the maximum amount.
        import demisto_sdk.commands.common.update_id_set as uis
        mocker.patch.object(uis, 'cpu_count', return_value=1)
        PackDependencies.find_dependencies(pack_name, silent_mode=True, update_pack_metadata=True)


@pytest.mark.parametrize('test_number', range(5))
def test_dependencies(mocker, repo, test_number):
    """ This test will run 5 times, when each time it will randomly generate dependencies in the repo and verify that
        the expected dependencies has been updated in the pack metadata correctly.

        Given
        - Content repository
        When
        - Running find_dependencies
        Then
        - Update packs dependencies in pack metadata
    """
    # Note: if DEMISTO_SDK_ID_SET_REFRESH_INTERVAL is set it can fail the test
    mock_is_external_repo(mocker, False)
    mocker.patch.dict(os.environ, {'DEMISTO_SDK_ID_SET_REFRESH_INTERVAL': '-1'})
    assert os.getenv('DEMISTO_SDK_ID_SET_REFRESH_INTERVAL') == '-1'
    number_of_packs = 10
    repo.setup_content_repo(number_of_packs)
    repo.setup_one_pack('CommonTypes')

    pack_to_verify = random.choice(range(number_of_packs))

    number_of_methods_to_choose = random.choice(range(1, len(METHODS_POOL)))
    dependencies = run_random_methods(repo, pack_to_verify, METHODS_POOL.copy(), number_of_methods_to_choose)

    run_find_dependencies(mocker, repo.path, f'pack_{pack_to_verify}')

    dependencies_from_pack_metadata = repo.packs[pack_to_verify].pack_metadata.read_json_as_dict().get(
        'dependencies').keys()

    if f'pack_{pack_to_verify}' in dependencies:
        dependencies.remove(f'pack_{pack_to_verify}')

    assert IsEqualFunctions.is_lists_equal(list(dependencies), list(dependencies_from_pack_metadata))


@pytest.mark.parametrize('entity_class', CLASSES)
def test_specific_entity(mocker, repo, entity_class):
    """ This test will run for each entity in the repo, when each time it will randomly generate dependencies
        in the repo and verify that the expected dependencies has been updated in the pack metadata correctly.

        Given
        - Content repository and content entity
        When
        - Running find_dependencies
        Then
        - Update packs dependencies in pack metadata
    """
    mock_is_external_repo(mocker, False)
    # Note: if DEMISTO_SDK_ID_SET_REFRESH_INTERVAL is set it can fail the test
    mocker.patch.dict(os.environ, {'DEMISTO_SDK_ID_SET_REFRESH_INTERVAL': '-1'})
    assert os.getenv('DEMISTO_SDK_ID_SET_REFRESH_INTERVAL') == '-1'
    number_of_packs = 20
    repo.setup_content_repo(number_of_packs)
    repo.setup_one_pack('CommonTypes')

    methods_pool: list = \
        [(method_name, entity_class) for method_name in list(entity_class.__dict__.keys())
         if '_' != method_name[0]]

    dependencies = run_random_methods(repo, 0, methods_pool, len(methods_pool))

    run_find_dependencies(mocker, repo.path, 'pack_0')

    dependencies_from_pack_metadata = repo.packs[0].pack_metadata.read_json_as_dict().get('dependencies').keys()

    if 'pack_0' in dependencies:
        dependencies.remove('pack_0')

    assert IsEqualFunctions.is_lists_equal(list(dependencies), list(dependencies_from_pack_metadata))


def mock_is_external_repo(mocker, is_external_repo_return):
    return mocker.patch(
        'demisto_sdk.commands.find_dependencies.find_dependencies.is_external_repository',
        return_value=is_external_repo_return
    )


def test_dependencies_case_1(mocker, repo):
    """
    Given
        - Content repo with the following items:
            -"foo" pack containing:
              - playbook_foo
              - integration_foo
            - "bar" pack containing:
              - script_bar
            - "CommonTypes" pack containing:
              - incident field Email

            - playbook_foo using:
              - integration_foo is not skippable
              - script_bar is skippable
              - incident field Email as an input

    When
        - running find_dependencies

    Then
        - foo pack's pack_metadata should include the following dependencies:
          - bar
          - CommonTypes

    """
    mock_is_external_repo(mocker, False)
    # setup the packs
    pack_foo = repo.create_pack('foo')
    pack_bar = repo.create_pack('bar')
    pack_common_types = repo.create_pack('CommonTypes')

    playbook_foo = pack_foo.create_playbook('playbook_foo')
    integration_foo = pack_foo.create_integration('integration_foo', yml={'name': '=integration_foo', 'category': '',
                                                                          'script': {'type': 'python'}})
    script_bar = pack_bar.create_script('script_bar', yml={'script': '', 'type': 'python', 'name': 'script_bar'})
    incident_field_email = pack_common_types.create_incident_field(name='incident_Email',
                                                                   content={'id': 'incident_Email',
                                                                            'name': 'incident_Email'})

    # make playbook_foo depend on integration_foo
    PlaybookDependencies.make_playbook_depend_on_integration_not_skippable(
        playbook_foo,
        integration_foo
    )

    # make playbook_foo depend on script_bar
    PlaybookDependencies.make_playbook_depend_on_script_skippable(
        playbook_foo,
        script_bar
    )

    # make playbook_foo depend on incident field Email
    PlaybookDependencies.make_playbook_depend_on_incident_field_input_complex(
        playbook_foo,
        incident_field_email
    )

    run_find_dependencies(mocker, repo.path, 'foo')

    expected_dependencies = ['bar', 'CommonTypes']
    dependencies_from_pack_metadata = pack_foo.pack_metadata.read_json_as_dict().get('dependencies').keys()

    assert IsEqualFunctions.is_lists_equal(expected_dependencies, list(dependencies_from_pack_metadata))


def test_dependencies_case_2(mocker, repo):
    """
    Given
        - Content repo with the following items:
            -"foo" pack containing:
              - integration_foo which is a feed integration
            - "bar" pack containing:
              - mapper_in_bar

            - integration_foo using:
              - mapper_in_bar

    When
        - running find_dependencies

    Then
        - foo pack's pack_metadata should include the following dependencies:
          - bar
          - CommonTypes

    """
    mock_is_external_repo(mocker, False)
    # setup the packs
    pack_foo = repo.create_pack('foo')
    pack_bar = repo.create_pack('bar')
    pack_common_types = repo.create_pack('CommonTypes')

    integration_foo = pack_foo.create_integration('integration_foo', yml={'name': 'integration_foo', 'category': '',
                                                                          'script': {'type': 'python'}})
    mapper_in_bar = pack_bar.create_mapper(name='mapper_in_bar', content={'id': 'mapper_in_bar',
                                                                          'name': 'mapper_in_bar',
                                                                          'mapping': {},
                                                                          'type': 'mapping'})
    # Pack can not be empty
    pack_common_types.create_integration('integration_common_types',
                                         yml={'name': 'integration_common_types', 'category': '',
                                              'script': {'type': 'python'}})

    # make integration_foo feed
    IntegrationDependencies.make_integration_feed(
        integration_foo
    )

    # make integration_foo depend on mapper_in_bar
    IntegrationDependencies.make_integration_depend_on_mapper_in(
        integration_foo,
        mapper_in_bar
    )

    run_find_dependencies(mocker, repo.path, 'foo')

    expected_dependencies = ['bar', 'CommonTypes']
    dependencies_from_pack_metadata = repo.packs[0].pack_metadata.read_json_as_dict().get('dependencies').keys()

    assert IsEqualFunctions.is_lists_equal(expected_dependencies, list(dependencies_from_pack_metadata))


# demisto_sdk/commands/common/tests/description_test.py

import glob
import os

import pytest
from ruamel.yaml import YAML

from demisto_sdk.commands.common.hook_validations.description import \
    DescriptionValidator
from TestSuite.test_tools import ChangeCWD


@pytest.mark.parametrize('integration_obj', [
    ({'script': {'script': 'Here Comes The Script'},
      'category': 'ok'}),
    ({'deprecated': True,
      'category': 'ok'})
])
def test_is_duplicate_description_unified_deprecated_integration(mocker, tmp_path, integration_obj):
    """
    Given:
        - Case A: Content pack with unified integration
        - Case B: Content pack with deprecated integration

    When:
        - Running detailed description validator on the integration

    Then:
        - Ensure validation passes
        - Ensure no warning is printed
    """
    mocker.patch.object(DescriptionValidator, 'handle_error')
    integration_dir = tmp_path / 'Packs' / 'SomePack' / 'Integrations' / 'SomeIntegration'
    integration_dir.mkdir(parents=True)
    unified_integration_yml = integration_dir / 'SomeIntegration.yml'
    YAML(typ='safe', pure=True).dump(integration_obj, unified_integration_yml.open('w'))
    description_validator = DescriptionValidator(str(unified_integration_yml))
    assert description_validator.is_duplicate_description()
    assert not DescriptionValidator.handle_error.called


def test_is_duplicate_description_given(pack, mocker):
    """
    Given:
        - Description file path

    When:
        - Running detailed description validator on the integration.

    Then:
        - Ensure validation passes
        - Ensure handle error is not called.
    """
    mocker.patch.object(DescriptionValidator, 'handle_error')
    integration = pack.create_integration()
    integration.create_default_integration()
    description_validator = DescriptionValidator(integration.description.path)
    assert description_validator.is_duplicate_description()
    assert not DescriptionValidator.handle_error.called


@pytest.mark.parametrize("file_input, result",
                         [("### Community Contributed Integration\n### OtherSection", False),
                          ("### partner Contributed Integration", False),
                          ("### Other section", True)])
def test_is_valid_file(integration, file_input, result):
    """
    Given
        - Description file with Contribution details or not
    When
        - Run validate on Description file
    Then
        - Ensure no Contribution details in the file
    """

    integration.description.write(file_input)
    description_path = integration.description.path
    with ChangeCWD(integration.repo_path):
        description_validator = DescriptionValidator(description_path)
        answer = description_validator.is_valid_file()

    assert answer == result
    assert description_validator._is_valid == answer


def test_is_valid_description_name(repo):
    """
        Given
            - An integration description with a valid name

        When
            - Validating the integration description name

        Then
            - Ensure that description validator for integration passes.
    """

    pack = repo.create_pack('PackName')

    integration = pack.create_integration('IntName')
    integration.create_default_integration()

    description_validator = DescriptionValidator(integration.yml.path)

    assert description_validator.is_valid_description_name()


def test_is_invalid_description_name(repo):
    """
        Given
            - An integration description with a invalid name

        When
            - Validating the integration description name

        Then
            - Ensure that description validator for integration failed.
    """

    pack = repo.create_pack('PackName')

    integration = pack.create_integration('IntName')

    description_path = glob.glob(os.path.join(os.path.dirname(integration.yml.path), '*_description.md'))
    new_name = f'{description_path[0].rsplit("/", 1)[0]}/IntName_desc.md'

    os.rename(description_path[0], new_name)
    with ChangeCWD(repo.path):
        description_validator = DescriptionValidator(integration.yml.path)

        assert not description_validator.is_valid_description_name()


def test_demisto_in_description(repo):
    """
        Given
            - An integration description with the word 'Demisto'.

        When
            - Running verify_demisto_in_description_content.

        Then
            - Ensure that the validation fails.
    """

    pack = repo.create_pack('PackName')
    integration = pack.create_integration('IntName')
    integration.create_default_integration()
    integration.description.write('This checks if we have the word Demisto in the description.')

    with ChangeCWD(repo.path):
        description_validator = DescriptionValidator(integration.yml.path)

        assert not description_validator.verify_demisto_in_description_content()

        description_validator = DescriptionValidator(integration.description.path)

        assert not description_validator.verify_demisto_in_description_content()


def test_demisto_not_in_description(repo):
    """
        Given
            - An integration description without the word 'Demisto'.

        When
            - Running verify_demisto_in_description_content.

        Then
            - Ensure that the validation passes.
    """

    pack = repo.create_pack('PackName')
    integration = pack.create_integration('IntName')
    integration.create_default_integration()
    integration.description.write('This checks if we have the word XSOAR in the description.')

    with ChangeCWD(repo.path):
        description_validator = DescriptionValidator(integration.yml.path)

        assert description_validator.verify_demisto_in_description_content()

        description_validator = DescriptionValidator(integration.description.path)

        assert description_validator.verify_demisto_in_description_content()


# demisto_sdk/commands/common/tests/docker_test.py

import os

import mock
import pytest

from demisto_sdk.commands.common.errors import Errors
from demisto_sdk.commands.common.hook_validations.docker import \
    DockerImageValidator
from demisto_sdk.commands.common.legacy_git_tools import git_path
from demisto_sdk.commands.common.tools import get_yaml
from TestSuite.test_tools import ChangeCWD

RETURN_ERROR_TARGET = 'GetDockerImageLatestTag.return_error'

MOCK_TAG_LIST = [{
    u'last_updated': u'2019-10-23T09:13:30.84299Z',
    u'name': u'1.0.0.2876',
    u'repository': 7863337,
    u'creator': 4824052,
    u'image_id': None,
    u'v2': True,
    u'last_updater_username': u'containersci',
    u'last_updater': 4824052,
    u'images': [{
        u'features': u'',
        u'os_features': u'',
        u'variant': None,
        u'os_version': None,
        u'architecture': u'amd64',
        u'os': u'linux',
        u'digest': u'DIGEST',
        u'size': 79019268
    }],
    u'full_size': 79019268,
    u'id': 73482510
}, {
    u'last_updated': u'2019-10-16T06:47:29.631011Z',
    u'name': u'1.0.0.2689',
    u'repository': 7863337,
    u'creator': 4824052,
    u'image_id': None,
    u'v2': True,
    u'last_updater_username': u'containersci',
    u'last_updater': 4824052,
    u'images': [{
        u'features': u'',
        u'os_features': u'',
        u'variant': None,
        u'os_version': None,
        u'architecture': u'amd64',
        u'os': u'linux',
        u'digest': u'DIGEST',
        u'size': 77021619
    }],
    u'full_size': 77021619,
    u'id': 72714981
}]

FILES_PATH = os.path.normpath(os.path.join(__file__, f'{git_path()}/demisto_sdk/tests', 'test_files'))
TEST_INTEGRATION_FILE = os.path.join(FILES_PATH, 'fake_integration.yml')
TEST_SCRIPT_FILE = os.path.join(FILES_PATH, 'fake-script.yml')


def mock_docker_image_validator():
    with mock.patch.object(DockerImageValidator, '__init__', lambda x, y, z, w: None):
        docker_image_validator = DockerImageValidator(None, None, None)
        docker_image_validator.yml_file = {}
        docker_image_validator.file_path = "PATH"
        docker_image_validator.ignored_errors = {}
        docker_image_validator.checked_files = set()
        docker_image_validator.suppress_print = False
        docker_image_validator.json_file_path = ''
        return docker_image_validator


class TestDockerImage:
    # demisto/python-deb doesn't contain a latest tag
    @pytest.mark.parametrize('image', ['python', 'python-deb', 'python3', 'python3-deb'])
    def test_get_docker_image_latest_tag(self, image):
        docker_image_validator = mock_docker_image_validator()
        docker_image_validator.docker_image_latest_tag = '1.0.3'
        docker_image_validator.docker_image_name = 'demisto/python'
        tag = docker_image_validator.get_docker_image_latest_tag(docker_image_name=f'demisto/{image}',
                                                                 yml_docker_image='')
        # current latest tag is 2.7.16.2728 or 3.7.2.2728 disable-secrets-detection
        assert int(tag.split('.')[3]) >= 2728

    data_test_none_demisto_docker = [
        ('blabla/google-api-py3', '1.0.0.5992', ''),
        ('unknownvuser/v-alpine', 'at_v_commit-b17ade1257cfe086c1742c91deeb6c606037b893', ''),
        ('feedparser', 'latest', '')
    ]

    @pytest.mark.parametrize('docker, docker_tag, expected_output', data_test_none_demisto_docker)
    def test_none_demisto_docker(self, docker, docker_tag, expected_output):
        docker_image_validator = mock_docker_image_validator()
        assert docker_image_validator.get_docker_image_latest_tag(docker_image_name=docker,
                                                                  yml_docker_image='{}:{}'.format(docker,
                                                                                                  docker_tag)) == expected_output

    # disable-secrets-detection-start
    def test_get_docker_image_from_yml(self):
        docker_validator = mock_docker_image_validator()
        docker_validator.yml_file = get_yaml(TEST_INTEGRATION_FILE)
        docker_validator.is_integration = True
        docker_image = docker_validator.get_docker_image_from_yml()
        assert docker_image == "demisto/pyjwt:1.0"
        # Test script case
        docker_validator.yml_file = get_yaml(TEST_SCRIPT_FILE)
        docker_validator.is_integration = False
        docker_image = docker_validator.get_docker_image_from_yml()
        assert docker_image == "demisto/stix2:1.0.0.204"

    # disable-secrets-detection-end

    def test_lexical_find_latest_tag(self):
        tag_list = ["2.0.2000", "2.1.2700", "2.1.373", "latest"]
        tag = DockerImageValidator.lexical_find_latest_tag(tag_list)
        assert tag == "2.1.2700"

    def test_find_latest_tag_by_date(self):
        tag = DockerImageValidator.find_latest_tag_by_date(MOCK_TAG_LIST)
        assert tag == "1.0.0.2876"

    @pytest.mark.parametrize('www_auth, expected', [('AAArealm="2",service="3"AAA', ('2', '3')), ('bbb', ())])
    def test_parse_www_auth(self, www_auth, expected):
        assert expected == DockerImageValidator.parse_www_auth(www_auth)

    # disable-secrets-detection-start
    @pytest.mark.parametrize('input_tags, output_tags',
                             [(['1.2.3.0', '4.5.6.0', '7.8.9.0'], ['4.5.6.0', '1.2.3.0', '7.8.9.0']),
                              (['1.2.3.0', '4.a.6.0', '7.8.9.0'], ['7.8.9.0', '1.2.3.0']),
                              (['aaa', 'bbb'], []), (['6a.7.6'], []), (['6..4'], [])])
    # disable-secrets-detection-end
    def test_clear_non_numbered_tags(self, input_tags, output_tags):
        assert sorted(output_tags) == sorted(DockerImageValidator.clear_non_numbered_tags(input_tags))

    # disable-secrets-detection-start
    def test_parse_docker_image(self):
        docker_image_validator = mock_docker_image_validator()
        docker_image_validator.docker_image_latest_tag = '1.0.3'
        docker_image_validator.docker_image_name = 'demisto/python'
        assert 'demisto/python', '1.3-alpine' == docker_image_validator.parse_docker_image(
            docker_image='demisto/python:1.3-alpine')
        assert 'demisto/slack', '1.2.3.4' == docker_image_validator.parse_docker_image(
            docker_image='demisto/slack:1.2.3.4')
        assert 'demisto/python', '' == docker_image_validator.parse_docker_image(
            docker_image='demisto/python/1.2.3.4')
        assert ('', '') == docker_image_validator.parse_docker_image(docker_image='blah/blah:1.2.3.4')

    # disable-secrets-detection-end
    def test_is_docker_image_latest_tag_with_default_image(self):
        """
        Given
        - The default docker image - 'demisto/python:1.3-alpine'

        When
        - The most updated docker image in docker-hub is '1.0.3'

        Then
        -  If the docker image is numeric and the most update one, it is Valid
        -  If the docker image is not numeric and labeled "latest", it is Invalid
       """
        docker_image_validator = mock_docker_image_validator()
        docker_image_validator.code_type = 'python'
        docker_image_validator.docker_image_latest_tag = '1.0.3'
        docker_image_validator.docker_image_name = 'demisto/python'
        docker_image_validator.is_latest_tag = True
        docker_image_validator.is_modified_file = False
        docker_image_validator.docker_image_tag = '1.3-alpine'
        docker_image_validator.is_valid = True
        docker_image_validator.yml_docker_image = 'demisto/python:1.3-alpine'

        assert docker_image_validator.is_docker_image_latest_tag() is False
        assert docker_image_validator.is_latest_tag is False
        assert docker_image_validator.is_docker_image_valid() is False

    def test_is_docker_image_latest_tag_with_tag_labeled_latest(self):
        """
        Given
        - A docker image with "latest" as tag

        When
        - The most updated docker image in docker-hub is '1.0.3'

        Then
        -  If the docker image is numeric and the most update one, it is Valid
        -  If the docker image is not numeric and labeled "latest", it is Invalid
       """
        docker_image_validator = mock_docker_image_validator()
        docker_image_validator.docker_image_latest_tag = 'latest'
        docker_image_validator.docker_image_name = 'demisto/python'
        docker_image_validator.code_type = 'python'
        docker_image_validator.is_latest_tag = True
        docker_image_validator.is_valid = True
        docker_image_validator.docker_image_tag = 'latest'
        docker_image_validator.yml_docker_image = 'demisto/python:latest'

        assert docker_image_validator.is_docker_image_latest_tag() is False
        assert docker_image_validator.is_latest_tag is False
        assert docker_image_validator.is_docker_image_valid() is False

    def test_is_docker_image_latest_tag_with_latest_tag(self):
        """
       Given
       - A docker image with '1.0.3' as tag

       When
       - The most updated docker image in docker-hub is '1.0.3'

       Then
       -  If the docker image is numeric and the most update one, it is Valid
       -  If the docker image is not numeric and labeled "latest", it is Invalid
      """
        docker_image_validator = mock_docker_image_validator()
        docker_image_validator.docker_image_latest_tag = '1.0.3'
        docker_image_validator.docker_image_name = 'demisto/python'
        docker_image_validator.code_type = 'python'
        docker_image_validator.is_latest_tag = True
        docker_image_validator.is_valid = True
        docker_image_validator.docker_image_tag = '1.0.3'
        docker_image_validator.yml_docker_image = 'demisto/python:1.0.3'

        assert docker_image_validator.is_docker_image_latest_tag() is True
        assert docker_image_validator.is_latest_tag is True
        assert docker_image_validator.is_docker_image_valid() is True

    def test_is_docker_image_latest_tag_with_numeric_but_not_most_updated(self):
        """
       Given
       - A docker image with '1.0.2' as tag

       When
       - The most updated docker image in docker-hub is '1.0.3'

       Then
       -  If the docker image is numeric and the most update one, it is Valid
       -  If the docker image is not numeric and labeled "latest", it is Invalid
       - If the docker image is not the most updated one it is invalid
      """
        docker_image_validator = mock_docker_image_validator()
        docker_image_validator.docker_image_latest_tag = '1.0.3'
        docker_image_validator.docker_image_name = 'demisto/python'
        docker_image_validator.code_type = 'python'
        docker_image_validator.is_latest_tag = True
        docker_image_validator.docker_image_tag = '1.0.2'
        docker_image_validator.is_valid = True
        docker_image_validator.yml_docker_image = 'demisto/python:1.0.2'
        docker_image_validator.is_iron_bank = False
        assert docker_image_validator.is_docker_image_latest_tag() is False
        assert docker_image_validator.is_latest_tag is False
        assert docker_image_validator.is_docker_image_valid() is False

    def test_is_docker_image_latest_tag_without_tag(self):
        """
       Given
       - A latest docker image has an empty tag

       When
       - The most updated docker image in docker-hub is '1.0.3'

       Then
       -  If the docker image is numeric and the most update one, it is Valid
       -  If the docker image is not numeric and labeled "latest", it is Invalid
      """
        docker_image_validator = mock_docker_image_validator()
        docker_image_validator.docker_image_latest_tag = ''
        docker_image_validator.docker_image_name = 'demisto/python'
        docker_image_validator.code_type = 'python'
        docker_image_validator.is_latest_tag = True
        docker_image_validator.docker_image_tag = '1.0.2'
        docker_image_validator.is_valid = True
        docker_image_validator.yml_docker_image = 'demisto/python:1.0.2'

        assert docker_image_validator.is_docker_image_latest_tag() is False
        assert docker_image_validator.is_latest_tag is False
        assert docker_image_validator.is_docker_image_valid() is False

    @pytest.mark.parametrize('code_type, expected', [('javascript', True), ('python', False)])
    def test_no_dockerimage_in_yml_file(self, code_type, expected):
        """
        Given
        - A yml file (integration/script) written in [javascript, python] with no dockerimage.

        When
        - Running DockerImageValidator

        Then
        -  If the integration / script is written in javascript, it is valid
        -  If the integration / script is written in python, it is invalid
        """
        docker_image_validator = mock_docker_image_validator()

        docker_image_validator.is_valid = True
        docker_image_validator.is_latest_tag = True
        docker_image_validator.yml_docker_image = None
        docker_image_validator.docker_image_latest_tag = None
        docker_image_validator.docker_image_name = None
        docker_image_validator.docker_image_tag = None
        docker_image_validator.code_type = code_type

        assert docker_image_validator.is_docker_image_valid() is expected

    @pytest.mark.parametrize('code_type', ['javascript', 'python'])
    def test_dockerimage_in_yml_file(self, code_type):
        """
        Given
        - A yml file (integration/script) written in [javascript, python] with correct dockerimage.

        When
        - Running DockerImageValidator

        Then
        -  If the integration / script is written in javascript, it is valid
        -  If the integration / script is written in python, it is valid
        """
        docker_image_validator = mock_docker_image_validator()

        docker_image_validator.is_valid = True
        docker_image_validator.is_latest_tag = True
        docker_image_validator.yml_docker_image = 'demisto/python:1.0.2'
        docker_image_validator.docker_image_latest_tag = '1.0.2'
        docker_image_validator.docker_image_name = 'demisto/python'
        docker_image_validator.docker_image_tag = '1.0.2'
        docker_image_validator.code_type = code_type

        assert docker_image_validator.is_docker_image_valid() is True

    def test_non_existing_docker(self, integration, capsys, requests_mock, mocker):
        docker_image = 'demisto/nonexistingdocker:1.4.0'
        integration.yml.write_dict(
            {
                'script': {
                    'subtype': 'python3',
                    'type': 'python',
                    'dockerimage': docker_image
                }
            }
        )
        error, code = Errors.non_existing_docker(docker_image)
        mocker.patch.object(DockerImageValidator, 'docker_auth', return_value='auth')
        requests_mock.get(
            "https://hub.docker.com/v2/repositories/demisto/nonexistingdocker/tags",
            json={'results': []}
        )
        with ChangeCWD(integration.repo_path):
            validator = DockerImageValidator(integration.yml.path, True, True)
            assert validator.is_docker_image_valid() is False
            captured = capsys.readouterr()
            assert validator.is_valid is False
            assert error in captured.out
            assert code in captured.out

    class TestIronBankDockerParse:
        def test_get_latest_commit(self, integration, requests_mock):
            """
            Given:
                An example existing project with successful commits in master in Iron Bank.
            When:
                Validating docker image of Iron Bank pack.
            Then:
                Validates we extract correctly the commit id.
            """
            api_url = 'https://repo1.dso.mil/api/v4/projects/dsop%2Fopensource%2Fpalo-alto-networks%2Ftest%2Ftest_project/pipelines'
            requests_mock.get(
                api_url,
                json=[{'id': 433333,
                       'project_id': 7070,
                       'sha': 'sha_1',
                       'ref': 'master',
                       'status': 'success',
                       'created_at': '2021-08-19T09:18:35.547Z',
                       'updated_at': '2021-08-19T09:38:21.743Z',
                       'web_url': 'https://repo1.dso.mil/dsop/opensource/palo-alto-networks/test/test_project/-/pipelines/433333'},
                      {'id': 432507,
                       'project_id': 7070,
                       'sha': 'sha_2',
                       'ref': 'master',
                       'status': 'success',
                       'created_at': '2021-08-18T22:19:19.843Z',
                       'updated_at': '2021-08-18T22:40:29.950Z',
                       'web_url': 'https://repo1.dso.mil/dsop/opensource/palo-alto-networks/test/test_project/-/pipelines/432507'}]
            )

            DockerImageValidator.file_path = integration.yml.path
            DockerImageValidator.is_iron_bank = True
            docker_image_name = 'test/test_project:1.0.2'
            DockerImageValidator.yml_docker_image = docker_image_name
            res = DockerImageValidator._get_latest_commit(api_url, docker_image_name)
            assert 'sha_1' == res

        FAIL_CASES_GET_COMMIT = [
            ([], 200,
             'The docker image in your integration/script does not have a tag in Iron Bank. '
             'Please use only images that are already in Iron Bank, or upload your image to it.'),
            ({}, 404,
             'The docker image in your integration/script cannot be found in Iron Bank. '
             'Please create the image: test/test_project:1.0.2 in Iron Bank.'),
        ]

        @pytest.mark.parametrize('mock_results, mocked_status, expected', FAIL_CASES_GET_COMMIT)
        def test_get_latest_commit_fails(self, mocker, integration, requests_mock,
                                         mock_results, mocked_status, expected):
            """
            Given:
                - A project with no successful commit in master in Iron Bank.
                - A project that does not exists in Iron Bank.
            When:
                Validating docker image of Iron Bank pack.
            Then:
                Validates we show the correct error.
            """
            api_url = 'https://repo1.dso.mil/api/v4/projects/dsop%2Fopensource%2Fpalo-alto-networks%2Ftest%2Ftest_project/pipelines'
            requests_mock.get(
                api_url,
                status_code=mocked_status,
                json=mock_results
            )

            DockerImageValidator.is_iron_bank = True
            docker_image_name = 'test/test_project:1.0.2'
            DockerImageValidator.yml_docker_image = docker_image_name
            DockerImageValidator.file_path = integration.yml.path

            with pytest.raises(Exception) as e:
                DockerImageValidator._get_latest_commit(api_url, docker_image_name)

            assert str(e.value) == expected

        def test_get_manifest_from_commit(self, integration, requests_mock):
            """
            Given:
                An example existing commit with successful commits in master with Manifest file in Iron Bank.
            When:
                Validating docker image of Iron Bank pack.
            Then:
                Validates we send the correct request to Iron Bank.
            """
            manifest_url = 'https://repo1.dso.mil/api/v4/projects/dsop%2Fopensource%2Fpalo-alto-networks%2Ftest%2F'\
                           'test_project/repository/files/hardening_manifest.yaml/raw'
            request_mock = requests_mock.get(
                manifest_url,
                text="""apiVersion: v1\nname: opensource/palo-alto-networks/test/test_project\ntags:\n- 1.0.1.23955\n"""
            )

            DockerImageValidator.file_path = integration.yml.path
            DockerImageValidator.is_iron_bank = True
            docker_image_name = 'test/test_project:1.0.2'
            DockerImageValidator.yml_docker_image = docker_image_name
            DockerImageValidator._get_manifest_from_commit(manifest_url, 'sha1')
            assert request_mock.last_request.query == 'ref=sha1'
            assert request_mock.last_request.path == '/api/v4/projects/dsop%2fopensource%2fpalo-alto-networks%2f' \
                                                     'test%2ftest_project/repository/files/hardening_manifest.yaml/raw'

        FAIL_CASES_GET_MANIFEST = [
            ('', 404,
             'Missing manifest file in the latest successful commit.'),
        ]

        @pytest.mark.parametrize('mock_results, mocked_status, expected', FAIL_CASES_GET_MANIFEST)
        def test_get_manifest_from_commit_fails(self, mocker, integration, requests_mock,
                                                mock_results, mocked_status, expected):
            """
            Given:
                - A project without manifest file in master in Iron Bank.
            When:
                Validating docker image of Iron Bank pack.
            Then:
                Validates we show the correct error.
            """
            manifest_url = 'https://repo1.dso.mil/api/v4/projects/dsop%2Fopensource%2Fpalo-alto-networks%2Ftest%2F' \
                           'test_project/repository/files/hardening_manifest.yaml/raw'
            requests_mock.get(
                manifest_url,
                status_code=mocked_status,
                text=mock_results
            )

            DockerImageValidator.is_iron_bank = True
            docker_image_name = 'test/test_project:1.0.2'
            DockerImageValidator.yml_docker_image = docker_image_name
            DockerImageValidator.file_path = integration.yml.path
            with pytest.raises(Exception) as e:
                DockerImageValidator._get_manifest_from_commit(manifest_url, 'sha1')
            assert str(e.value) == expected


# demisto_sdk/commands/common/tests/docker_util_test.py

import platform
import uuid

import pytest

from demisto_sdk.commands.common.docker_util import *
from demisto_sdk.commands.common.legacy_git_tools import git_path

FILES_PATH = os.path.normpath(os.path.join(__file__, f'{git_path()}/demisto_sdk/tests', 'test_files'))
running_inside_docker = pytest.mark.skipif(platform.system().lower() == 'linux',
                                           reason="probably running in docker and this test requires a connection to the docker deamon")


def create():
    return ContainerRunner('demisto/python3', str(uuid.uuid4()))


@running_inside_docker
class TestContainerRunner:

    class TestExecCommand:

        def setup_class(self):
            self.container_runner = create()

        data_test_exec_status = [
            ('echo test', 0),
            ('blabla blabla', 127),
        ]

        @pytest.mark.parametrize('command, status', data_test_exec_status)
        def test_exec_status(self, command, status):
            """
            Given:
                a general command (including illegal)
            When:
                run on the container
            Then:
                verify that we got the correct exit code

            """
            assert self.container_runner.exec(command=command)['StatusCode'] == status

        data_test_exec_outputs = [
            ('echo test', b'test\n'),
            ('pwd', b'/\n')
        ]

        @pytest.mark.parametrize('command, outputs', data_test_exec_outputs)
        def test_exec_outputs(self, command, outputs):
            """
            Given:
                a legal command
            When:
                run on the container
            Then:
                verify that we got the correct output

            """
            assert self.container_runner.exec(command=command)['Outputs'] == outputs

        data_test_exec_errors = [
            ('echo test', None),
            (
                'cd bla/bla',
                {
                    'Message':
                        'OCI runtime create failed: container_linux.go:367: starting container process caused:'
                        ' exec: "cd": executable file not found in $PATH: unknown'
                }
            )
        ]

        @pytest.mark.parametrize('command, error', data_test_exec_errors)
        def test_exec_errors(self, command, error):
            """
            Given:
                an illegal command
            When:
                run on the container
            Then:
                verify that we got the correct error

            """
            assert self.container_runner.exec(command=command)['Error'] == error

        def test_exec_committed(self):
            """
            Given:
                commands running in a flow
            When:
                run on the container
            Then:
                verify that we committed the container after the command runs.
            """
            self.container_runner.exec('echo test')
            file_name = 'just_a_txt_file.txt'
            file_full_path = os.path.join(FILES_PATH, file_name)
            self.container_runner.exec(f'mkdir -p {FILES_PATH}')
            with open(file_full_path, 'rb') as _file:
                self.container_runner.import_file(_file.read(), file_full_path)
            self.container_runner.exec('echo test')
            assert self.container_runner.container.image != self.container_runner.container.client.images.get(
                self.container_runner._base_image_name)

    class TestImportFile:
        def setup_class(self):
            self.container_runner = create()

        @pytest.mark.parametrize('file_name', ['just_a_txt_file.txt'])
        def test_import_file(self, file_name):
            """
            Given:
                file to import to the container
            When:
                importing the file
            Then:
                verify that the file exist in the container.
            """
            file_path = FILES_PATH
            file_full_path = os.path.join(FILES_PATH, file_name)
            self.container_runner.exec(f'mkdir -p {file_path}')
            with open(file_full_path, 'rb') as _file:
                data = _file.read()
            self.container_runner.import_file(data, file_full_path)
            assert self.container_runner.export_file(file_full_path) == data

    class TestExportFile:
        def setup_class(self):
            self.container_runner = create()

        @pytest.mark.parametrize('file_name', ['just_a_txt_file.txt'])
        def test_export_file(self, file_name):
            """
            Given:
                file to export from the container
            When:
                export the file
            Then:
                verify that we got the file with the right data.
            """
            file_full_path = os.path.join(FILES_PATH, file_name)
            self.container_runner.exec(f'mkdir -p {FILES_PATH}')
            with open(file_full_path, 'rb') as _file:
                data = _file.read()
            self.container_runner.import_file(data, file_full_path)
            assert self.container_runner.export_file(file_full_path) == data

    class TestContainerCreation:
        def setup_class(self):
            self.container_runner = create()

        def test_with_container(self, mocker):
            """
            Given:
                container_runner.container is called in the code
            When:
                the container still doesn't exist.
            Then:
                verify that "_create_container" was called.
            """
            assert self.container_runner._container_obj is None
            caller = mocker.patch.object(self.container_runner, '_create_container')
            self.container_runner.container
            assert caller.called.numerator == 1

        def test_with_create_container(self):
            """
            Given:
                "_create_container" is called in the code
            When:
                the container already exist.
            Then:
                verify that "_create_container" is able to be called.
            """
            self.container_runner.container
            assert self.container_runner._container_obj is not None
            self.container_runner._create_container()

    class TestRemoveContainerAndImages:
        def setup_class(self):
            self.container_runner = create()

        def test_remove_container(self):
            """
            Given:
                a container
            When:
                "remove_container" is called
            Then:
                verify that container was successfully removed.
            """
            self.container_runner.exec('echo test')
            container_id = self.container_runner.container.id
            self.container_runner.remove_container()
            with pytest.raises(docker.errors.NotFound):
                self.container_runner.container.client.containers.get(container_id)

        def test_remove_images(self):
            """
            Given:
                a container
            When:
                "remove_images" is called
            Then:
                verify that the images was successfully removed.
            """
            self.container_runner.exec('echo test1')
            self.container_runner.exec('echo test2')
            self.container_runner.exec('echo test3')
            image = self.container_runner._image_name
            self.container_runner.remove_images()
            with pytest.raises(docker.errors.ImageNotFound):
                self.container_runner.container.client.images.get(image)


@running_inside_docker
class TestDockerTools:

    class TestRemoveContainer:

        @staticmethod
        def remove(name):
            try:
                DockerTools.remove_container(name)
            except:     # noqa: E722
                pass

        @pytest.mark.parametrize('container_name', ['test_remove_running_container'])
        def test_remove_running_container(self, container_name):
            """
            Given:
                a running container
            When:
                "remove_container" is called with Force=True
            Then:
                verify that container was successfully removed.
            """
            self.remove(container_name)
            client.containers.run('demisto/python3', name=container_name, command='sleep 60', detach=True)
            DockerTools.remove_container(container_name, ignore_container_not_found=False, force=True)
            with pytest.raises(docker.errors.NotFound):
                client.containers.get(container_name)

        @pytest.mark.parametrize('container_name', ['test_remove_existing_container'])
        def test_remove_existing_container(self, container_name):
            self.remove(container_name)
            """
            Given:
                a created container
            When:
                "remove_container" is called
            Then:
                verify that container was successfully removed.
            """
            client.containers.create('demisto/python3', name=container_name, command='sleep 60', detach=True)
            DockerTools.remove_container(container_name, ignore_container_not_found=False)
            with pytest.raises(docker.errors.NotFound):
                client.containers.get(container_name)

        @pytest.mark.parametrize('container_name', ['test_remove_non_existing_container'])
        def test_remove_non_existing_container(self, container_name):
            """
            Given:
                a non existing container
            When:
                "remove_container" is called with ignore_container_not_found=True
            Then:
                verify that no Exception was raised.
            """
            self.remove(container_name)
            DockerTools.remove_container(container_name, ignore_container_not_found=True)

    class TestRemoveImages:

        images = ['demisto/python3']
        tag = 'test-docker'

        def setup(self):
            self.to_delete = []
            for image in self.images:
                container = ContainerRunner(image=image, container_name=str(uuid.uuid4()))
                container.import_file(b'just_something', 'just_something.txt')
                image_to_save = container.container.commit()
                self.to_delete.append(image_to_save.id)
                image_to_save.tag(image, self.tag)
                self.to_delete.append(image_to_save.id)

        def teardown(self):
            for image_id in self.to_delete:
                try:
                    client.images.remove(image_id)
                except:     # noqa: E722
                    pass

        @pytest.mark.parametrize('image_name', images)
        def test_remove_running_image(self, image_name):
            """
            Given:
                a running container
            When:
                "remove_image" is called with force=True
            Then:
                verify that image was successfully removed.
            """
            image_name = f'{image_name}:{self.tag}'
            container = client.containers.run(image=image_name, command='sleep 60', detach=True)
            DockerTools.remove_image(image_name, ignore_image_not_found=False, force=True)
            self.to_delete.append(container.image.id)
            container.remove(force=True)
            with pytest.raises(docker.errors.ImageNotFound):
                client.images.get(image_name)

        @pytest.mark.parametrize('image_name', images)
        def test_remove_existing_image(self, image_name):
            """
            Given:
                a created container
            When:
                "remove_image" is called
            Then:
                verify that image was successfully removed.
            """
            image_name = f'{image_name}:{self.tag}'
            client.containers.create(image=image_name).remove()
            DockerTools.remove_image(image_name, ignore_image_not_found=False)
            with pytest.raises(docker.errors.ImageNotFound):
                client.images.get(image_name)


class TestHelperFunctions:

    @pytest.mark.parametrize('data', [b'test'])
    def test_create_tar_info(self, data):
        """
        Given:
            data like in a file
        When:
            creating the tar info object
        Then:
            verify that the tar info object created correct.
        """
        tar_info = ContainerRunner._create_tar_info(name='test.txt', file_bytes=data)
        file_obj = tar_info['fileobj']
        tar_info = tar_info['tarinfo']
        assert file_obj.read() == data
        assert tar_info.size == len(data)
        assert tar_info.name == 'test.txt'


# demisto_sdk/commands/common/tests/errors_test.py

import unittest

from demisto_sdk.commands.common.errors import Errors


class TestErrors(unittest.TestCase):
    def test_file_name_includes_spaces(self):
        """
        Given: File Name with spaces
        When: Returning an error message
        Then: Return error message with the input value as a tuple containing error and error code.
        """
        file_name = "test file.gif"
        expected_result = ("Please remove spaces from the file's name: 'test file.gif'.", 'BA103')
        result = Errors.file_name_include_spaces_error(file_name)
        assert expected_result == result

    def test_wrong_required_value(self):
        """
        Given: Param value
        When: Returning an error message
        Then: Return error message with the input value as a tuple containing error and error code.
        """
        param_name = "test param"
        expected_result = ("The required field of the test param parameter should be False", "IN102")
        result = Errors.wrong_required_value(param_name)
        assert result == expected_result

    def test_pack_metadata_empty(self):
        """
        Given: None
        When: Returning an error message
        Then: Return error message with the input value as a tuple containing error and error code.
        """
        expected_result = ("Pack metadata is empty.", "PA105")
        result = Errors.pack_metadata_empty()
        assert result == expected_result

    def test_might_need_rns(self):
        """
        Given: None
        When: Returning an error message
        Then: Return error message with the input value as a tuple containing error and error code.
        """
        expected_result = "You may need RN in this file, please verify if they are required."
        result = Errors.might_need_release_notes()
        assert result == expected_result

    def test_unknown_file(self):
        """
        Given: None
        When: Returning an error message
        Then: Return error message with the input value as a string containing error.
        """
        expected_result = "File type is unknown, check it out."
        result = Errors.unknown_file()
        assert result == expected_result

    def test_id_change(self):
        """
        Given: None
        When: Returning an error message
        Then: Return error message with the input value as a string containing error.
        """
        expected_result = "You've changed the ID of the file, please undo this change."
        result = Errors.id_changed()
        assert result == expected_result

    def test_id_might_change(self):
        """
        Given: None
        When: Returning an error message
        Then: Return error message with the input value as a string containing error.
        """
        expected_result = "ID may have changed, please make sure to check you have the correct one."
        result = Errors.id_might_changed()
        assert result == expected_result

    def test_id_should_equal(self):
        """
        Given: File name and file ID
        When: Returning an error message
        Then: Return error message with the input value as a tuple containing error and error code.
        """
        expected_result = ("The File's name, which is: 'FileName', should be equal to its ID, which "
                           "is: 'FileID'. please update the file.", "BA101")
        name = "FileName"
        file_id = "FileID"
        result = Errors.id_should_equal_name(name, file_id)
        assert result == expected_result

    def test_file_type_not_supported(self):
        """
        Given: None
        When: Returning an error message
        Then: Return error message with the input value as a tuple containing error and error code.
        """
        error_statement = "The file type is not supported in the validate command.\n" \
                          "The validate command supports: Integrations, Scripts, Playbooks, " \
                          "Incident fields, Incident types, Indicator fields, Indicator types, Objects fields," \
                          " Object types, Object modules, Images, Release notes, Layouts, Jobs and Descriptions."
        expected_result = (error_statement, "BA102")
        result = Errors.file_type_not_supported()
        assert result == expected_result

    def test_invalid_context_output(self):
        """
        Given: Command Name and Output Name
        When: Returning an error message
        Then: Return error message with the input value as a tuple containing error and error code.
        """
        expected_result = ("Invalid context output for command TestCommand. Output is BadOutput",
                           "IN115")
        command_name = "TestCommand"
        output_name = "BadOutput"
        result = Errors.invalid_context_output(command_name, output_name)
        assert result == expected_result

    def test_wrong_display_name(self):
        """
        Given: Param Name and Param Display
        When: Returning an error message
        Then: Return error message with the input value as a tuple containing error and error code.
        """
        expected_result = ('The display name of the ParamName parameter should be \'ParamDisplay\'',
                           "IN100")
        param_name = "ParamName"
        param_display = "ParamDisplay"
        result = Errors.wrong_display_name(param_name, param_display)
        assert result == expected_result

    def test_image_path_error(self):
        """
        Given: Invalid image path and an alternative valid image path
        When: Returning an error message
        Then: Return error message with the input value as a tuple containing error and error code.
        """
        path = "https://github.com/demisto/content/blob/123/Packs/TestPack/doc_files/test.png"
        alternative_path = "https://github.com/demisto/content/raw/123/Packs/TestPack/doc_files/test.png"
        error_statement = f'Detected following image url:\n{path}\n' \
                          f'Which is not the raw link. You probably want to use the following raw image url:\n' \
                          f'{alternative_path}'
        expected_result = (error_statement, "RM101")
        result = Errors.image_path_error(path, alternative_path)
        assert result == expected_result

    def test_integration_is_skipped(self):
        """
        Given: Name of a skipped integration, with no `skip comment`
        When: Returning an error message
        Then: Compile an error message, without the comment part.
        """
        integration_id = "dummy_integration"
        expected = f"The integration {integration_id} is currently in skipped. Please add working tests and unskip."

        assert Errors.integration_is_skipped(integration_id, skip_comment=None)[0] == expected
        assert Errors.integration_is_skipped(integration_id, skip_comment='')[0] == expected
        assert Errors.integration_is_skipped(integration_id)[0] == expected  # skip_comment argument is None by default

    def test_integration_is_skipped__comment(self):
        integration_id = "dummy_integration"
        skip_comment = "Issue 00000"

        expected = f"The integration {integration_id} is currently in skipped. Please add working tests and " + \
                   f"unskip. Skip comment: {skip_comment}"

        result = Errors.integration_is_skipped(integration_id, skip_comment)
        assert result[0] == expected


# demisto_sdk/commands/common/tests/generic_field_test.py

import copy
from collections import namedtuple

from mock import patch

from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator
from demisto_sdk.tests.test_files.validate_integration_test_valid_types import \
    GENERIC_FIELD


def mock_structure(file_path=None, current_file=None, old_file=None):
    with patch.object(StructureValidator, '__init__', lambda a, b: None):
        structure = StructureValidator(file_path)
        structure.is_valid = True
        structure.scheme_name = 'genericfield'
        structure.file_path = file_path
        structure.current_file = current_file
        structure.old_file = old_file
        structure.prev_ver = 'master'
        structure.branch_name = ''
        structure.quite_bc = False
        structure.skip_schema_check = False
        structure.pykwalify_logs = False
        structure.scheme_name = namedtuple('scheme_name', 'value')(value='genericfield')
        structure.checked_files = set()
        structure.ignored_errors = dict()
        structure.suppress_print = True
        structure.json_file_path = None
        return structure


class TestGenericField:
    def test_simple_generic_field_is_fine(self, pack):
        field = pack.create_generic_field(pack.name, GENERIC_FIELD)
        structure = mock_structure(field.path, field.read_json_as_dict())
        assert structure.is_valid_scheme()

    def test_openended_removed(self, pack):
        generic_field = copy.deepcopy(GENERIC_FIELD)
        generic_field['openEnded'] = True
        field = pack.create_generic_field(pack.name, generic_field)
        structure = mock_structure(field.path, field.read_json_as_dict())
        assert structure.is_valid_scheme()

    def test_no_genericModuleId(self, pack):
        generic_field = copy.deepcopy(GENERIC_FIELD)
        del generic_field['genericModuleId']
        field = pack.create_generic_field(pack.name, generic_field)
        structure = mock_structure(field.path, field.read_json_as_dict())
        assert structure.is_valid_scheme() is False


# demisto_sdk/commands/common/tests/id_test.py

from demisto_sdk.commands.common.configuration import Configuration
from demisto_sdk.commands.common.hook_validations.id import IDSetValidations
from TestSuite.test_tools import ChangeCWD

CONFIG = Configuration()

# Layouts tests consts:
LAYOUTS_CONTAINER_DATA = {'my_layoutscontainer': {
    'id': 'my_layoutscontainer',
    'name': 'my_layoutscontainer',
    'indicatorsDetails': {
            "tabs": [
                {
                    "id": "tjlpilelnw",
                    "name": "Profile",
                    "sections": [
                        {
                            "description": "Incidents that affected the user profile.",
                            "h": 2,
                            "i": "tjlpilelnw-978b0c1e-6739-432d-82d1-3b6641eed99f-tjlpilelnw",
                            "maxW": 3,
                            "minH": 1,
                            "minW": 2,
                            "moved": False,
                            "name": "User incidents",
                            "static": False,
                            "query": "script_to_test",
                            "queryType": "script",
                            "type": "dynamicIndicator",
                            "w": 1,
                            "x": 0,
                            "y": 0
                        }
                    ]
                }
            ]
    }}}
LAYOUT_DATA = {'my-layout': {
    'typename': 'my-layout',
    'scripts': ['script_to_test']
}}


def test_is_incident_type_using_real_playbook__happy_flow():
    """
    Given
        - incident type which has an existing default playbook id.
        - id_set.json

    When
        - is_playbook_found is called with an id_set.json

    Then
        - Ensure that the playbook is in the id set.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    incident_type_data = {
        "Zimperium Event": {
            "playbooks": "Zimperium Incident Enrichment"
        }
    }
    validator.playbook_set = [{'Zimperium Incident Enrichment': {
        'name': 'Zimperium Incident Enrichment',
        'file_path': 'Packs/Zimperium/Playbooks/Zimperium_Incident_Enrichment.yml',
        'fromversion': '5.0.0'}
    }]

    assert validator._is_incident_type_default_playbook_found(incident_type_data=incident_type_data) is True, \
        "The incident type default playbook id does not exist in the id set"


def test_is_incident_type_using_real_playbook__no_matching_playbook_id():
    """
    Given
        - incident type which has a non existing default playbook id.
        - id_set.json

    When
        - is_playbook_found is called with an id_set.json

    Then
        - Ensure that the playbook is in the id set.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    incident_type_data = {
        "Zimperium Event": {
            "playbooks": "a fake playbook id"
        }
    }
    validator.playbook_set = [{'Zimperium Incident Enrichment': {
        'name': 'Zimperium Incident Enrichment',
        'file_path': 'Packs/Zimperium/Playbooks/Zimperium_Incident_Enrichment.yml',
        'fromversion': '5.0.0'}
    }]

    assert validator._is_incident_type_default_playbook_found(incident_type_data=incident_type_data) is False


def test_is_incident_field_using_existing_script_positive():
    """
    Given
        - incident field which has an existing script id.
        - id_set.json

    When
        - is_incident_field_scripts_found is called with an id_set.json

    Then
        - Ensure that the script is in the id set - i.e is_incident_field_scripts_found returns True.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    incident_field_data = {'Incident_field_test': {
        'id': 'Incident_field_test',
        'name': 'Incident_field_test',
        'scripts': ['script_to_test']
    }
    }
    validator.script_set = [{"script_to_test": {
        "name": "script_to_test",
                "file_path": "Packs/DeveloperTools/TestPlaybooks/script-script_to_test.yml",
                "fromversion": "5.0.0",
                "pack": "DeveloperTools"
    }
    }]

    assert validator._is_incident_field_scripts_found(incident_field_data=incident_field_data) is True, \
        "The incident field's script id is in the id set thus result should be True."


def test_is_incident_field_using_existing_script_negative():
    """
    Given
        - incident field which has a script id that doesn't exist.
        - id_set.json

    When
        - is_incident_field_scripts_found is called with an id_set.json

    Then
        - Ensure that the script is not in the id set - i.e is_incident_field_scripts_found returns False.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    incident_field_data = {'Incident_field_test': {
        'id': 'Incident_field_test',
        'name': 'Incident_field_test',
        'scripts': ['a fake script id']
    }
    }

    validator.script_set = [{"script_to_test": {
        "name": "script_to_test",
                "file_path": "Packs/DeveloperTools/TestPlaybooks/script-script_to_test.yml",
                "fromversion": "5.0.0",
                "pack": "DeveloperTools"
    }
    }]

    assert validator._is_incident_field_scripts_found(incident_field_data=incident_field_data) is False, \
        "The incident field's script id is not in the id set thus result should be False."


def test_is_incident_field_using_existing_script_with_command_positive():
    """
    Given
        - incident field which has an existing script id and command.
        - id_set.json

    When
        - is_incident_field_scripts_found is called with an id_set.json

    Then
        - Ensure that the script is in the id set and command - i.e is_incident_field_scripts_found returns True.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    incident_field_data = {'Incident_field_test': {
        'id': 'Incident_field_test',
        'name': 'Incident_field_test',
        'scripts': ['script_to_test|||command_to_test']
    }
    }

    validator.script_set = [{"script_to_test_1": {
        "name": "script_to_test_1",
                "file_path": "Packs/DeveloperTools/TestPlaybooks/script-script_to_test_1.yml",
                "fromversion": "5.0.0",
                "pack": "DeveloperTools"
    }
    }]

    validator.integration_set = [
        {
            "script_to_test": {
                "name": "script_to_test",
                "file_path": "Packs/Slack/Integrations/script_to_test/script_to_test.yml",
                "fromversion": "5.0.0",
                "commands": [
                    "command_to_test"
                ],
                "pack": "script_to_test"
            }
        }]

    assert validator._is_incident_field_scripts_found(incident_field_data=incident_field_data) is True, \
        "The incident field's script id is in the id set thus result should be True."


def test_is_incident_field_using_existing_script_with_command_negative():
    """
    Given
        - incident field which has an existing script id and non-existing command.
        - id_set.json

    When
        - is_incident_field_scripts_found is called with an id_set.json

    Then
        - Ensure that the script is in the id set and non-existing command - i.e is_incident_field_scripts_found returns False.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    incident_field_data = {'Incident_field_test': {
        'id': 'Incident_field_test',
        'name': 'Incident_field_test',
        'scripts': ['script_to_test|||command_to_test']
    }
    }

    validator.script_set = [{"script_to_test_1": {
        "name": "script_to_test_1",
                "file_path": "Packs/DeveloperTools/TestPlaybooks/script-script_to_test_1.yml",
                "fromversion": "5.0.0",
                "pack": "DeveloperTools"
    }
    }]

    validator.integration_set = [
        {
            "script_to_test": {
                "name": "script_to_test",
                "file_path": "Packs/Slack/Integrations/script_to_test/script_to_test.yml",
                "fromversion": "5.0.0",
                "commands": [
                    "command_to_test_1"
                ],
                "pack": "script_to_test"
            }
        }]

    assert validator._is_incident_field_scripts_found(incident_field_data=incident_field_data) is False, \
        "The incident field's script id is in the id set but the command not thus result should be False."


def test_is_incident_field_using_existing_script_no_scripts():
    """
    Given
        - incident field without scripts.
        - id_set.json

    When
        - is_incident_field_scripts_found is called with an id_set.json

    Then
        - Ensure is_incident_field_scripts_found returns True.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    incident_field_data = {'Incident_field_test': {
        'id': 'Incident_field_test',
        'name': 'Incident_field_test'
    }
    }

    validator.script_set = [{"script_to_test": {
        "name": "script_to_test",
                "file_path": "Packs/DeveloperTools/TestPlaybooks/script-script_to_test.yml",
                "fromversion": "5.0.0",
                "pack": "DeveloperTools"
    }
    }]

    assert validator._is_incident_field_scripts_found(incident_field_data=incident_field_data) is True, \
        "The incident field doesn't have any scripts thus the result should be True."


def test_is_layouts_container_using_existing_script_positive():
    """
    Given
        - layouts container which has an existing script id.
        - id_set.json

    When
        - is_layouts_container_scripts_found is called with an id_set.json

    Then
        - Ensure that the script is in the id set - i.e is_layouts_container_scripts_found returns True.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.script_set = [{"script_to_test": {
        "name": "script_to_test",
                "file_path": "Packs/DeveloperTools/TestPlaybooks/script-script_to_test.yml",
                "fromversion": "6.0.0",
                "pack": "DeveloperTools"
    }
    }]

    assert validator._is_layouts_container_scripts_found(layouts_container_data=LAYOUTS_CONTAINER_DATA) is True, \
        "The layouts container's script id is in the id set thus result should be True."


def test_is_layouts_container_using_existing_script_negative():
    """
    Given
        - layouts container which has a script that doesn't exist.
        - id_set.json

    When
        - is_layouts_container_scripts_found is called with an id_set.json

    Then
        - Ensure that the script is not in the id set - i.e is_layouts_container_scripts_found returns False.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.script_set = [{"other_script_id": {
        "name": "other_script_id",
                "file_path": "Packs/DeveloperTools/TestPlaybooks/script-other_script_id.yml",
                "fromversion": "6.0.0",
                "pack": "DeveloperTools"
    }
    }]

    assert validator._is_layouts_container_scripts_found(layouts_container_data=LAYOUTS_CONTAINER_DATA) is False, \
        "The layouts container's script id is not in the id set thus result should be False."


def test_is_layouts_container_using_existing_script_no_scripts():
    """
    Given
        - layouts container which has no scripts.
        - id_set.json

    When
        - is_layouts_container_scripts_found is called with an id_set.json

    Then
        - Ensure that is_layouts_container_scripts_found returns True.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    layouts_container_data_without_scripts = {'my_layoutscontainer': {
        'id': 'my_layoutscontainer',
        'name': 'my_layoutscontainer',
        'indicatorsDetails': {
            "tabs": [
                {
                    "id": "tjlpilelnw",
                    "name": "Profile",
                    "sections": [
                        {
                            "description": "Incidents that affected the user profile.",
                            "h": 2,
                            "i": "tjlpilelnw-978b0c1e-6739-432d-82d1-3b6641eed99f-tjlpilelnw",
                            "maxW": 3,
                            "minH": 1,
                            "minW": 2,
                            "moved": False,
                            "name": "User incidents",
                            "static": False,
                            "type": "dynamicIndicator",
                            "w": 1,
                            "x": 0,
                            "y": 0
                        }
                    ]
                }
            ]
        }}}

    validator.script_set = [{"script_to_test": {
        "name": "script_to_test",
                "file_path": "Packs/DeveloperTools/TestPlaybooks/script-script_to_test.yml",
                "fromversion": "6.0.0",
                "pack": "DeveloperTools"
    }
    }]

    assert validator._is_layouts_container_scripts_found(layouts_container_data=layouts_container_data_without_scripts)\
        is True, "The layouts container's doesn't have any scripts thus result should be False."


def test_is_layout_using_existing_script_positive():
    """
    Given
        - layout which has an existing script id.
        - id_set.json

    When
        - is_layout_scripts_found is called with an id_set.json

    Then
        - Ensure that the script is in the id set - i.e is_layout_scripts_found returns True.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.script_set = [{"script_to_test": {
        "name": "script_to_test",
                "file_path": "Packs/DeveloperTools/TestPlaybooks/script-script_to_test.yml",
                "fromversion": "6.0.0",
                "pack": "DeveloperTools"
    }
    }]

    assert validator._is_layout_scripts_found(layout_data=LAYOUT_DATA) is True, \
        "The layout's script id is in the id set thus result should be True."


def test_is_layout_using_existing_script_negative():
    """
    Given
        - layout which has has a script that doesn't exist.
        - id_set.json

    When
        - is_layout_scripts_found is called with an id_set.json

    Then
        - Ensure that the script is not in the id set - i.e is_layout_scripts_found returns False.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.script_set = [{"other_script_id": {
        "name": "other_script_id",
                "file_path": "Packs/DeveloperTools/TestPlaybooks/script-other_script_id.yml",
                "fromversion": "6.0.0",
                "pack": "DeveloperTools"
    }
    }]

    assert validator._is_layout_scripts_found(layout_data=LAYOUT_DATA) is False, \
        "The layout's script id is not in the id set thus result should be False."


def test_is_layout_using_existing_script_no_scripts():
    """
    Given
        - layout which has no scripts.
        - id_set.json

    When
        - is_layout_scripts_found is called with an id_set.json

    Then
        - Ensure that is_layout_scripts_found returns True.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    layout_data_without_scripts = {'my-layout': {
        'typename': 'my-layout',
        'scripts': []
    }}

    validator.script_set = [{"script_to_test": {
        "name": "script_to_test",
                "file_path": "Packs/DeveloperTools/TestPlaybooks/script-script_to_test.yml",
                "fromversion": "6.0.0",
                "pack": "DeveloperTools"
    }
    }]

    assert validator._is_layout_scripts_found(layout_data=layout_data_without_scripts) is True, \
        "The layout doesn't have any scripts thus result should be True."


def test_is_layout_using_existing_script_ignore_builtin_scripts():
    """
    Given
        - layout which has a builtin script id.
        - id_set.json

    When
        - is_layout_scripts_found is called with an id_set.json

    Then
        - Ensure that the builtin script is ignored - i.e is_layout_scripts_found returns True.
    """

    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.script_set = [{"script_to_test": {
        "name": "script_to_test",
        "file_path": "Packs/DeveloperTools/TestPlaybooks/script-script_to_test.yml",
        "fromversion": "6.0.0",
        "pack": "DeveloperTools"
    }
    }]

    layout_data = {'my-layout': {
        'typename': 'my-layout',
        'scripts': ['Builtin|||removeIndicatorField']
    }}

    assert validator._is_layout_scripts_found(layout_data=layout_data) is True, \
        "The layout's script id is a builtin script therefore ignored and thus the result should be True."


def test_is_layout_using_script_validate_integration_commands_scripts():
    """
    Given
        - layout which has an integration command script id .
        - id_set.json

    When
        - is_layout_scripts_found is called with an id_set.json

    Then
        - Ensure that the integration command script is not ignored - i.e is_layout_scripts_found returns True.
    """

    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.script_set = [{"script_to_test": {
        "name": "script_to_test",
        "file_path": "Packs/DeveloperTools/TestPlaybooks/script-script_to_test.yml",
        "fromversion": "6.0.0",
        "pack": "DeveloperTools"
    }
    }]
    validator.integration_set = [
        {
            "SlackV2": {
                "name": "SlackV2",
                "file_path": "Packs/Slack/Integrations/Slack/Slack.yml",
                "fromversion": "5.0.0",
                "commands": [
                    "send-notification",
                    "slack-send"
                ],
                "pack": "Slack"
            }
        },
        {
            "Mail Sender (New)": {
                "name": "Mail Sender (New)",
                "file_path": "Packs/MailSenderNew/Integrations/MailSenderNew/MailSenderNew.yml",
                "commands": [
                    "send-mail"
                ],
                "tests": [
                    "Mail Sender (New) Test"
                ],
                "pack": "MailSenderNew"
            }
        }
    ]

    layout_data = {'my-layout': {
        'typename': 'my-layout',
        'scripts': ['SlackV2|||send-notification', 'Mail Sender (New)|||send-mail']
    }}

    assert validator._is_layout_scripts_found(layout_data=layout_data) is True, \
        "The layout's script id is an integration command which is real command."


def test_is_layout_using_script_validate_integration_commands_scripts_on_wrong_command():
    """
    Given
        - layout which has an integration command script id which is not existing in the id_set.json .
        - id_set.json

    When
        - is_layout_scripts_found is called with an id_set.json

    Then
        - Ensure that the integration command script is not ignored - i.e is_layout_scripts_found returns False.
    """

    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.script_set = [{"script_to_test": {
        "name": "script_to_test",
        "file_path": "Packs/DeveloperTools/TestPlaybooks/script-script_to_test.yml",
        "fromversion": "6.0.0",
        "pack": "DeveloperTools"
    }
    }]
    validator.integration_set = [
        {
            "SlackV2": {
                "name": "SlackV2",
                "file_path": "Packs/Slack/Integrations/Slack/Slack.yml",
                "fromversion": "5.0.0",
                "commands": [
                    "slack-send"
                ],
                "pack": "Slack"
            }
        },
        {
            "Mail Sender (New)": {
                "name": "Mail Sender (New)",
                "file_path": "Packs/MailSenderNew/Integrations/MailSenderNew/MailSenderNew.yml",
                "commands": [
                    "send-mail"
                ],
                "tests": [
                    "Mail Sender (New) Test"
                ],
                "pack": "MailSenderNew"
            }
        }
    ]

    layout_data = {'my-layout': {
        'typename': 'my-layout',
        'scripts': ['SlackV2|||send-notification', 'Mail Sender (New)|||send-mail']
    }}

    assert validator._is_layout_scripts_found(layout_data=layout_data) is False, \
        "The layout's script id is an integration command which is not real command."


def test_is_non_real_command_found__happy_flow():
    """
    Given
        - script which has a valid command.

    When
        - is_non_real_command_found is called

    Then
        - Ensure that the scripts depend-on commands are valid.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    script_data = {
        'name': 'OktaUpdateUser',
        'fidepends-le_path': 'Packs/DeprecatedContent/Scripts/script-OktaUpdateUser.yml',
        'fromversion': '5.0.0', 'deprecated': True, 'depends_on': ['okta-update-user'],
        'tests': ['No test - deprecated script with no test prior'], 'pack': 'DeprecatedContent'
    }

    assert validator._is_non_real_command_found(script_data=script_data) is True, \
        "The script has a non real command"


def test_is_non_real_command_found__bad_command_name():
    """
    Given
        - script which has a non valid command.

    When
        - is_non_real_command_found is called

    Then
        - Ensure that the scripts depend-on commands are non valid.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    script_data = {
        'name': 'OktaUpdateUser',
        'fidepends-le_path': 'Packs/DeprecatedContent/Scripts/script-OktaUpdateUser.yml',
        'fromversion': '5.0.0', 'deprecated': True, 'depends_on': ['okta-update-user', 'okta-update-user-copy'],
        'tests': ['No test - deprecated script with no test prior'], 'pack': 'DeprecatedContent'
    }

    assert validator._is_non_real_command_found(script_data=script_data) is False, \
        "The script has a non real command"


def test_is_non_real_command_found__no_depend_on_name():
    """
    Given
        - script which has no executeCommand.

    When
        - is_non_real_command_found is called

    Then
        - Ensure that the scripts depend-on commands are valid.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    script_data = {
        'name': 'OktaUpdateUser',
        'fidepends-le_path': 'Packs/CommonScripts/Scripts/NoExecuteCommand.yml',
        'fromversion': '5.0.0', 'deprecated': True,
        'tests': ['No test'], 'pack': 'CommonScripts'
    }

    assert validator._is_non_real_command_found(script_data=script_data) is True, \
        "The script has a non real command"


def test_is_integration_classifier_and_mapper_found__exist():
    """
    Given
        - integration which has classifier and mapper.

    When
        - _is_integration_handled_in_classifier_and_mapper is called

    Then
        - Ensure that the integration classifier and mapper were found.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.mappers_set = [{
        "Claroty-mapper":
            {
                "name": "Claroty - Incoming Mapper",
                "file_path": "Packs/Claroty/Classifiers/classifier-mapper-incoming-Claroty.json",
                "fromversion": "6.0.0",
                "pack": "Claroty",
                "incident_types": [
                    "Claroty Integrity Incident",
                    "Claroty Security Incident",
                    "Claroty IT Incident"
                ],
                "incident_fields": [
                    "Claroty Related Assets",
                    "Claroty Category",
                    "Claroty Alert Status",
                    "Claroty Network ID",
                    "Claroty Resource ID",
                    "Claroty Alert Type",
                    "Claroty Alert Resolved"
                ]
            }
    }]

    validator.classifiers_set = [
        {"Claroty":
            {
                "name": "Claroty - Classifier",
                "file_path": "Packs/Claroty/Classifiers/classifier-Claroty.json",
                "fromversion": "6.0.0",
                "pack": "Claroty",
                "incident_types": [
                    "Claroty Integrity Incident",
                    "Claroty Security Incident"
                ]
            }
         }
    ]

    integration_data = {
        "name": "Claroty",
        "file_path": "Packs/Claroty/Integrations/Claroty/Claroty.yml",
        "fromversion": "5.0.0",
        "commands": [
            "claroty-get-assets",
            "claroty-query-alerts",
            "claroty-resolve-alert",
            "claroty-get-single-alert"
        ],
        "pack": "Claroty",
        "classifiers": "Claroty",
        "mappers": [
            "Claroty-mapper"
        ]

    }

    assert validator._is_integration_classifier_and_mapper_found(integration_data=integration_data) is True, \
        "The incident classifier and mapper were not found"


def test_is_integration_classifier_and_mapper_found__mapper_not_exist():
    """
    Given
        - integration which has classifier and mapper.

    When
        - _is_integration_handled_in_classifier_and_mapper is called

    Then
        - Ensure that the integration mapper was not found.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.mappers_set = [{
        "Claroty-mapper_wrong":
            {
                "name": "Claroty - Incoming Mapper",
                "file_path": "Packs/Claroty/Classifiers/classifier-mapper-incoming-Claroty.json",
                "fromversion": "6.0.0",
                "pack": "Claroty",
                "incident_types": [
                    "Claroty Integrity Incident",
                    "Claroty Security Incident",
                    "Claroty IT Incident"
                ],
                "incident_fields": [
                    "Claroty Related Assets",
                    "Claroty Category",
                    "Claroty Alert Status",
                    "Claroty Network ID",
                    "Claroty Resource ID",
                    "Claroty Alert Type",
                    "Claroty Alert Resolved"
                ]
            }
    }]

    validator.classifiers_set = [{
        "Claroty":
            {
                "name": "Claroty - Classifier",
                "file_path": "Packs/Claroty/Classifiers/classifier-Claroty.json",
                "fromversion": "6.0.0",
                "pack": "Claroty",
                "incident_types": [
                    "Claroty Integrity Incident",
                    "Claroty Security Incident"
                ]
            }
    }
    ]

    integration_data = {
        "name": "Claroty",
        "file_path": "Packs/Claroty/Integrations/Claroty/Claroty.yml",
        "fromversion": "5.0.0",
        "commands": [
            "claroty-get-assets",
            "claroty-query-alerts",
            "claroty-resolve-alert",
            "claroty-get-single-alert"
        ],
        "pack": "Claroty",
        "classifiers": "Claroty",
        "mappers": [
            "Claroty-mapper"
        ]

    }

    assert validator._is_integration_classifier_and_mapper_found(integration_data=integration_data) is False, \
        "The incident mapper was found"


def test_is_integration_classifier_and_mapper_found__classifier_not_exist():
    """
    Given
        - integration which has classifier and mapper.

    When
        - _is_integration_handled_in_classifier_and_mapper is called

    Then
        - Ensure that the integration classifier was not found.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.mappers_set = [{
        "Claroty-mapper":
            {
                "name": "Claroty - Incoming Mapper",
                "file_path": "Packs/Claroty/Classifiers/classifier-mapper-incoming-Claroty.json",
                "fromversion": "6.0.0",
                "pack": "Claroty",
                "incident_types": [
                    "Claroty Integrity Incident",
                    "Claroty Security Incident",
                    "Claroty IT Incident"
                ],
                "incident_fields": [
                    "Claroty Related Assets",
                    "Claroty Category",
                    "Claroty Alert Status",
                    "Claroty Network ID",
                    "Claroty Resource ID",
                    "Claroty Alert Type",
                    "Claroty Alert Resolved"
                ]
            }
    }]

    validator.classifiers_set = [{
        "Claroty_wrong_classifier":
            {
                "name": "Claroty - Classifier",
                "file_path": "Packs/Claroty/Classifiers/classifier-Claroty.json",
                "fromversion": "6.0.0",
                "pack": "Claroty",
                "incident_types": [
                    "Claroty Integrity Incident",
                    "Claroty Security Incident"
                ]
            }
    }
    ]

    integration_data = {
        "name": "Claroty",
        "file_path": "Packs/Claroty/Integrations/Claroty/Claroty.yml",
        "fromversion": "5.0.0",
        "commands": [
            "claroty-get-assets",
            "claroty-query-alerts",
            "claroty-resolve-alert",
            "claroty-get-single-alert"
        ],
        "pack": "Claroty",
        "classifiers": "Claroty",
        "mappers": [
            "Claroty-mapper"
        ]

    }

    assert validator._is_integration_classifier_and_mapper_found(integration_data=integration_data) is False, \
        "The incident classifier was found"


def test_is_classifier_incident_types_found__exists():
    """
    Given
        - classifier which has incident types.

    When
        - _is_classifier_incident_types_found is called

    Then
        - Ensure that the classifier incident types were found.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.incident_types_set = [
        {
            "Claroty Integrity Incident": {
                "name": "Claroty Integrity Incident",
                "file_path": "Packs/Claroty/IncidentTypes/incidenttype-Claroty_Integrity_Incident.json",
                "fromversion": "5.0.0",
                "pack": "Claroty",
                "playbooks": "Claroty Incident"
            }
        },
        {
            "Claroty Security Incident": {
                "name": "Claroty Security Incident",
                "file_path": "Packs/Claroty/IncidentTypes/incidenttype-Claroty_Security_Incident.json",
                "fromversion": "5.0.0",
                "pack": "Claroty",
                "playbooks": "Claroty Incident"
            }
        }
    ]

    classifier_data = {
        "name": "Claroty - Classifier",
        "file_path": "Packs/Claroty/Classifiers/classifier-Claroty.json",
        "fromversion": "6.0.0",
        "pack": "Claroty",
        "incident_types": [
            "Claroty Integrity Incident",
            "Claroty Security Incident"
        ]
    }

    assert validator._is_classifier_incident_types_found(classifier_data=classifier_data) is True, \
        "The classifier incidenttypes were not found"


def test_is_classifier_incident_types_found__missing_classifier():
    """
    Given
        - classifier which has incident types.

    When
        - _is_classifier_incident_types_found is called

    Then
        - Ensure that the missing incident type was not found.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.incident_types_set = [
        {
            "Claroty Security Incident": {
                "name": "Claroty Security Incident",
                "file_path": "Packs/Claroty/IncidentTypes/incidenttype-Claroty_Security_Incident.json",
                "fromversion": "5.0.0",
                "pack": "Claroty",
                "playbooks": "Claroty Incident"
            }
        }
    ]

    classifier_data = {
        "name": "Claroty - Classifier",
        "file_path": "Packs/Claroty/Classifiers/classifier-Claroty.json",
        "fromversion": "6.0.0",
        "pack": "Claroty",
        "incident_types": [
            "Claroty Integrity Incident",
            "Claroty Security Incident"
        ]
    }

    assert validator._is_classifier_incident_types_found(classifier_data=classifier_data) is False, \
        "The classifier incidenttypes was found"


def test_is_mapper_incident_types_found__exists():
    """
    Given
        - mapper which has incident types.

    When
        - _is_mapper_incident_types_found is called

    Then
        - Ensure that the mapper incident types were found.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.incident_types_set = [
        {
            "Claroty Integrity Incident": {
                "name": "Claroty Integrity Incident",
                "file_path": "Packs/Claroty/IncidentTypes/incidenttype-Claroty_Integrity_Incident.json",
                "fromversion": "5.0.0",
                "pack": "Claroty",
                "playbooks": "Claroty Incident"
            }
        },
        {
            "Claroty Security Incident": {
                "name": "Claroty Security Incident",
                "file_path": "Packs/Claroty/IncidentTypes/incidenttype-Claroty_Security_Incident.json",
                "fromversion": "5.0.0",
                "pack": "Claroty",
                "playbooks": "Claroty Incident"
            }
        }
    ]

    mapper_data = {
        "name": "Claroty - Incoming Mapper",
        "file_path": "Packs/Claroty/Classifiers/classifier-mapper-incoming-Claroty.json",
        "fromversion": "6.0.0",
        "pack": "Claroty",
        "incident_types": [
            "Claroty Integrity Incident",
            "Claroty Security Incident"
        ],
        "incident_fields": [
            "Claroty Related Assets",
            "Claroty Category",
            "Claroty Alert Status",
            "Claroty Network ID",
            "Claroty Resource ID",
            "Claroty Alert Type",
            "Claroty Alert Resolved"
        ]
    }

    assert validator._is_mapper_incident_types_found(mapper_data=mapper_data) is True, \
        "The mapper incidenttypes were not found"


def test_is_mapper_incident_types_found__missing_classifier():
    """
    Given
        - mapper which has incident types.

    When
        - _is_mapper_incident_types_found is called

    Then
        - Ensure that the missing incident type was not found.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.incident_types_set = [
        {
            "Claroty Security Incident": {
                "name": "Claroty Security Incident",
                "file_path": "Packs/Claroty/IncidentTypes/incidenttype-Claroty_Security_Incident.json",
                "fromversion": "5.0.0",
                "pack": "Claroty",
                "playbooks": "Claroty Incident"
            }
        }
    ]

    mapper_data = {
        "name": "Claroty - Incoming Mapper",
        "file_path": "Packs/Claroty/Classifiers/classifier-mapper-incoming-Claroty.json",
        "fromversion": "6.0.0",
        "pack": "Claroty",
        "incident_types": [
            "Claroty Integrity Incident",
            "Claroty Security Incident"
        ],
        "incident_fields": [
            "Claroty Related Assets",
            "Claroty Category",
            "Claroty Alert Status",
            "Claroty Network ID",
            "Claroty Resource ID",
            "Claroty Alert Type",
            "Claroty Alert Resolved"
        ]
    }

    assert validator._is_mapper_incident_types_found(mapper_data=mapper_data) is False, \
        "The mapper incidenttypes was found"


def test_is_unique_file_valid_in_set(pack):
    """
    Given
        - pack with pack_metadata file.
    When
        - is_unique_file_valid_in_set is called
    Then
        - Ensure it is valid and no error is returned.
    """
    pack_metadata_data = {
        "VMware": {
            "name": "VMware",
            "current_version": "1.1.0",
            "author": "Cortex XSOAR",
            "certification": "certified",
            "tags": [],
            "use_cases": [],
            "categories": [
                "IT Services"
            ],
            "id": "VMware"
        }
    }
    pack.pack_metadata.write_json(pack_metadata_data)
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)
    is_valid, error = validator.is_unique_file_valid_in_set(pack_path=pack.path)
    assert is_valid
    assert not error


def test_new_valid_is_pack_display_name_already_exist():
    """
    Given
        - pack_metadata file with a pack name that does not exist in our repo.
    When
        - _is_pack_display_name_already_exist is called
    Then
        - Ensure it is valid and no error is returned.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.packs_set = {
        "VMware": {
            "name": "VMware",
            "current_version": "1.1.0",
            "author": "Cortex XSOAR",
            "certification": "certified",
            "tags": [],
            "use_cases": [],
            "categories": [
                "IT Services"
            ],
            "id": "VMware"
        }
    }

    pack_metadata_data = {
        "VMware": {
            "name": "VMware",
            "current_version": "1.1.0",
            "author": "Cortex XSOAR",
            "certification": "certified",
            "tags": [],
            "use_cases": [],
            "categories": [
                "IT Services"
            ],
            "id": "VMware"
        }
    }
    is_valid, error = validator._is_pack_display_name_already_exist(pack_metadata_data=pack_metadata_data)
    assert is_valid
    assert not error


def test_valid_is_pack_display_name_already_exist():
    """
    Given
        - pack_metadata file with a pack name that does not exist in our repo.
    When
        - _is_pack_display_name_already_exist is called
    Then
        - Ensure it is valid and no error is returned.
    """

    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.packs_set = {
        "VMware": {
            "name": "VMware",
            "current_version": "1.1.0",
            "author": "Cortex XSOAR",
            "certification": "certified",
            "tags": [],
            "use_cases": [],
            "categories": [
                "IT Services"
            ],
            "id": "VMware"
        }
    }

    pack_metadata_data = {
        "VMware2": {
            "name": "VMware2",
            "current_version": "1.1.0",
            "author": "Cortex XSOAR",
            "certification": "certified",
            "tags": [],
            "use_cases": [],
            "categories": [
                "IT Services"
            ],
            "id": "VMware"
        }
    }
    is_valid, error = validator._is_pack_display_name_already_exist(pack_metadata_data=pack_metadata_data)
    assert is_valid
    assert not error


def test_invalid_is_pack_display_name_already_exist():
    """
    Given
        - pack_metadata file with a pack name that already exists in our repo.
    When
        - _is_pack_display_name_already_exist is called
    Then
        - Ensure it is invalid and the error message is returned.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.packs_set = {
        "VMware": {
            "name": "VMware",
            "current_version": "1.1.0",
            "author": "Cortex XSOAR",
            "certification": "certified",
            "tags": [],
            "use_cases": [],
            "categories": [
                "IT Services"
            ],
            "id": "VMware"
        }
    }

    pack_metadata_data = {
        "VMwareV2": {
            "name": "VMware",
            "current_version": "1.1.0",
            "author": "Cortex XSOAR",
            "certification": "certified",
            "tags": [],
            "use_cases": [],
            "categories": [
                "IT Services"
            ],
            "id": "VMware"
        }
    }
    is_valid, error = validator._is_pack_display_name_already_exist(pack_metadata_data=pack_metadata_data)
    assert not is_valid
    assert error == ("A pack named: VMware already exists in content repository, change the pack's "
                     "name in the metadata file.", 'PA122')


def test_new_invalid_is_pack_display_name_already_exist():
    """
    Given
        - pack_metadata file with a pack name that already exists in our repo.
    When
        - _is_pack_display_name_already_exist is called
    Then
        - Ensure it is invalid and the error message is returned.
    """
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)

    validator.packs_set = {
        "CiscoEmailSecurity": {
            "name": "Cisco Email Security",
            "current_version": "1.1.0",
            "author": "Cortex XSOAR",
            "certification": "certified",
            "tags": [],
            "use_cases": [],
            "categories": [
                "IT Services"
            ],
            "id": "VMware"
        }
    }

    pack_metadata_data = {
        "CiscoEmailSecurityV2": {
            "name": "Cisco Email Security",
            "current_version": "1.1.0",
            "author": "Cortex XSOAR",
            "certification": "certified",
            "tags": [],
            "use_cases": [],
            "categories": [
                "IT Services"
            ],
            "id": "VMware"
        }
    }
    is_valid, error = validator._is_pack_display_name_already_exist(pack_metadata_data=pack_metadata_data)
    assert not is_valid
    assert error == ("A pack named: Cisco Email Security already exists in content repository, change the pack's "
                     "name in the metadata file.", 'PA122')


class TestPlaybookEntitiesVersionsValid:
    validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)
    playbook_path = "Packs/Example/Playbooks/playbook-Example_Playbook.yml"
    playbook_with_valid_versions = {"Example Playbook": {
        "name": "Example Playbook",
        "file_path": playbook_path,
        "fromversion": "5.5.0",
        "pack": "Example",
        "implementing_scripts": [
            "Script_version_5",
            "Script_no_version",
            "Script_version_5_5"
        ],
        "implementing_playbooks": [
            "SubPlaybook_version_5",
            "SubPlaybook_no_version",
            "SubPlaybook_version_5_5",
            "playbook_dup"
        ],
        "command_to_integration": {
            "test-command": [
                "Integration_version_5",
                "Integration_version_4"
            ]
        }
    }}

    playbook_with_invalid_scripts_version = {"Example Playbook": {
        "name": "Example Playbook",
        "file_path": playbook_path,
        "fromversion": "5.0.0",
        "pack": "Example",
        "implementing_scripts": [
            "Script_version_5_5"
        ]
    }}
    playbook_with_invalid_sub_playbook_version_from_version_5_0_0 = {"Example Playbook": {
        "name": "Example Playbook",
        "file_path": playbook_path,
        "fromversion": "5.0.0",
        "pack": "Example",
        "implementing_playbooks": [
            "SubPlaybook_version_5_5",
            "playbook_dup"
        ]
    }}
    playbook_with_invalid_sub_playbook_version_from_version_6_0_0 = {"Example Playbook": {
        "name": "Example Playbook",
        "file_path": playbook_path,
        "fromversion": "6.0.0",
        "pack": "Example",
        "implementing_playbooks": [
            "SubPlaybook_version_6_5"
        ]
    }}
    playbook_with_sub_playbook_not_in_id_set = {"Example Playbook": {
        "name": "Example Playbook",
        "file_path": playbook_path,
        "fromversion": "5.0.0",
        "pack": "Example",
        "implementing_playbooks": [
            "SubPlaybook_not_in_id_set"
        ]
    }}
    playbook_with_valid_sub_playbook_name = {"Example Playbook": {
        "name": "Example Playbook",
        "file_path": playbook_path,
        "fromversion": "5.0.0",
        "pack": "Example",
        "implementing_playbooks": [
            "SubPlaybook_version_5_5"
        ]
    }}
    playbook_with_invalid_sub_playbook_name = {"Example Playbook": {
        "name": "Example Playbook",
        "file_path": playbook_path,
        "fromversion": "5.0.0",
        "pack": "Example",
        "implementing_playbooks": [
            "SubPlaybook_version_5_5 "
        ]
    }}
    id_set = {
        'playbooks': [
            {
                'playbook_dup': {
                    'name': 'test',
                    'fromversion': "5.0.0",
                    'toversion': "5.9.9",
                    "file_path": playbook_path,
                    "command_to_integration": {
                        "test-command": [
                            "Integration_version_4",
                            "Integration_version_5"
                        ]
                    }
                }
            },
            {
                'playbook_dup': {
                    'name': 'test',
                    'fromversion': "6.0.0",
                    "file_path": playbook_path,
                    "command_to_integration": {
                        "test-command": [
                            "Integration_version_4",
                            "Integration_version_5"
                        ]
                    }
                }
            },
            {
                'SubPlaybook_version_5': {
                    'name': 'SubPlaybook_version_5',
                    'fromversion': "5.0.0",
                    "file_path": playbook_path,
                }
            },
            {
                'SubPlaybook_no_version': {
                    'name': 'SubPlaybook_no_version',
                    'fromversion': "",
                    "file_path": playbook_path,
                }
            },
            {
                'SubPlaybook_version_5_5': {
                    'name': 'SubPlaybook_version_5_5',
                    'fromversion': "5.5.0",
                    "file_path": playbook_path,
                }
            },
            {
                'SubPlaybook_version_6_5': {
                    'name': 'SubPlaybook_version_6_5',
                    'fromversion': "6.5.0",
                    "file_path": playbook_path,
                }
            },
            {
                'Example Playbook': {
                    'name': 'test',
                    'fromversion': "5.5.0",
                    "file_path": playbook_path,
                    "command_to_integration": {
                        "test-command": [
                            "Integration_version_4",
                            "Integration_version_5"
                        ]
                    }
                }
            }
        ],
        'integrations': [
            {
                'Integration_version_5': {
                    'fromversion': "5.0.0"
                }
            },
            {
                'Integration_version_4': {
                    'fromversion': "4.0.0"
                }
            }
        ],
        'scripts': [
            {
                'Script_version_5': {
                    'fromversion': "5.0.0"
                }
            },
            {
                'Script_no_version': {
                    'fromversion': ""
                }
            },
            {
                'Script_version_5_5': {
                    'fromversion': "5.5.0"
                }
            }
        ],
    }

    def test_are_playbook_entities_versions_valid_scripts_and_subplaybooks(self, repo, mocker):
        """

        Given
            - an id_set file
            - a Playbook those entities:
                * implementing_scripts
                * implementing_playbooks

        When
            - _are_playbook_entities_versions_valid is called

        Then
            - Validates that each entity version match the playbook version.

        """
        pack = repo.create_pack("Pack1")
        playbook = pack.create_playbook('MyPlay')
        playbook.create_default_playbook()
        self.validator.playbook_set = self.id_set["playbooks"]
        self.validator.integration_set = self.id_set["integrations"]
        self.validator.script_set = self.id_set["scripts"]

        with ChangeCWD(repo.path):
            is_sub_playbook_invalid, error = self.validator._are_playbook_entities_versions_valid(
                self.playbook_with_sub_playbook_not_in_id_set, playbook.yml.path)
            assert not is_sub_playbook_invalid
            assert "do not exist in the id_set" in error

            # all playbook's entities has valid versions
            is_playbook_version_valid, error = self.validator._are_playbook_entities_versions_valid(
                self.playbook_with_valid_versions, playbook.yml.path)
            assert is_playbook_version_valid
            assert error is None

            # playbook uses scripts with invalid versions
            is_script_version_invalid, error = self.validator._are_playbook_entities_versions_valid(
                self.playbook_with_invalid_scripts_version, playbook.yml.path)
            assert not is_script_version_invalid

            # playbook uses sub playbooks with invalid versions
            is_sub_playbook_version_invalid, error = self.validator._are_playbook_entities_versions_valid(
                self.playbook_with_invalid_sub_playbook_version_from_version_5_0_0, playbook.yml.path)
            assert not is_sub_playbook_version_invalid

    def test_are_playbook_entities_versions_valid_skip_unavailable(self, repo, mocker):
        """
        Given
            - an id_set file
            - a Playbook that is implemented by sub-playbooks with mismatched fromversions:
                - once with skipunavailable, and from version 5.0.0 - should fail
                - once with skipunavailable, and from version 6.0.0 - shouldn't fail
                - once without skipunavailable - should fail

        When
            - _are_playbook_entities_versions_valid is called

        Then
            - Validates that validation fails when skipunavailable is not set and passes otherwise
        """
        pack = repo.create_pack("Pack1")
        playbook = pack.create_playbook('MyPlay')
        playbook.create_default_playbook()
        playbook_data = playbook.yml.read_dict()

        self.validator.playbook_set = self.id_set["playbooks"]
        self.validator.integration_set = self.id_set["integrations"]
        self.validator.script_set = self.id_set["scripts"]

        with ChangeCWD(repo.path):
            # playbook uses sub playbooks with invalid versions, skipunavailable is set but
            # mainplaybook fromversion is 5.0.0 - should fail
            playbook_data['tasks'] = {
                '0': {
                    'id': '0',
                    'task': {
                        'playbookName': 'SubPlaybook_version_5_5'
                    },
                    'skipunavailable': True
                }
            }
            playbook.yml.write_dict(playbook_data)
            is_sub_playbook_version_invalid, error = self.validator._are_playbook_entities_versions_valid(
                self.playbook_with_invalid_sub_playbook_version_from_version_5_0_0, playbook.yml.path)
            assert not is_sub_playbook_version_invalid

            # playbook uses sub playbooks with invalid versions, skipunavailable is set and
            # mainplaybook fromversion is 6.0.0 - shouldn't fail
            playbook_data['tasks'] = {
                '0': {
                    'id': '0',
                    'task': {
                        'playbookName': 'SubPlaybook_version_6_5'
                    },
                    'skipunavailable': True
                }
            }
            playbook.yml.write_dict(playbook_data)
            is_sub_playbook_version_invalid, error = self.validator._are_playbook_entities_versions_valid(
                self.playbook_with_invalid_sub_playbook_version_from_version_6_0_0, playbook.yml.path)
            assert is_sub_playbook_version_invalid

            # playbook uses sub playbooks with invalid versions but no skipunavailable
            playbook_data['tasks'] = {
                '0': {
                    'id': '0',
                    'task': {
                        'playbookName': 'SubPlaybook_version_5_5'
                    },
                    'skipunavailable': False
                }
            }
            playbook.yml.write_dict(playbook_data)
            is_sub_playbook_version_invalid, error = self.validator._are_playbook_entities_versions_valid(
                self.playbook_with_invalid_sub_playbook_version_from_version_5_0_0, playbook.yml.path)
            assert not is_sub_playbook_version_invalid

    def test_are_playbook_entities_versions_valid_integration_commands(self, repo, mocker):
        """

        Given
            - an id_set file
            - a Playbook those entities:
                * command_to_integration

        When
            - _are_playbook_entities_versions_valid is called

        Then
            - Validates that integrations version match the playbook version.
        """
        pack = repo.create_pack("Pack1")
        playbook = pack.create_playbook('MyPlay')
        playbook.create_default_playbook()

        # setup validator
        validator = IDSetValidations(is_circle=False, is_test_run=True, configuration=CONFIG)
        validator.playbook_set = [{
            playbook.name: {
                'name': playbook.name,
                'fromversion': "5.5.0",
                "file_path": playbook.yml.path,
                "command_to_integration": {
                    "test-command": [
                        "Integration_version_4",
                        "Integration_version_5"
                    ]
                }
            }
        }]

        validator.integration_set = self.id_set["integrations"]
        validator.script_set = self.id_set["scripts"]

        playbook_with_invalid_integration_version = {playbook.name: {
            "name": playbook.name,
            "file_path": playbook.yml.path,
            "fromversion": "3.0.0",
            "pack": "Example",
            "command_to_integration": {
                "test-command": [
                    "Integration_version_4",
                    "Integration_version_5"
                ]
            }
        }}

        playbook_with_valid_integration_version = {playbook.name: {
            "name": playbook.name,
            "file_path": playbook.yml.path,
            "fromversion": "5.0.0",
            "pack": "Example",
            "command_to_integration": {
                "test-command": [
                    "Integration_version_4",
                    "Integration_version_5"
                ]
            }
        }}

        with ChangeCWD(repo.path):
            # playbook uses integration's commands with invalid versions
            mocker.patch.object(validator, 'handle_error', return_value=True)
            is_integration_version_invalid, error = validator._are_playbook_entities_versions_valid(
                playbook_with_invalid_integration_version, playbook.yml.path)
            assert not is_integration_version_invalid

            # playbook uses integration's commands with valid versions
            mocker.patch.object(validator, 'handle_error', return_value=True)
            is_integration_version_invalid, error = validator._are_playbook_entities_versions_valid(
                playbook_with_valid_integration_version, playbook.yml.path)
            assert is_integration_version_invalid

    def test_playbook_sub_playbook_exist(self, repo, mocker):
        """

        Given
        - A playbook with sub playbook
        - An id_set file.

        When
        - validating playbook - sub playbook exists in id_set

        Then
        - In case sub playbook names does not exist in id_set , prints a warning.
        """
        pack = repo.create_pack("Pack1")
        self.validator.playbook_set = self.id_set["playbooks"]

        with ChangeCWD(repo.path):
            # playbook uses existing sub playbooks
            is_subplaybook_name_exist = self.validator.is_subplaybook_name_valid(
                self.playbook_with_valid_sub_playbook_name, pack.path)
            assert is_subplaybook_name_exist

    def test_playbook_sub_playbook_not_exist(self, repo, mocker):
        """

        Given
        - A playbook with sub playbook names
        - An id_set file.

        When
        - validating playbook - sub playbook does not exist in id_set

        Then
        - In case playbook name does not exist in id_set , prints a warning.
        """
        pack = repo.create_pack("Pack1")
        self.validator.playbook_set = self.id_set["playbooks"]

        with ChangeCWD(repo.path):
            # playbook uses existing sub playbooks
            is_subplaybook_name_exist = self.validator.is_subplaybook_name_valid(
                self.playbook_with_invalid_sub_playbook_name, pack.path)
            assert not is_subplaybook_name_exist


# demisto_sdk/commands/common/tests/image_test.py

import json
import os

from demisto_sdk.commands.common.hook_validations import image
from demisto_sdk.commands.common.hook_validations.integration import \
    IntegrationValidator
from demisto_sdk.commands.common.legacy_git_tools import git_path
from demisto_sdk.commands.common.tests.integration_test import mock_structure
from TestSuite.file import File
from TestSuite.test_tools import ChangeCWD


def test_is_not_default_image():
    int_path = os.path.normpath(os.path.join(__file__, f'{git_path()}/demisto_sdk/tests', 'test_files',
                                             'integration-Zoom.yml'))
    image_validator = image.ImageValidator(int_path)
    assert image_validator.is_not_default_image() is False

    image_path = os.path.normpath(os.path.join(__file__, f'{git_path()}/demisto_sdk/tests', 'test_files',
                                               'default_image.png'))
    image_validator = image.ImageValidator(image_path)
    assert image_validator.is_not_default_image() is False

    image_path = os.path.normpath(os.path.join(__file__, f'{git_path()}/demisto_sdk/commands/init/templates',
                                               'HelloWorld', 'HelloWorld_image.png'))
    image_validator = image.ImageValidator(image_path)
    assert image_validator.is_not_default_image() is False

    int_path = os.path.normpath(os.path.join(__file__, f'{git_path()}/demisto_sdk/tests', 'test_files',
                                             'fake_integration.yml'))
    image_validator = image.ImageValidator(int_path)
    assert image_validator.is_not_default_image() is False


def test_is_valid_image_positive(monkeypatch):
    """
    Given
        - An integration is with a valid non default image

    When
        - Validating this integration

    Then
        - Ensure integration is considered valid
    """
    integration_path = os.path.normpath(
        os.path.join(f'{git_path()}/demisto_sdk/tests', 'test_files', 'not_default_image_integration-Zoom.yml')
    )
    structure = mock_structure(file_path=integration_path)
    # Adding monkey patching this will make image validator behave like this is an integration outside of
    # pack context and ignore the image that's in the same folder as the file
    monkeypatch.setattr('demisto_sdk.commands.common.hook_validations.image.PACKS_INTEGRATION_NON_SPLIT_YML_REGEX',
                        integration_path)
    validator = IntegrationValidator(structure)
    assert validator.is_valid_image() is True


def test_image_in_both_yml_and_directory(monkeypatch):
    """
    Given
        - An integration that has image in both yml file and in the yml directory

    When
        - Validating this integration

    Then
        - Ensure integration is considered non-valid
    """
    integration_path = os.path.normpath(
        os.path.join(f'{git_path()}/demisto_sdk/tests', 'test_files', 'not_default_image_integration-Zoom.yml')
    )
    structure = mock_structure(file_path=integration_path)
    validator = IntegrationValidator(structure)
    assert validator.is_valid_image() is False


def test_image_when_invalid_type(monkeypatch):
    """
    Given
        - An integration that has an invalid image

    When
        - Validating this integration

    Then
        - Ensure integration is considered non-valid.
    """
    integration_path = os.path.normpath(
        os.path.join(f'{git_path()}/demisto_sdk/tests', 'test_files', 'not_default_image_integration-Zoom.yml')
    )
    structure = mock_structure(file_path=integration_path)
    validator = IntegrationValidator(structure)
    assert validator.is_valid_image() is False


def test_no_image_integration(monkeypatch):
    """
    Given
        - A new integration yml that does not have an image in its pack

    When
        - Validating this integration

    Then
        - Ensure integration is considered non-valid.
    """
    integration_path = os.path.normpath(
        os.path.join(f'{git_path()}/demisto_sdk/tests', 'test_files', 'DummyPack', 'Integrations',
                     'integration-DummyIntegration.yml')
    )
    structure = mock_structure(file_path=integration_path)
    validator = IntegrationValidator(structure)
    assert validator.is_valid_image() is False


def test_json_outputs_where_no_image_in_integration(repo):
    """
        Given
            - An integration without an existing image
            - A json file for writing the outputs

        When
            - Validating the image integration

        Then
            - Ensure that the outputs are correct.
    """
    # Create pack and integration
    pack = repo.create_pack('PackName')
    integration = pack.create_integration('IntName')
    integration.create_default_integration()

    # Remove the integration image
    image_path = os.path.join(integration.path, 'IntName_image.png')
    if os.path.exists(image_path):
        os.remove(image_path)

    with ChangeCWD(repo.path):
        # Run the image validator with a json file path
        json_file_path = os.path.join(integration.path, 'json_outputs.json')
        image_validator = image.ImageValidator(integration.yml.path, json_file_path=json_file_path)

        # Check the outputs in the json file
        with open(image_validator.json_file_path, "r") as r:
            json_outputs = json.loads(r.read())

            assert json_outputs[0]['filePath'] == image_path
            assert json_outputs[0]['fileType'] == 'png'
            assert json_outputs[0]['entityType'] == 'image'


def test_is_valid_image_name_with_valid_name(repo):
    """
        Given
            - An integration image with a valid name

        When
            - Validating the integration image name

        Then
            - Ensure that image validator for integration passes.
    """

    pack = repo.create_pack('PackName')

    integration = pack.create_integration('IntName')
    integration.create_default_integration()

    image_validator = image.ImageValidator(integration.yml.path)

    assert image_validator.is_valid_image_name()


def test_is_valid_image_name_with_invalid_name(repo):
    """
        Given
            - An integration image with a invalid name

        When
            - Validating the integration image name

        Then
            - Ensure that image validator for integration failed.
    """

    pack = repo.create_pack('PackName')

    integration = pack.create_integration('IntName')
    integration.create_default_integration()

    if os.path.exists(integration.image.path):
        os.remove(integration.image.path)
        integration.image = None

    integration.image = File(integration._tmpdir_integration_path / f'{integration.name}_img.png',
                             integration._repo.path)

    with ChangeCWD(repo.path):

        image_validator = image.ImageValidator(integration.image.path)

        assert not image_validator.is_valid_image_name()


# demisto_sdk/commands/common/tests/incident_field_test.py

import pytest
from mock import patch

from demisto_sdk.commands.common.hook_validations.incident_field import (
    GroupFieldTypes, IncidentFieldValidator)
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator


class TestIncidentFieldsValidator:
    NAME_SANITY_FILE = {
        'cliName': 'sanityname',
        'name': 'sanity name',
        'id': 'incident',
        'content': True,
    }

    BAD_NAME_1 = {
        'cliName': 'sanityname',
        'name': 'Incident',
        'content': True,
    }

    BAD_NAME_2 = {
        'cliName': 'sanityname',
        'name': 'case',
        'content': True,
    }

    BAD_NAME_3 = {
        'cliName': 'sanityname',
        'name': 'Playbook',
        'content': True,
    }

    GOOD_NAME_4 = {
        'cliName': 'sanityname',
        'name': 'Alerting feature',
        'content': True,
    }

    BAD_NAME_5 = {
        'cliName': 'sanity name',
        'name': 'INciDeNts',
        'content': True,
    }

    INPUTS_NAMES = [
        (NAME_SANITY_FILE, False),
        (BAD_NAME_1, True),
        (BAD_NAME_2, True),
        (BAD_NAME_3, True),
        (GOOD_NAME_4, False),
        (BAD_NAME_5, True)
    ]

    @pytest.mark.parametrize('current_file, answer', INPUTS_NAMES)
    def test_is_valid_name_sanity(self, current_file, answer):
        import os
        import sys
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = IncidentFieldValidator(structure)
            validator.current_file = current_file

            with open("file", 'w') as temp_out:
                old_stdout = sys.stdout
                sys.stdout = temp_out
                validator.is_valid_name()
                sys.stdout = old_stdout

            with open('file', 'r') as temp_out:
                output = temp_out.read()
                assert ('IF100' in str(output)) is answer
            # remove the temp file
            os.system('rm -rf file')

    CONTENT_1 = {
        'content': True
    }

    CONTENT_BAD_1 = {
        'content': False
    }

    CONTENT_BAD_2 = {
        'something': True
    }

    INPUTS_FLAGS = [
        (CONTENT_1, True),
        (CONTENT_BAD_1, False),
        (CONTENT_BAD_2, False)
    ]

    @pytest.mark.parametrize('current_file, answer', INPUTS_FLAGS)
    def test_is_valid_content_flag_sanity(self, current_file, answer):
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = IncidentFieldValidator(structure)
            validator.current_file = current_file
            assert validator.is_valid_content_flag() is answer

    SYSTEM_FLAG_1 = {
        'system': False,
        'content': True,
    }

    SYSTEM_FLAG_BAD_1 = {
        'system': True,
        'content': True,
    }

    INPUTS_SYSTEM_FLAGS = [
        (SYSTEM_FLAG_1, True),
        (SYSTEM_FLAG_BAD_1, False)
    ]

    @pytest.mark.parametrize('current_file, answer', INPUTS_SYSTEM_FLAGS)
    def test_is_valid_system_flag_sanity(self, current_file, answer):
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = IncidentFieldValidator(structure)
            validator.current_file = current_file
            assert validator.is_valid_system_flag() is answer

    VALID_CLINAMES_AND_GROUPS = [
        ("validind", GroupFieldTypes.INCIDENT_FIELD),
        ("validind", GroupFieldTypes.EVIDENCE_FIELD),
        ("validind", GroupFieldTypes.INDICATOR_FIELD)
    ]

    @pytest.mark.parametrize("cliname, group", VALID_CLINAMES_AND_GROUPS)
    def test_is_cliname_is_builtin_key(self, cliname, group):
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            current_file = {"cliName": cliname, "group": group}
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = IncidentFieldValidator(structure)
            validator.current_file = current_file
            assert validator.is_cliname_is_builtin_key()

    INVALID_CLINAMES_AND_GROUPS = [
        ("id", GroupFieldTypes.INCIDENT_FIELD),
        ("id", GroupFieldTypes.EVIDENCE_FIELD),
        ("id", GroupFieldTypes.INDICATOR_FIELD)
    ]

    @pytest.mark.parametrize("cliname, group", INVALID_CLINAMES_AND_GROUPS)
    def test_is_cliname_is_builtin_key_invalid(self, cliname, group):
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            current_file = {"cliName": cliname, "group": group}
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = IncidentFieldValidator(structure)
            validator.current_file = current_file
            assert not validator.is_cliname_is_builtin_key()

    VALID_CLINAMES = [
        "agoodid",
        "anot3erg00did",
    ]

    @pytest.mark.parametrize("cliname", VALID_CLINAMES)
    def test_matching_cliname_regex(self, cliname):
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            current_file = {"cliName": cliname}
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = IncidentFieldValidator(structure)
            validator.current_file = current_file
            assert validator.is_matching_cliname_regex()

    INVALID_CLINAMES = [
        "invalid cli",
        "invalid_cli",
        "invalid$$cli",
        "לאסליטוב",
    ]

    @pytest.mark.parametrize("cliname", INVALID_CLINAMES)
    def test_matching_cliname_regex_invalid(self, cliname):
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            current_file = {"cliName": cliname}
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = IncidentFieldValidator(structure)
            validator.current_file = current_file
            assert not validator.is_matching_cliname_regex()

    @pytest.mark.parametrize("cliname, group", VALID_CLINAMES_AND_GROUPS)
    def test_is_valid_cliname(self, cliname, group):
        current_file = {"cliName": cliname, "group": group}
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = IncidentFieldValidator(structure)
            validator.current_file = current_file
            assert validator.is_valid_cliname()

    @pytest.mark.parametrize("cliname, group", INVALID_CLINAMES_AND_GROUPS)
    def test_is_valid_cliname_invalid(self, cliname, group):
        current_file = {"cliName": cliname, "group": group}
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = IncidentFieldValidator(structure)
            validator.current_file = current_file
            assert not validator.is_valid_cliname()

    data_is_valid_version = [
        (-1, True),
        (0, False),
        (1, False),
    ]

    @pytest.mark.parametrize('version, is_valid', data_is_valid_version)
    def test_is_valid_version(self, version, is_valid):
        structure = StructureValidator("")
        structure.current_file = {"version": version}
        validator = IncidentFieldValidator(structure)
        assert validator.is_valid_version() == is_valid, f'is_valid_version({version}) returns {not is_valid}.'

    IS_FROM_VERSION_CHANGED_NO_OLD = {}  # type: dict[any, any]
    IS_FROM_VERSION_CHANGED_OLD = {"fromVersion": "5.0.0"}
    IS_FROM_VERSION_CHANGED_NEW = {"fromVersion": "5.0.0"}
    IS_FROM_VERSION_CHANGED_NO_NEW = {}  # type: dict[any, any]
    IS_FROM_VERSION_CHANGED_NEW_HIGHER = {"fromVersion": "5.5.0"}
    IS_CHANGED_FROM_VERSION_INPUTS = [
        (IS_FROM_VERSION_CHANGED_NO_OLD, IS_FROM_VERSION_CHANGED_NO_OLD, False),
        (IS_FROM_VERSION_CHANGED_NO_OLD, IS_FROM_VERSION_CHANGED_NEW, True),
        (IS_FROM_VERSION_CHANGED_OLD, IS_FROM_VERSION_CHANGED_NEW, False),
        (IS_FROM_VERSION_CHANGED_NO_OLD, IS_FROM_VERSION_CHANGED_NO_NEW, False),
        (IS_FROM_VERSION_CHANGED_OLD, IS_FROM_VERSION_CHANGED_NEW_HIGHER, True),
    ]

    @pytest.mark.parametrize("current_from_version, old_from_version, answer", IS_CHANGED_FROM_VERSION_INPUTS)
    def test_is_changed_from_version(self, current_from_version, old_from_version, answer):
        structure = StructureValidator("")
        structure.old_file = old_from_version
        structure.current_file = current_from_version
        validator = IncidentFieldValidator(structure)
        assert validator.is_changed_from_version() is answer
        structure.quite_bc = True
        assert validator.is_changed_from_version() is False

    data_required = [
        (True, False),
        (False, True),
    ]

    @pytest.mark.parametrize('required, is_valid', data_required)
    def test_is_valid_required(self, required, is_valid):
        structure = StructureValidator("")
        structure.current_file = {"required": required}
        validator = IncidentFieldValidator(structure)
        assert validator.is_valid_required() == is_valid, f'is_valid_required({required})' \
                                                          f' returns {not is_valid}.'

    data_is_changed_type = [
        ('shortText', 'shortText', False),
        ('shortText', 'longText', True),
        ('number', 'number', False),
        ('shortText', 'number', True),
        ('timer', 'timer', False),
        ('timer', 'number', True),
        ('timer', 'shortText', True),
        ('singleSelect', 'singleSelect', False),
        ('singleSelect', 'shortText', True)
    ]

    @pytest.mark.parametrize('current_type, old_type, is_valid', data_is_changed_type)
    def test_is_changed_type(self, current_type, old_type, is_valid):
        structure = StructureValidator("")
        structure.current_file = {"type": current_type}
        structure.old_file = {"type": old_type}
        validator = IncidentFieldValidator(structure)
        assert validator.is_changed_type() == is_valid, f'is_changed_type({current_type}, {old_type})' \
                                                        f' returns {not is_valid}.'
        structure.quite_bc = True
        assert validator.is_changed_type() is False

    TYPES_FROMVERSION = [
        ('grid', '5.5.0', 'indicatorfield', True),
        ('grid', '5.0.0', 'indicatorfield', False),
        ('number', '5.0.0', 'indicatorfield', True),
        ('grid', '5.0.0', 'incidentfield', True)
    ]

    @pytest.mark.parametrize('field_type, from_version, file_type, is_valid', TYPES_FROMVERSION)
    def test_is_valid_grid_fromversion(self, field_type, from_version, file_type, is_valid):
        """
            Given
            - an invalid indicator-field - the field is of type grid but fromVersion is < 5.5.0.

            When
            - Running is_valid_indicator_grid_fromversion on it.

            Then
            - Ensure validate fails on versions < 5.5.0.
        """
        structure = StructureValidator("")
        structure.file_type = file_type
        structure.current_file = {"fromVersion": from_version, "type": field_type}
        validator = IncidentFieldValidator(structure)
        assert validator.is_valid_indicator_grid_fromversion() == is_valid, \
            f'is_valid_grid_fromVersion({field_type}, {from_version} returns {not is_valid}'

    FIELD_NAME1 = {
        'name': 'pack name field',
    }
    FIELD_NAME2 = {
        'name': 'pack prefix field',
    }
    FIELD_NAME3 = {
        'name': 'field',
    }
    PACK_METADATA1 = {'name': 'pack name', 'itemPrefix': ['pack prefix']}
    PACK_METADATA2 = {'name': 'pack name'}

    INPUTS_NAMES2 = [
        (FIELD_NAME1, PACK_METADATA1, False),
        (FIELD_NAME1, PACK_METADATA2, True),
        (FIELD_NAME2, PACK_METADATA1, True),
        (FIELD_NAME2, PACK_METADATA2, False),
        (FIELD_NAME3, PACK_METADATA1, False)
    ]

    @pytest.mark.parametrize('current_file,pack_metadata, answer', INPUTS_NAMES2)
    def test_is_valid_name_prefix(self, current_file, pack_metadata, answer, mocker):
        """
            Given
            - A set of indicator fields

            When
            - Running is_valid_incident_field_name_prefix on it.

            Then
            - Ensure validate fails when the field name does not start with the pack name prefix.
        """
        from demisto_sdk.commands.common.hook_validations import incident_field
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = IncidentFieldValidator(structure, )
            validator.current_file = current_file
            mocker.patch.object(incident_field, 'get_pack_metadata', return_value=pack_metadata)
            assert validator.is_valid_incident_field_name_prefix() == answer


# demisto_sdk/commands/common/tests/incident_type_test.py

from typing import Optional

import pytest
from mock import patch

from demisto_sdk.commands.common.hook_validations.incident_type import \
    IncidentTypeValidator
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator


def mock_structure(file_path=None, current_file=None, old_file=None):
    # type: (Optional[str], Optional[dict], Optional[dict]) -> StructureValidator
    with patch.object(StructureValidator, '__init__', lambda a, b: None):
        structure = StructureValidator(file_path)
        structure.is_valid = True
        structure.scheme_name = 'incident_type'
        structure.file_path = file_path
        structure.current_file = current_file
        structure.old_file = old_file
        structure.prev_ver = 'master'
        structure.branch_name = ''
        return structure


data_is_valid_version = [
    (-1, True),
    (0, False),
    (1, False),
]


@pytest.mark.parametrize('version, is_valid', data_is_valid_version)
def test_is_valid_version(version, is_valid):
    structure = StructureValidator("")
    structure.current_file = {"version": version}
    validator = IncidentTypeValidator(structure)
    assert validator.is_valid_version() == is_valid, f'is_valid_version({version}) returns {not is_valid}.'


data_is_id_equal_name = [
    ('AWS EC2 Instance Misconfiguration', 'AWS EC2 Instance Misconfiguration', True),
    ('AWS EC2 Instance Misconfiguration', 'AWS EC2 Instance Wrong configuration', False)
]


@pytest.mark.parametrize('id_, name, is_valid', data_is_id_equal_name)
def test_is_id_equal_name(id_, name, is_valid):
    structure = StructureValidator("")
    structure.current_file = {"id": id_, "name": name}
    validator = IncidentTypeValidator(structure)
    assert validator.is_id_equals_name() == is_valid, f'is_id_equal_name returns {not is_valid}.'


data_is_including_int_fields = [
    ({"fromVersion": "5.0.0", "hours": 1, "days": 2, "weeks": 3, "hoursR": 1, "daysR": 2, "weeksR": 3}, True),
    ({"fromVersion": "5.0.0", "hours": 1, "days": 2, "weeks": "3", "hoursR": 1, "daysR": 2, "weeksR": 3}, False),
    ({"fromVersion": "5.0.0", "hours": 1, "days": 2, "weeks": 3, "hoursR": 1, "daysR": 2}, False),
]


@pytest.mark.parametrize('current_file, is_valid', data_is_including_int_fields)
def test_is_including_fields(current_file, is_valid):
    structure = mock_structure("", current_file)
    validator = IncidentTypeValidator(structure)
    assert validator.is_including_int_fields() == is_valid, f'is_including_int_fields returns {not is_valid}.'


IS_FROM_VERSION_CHANGED_NO_OLD = {}  # type: dict[any, any]
IS_FROM_VERSION_CHANGED_OLD = {"fromVersion": "5.0.0"}
IS_FROM_VERSION_CHANGED_NEW = {"fromVersion": "5.0.0"}
IS_FROM_VERSION_CHANGED_NO_NEW = {}  # type: dict[any, any]
IS_FROM_VERSION_CHANGED_NEW_HIGHER = {"fromVersion": "5.5.0"}
IS_CHANGED_FROM_VERSION_INPUTS = [
    (IS_FROM_VERSION_CHANGED_NO_OLD, IS_FROM_VERSION_CHANGED_NO_OLD, False),
    (IS_FROM_VERSION_CHANGED_NO_OLD, IS_FROM_VERSION_CHANGED_NEW, True),
    (IS_FROM_VERSION_CHANGED_OLD, IS_FROM_VERSION_CHANGED_NEW, False),
    (IS_FROM_VERSION_CHANGED_NO_OLD, IS_FROM_VERSION_CHANGED_NO_NEW, False),
    (IS_FROM_VERSION_CHANGED_OLD, IS_FROM_VERSION_CHANGED_NEW_HIGHER, True),
]


@pytest.mark.parametrize("current_from_version, old_from_version, answer", IS_CHANGED_FROM_VERSION_INPUTS)
def test_is_changed_from_version(current_from_version, old_from_version, answer):
    structure = StructureValidator("")
    structure.old_file = old_from_version
    structure.current_file = current_from_version
    validator = IncidentTypeValidator(structure)
    assert validator.is_changed_from_version() is answer
    structure.quite_bc = True
    assert validator.is_changed_from_version() is False


IS_VALID_PLAYBOOK_ID = [
    ('valid playbook', True),
    ('', True),
    ('12b3a41b-04ce-4417-89b3-4efd95d28012', False),
    ('abbababb-aaaa-bbbb-cccc-abcdabcdabcd', False)
]


@pytest.mark.parametrize("playbook_id, is_valid", IS_VALID_PLAYBOOK_ID)
def test_is_valid_playbook_id(playbook_id, is_valid):
    structure = StructureValidator("")
    structure.current_file = {"playbookId": playbook_id}
    validator = IncidentTypeValidator(structure)
    assert validator.is_valid_playbook_id() == is_valid


def test_is_valid_autoextract_no_extract_rules():
    """
    Given
    - an incident type without auto extract section .

    When
    - Running is_valid_autoextract on it.

    Then
    - Ensure returns True.
    """
    structure = StructureValidator("")
    validator = IncidentTypeValidator(structure)
    assert validator.is_valid_autoextract()


EXTRACT_VARIATIONS = [
    ({"extractAsIsIndicatorTypeId": "", "isExtractingAllIndicatorTypes": False, "extractIndicatorTypesIDs": []}, True),
    ({"extractAsIsIndicatorTypeId": "IP", "isExtractingAllIndicatorTypes": False, "extractIndicatorTypesIDs": []}, True),
    ({"extractAsIsIndicatorTypeId": "", "isExtractingAllIndicatorTypes": False,
      "extractIndicatorTypesIDs": ["IP", "CIDR"]}, True),
    ({"extractAsIsIndicatorTypeId": "", "isExtractingAllIndicatorTypes": True, "extractIndicatorTypesIDs": []}, True),
    ({"extractAsIsIndicatorTypeId": "IP", "isExtractingAllIndicatorTypes": False,
      "extractIndicatorTypesIDs": ["IP"]}, False),
    ({"extractAsIsIndicatorTypeId": "IP", "isExtractingAllIndicatorTypes": True,
      "extractIndicatorTypesIDs": []}, False),
    ({"extractAsIsIndicatorTypeId": "", "isExtractingAllIndicatorTypes": True,
      "extractIndicatorTypesIDs": ["IP"]}, False),
    ({"extractAsIsIndicatorTypeId": "IP", "isExtractingAllIndicatorTypes": True,
      "extractIndicatorTypesIDs": ["IP"]}, False),
    ({}, False)
]


@pytest.mark.parametrize("extract_field, answer", EXTRACT_VARIATIONS)
def test_is_valid_autoextract_fields(extract_field, answer):
    """
    Given
    - an incident type with a valid or invalid auto extract section .

    When
    - Running is_valid_autoextract on it.

    Then
    - Ensure returns True if the field is formatted correctly and False otherwise.
    """
    structure = StructureValidator("")
    validator = IncidentTypeValidator(structure)
    validator.current_file['extractSettings'] = {
        'mode': "All",
        'fieldCliNameToExtractSettings': {
            "incident_field": extract_field
        }
    }
    assert validator.is_valid_autoextract() is answer


EXTRACTION_MODE_VARIATIONS = [
    ('All', True),
    ('Specific', True),
    (None, False),
    ('', False),
    ('all', False)
]


@pytest.mark.parametrize("extract_mode, answer", EXTRACTION_MODE_VARIATIONS)
def test_is_valid_autoextract_mode(extract_mode, answer):
    """
    Given
    - an incident type with a valid or invalid auto extract mode.

    When
    - Running is_valid_autoextract on it.

    Then
    - Ensure returns True if the field is formatted correctly and False otherwise.
    """
    structure = StructureValidator("")
    validator = IncidentTypeValidator(structure)
    validator.current_file['extractSettings'] = {
        'mode': extract_mode,
        'fieldCliNameToExtractSettings': {
            "incident_field": {
                "extractAsIsIndicatorTypeId": "",
                "isExtractingAllIndicatorTypes": False,
                "extractIndicatorTypesIDs": []
            }
        }
    }
    assert validator.is_valid_autoextract() is answer


# demisto_sdk/commands/common/tests/integration_test.py

import os
from copy import deepcopy
from typing import Dict, List, Optional

import pytest
from mock import mock_open, patch

from demisto_sdk.commands.common.constants import (FEED_REQUIRED_PARAMS,
                                                   FETCH_REQUIRED_PARAMS,
                                                   FIRST_FETCH_PARAM,
                                                   MAX_FETCH_PARAM)
from demisto_sdk.commands.common.default_additional_info_loader import \
    load_default_additional_info_dict
from demisto_sdk.commands.common.hook_validations.integration import \
    IntegrationValidator
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator
from demisto_sdk.commands.common.legacy_git_tools import git_path
from TestSuite.test_tools import ChangeCWD

default_additional_info = load_default_additional_info_dict()

FEED_REQUIRED_PARAMS_STRUCTURE = [dict(required_param.get('must_equal'), **required_param.get('must_contain'),
                                       name=required_param.get('name')) for required_param in FEED_REQUIRED_PARAMS]


def mock_structure(file_path=None, current_file=None, old_file=None, quite_bc=False):
    # type: (Optional[str], Optional[dict], Optional[dict]) -> StructureValidator
    with patch.object(StructureValidator, '__init__', lambda a, b: None):
        structure = StructureValidator(file_path)
        structure.is_valid = True
        structure.scheme_name = 'integration'
        structure.file_path = file_path
        structure.current_file = current_file
        structure.old_file = old_file
        structure.prev_ver = 'master'
        structure.branch_name = ''
        structure.quite_bc = quite_bc
        return structure


class TestIntegrationValidator:
    SCRIPT_WITH_DOCKER_IMAGE_1 = {"script": {"dockerimage": "test"}}
    SCRIPT_WITH_DOCKER_IMAGE_2 = {"script": {"dockerimage": "test1"}}
    SCRIPT_WITH_NO_DOCKER_IMAGE = {"script": {"no": "dockerimage"}}
    EMPTY_CASE = {}  # type: dict[any, any]
    IS_DOCKER_IMAGE_CHANGED = [
        (SCRIPT_WITH_DOCKER_IMAGE_1, SCRIPT_WITH_NO_DOCKER_IMAGE, True),
        (SCRIPT_WITH_DOCKER_IMAGE_1, SCRIPT_WITH_DOCKER_IMAGE_2, True),
        (EMPTY_CASE, EMPTY_CASE, False),
        (EMPTY_CASE, SCRIPT_WITH_DOCKER_IMAGE_1, True),
        (SCRIPT_WITH_DOCKER_IMAGE_1, EMPTY_CASE, True)
    ]

    REQUIED_FIELDS_FALSE = {"configuration": [{"name": "test", "required": False}]}
    REQUIED_FIELDS_TRUE = {"configuration": [{"name": "test", "required": True}]}
    IS_ADDED_REQUIRED_FIELDS_INPUTS = [
        (REQUIED_FIELDS_FALSE, REQUIED_FIELDS_TRUE, False),
        (REQUIED_FIELDS_TRUE, REQUIED_FIELDS_FALSE, True),
        (REQUIED_FIELDS_TRUE, REQUIED_FIELDS_TRUE, False),
        (REQUIED_FIELDS_FALSE, REQUIED_FIELDS_FALSE, False)
    ]

    @pytest.mark.parametrize("current_file, old_file, answer", IS_ADDED_REQUIRED_FIELDS_INPUTS)
    def test_is_added_required_fields(self, current_file, old_file, answer):
        structure = mock_structure("", current_file, old_file)
        validator = IntegrationValidator(structure)
        assert validator.is_added_required_fields() is answer
        structure.quite_bc = True
        assert validator.is_added_required_fields() is False  # if quite_bc is true should always succeed

    IS_CHANGED_REMOVED_YML_FIELDS_INPUTS = [
        ({"script": {"isfetch": True, "feed": False}}, {"script": {"isfetch": True, "feed": False}}, False),
        ({"script": {"isfetch": True}}, {"script": {"isfetch": True, "feed": False}}, False),
        ({"script": {"isfetch": False, "feed": False}}, {"script": {"isfetch": True, "feed": False}}, True),
        ({"script": {"feed": False}}, {"script": {"isfetch": True, "feed": False}}, True),

    ]

    @pytest.mark.parametrize("current_file, old_file, answer", IS_CHANGED_REMOVED_YML_FIELDS_INPUTS)
    def test_is_changed_removed_yml_fields(self, current_file, old_file, answer):
        """
        Given
        - integration script with different fields

        When
        - running the validation is_changed_removed_yml_fields()

        Then
        - upon removal or change of some fields from true to false: it should set is_valid to False and return True
        - upon non removal or change of some fields from true to false: it should set is_valid to True and return False
        """

        structure = mock_structure("", current_file, old_file)
        validator = IntegrationValidator(structure)
        assert validator.is_changed_removed_yml_fields() is answer
        assert validator.is_valid is not answer
        structure.quite_bc = True
        assert validator.is_changed_removed_yml_fields() is False  # if quite_bc is true should always succeed

    IS_REMOVED_INTEGRATION_PARAMETERS_INPUTS = [
        ({"configuration": [{"name": "test"}]}, {"configuration": [{"name": "test"}]}, False),
        ({"configuration": [{"name": "test"}, {"name": "test2"}]}, {"configuration": [{"name": "test"}]}, False),
        ({"configuration": [{"name": "test"}]}, {"configuration": [{"name": "test"}, {"name": "test2"}]}, True),
        ({"configuration": [{"name": "test"}]}, {"configuration": [{"name": "old_param"}, {"name": "test2"}]}, True),
    ]

    @pytest.mark.parametrize("current_file, old_file, answer", IS_REMOVED_INTEGRATION_PARAMETERS_INPUTS)
    def test_is_removed_integration_parameters(self, current_file, old_file, answer):
        """
        Given
        - integration configuration with different parameters

        When
        - running the validation is_removed_integration_parameters()

        Then
        - upon removal of parameters: it should set is_valid to False and return True
        - upon non removal or addition of parameters: it should set is_valid to True and return False
        """
        structure = mock_structure("", current_file, old_file)
        validator = IntegrationValidator(structure)
        assert validator.is_removed_integration_parameters() is answer
        assert validator.is_valid is not answer
        structure.quite_bc = True
        assert validator.is_removed_integration_parameters() is False  # if quite_bc is true should always succeed

    CONFIGURATION_JSON_1 = {"configuration": [{"name": "test", "required": False}, {"name": "test1", "required": True}]}
    EXPECTED_JSON_1 = {"test": False, "test1": True}
    FIELD_TO_REQUIRED_INPUTS = [
        (CONFIGURATION_JSON_1, EXPECTED_JSON_1),
    ]

    @pytest.mark.parametrize("input_json, expected", FIELD_TO_REQUIRED_INPUTS)
    def test_get_field_to_required_dict(self, input_json, expected):
        assert IntegrationValidator._get_field_to_required_dict(input_json) == expected

    IS_CONTEXT_CHANGED_OLD = [{"name": "test", "outputs": [{"contextPath": "test"}]}]
    IS_CONTEXT_CHANGED_NEW = [{"name": "test", "outputs": [{"contextPath": "test2"}]}]
    IS_CONTEXT_CHANGED_ADDED_PATH = [{"name": "test", "outputs": [{"contextPath": "test"}, {"contextPath": "test2"}]}]
    IS_CONTEXT_CHANGED_ADDED_COMMAND = [{"name": "test", "outputs": [{"contextPath": "test"}]},
                                        {"name": "test2", "outputs": [{"contextPath": "new command"}]}]
    IS_CONTEXT_CHANGED_NO_OUTPUTS = [{"name": "test"}]
    IS_CHANGED_CONTEXT_INPUTS = [
        (IS_CONTEXT_CHANGED_OLD, IS_CONTEXT_CHANGED_OLD, False),
        (IS_CONTEXT_CHANGED_NEW, IS_CONTEXT_CHANGED_OLD, True),
        (IS_CONTEXT_CHANGED_NEW, IS_CONTEXT_CHANGED_ADDED_PATH, True),
        (IS_CONTEXT_CHANGED_ADDED_PATH, IS_CONTEXT_CHANGED_NEW, False),
        (IS_CONTEXT_CHANGED_ADDED_COMMAND, IS_CONTEXT_CHANGED_OLD, False),
        (IS_CONTEXT_CHANGED_ADDED_COMMAND, IS_CONTEXT_CHANGED_NEW, True),
        (IS_CONTEXT_CHANGED_NO_OUTPUTS, IS_CONTEXT_CHANGED_NO_OUTPUTS, False),
        (IS_CONTEXT_CHANGED_NO_OUTPUTS, IS_CONTEXT_CHANGED_OLD, True),
    ]

    @pytest.mark.parametrize("current, old, answer", IS_CHANGED_CONTEXT_INPUTS)
    def test_is_changed_context_path(self, current, old, answer):
        current = {'script': {'commands': current}}
        old = {'script': {'commands': old}}
        structure = mock_structure("", current, old)
        validator = IntegrationValidator(structure)
        assert validator.is_changed_context_path() is answer
        structure.quite_bc = True
        assert validator.is_changed_context_path() is False  # if quite_bc is true should always succeed

    CHANGED_COMMAND_INPUT_1 = [{"name": "test", "arguments": [{"name": "test"}]}]
    CHANGED_COMMAND_INPUT_2 = [{"name": "test", "arguments": [{"name": "test1"}]}]
    CHANGED_COMMAND_NAME_INPUT = [{"name": "test1", "arguments": [{"name": "test1"}]}]
    CHANGED_COMMAND_INPUT_ADDED_ARG = [{"name": "test", "arguments": [{"name": "test"}, {"name": "test1"}]}]
    CHANGED_COMMAND_INPUT_REQUIRED = [{"name": "test", "arguments": [{"name": "test", "required": True}]}]
    CHANGED_COMMAND_INPUT_ADDED_REQUIRED = [
        {"name": "test", "arguments": [{"name": "test"}, {"name": "test1", "required": True}]}]
    CHANGED_COMMAND_OR_ARG_INPUTS = [
        (CHANGED_COMMAND_INPUT_1, CHANGED_COMMAND_INPUT_REQUIRED, False),
        (CHANGED_COMMAND_INPUT_ADDED_REQUIRED, CHANGED_COMMAND_INPUT_1, True),
        (CHANGED_COMMAND_INPUT_1, CHANGED_COMMAND_INPUT_ADDED_REQUIRED, True),
        (CHANGED_COMMAND_INPUT_ADDED_ARG, CHANGED_COMMAND_INPUT_1, False),
        (CHANGED_COMMAND_INPUT_1, CHANGED_COMMAND_INPUT_ADDED_ARG, True),
        (CHANGED_COMMAND_INPUT_1, CHANGED_COMMAND_INPUT_2, True),
        (CHANGED_COMMAND_NAME_INPUT, CHANGED_COMMAND_INPUT_1, True),
        (CHANGED_COMMAND_NAME_INPUT, CHANGED_COMMAND_NAME_INPUT, False),
    ]

    @pytest.mark.parametrize("current, old, answer", CHANGED_COMMAND_OR_ARG_INPUTS)
    def test_is_changed_command_name_or_arg(self, current, old, answer):
        current = {'script': {'commands': current}}
        old = {'script': {'commands': old}}
        structure = mock_structure("", current, old)
        validator = IntegrationValidator(structure)
        assert validator.is_changed_command_name_or_arg() is answer
        structure.quite_bc = True
        assert validator.is_changed_command_name_or_arg() is False  # if quite_bc is true should always succeed

    WITHOUT_DUP = [{"name": "test"}, {"name": "test1"}]
    DUPLICATE_PARAMS_INPUTS = [
        (WITHOUT_DUP, True)
    ]

    @pytest.mark.parametrize("current, answer", DUPLICATE_PARAMS_INPUTS)
    def test_no_duplicate_params(self, current, answer):
        current = {'configuration': current}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        assert validator.has_no_duplicate_params() is answer

    @patch('demisto_sdk.commands.common.hook_validations.integration.print_error')
    def test_with_duplicate_params(self, print_error):
        """
        Given
        - integration configuration contains duplicate parameter (called test)

        When
        - running the validation has_no_duplicate_params()

        Then
        - it should set is_valid to False
        - it should return True (there are duplicate params)
        - it should print an error message that contains the duplicated param name
        """
        # from demisto_sdk.commands.common.tools import print_error
        # mocker.patch(tools, 'print_error')

        current = {
            'configuration': [
                {'name': 'test'},
                {'name': 'test'}
            ]
        }
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)

        assert validator.has_no_duplicate_params() is False
        assert validator.is_valid is False

    WITHOUT_DUP_ARGS = [{"name": "testing", "arguments": [{"name": "test1"}, {"name": "test2"}]}]
    WITH_DUP_ARGS = [{"name": "testing", "arguments": [{"name": "test1"}, {"name": "test1"}]}]
    WITH_DUP_ARGS_NON_IDENTICAL = [
        {"name": "testing", "arguments": [{"name": "test1", "desc": "hello"}, {"name": "test1", "desc": "hello1"}]},
    ]

    DUPLICATE_ARGS_INPUTS = [
        (WITHOUT_DUP_ARGS, True),
        (WITH_DUP_ARGS, False),
        (WITH_DUP_ARGS_NON_IDENTICAL, False),
    ]

    @pytest.mark.parametrize("current, answer", DUPLICATE_ARGS_INPUTS)
    def test_has_no_duplicate_args(self, current, answer):
        current = {'script': {'commands': current}}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        assert validator.has_no_duplicate_args() is answer

    WITH_DEFAULT_INFO = [{"name": "API key", "additionalinfo": default_additional_info['API key']}]
    MISSING_DEFAULT_INFO = [{"name": "API key", "additionalinfo": ""}]
    NON_DEFAULT_INFO = [{"name": "API key", "additionalinfo": "you know, the API key"}]

    DEFAULT_INFO_INPUTS = [
        (WITH_DEFAULT_INFO, True, False),
        (MISSING_DEFAULT_INFO, False, False),
        (NON_DEFAULT_INFO, True, True)]

    @pytest.mark.parametrize("args, answer, expecting_warning", DEFAULT_INFO_INPUTS)
    def test_default_params_default_info(self, capsys, args: List[Dict], answer: str, expecting_warning: bool):
        validator = IntegrationValidator(mock_structure("", {"configuration": args}))
        assert validator.default_params_have_default_additional_info() is answer

        if expecting_warning:
            from demisto_sdk.commands.common.errors import Errors
            warning_message, warning_code = Errors.non_default_additional_info(['API key'])
            expected_warning = f"[WARNING]: : [{warning_code}] - {warning_message}"
            captured = capsys.readouterr()
            assert captured.out.lstrip("\":").strip() == expected_warning
            assert not captured.err

    NO_INCIDENT_INPUT = [
        ({"script": {"commands": [{"name": "command1", "arguments": [{"name": "arg1"}]}]}}, True),
        ({"script": {"commands": [{"name": "command_incident", "arguments": [{"name": "arg1"}]}]}}, False),
        ({"script": {"commands": [{"name": "command1", "arguments": [{"name": "incident_arg"}]}]}}, False)
    ]

    @pytest.mark.parametrize("content, answer", NO_INCIDENT_INPUT)
    def test_no_incident_in_core_pack(self, content, answer):
        """
        Given
            - An integration with commands' names and arguments.
        When
            - running no_incident_in_core_packs.
        Then
            - validate that commands' names and arguments do not contain the word incident.
        """
        structure = mock_structure("", content)
        validator = IntegrationValidator(structure)
        assert validator.no_incident_in_core_packs() is answer
        assert validator.is_valid is answer

    PYTHON3_SUBTYPE = {
        "type": "python",
        "subtype": "python3"
    }
    PYTHON2_SUBTYPE = {
        "type": "python",
        "subtype": "python2"
    }

    BLA_BLA_SUBTYPE = {
        "type": "python",
        "subtype": "blabla"
    }
    INPUTS_SUBTYPE_TEST = [
        (PYTHON2_SUBTYPE, PYTHON3_SUBTYPE, True),
        (PYTHON3_SUBTYPE, PYTHON2_SUBTYPE, True),
        (PYTHON3_SUBTYPE, PYTHON3_SUBTYPE, False),
        (PYTHON2_SUBTYPE, PYTHON2_SUBTYPE, False)
    ]

    @pytest.mark.parametrize("current, old, answer", INPUTS_SUBTYPE_TEST)
    def test_is_changed_subtype(self, current, old, answer):
        current, old = {'script': current}, {'script': old}
        structure = mock_structure("", current, old)
        validator = IntegrationValidator(structure)
        assert validator.is_changed_subtype() is answer
        structure.quite_bc = True
        assert validator.is_changed_subtype() is False  # if quite_bc is true should always succeed

    INPUTS_VALID_SUBTYPE_TEST = [
        (PYTHON2_SUBTYPE, True),
        (PYTHON3_SUBTYPE, True),
        ({"type": "python", "subtype": "lies"}, False)
    ]

    @pytest.mark.parametrize("current, answer", INPUTS_VALID_SUBTYPE_TEST)
    def test_id_valid_subtype(self, current, answer):
        current = {'script': current}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        assert validator.is_valid_subtype() is answer

    DEFAULT_ARGS_DIFFERENT_ARG_NAME = [
        {"name": "cve", "arguments": [{"name": "cve_id", "required": False, "default": True, 'isArray': True}]}]
    DEFAULT_ARGS_MISSING_UNREQUIRED_DEFAULT_FIELD = [
        {"name": "email", "arguments": [{"name": "email", "required": False, "default": True, 'isArray': True},
                                        {"name": "verbose"}]}]
    DEFAULT_ARGS_MISSING_DEFAULT_PARAM_WHEN_ALLOWED = [
        {"name": "endpoint", "arguments": [{"name": "id", "required": False, "default": False}]}]
    DEFAULT_ARGS_INVALID_PARMA_MISSING_DEFAULT = [{"name": "file", "required": True, "default": True, 'isArray': True},
                                                  {"name": "verbose"}]
    DEFAULT_ARGS_INVALID_NOT_DEFAULT = [
        {"name": "email", "arguments": [{"name": "email", "required": False, "default": False}, {"name": "verbose"}]}]
    DEFAULT_ARGS_INVALID_COMMAND = [{"name": "file", "required": True, "default": False}, {"name": "verbose"}]
    DEFAULT_ARGS_MISSING_DEFAULT_PARAM_WHEN_NOT_ALLOWED = [
        {"name": "email", "arguments": [{"name": "verbose", "required": False, "default": False, "isArray": True}]}]
    DEFAULT_ARGS_NOT_ARRAY = [
        {"name": "email", "arguments": [{"name": "email", "required": False, "default": True, "isArray": False},
                                        {"name": "verbose"}]}]
    DEFAULT_ARGS_INPUTS = [
        (DEFAULT_ARGS_DIFFERENT_ARG_NAME, True),
        (DEFAULT_ARGS_MISSING_UNREQUIRED_DEFAULT_FIELD, True),
        (DEFAULT_ARGS_MISSING_DEFAULT_PARAM_WHEN_ALLOWED, True),
        (DEFAULT_ARGS_INVALID_PARMA_MISSING_DEFAULT, False),
        (DEFAULT_ARGS_INVALID_NOT_DEFAULT, False),
        (DEFAULT_ARGS_INVALID_COMMAND, False),
        (DEFAULT_ARGS_MISSING_DEFAULT_PARAM_WHEN_NOT_ALLOWED, False),
        (DEFAULT_ARGS_NOT_ARRAY, False)
    ]

    @pytest.mark.parametrize("current, answer", DEFAULT_ARGS_INPUTS)
    def test_is_valid_default_array_argument_in_reputation_command(self, current, answer):
        """
        Given: Integration reputation command with arguments.

        When: running is_valid_default_argument_in_reputation command.

        Then: Validate that matching default arg name yields True, else yields False.
        """
        current = {"script": {"commands": current}}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_valid_default_array_argument_in_reputation_command() is answer

    MULTIPLE_DEFAULT_ARGS_1 = [
        {"name": "msgraph-list-users",
         "arguments": [{"name": "users", "required": False, "default": False}, {"name": "verbose"}]}]
    MULTIPLE_DEFAULT_ARGS_2 = [
        {"name": "msgraph-list-users",
         "arguments": [{"name": "users", "required": False, "default": True}, {"name": "verbose"}]}]
    MULTIPLE_DEFAULT_ARGS_INVALID_1 = [
        {"name": "msgraph-list-users",
         "arguments": [{"name": "users", "required": False, "default": True}, {"name": "verbose", "default": True}]}]

    DEFAULT_ARGS_INPUTS = [
        (MULTIPLE_DEFAULT_ARGS_1, True),
        (MULTIPLE_DEFAULT_ARGS_2, True),
        (MULTIPLE_DEFAULT_ARGS_INVALID_1, False),
    ]

    @pytest.mark.parametrize("current, answer", DEFAULT_ARGS_INPUTS)
    def test_is_valid_default_argument(self, current, answer):
        """
        Given: Integration command with arguments.

        When: running is_valid_default_argument command.

        Then: Validate that up to 1 default arg name yields True, else yields False.
        """
        current = {"script": {"commands": current}}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_valid_default_argument() is answer

    MOCK_REPUTATIONS_1 = [{"contextPath": "Int.lol", "description": "desc", "type": "number"},
                          {"contextPath": "DBotScore.lives.matter"}]
    MOCK_REPUTATIONS_2 = [{"name": "panorama-commit-status", "outputs": 1}]
    MOCK_REPUTATIONS_INVALID_EMAIL = [
        {"contextPath": "DBotScore.Indicator", "description": "The indicator that was tested.", "type": "string"},
        {"contextPath": "DBotScore.Type", "description": "The indicator type.", "type": "string"},
        {"contextPath": "DBotScore.Vendor", "description": "Vendor used to calculate the score.", "type": "string"},
        {"contextPath": "DBotScore.Sc0re", "description": "The actual score.", "type": "int"},
        {"contextPath": "Email.To", "description": "email to", "type": "string"}]
    MOCK_REPUTATIONS_INVALID_FILE = [
        {"contextPath": "DBotScore.Indicator", "description": "The indicator that was tested.", "type": "string"},
        {"contextPath": "DBotScore.Type", "description": "The indicator type.", "type": "string"},
        {"contextPath": "DBotScore.Vendor", "description": "Vendor used to calculate the score.", "type": "string"},
        {"contextPath": "DBotScore.Score", "description": "The actual score.", "type": "int"},
        {"contextPath": "File.Md5", "description": "The MD5 hash of the file.", "type": "string"}]
    MOCK_REPUTATIONS_VALID_IP = [
        {"contextPath": "DBotScore.Indicator", "description": "The indicator that was tested.", "type": "string"},
        {"contextPath": "DBotScore.Type", "description": "The indicator type.", "type": "string"},
        {"contextPath": "DBotScore.Vendor", "description": "Vendor used to calculate the score.", "type": "string"},
        {"contextPath": "DBotScore.Score", "description": "The actual score.", "type": "int"},
        {"contextPath": "IP.Address", "description": "IP address", "type": "string"}]
    MOCK_REPUTATIONS_VALID_ENDPOINT = [
        {"contextPath": 'Endpoint.Hostname', "description": "The endpoint's hostname.", "type": "string"},
        {"contextPath": 'Endpoint.IPAddress', "description": "The endpoint's IP address.", "type": "string"},
        {"contextPath": 'Endpoint.ID', "description": "The endpoint's ID.", "type": "string"}]

    IS_OUTPUT_FOR_REPUTATION_INPUTS = [
        (MOCK_REPUTATIONS_1, "not bang", True),
        (MOCK_REPUTATIONS_2, "not bang", True),
        (MOCK_REPUTATIONS_INVALID_EMAIL, "email", False),
        (MOCK_REPUTATIONS_INVALID_FILE, "file", False),
        (MOCK_REPUTATIONS_VALID_IP, "ip", True),
        (MOCK_REPUTATIONS_VALID_ENDPOINT, "endpoint", True)
    ]

    @pytest.mark.parametrize("current, name, answer", IS_OUTPUT_FOR_REPUTATION_INPUTS)
    def test_is_outputs_for_reputations_commands_valid(self, current, name, answer):
        current = {"script": {"commands": [{"name": name, "outputs": current}]}}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_outputs_for_reputations_commands_valid() is answer
        structure.quite_bc = True
        assert validator.is_outputs_for_reputations_commands_valid() is True  # if quite_bc is true should succeed

    CASE_EXISTS_WITH_DEFAULT_TRUE = [
        {"name": "endpoint", "arguments": [{"name": "ip", "required": False, "default": True}],
         "outputs": [{'contextPath': 'Endpoint.ID'}, {'contextPath': 'Endpoint.IPAddress'},
                     {'contextPath': 'Endpoint.Hostname'}]}]
    CASE_REQUIRED_ARG_WITH_DEFAULT_FALSE = [
        {"name": "endpoint", "arguments": [{"name": "id", "required": False, "default": False}],
         "outputs": [{'contextPath': 'Endpoint.ID'}, {'contextPath': 'Endpoint.IPAddress'},
                     {'contextPath': 'Endpoint.Hostname'}]}]
    CASE_INVALID_MISSING_REQUIRED_ARGS = [
        {"name": "endpoint", "arguments": [{"name": "url", "required": False, "default": True}]}]
    CASE_INVALID_NON_DEFAULT_ARG_WITH_DEFAULT_TRUE = [
        {"name": "endpoint", "arguments": [{"name": "id", "required": False, "default": True}]}]
    CASE_INVALID_MISSING_OUTPUT = [
        {"name": "endpoint", "arguments": [{"name": "ip", "required": False, "default": True}],
         "outputs": [{'contextPath': 'Endpoint.IPAddress'}, {'contextPath': 'Endpoint.Hostname'},
                     {'contextPath': 'Endpoint.Test'}]}]
    ENDPOINT_CASES = [
        (CASE_EXISTS_WITH_DEFAULT_TRUE, True),
        (CASE_REQUIRED_ARG_WITH_DEFAULT_FALSE, True),
        (CASE_INVALID_MISSING_REQUIRED_ARGS, False),
        (CASE_INVALID_NON_DEFAULT_ARG_WITH_DEFAULT_TRUE, False)
    ]

    @pytest.mark.parametrize("current, answer", ENDPOINT_CASES)
    def test_is_valid_endpoint_command(self, current, answer):
        """
        Given: Endpoint command with arguments and outputs in yml.

        When: running is_valid_endpoint_command.

        Then: Validate that at least one of the required input exists (with correct deafult field)
         and the relevant outputs are correct.
        """
        current = {"script": {"commands": current}}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_valid_endpoint_command() is answer

    VALID_BETA = {"commonfields": {"id": "newIntegration"}, "name": "newIntegration",
                  "display": "newIntegration (Beta)", "beta": True}
    VALID_BETA_DEPRECATED = {"commonfields": {"id": "Proofpoint Server Protection"},
                             "name": "Proofpoint Server Protection",
                             "display": "Proofpoint Protection Server (Deprecated)",
                             "beta": True, "deprecated": True,
                             "description": "Deprecated. The integration uses an unsupported scraping API. "
                                            "Use Proofpoint Protection Server v2 instead."}
    INVALID_BETA_DISPLAY = {"commonfields": {"id": "newIntegration"}, "name": "newIntegration",
                            "display": "newIntegration", "beta": True}
    INVALID_BETA_ID = {"commonfields": {"id": "newIntegration-beta"}, "name": "newIntegration",
                       "display": "newIntegration", "beta": True}
    INVALID_BETA_NAME = {"commonfields": {"id": "newIntegration"}, "name": "newIntegration (Beta)",
                         "display": "newIntegration", "beta": True}
    INVALID_BETA_ALL_BETA = {"commonfields": {"id": "newIntegration beta"}, "name": "newIntegration beta",
                             "display": "newIntegration (Beta)"}
    INVALID_BETA_CHANGED_NAME_NO_BETA_FIELD = {"commonfields": {"id": "newIntegration beta"},
                                               "name": "newIntegration beta",
                                               "display": "newIntegration changed (Beta)"}
    IS_VALID_BETA_INPUTS = [
        (VALID_BETA, True, True),
        (VALID_BETA_DEPRECATED, False, True),
        (INVALID_BETA_DISPLAY, True, False),
        (INVALID_BETA_ID, True, False),
        (INVALID_BETA_NAME, True, False),
        (INVALID_BETA_ALL_BETA, INVALID_BETA_CHANGED_NAME_NO_BETA_FIELD, False),
    ]

    @pytest.mark.parametrize("current, old, answer", IS_VALID_BETA_INPUTS)
    def test_is_valid_beta_integration(self, current, old, answer):
        structure = mock_structure("", current, old)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        validator.old_file = old
        assert validator.is_valid_beta() is answer

    PROXY_VALID = [{"name": "proxy", "type": 8, "display": "Use system proxy settings", "required": False}]
    PROXY_WRONG_TYPE = [{"name": "proxy", "type": 9, "display": "Use system proxy settings", "required": False}]
    PROXY_WRONG_DISPLAY = [{"name": "proxy", "type": 8, "display": "bla", "required": False}]
    PROXY_WRONG_REQUIRED = [{"name": "proxy", "type": 8, "display": "Use system proxy settings", "required": True}]
    IS_PROXY_INPUTS = [
        (PROXY_VALID, True),
        (PROXY_WRONG_TYPE, False),
        (PROXY_WRONG_DISPLAY, False),
        (PROXY_WRONG_REQUIRED, False)
    ]

    @pytest.mark.parametrize("current, answer", IS_PROXY_INPUTS)
    def test_is_proxy_configured_correctly(self, current, answer):
        current = {"configuration": current}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_proxy_configured_correctly() is answer

    UNSECURE_VALID = [
        {"name": "unsecure", "type": 8, "display": "Trust any certificate (not secure)", "required": False}]
    INSECURE_WRONG_DISPLAY = [
        {"name": "insecure", "type": 8, "display": "Use system proxy settings", "required": False}]
    UNSECURE_WRONG_DISPLAY = [
        {"name": "unsecure", "type": 8, "display": "Use system proxy settings", "required": False}]
    UNSECURE_WRONG_DISPLAY_AND_TYPE = [
        {"name": "unsecure", "type": 7, "display": "Use system proxy settings", "required": False}]
    IS_INSECURE_INPUTS = [
        (UNSECURE_VALID, True),
        (INSECURE_WRONG_DISPLAY, False),
        (UNSECURE_WRONG_DISPLAY, False),
        (UNSECURE_WRONG_DISPLAY_AND_TYPE, False)
    ]

    @pytest.mark.parametrize("current, answer", IS_INSECURE_INPUTS)
    def test_is_insecure_configured_correctly(self, current, answer):
        current = {"configuration": current}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_insecure_configured_correctly() is answer

    VALID_CHECKBOX_PARAM = [
        {"name": "test1", "type": 8, "display": "test1", "required": False}]
    INVALID_CHECKBOX_PARAM = [
        {"name": "test2", "type": 8, "display": "test2", "required": True}]

    IS_INSECURE_INPUTS = [
        (VALID_CHECKBOX_PARAM, True),
        (INVALID_CHECKBOX_PARAM, False)
    ]

    @pytest.mark.parametrize("current, answer", IS_INSECURE_INPUTS)
    def test_is_checkbox_param_configured_correctly(self, current, answer):
        current = {"configuration": current}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_checkbox_param_configured_correctly() is answer

    INVALID_CATEGORY = {"category": "Analytics & SIEMM"}
    VALID_CATEGORY1 = {"category": "Endpoint"}
    VALID_CATEGORY2 = {"category": "File Integrity Management"}

    IS_VALID_CATEGORY_INPUTS = [
        (VALID_CATEGORY1, True),
        (VALID_CATEGORY2, True),
        (INVALID_CATEGORY, False)
    ]

    @pytest.mark.parametrize("current, answer", IS_VALID_CATEGORY_INPUTS)
    def test_is_valid_category(self, current, answer):
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_valid_category() is answer

    VALID_DISPLAY_NON_HIDDEN = [
        {"name": "unsecure", "type": 8, "display": "Trust any certificate (not secure)", "required": False,
         "hidden": False}]
    VALID_DISPLAY_HIDDEN = [
        {"name": "insecure", "type": 8, "display": "Use system proxy settings", "required": False, "hidden": True}]
    INVALID_DISPLAY_NON_HIDDEN = [
        {"name": "unsecure", "type": 8, "display": "", "required": False, "hidden": False}]
    INVALID_NO_DISPLAY_NON_HIDDEN = [
        {"name": "unsecure", "type": 8, "required": False, "hidden": False}]
    VALID_NO_DISPLAY_TYPE_EXPIRATION = [
        {"name": "unsecure", "type": 17, "required": False, "hidden": False}]
    INVALID_DISPLAY_TYPE_EXPIRATION = [
        {"name": "unsecure", "type": 17, "display": "some display", "required": False, "hidden": False}]
    INVALID_DISPLAY_BUT_VALID_DISPLAYPASSWORD = [
        {"name": "credentials", "type": 9, "display": "", "displaypassword": "some display password", "required": True,
         "hiddenusername": True}]
    IS_VALID_DISPLAY_INPUTS = [
        (VALID_DISPLAY_NON_HIDDEN, True),
        (VALID_DISPLAY_HIDDEN, True),
        (INVALID_DISPLAY_NON_HIDDEN, False),
        (INVALID_NO_DISPLAY_NON_HIDDEN, False),
        (VALID_NO_DISPLAY_TYPE_EXPIRATION, True),
        (INVALID_DISPLAY_TYPE_EXPIRATION, False),
        (FEED_REQUIRED_PARAMS_STRUCTURE, True),
        (INVALID_DISPLAY_BUT_VALID_DISPLAYPASSWORD, True)
    ]

    @pytest.mark.parametrize("configuration_setting, answer", IS_VALID_DISPLAY_INPUTS)
    def test_is_valid_display_configuration(self, configuration_setting, answer):
        current = {"configuration": configuration_setting}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_not_valid_display_configuration() is not answer
        structure.quite_bc = True
        assert validator.is_not_valid_display_configuration() is False  # if quite_bc is true should always succeed

    VALID_FEED = [
        # Valid feed
        (True, "5.5.0"),
        # No feed, including from version
        (False, "4.5.0"),
        # No feed, no from version
        (False, None),
        # No feed, fromversion 5.5
        (False, "5.5.0"),
    ]

    @pytest.mark.parametrize("feed, fromversion", VALID_FEED)
    def test_valid_feed(self, feed, fromversion):
        current = {
            "script": {"feed": feed},
            "fromversion": fromversion,
            'configuration': deepcopy(FEED_REQUIRED_PARAMS_STRUCTURE)
        }
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        assert validator.is_valid_feed()

    INVALID_FEED = [
        # invalid from version
        (True, "5.0.0"),
        # Feed missing fromversion
        (True, None),
    ]

    @pytest.mark.parametrize("feed, fromversion", INVALID_FEED)
    def test_invalid_feed(self, feed, fromversion):
        current = {"script": {"feed": feed}, "fromversion": fromversion}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        assert not validator.is_valid_feed()

    V2_VALID = {"display": "integrationname v2", "name": "integrationname v2", "id": "integrationname v2"}
    V2_WRONG_DISPLAY_1 = {"display": "integrationname V2", "name": "integrationname V2", "id": "integrationname V2"}
    V2_WRONG_DISPLAY_2 = {"display": "integrationnameV2", "name": "integrationnameV2", "id": "integrationnameV2"}
    V2_WRONG_DISPLAY_3 = {"display": "integrationnamev2", "name": "integrationnamev2", "id": "integrationnamev2"}
    V2_NAME_INPUTS = [
        (V2_VALID, True),
        (V2_WRONG_DISPLAY_1, False),
        (V2_WRONG_DISPLAY_2, False),
        (V2_WRONG_DISPLAY_3, False)
    ]

    @pytest.mark.parametrize("current, answer", V2_NAME_INPUTS)
    def test_is_valid_display_name(self, current, answer):
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_valid_display_name() is answer

    def test_is_valid_description_positive(self):
        integration_path = os.path.normpath(
            os.path.join(f'{git_path()}/demisto_sdk/tests', 'test_files', 'integration-Zoom.yml')
        )
        structure = mock_structure(file_path=integration_path)
        validator = IntegrationValidator(structure)
        assert validator.is_valid_description() is True

    DEPRECATED_VALID = {"deprecated": True, "display": "ServiceNow (Deprecated)",
                        "description": "Deprecated. Use the XXXX integration instead."}
    DEPRECATED_VALID2 = {"deprecated": True, "display": "Feodo Tracker Hashes Feed (Deprecated)",
                         "description": "Deprecated. Feodo Tracker no longer supports this feed. "
                                        "No available replacement."}
    DEPRECATED_VALID3 = {"deprecated": True, "display": "Proofpoint Protection Server (Deprecated)",
                         "description": "Deprecated. The integration uses an unsupported scraping API. "
                                        "Use Proofpoint Protection Server v2 instead."}
    DEPRECATED_INVALID_DISPLAY = {"deprecated": True, "display": "ServiceNow (Old)",
                                  "description": "Deprecated. Use the XXXX integration instead."}
    DEPRECATED_INVALID_DESC = {"deprecated": True, "display": "ServiceNow (Deprecated)", "description": "Deprecated."}
    DEPRECATED_INVALID_DESC2 = {"deprecated": True, "display": "ServiceNow (Deprecated)",
                                "description": "Use the ServiceNow integration to manage..."}
    DEPRECATED_INVALID_DESC3 = {"deprecated": True, "display": "Proofpoint Protection Server (Deprecated)",
                                "description": "Deprecated. The integration uses an unsupported scraping API."}
    DEPRECATED_INPUTS = [
        (DEPRECATED_VALID, True),
        (DEPRECATED_VALID2, True),
        (DEPRECATED_VALID3, True),
        (DEPRECATED_INVALID_DISPLAY, False),
        (DEPRECATED_INVALID_DESC, False),
        (DEPRECATED_INVALID_DESC2, False),
        (DEPRECATED_INVALID_DESC3, False)
    ]

    @pytest.mark.parametrize("current, answer", DEPRECATED_INPUTS)
    def test_is_valid_deprecated_integration(self, current, answer):
        """
        Given
            - A deprecated integration with a display and description.

        When
            - running is_valid_as_deprecated.

        Then
            - an integration with an invalid display name or invalid description will be errored.
        """
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_valid_as_deprecated() is answer

    def test_valid_integration_parameters_display_name(self, integration):
        """
        Given
            - An integration with valid parameters display names.
        When
            - running is_valid_parameters_display_name.
        Then
            - an integration with a valid parameters display name is valid.
        """

        integration.yml.write_dict({'configuration': [
            {'display': 'Token'},
            {'display': 'Username'}
        ]})
        structure_validator = StructureValidator(integration.yml.path, predefined_scheme='integration')
        validator = IntegrationValidator(structure_validator)

        assert validator.is_valid_parameters_display_name()

    def test_invalid_integration_parameters_display_name(self, integration):
        """
        Given
            - An integration with invalid parameters display names.
        When
            - running is_valid_parameters_display_name.
        Then
            - an integration with an invalid parameters display name is invalid.
        """

        integration.yml.write_dict({'configuration': [
            {'display': 'token'},
            {'display': 'User_name'}
        ]})

        with ChangeCWD(integration.repo_path):
            structure_validator = StructureValidator(integration.yml.path, predefined_scheme='integration')
            validator = IntegrationValidator(structure_validator)

            assert not validator.is_valid_parameters_display_name()

    @pytest.mark.parametrize('support_level, expected_result', [('XSOAR', True), ('community', False)])
    def test_fromlicense_in_integration_parameters_fields(self, pack, support_level, expected_result):
        """
        Given
            - An integration from a contributor with not allowed key ('fromlicense') in parameters.
        When
            - Running is_valid_param.
        Then
            - an integration with an invalid parameters display name is invalid.
        """
        pack.pack_metadata.write_json({"support": support_level})
        integration = pack.create_integration('contributor')

        integration.yml.write_dict({'configuration': [{
            'name': 'token',
            'display': 'token',
            'fromlicense': 'encrypted'
        }]})

        with ChangeCWD(pack.repo_path):
            structure_validator = StructureValidator(integration.yml.path, predefined_scheme='integration')
            validator = IntegrationValidator(structure_validator)

            result = validator.has_no_fromlicense_key_in_contributions_integration()

        assert result == expected_result

    def test_valid_integration_path(self, integration):
        """
        Given
            - An integration with valid file path.
        When
            - running is_valid_integration_file_path.
        Then
            - an integration with a valid file path is valid.
        """

        structure_validator = StructureValidator(integration.yml.path, predefined_scheme='integration')
        validator = IntegrationValidator(structure_validator)
        validator.file_path = 'Packs/VirusTotal/Integrations/integration-VirusTotal_5.5.yml'

        assert validator.is_valid_integration_file_path()

        structure_validator = StructureValidator(integration.path, predefined_scheme='integration')
        validator = IntegrationValidator(structure_validator)
        validator.file_path = 'Packs/VirusTotal/Integrations/VirusTotal/Virus_Total.yml'

        assert validator.is_valid_integration_file_path()

    def test_invalid_integration_path(self, integration, mocker):
        """
        Given
            - An integration with invalid file path.
        When
            - running is_valid_integration_file_path.
        Then
            - an integration with an invalid file path is invalid.
        """

        structure_validator = StructureValidator(integration.yml.path, predefined_scheme='integration')
        validator = IntegrationValidator(structure_validator)
        validator.file_path = 'Packs/VirusTotal/Integrations/VirusTotal/integration-VirusTotal_5.5.yml'

        mocker.patch.object(validator, "handle_error", return_value=True)

        assert not validator.is_valid_integration_file_path()

        structure_validator = StructureValidator(integration.path, predefined_scheme='integration')
        validator = IntegrationValidator(structure_validator)
        validator.file_path = 'Packs/VirusTotal/Integrations/Virus_Total_5.yml'

        mocker.patch.object(validator, "handle_error", return_value=True)

        assert not validator.is_valid_integration_file_path()

    def test_folder_name_without_separators(self, pack):
        """
        Given
            - An integration without separators in folder name.
        When
            - running check_separators_in_folder.
        Then
            - Ensure the validate passes.
        """

        integration = pack.create_integration('myInt')

        structure_validator = StructureValidator(integration.yml.path)
        validator = IntegrationValidator(structure_validator)

        assert validator.check_separators_in_folder()

    def test_files_names_without_separators(self, pack):
        """
        Given
            - An integration without separators in files names.
        When
            - running check_separators_in_files.
        Then
            - Ensure the validate passes.
        """

        integration = pack.create_integration('myInt')

        structure_validator = StructureValidator(integration.yml.path)
        validator = IntegrationValidator(structure_validator)

        assert validator.check_separators_in_files()

    def test_folder_name_with_separators(self, pack):
        """
        Given
            - An integration with separators in folder name.
        When
            - running check_separators_in_folder.
        Then
            - Ensure the validate failed.
        """

        integration = pack.create_integration('my_Int')

        with ChangeCWD(integration.repo_path):
            structure_validator = StructureValidator(integration.yml.path)
            validator = IntegrationValidator(structure_validator)

            assert not validator.check_separators_in_folder()

    def test_files_names_with_separators(self, pack):
        """
        Given
            - An integration with separators in files names.
        When
            - running check_separators_in_files.
        Then
            - Ensure the validate failed.
        """

        integration = pack.create_integration('my_Int')

        with ChangeCWD(integration.repo_path):
            structure_validator = StructureValidator(integration.yml.path)
            validator = IntegrationValidator(structure_validator)

            assert not validator.check_separators_in_files()

    def test_name_contains_the_type(self, pack):
        """
        Given
            - An integration with a name that contains the word "integration".
        When
            - running name_not_contain_the_type.
        Then
            - Ensure the validate failed.
        """
        integration = pack.create_integration(yml={"name": "test_integration"})

        with ChangeCWD(pack.repo_path):
            structure_validator = StructureValidator(integration.yml.path)
            validator = IntegrationValidator(structure_validator)

            assert not validator.name_not_contain_the_type()

    def test_display_name_contains_the_type(self, pack):
        """
        Given
            - An integration with a display name that contains the word "integration".
        When
            - running name_not_contain_the_type.
        Then
            - Ensure the validate failed.
        """

        integration = pack.create_integration(yml={"display": "test_integration"})

        with ChangeCWD(pack.repo_path):
            structure_validator = StructureValidator(integration.yml.path)
            validator = IntegrationValidator(structure_validator)

            assert not validator.name_not_contain_the_type()

    def test_name_does_not_contains_the_type(self, pack):
        """
        Given
            - An integration with a name that does not contains "integration" string.
        When
            - running name_not_contain_the_type.
        Then
            - Ensure the validate passes.
        """

        integration = pack.create_integration(yml={"name": "test", "display": "test"})

        structure_validator = StructureValidator(integration.yml.path)
        validator = IntegrationValidator(structure_validator)

        assert validator.name_not_contain_the_type()

    @pytest.mark.parametrize('support, parameter_type, expected_result', [
        ('xsoar', 4, False),
        ('xsoar', 9, True),
        ('community', 4, True),
        ('partner', 4, True),
    ])
    def test_is_api_token_in_credential_type(self, pack, support, parameter_type, expected_result):
        """
        Given
            - An integration with API token parameter in non credential type.
        When
            - Running is_api_token_in_credential_type on `xsoar` support integration and non `xsoar` integration.
        Then
            - Ensure the validate on `xsoar` integration support failed on invalid type,
            the type of the parameter should be 9 (credentials).
        """

        pack.pack_metadata.write_json({
            'support': support
        })

        integration = pack.create_integration(yml={
            'configuration': [{
                'display': 'API token',
                'name': 'token',
                'type': parameter_type  # Encrypted text failed
            }]
        })

        with ChangeCWD(pack.repo_path):
            structure_validator = StructureValidator(integration.yml.path, predefined_scheme='integration')
            validator = IntegrationValidator(structure_validator)

            assert validator.is_api_token_in_credential_type() == expected_result

    IS_SKIPPED_INPUTS = [
        ({'skipped_integrations': {"SomeIntegration": "No instance"}}, False),
        ({'skipped_integrations': {"SomeOtherIntegration": "No instance"}}, True)
    ]

    @pytest.mark.parametrize("conf_dict, answer", IS_SKIPPED_INPUTS)
    def test_is_unskipped_integration(self, conf_dict, answer):
        """
        Given:
            - An integration.
            - conf file with configurations for the integration.

        When: running validate specifically on integration.

        Then: Validate the integration is not skipped.
        """
        current = {"commonfields": {"id": "SomeIntegration"}}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_unskipped_integration(conf_dict) is answer


class TestIsFetchParamsExist:
    def setup(self):
        config = {
            'configuration': deepcopy(FETCH_REQUIRED_PARAMS),
            'script': {'isfetch': True}
        }
        self.validator = IntegrationValidator(mock_structure("", config))

    def test_valid(self):
        assert self.validator.is_valid_fetch(), 'is_valid_fetch() returns False instead True'

    def test_sanity(self):
        # missing param in configuration
        self.validator.current_file['configuration'] = [t for t in self.validator.current_file['configuration']
                                                        if t['name'] != 'incidentType']
        assert self.validator.is_valid_fetch() is False, 'is_valid_fetch() returns True instead False'

    def test_missing_field(self):
        # missing param
        for i, t in enumerate(self.validator.current_file['configuration']):
            if t['name'] == 'incidentType':
                del self.validator.current_file['configuration'][i]['name']
        print(self.validator.current_file['configuration'])
        assert self.validator.is_valid_fetch() is False, 'is_valid_fetch() returns True instead False'

    def test_malformed_field(self, capsys):
        # incorrect param
        config = self.validator.current_file['configuration']
        self.validator.current_file['configuration'] = []
        for t in config:
            if t['name'] == 'incidentType':
                t['type'] = 123
            self.validator.current_file['configuration'].append(t)

        assert self.validator.is_valid_fetch() is False, 'is_valid_fetch() returns True instead False'
        captured = capsys.readouterr()
        out = captured.out
        print(out)
        assert "display: Incident type" in out
        assert "name: incidentType" in out
        assert "required: false" in out
        assert "type: 13" in out

    def test_not_fetch(self, capsys):
        self.test_malformed_field(capsys)
        self.validator.is_valid = True
        self.validator.current_file['script']['isfetch'] = False
        assert self.validator.is_valid_fetch(), 'is_valid_fetch() returns False instead True'


class TestIsValidMaxFetchAndFirstFetch:
    """
    Given
    - yml file of integration as config
    When
    - run validate checks
    Then
    - if the isfetch identifier is true make sure the first_fetch and max_fetch params exists
    - make sure max_fetch param has a default value
    """

    def setup(self):
        config = {
            'configuration': deepcopy([FIRST_FETCH_PARAM, MAX_FETCH_PARAM]),
            'script': {'isfetch': True}
        }
        self.validator = IntegrationValidator(mock_structure("", config))

    def test_valid(self):
        assert self.validator.is_valid_max_fetch_and_first_fetch(), 'is_valid_fetch() returns False instead True'

    def test_missing_max_fetch(self):
        # missing param in configuration
        self.validator.current_file['configuration'] = [t for t in self.validator.current_file['configuration']
                                                        if t['name'] != 'max_fetch']
        assert self.validator.is_valid_max_fetch_and_first_fetch() is False, \
            'is_valid_fetch() returns True instead False'

    def test_missing_default_value_in_max_fetch(self):
        # missing param in configuration
        for param in self.validator.current_file['configuration']:
            if param.get('name') == 'max_fetch':
                param.pop('defaultvalue')
        assert self.validator.is_valid_max_fetch_and_first_fetch() is False, \
            'is_valid_fetch() returns True instead False'

    def test_missing_fetch_time(self):
        # missing param in configuration
        self.validator.current_file['configuration'] = [t for t in self.validator.current_file['configuration']
                                                        if t['name'] != 'first_fetch']
        assert self.validator.is_valid_max_fetch_and_first_fetch() is False, \
            'is_valid_fetch() returns True instead False'

    def test_not_fetch(self):
        self.validator.is_valid = True
        self.validator.current_file['script']['isfetch'] = False
        assert self.validator.is_valid_max_fetch_and_first_fetch(), \
            'is_valid_fetch() returns False instead True'


class TestIsFeedParamsExist:

    def setup(self):
        config = {
            'configuration': deepcopy(FEED_REQUIRED_PARAMS_STRUCTURE),
            'script': {'feed': True}
        }
        self.validator = IntegrationValidator(mock_structure("", config))

    def test_valid(self):
        assert self.validator.all_feed_params_exist(), 'all_feed_params_exist() returns False instead True'

    def test_sanity(self):
        # missing param in configuration
        self.validator.current_file['configuration'] = [t for t in self.validator.current_file['configuration']
                                                        if not t.get('display')]
        assert self.validator.all_feed_params_exist() is False, 'all_feed_params_exist() returns True instead False'

    def test_missing_field(self):
        # missing param
        configuration = self.validator.current_file['configuration']
        for i in range(len(configuration)):
            if not configuration[i].get('display'):
                del configuration[i]['name']
        self.validator.current_file['configuration'] = configuration
        assert self.validator.all_feed_params_exist() is False, 'all_feed_params_exist() returns True instead False'

    def test_malformed_field(self):
        # incorrect param
        self.validator.current_file['configuration'] = []
        for t in self.validator.current_file['configuration']:
            if not t.get('display'):
                t['type'] = 123
            self.validator.current_file['configuration'].append(t)

        assert self.validator.all_feed_params_exist() is False, 'all_feed_params_exist() returns True instead False'

    def test_hidden_feed_reputation_field(self):
        # the feed reputation param is hidden
        configuration = self.validator.current_file['configuration']
        for item in configuration:
            if item.get('name') == 'feedReputation':
                item['hidden'] = True
        assert self.validator.all_feed_params_exist() is True, \
            'all_feed_params_exist() returns False instead True for feedReputation param'

    def test_additional_info_contained(self):
        """
        Given:
        - Parameters of feed integration.

        When:
        - Integration has all feed required params, and additionalinfo containing the expected additionalinfo parameter.

        Then:
        - Ensure that all_feed_params_exists() returns true.
        """
        configuration = self.validator.current_file['configuration']
        for item in configuration:
            if item.get('additionalinfo'):
                item['additionalinfo'] = f'''{item['additionalinfo']}.'''
        assert self.validator.all_feed_params_exist() is True, \
            'all_feed_params_exist() returns False instead True'

    NO_HIDDEN = {"configuration": [{"id": "new", "name": "new", "display": "test"}, {"d": "123", "n": "s", "r": True}]}
    HIDDEN_FALSE = {"configuration": [{"id": "n", "hidden": False}, {"display": "123", "name": "serer"}]}
    HIDDEN_TRUE = {"configuration": [{"id": "n", "n": "n"}, {"display": "123", "required": "false", "hidden": True}]}
    HIDDEN_TRUE_AND_FALSE = {"configuration": [{"id": "n", "hidden": False}, {"ty": "0", "r": "true", "hidden": True}]}
    HIDDEN_ALLOWED_TRUE = {"configuration": [{"name": "longRunning", "required": "false", "hidden": True}]}
    HIDDEN_ALLOWED_FEED_REPUTATION = {
        "configuration": [{"name": "feedReputation", "required": "false", "hidden": True}]}

    IS_VALID_HIDDEN_PARAMS = [
        (NO_HIDDEN, True),
        (HIDDEN_FALSE, True),
        (HIDDEN_TRUE, False),
        (HIDDEN_TRUE_AND_FALSE, False),
        (HIDDEN_ALLOWED_TRUE, True),
        (HIDDEN_ALLOWED_FEED_REPUTATION, True),
    ]

    @pytest.mark.parametrize("current, answer", IS_VALID_HIDDEN_PARAMS)
    def test_is_valid_hidden_params(self, current, answer):
        structure = mock_structure(current_file=current)
        validator = IntegrationValidator(structure)
        assert validator.is_valid_hidden_params() is answer

    @pytest.mark.parametrize("script_type, fromversion, res", [
        ('powershell', None, False),
        ('powershell', '4.5.0', False),
        ('powershell', '5.5.0', True),
        ('powershell', '5.5.1', True),
        ('powershell', '6.0.0', True),
        ('python', '', True),
        ('python', '4.5.0', True),
    ])
    def test_valid_pwsh(self, script_type, fromversion, res):
        current = {
            "script": {"type": script_type},
            "fromversion": fromversion,
        }
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        assert validator.is_valid_pwsh() == res

    def test_empty_commands(self):
        """
        Given: an integration with no commands

        When: running validate on integration with no command.

        Then: Validate it's valid.
        """
        current = {"script": {"commands": None}}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_valid_default_array_argument_in_reputation_command() is True

    @pytest.mark.parametrize('param', [
        {'commands': ['something']},
        {'isFetch': True},
        {'longRunning': True},
        {'feed': True}
    ])
    def test_is_there_a_runnable(self, param):
        """
        Given: one of any runnable integration

        When: running validate on integration with at least one of commands, fetch, feed or long-running

        Then: Validate it's valid.
        """
        current = {"script": param}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_there_a_runnable() is True

    def test_is_there_a_runnable_negative(self):
        """
        Given: an integration with no runnable param

        When: running validate on integration with no one of commands, fetch, feed or long-running

        Then: Validate it's invalid.
        """
        current = {"script": {}}
        structure = mock_structure("", current)
        validator = IntegrationValidator(structure)
        validator.current_file = current
        assert validator.is_there_a_runnable() is False


class TestisContextChanged:
    invalid_readme = """
#### Base Command

`test-command`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| arg | arg | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Test | - | - |

#### Command Example

```
!test-command
```

#### Human Readable Output
"""
    valid_readme = """
#### Base Command

`test-command`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| arg | arg | Required |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Test.test | - | - |

#### Command Example

```
!test-command
```

#### Human Readable Output
"""
    TEST_CASE = [
        (valid_readme, {"script": {'commands': [{
            'name': 'test-command',
            'outputs': [{'contextPath': 'Test.test', 'description': '-', 'type': '-'}]}]
        }},  # case README and YML are synced
            True  # expected results
        ),

        (invalid_readme, {"script": {'commands': [{
            'name': 'test-command',
            'outputs': [{'contextPath': 'Test.test', 'description': '-', 'type': '-'}]}]
        }},  # case context missing from README
            False  # expected results
        ),
        (valid_readme, {"script": {'commands': [{
            'name': 'test-command',
            'outputs': [{'contextPath': 'Test', 'description': '-', 'type': '-'}]}]
        }},  # case context missing from YML
            False  # expected results
        ),
    ]

    @pytest.mark.parametrize('readme, current_yml, expected', TEST_CASE)
    def test_is_context_correct_in_readme(self, readme, current_yml, expected):
        """
        Given: a changed YML file
        When: running validate on integration with at least one command
        Then: Validate it's synced with the README.
        """
        patcher = patch('os.path.exists')
        mock_thing = patcher.start()
        mock_thing.side_effect = lambda x: True
        with patch("builtins.open", mock_open(read_data=readme)) as _:
            current = {"script": {}}
            structure = mock_structure("Pack/Test", current)
            validator = IntegrationValidator(structure)
            validator.current_file = current_yml
            res = validator.is_context_correct_in_readme()
            assert res == expected
        patcher.stop()


# demisto_sdk/commands/common/tests/layout_test.py

import pytest
from mock import patch

from demisto_sdk.commands.common.hook_validations.layout import (
    LayoutsContainerValidator, LayoutValidator)
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator


def mock_structure(file_path=None, current_file=None, old_file=None):
    with patch.object(StructureValidator, '__init__', lambda a, b: None):
        structure = StructureValidator(file_path)
        structure.is_valid = True
        structure.scheme_name = 'layout'
        structure.file_path = file_path
        structure.current_file = current_file
        structure.old_file = old_file
        structure.prev_ver = 'master'
        structure.branch_name = ''
        return structure


class TestLayoutValidator:

    LAYOUT_WITH_VALID_INCIDENT_FIELD = {
        "layout": {"tabs": [{"sections": [{"items": [{"fieldId": "Incident Field"}]}]}]}
    }

    LAYOUT_CONTAINER_WITH_VALID_INCIDENT_FIELD = {
        "detailsV2": {"tabs": [{"sections": [{"items": [{"fieldId": "Incident Field"}]}]}]}
    }

    ID_SET_WITH_INCIDENT_FIELD = {"IncidentFields": [{"Incident Field": {"name": "Incident Field"}}],
                                  "IndicatorFields": [{"Incident Field": {"name": "Incident Field"}}]}

    ID_SET_WITHOUT_INCIDENT_FIELD = {"IncidentFields": [{"fields": {"name": "name"}}],
                                     "IndicatorFields": [{"fields": {"name": "name"}}]}

    IS_INCIDENT_FIELD_EXIST = [
        (LAYOUT_WITH_VALID_INCIDENT_FIELD, ID_SET_WITH_INCIDENT_FIELD, True, True),
        (LAYOUT_WITH_VALID_INCIDENT_FIELD, ID_SET_WITHOUT_INCIDENT_FIELD, True, False)
    ]

    @pytest.mark.parametrize("layout_json, id_set_json, is_circle, expected_result", IS_INCIDENT_FIELD_EXIST)
    def test_layout_is_incident_field_exist_in_content(self, repo, layout_json, id_set_json, is_circle,
                                                       expected_result):
        """
        Given
        - A layout with incident fields
        - An id_set file.
        When
        - validating layout
        Then
        - validating that incident fields exist in id_set.
        """
        repo.id_set.write_json(id_set_json)
        structure = mock_structure("", layout_json)
        validator = LayoutValidator(structure)
        assert validator.is_incident_field_exist(id_set_json, is_circle) == expected_result

    IS_INCIDENT_FIELD_EXIST = [
        (LAYOUT_CONTAINER_WITH_VALID_INCIDENT_FIELD, ID_SET_WITH_INCIDENT_FIELD, True, True),
        (LAYOUT_CONTAINER_WITH_VALID_INCIDENT_FIELD, ID_SET_WITHOUT_INCIDENT_FIELD, True, False)
    ]

    @pytest.mark.parametrize("layout_json, id_set_json, is_circle, expected_result", IS_INCIDENT_FIELD_EXIST)
    def test_layout_container_is_incident_field_exist_in_content(self, repo, layout_json, id_set_json, is_circle,
                                                                 expected_result):
        """
        Given
        - A layout container with incident fields
        - An id_set file.
        When
        - validating layout container
        Then
        - validating that incident fields exist in id_set.
        """
        repo.id_set.write_json(id_set_json)
        structure = mock_structure("", layout_json)
        validator = LayoutsContainerValidator(structure)
        assert validator.is_incident_field_exist(id_set_json, is_circle) == expected_result

    IS_MATCHING_NAME_ID_INPUT = [
        ({"id": "name", "name": "name"}, True),
        ({"id": "id_field", "name": "name_field"}, False)
    ]

    @pytest.mark.parametrize("layout_container, result", IS_MATCHING_NAME_ID_INPUT)
    def test_is_name_id_equal(self, repo, layout_container, result):
        """
        Given
        - A layout container with name and id
        When
        - validating layout container
        Then
        - validating that layout_container name and id are equal.
        """

        structure = mock_structure("", layout_container)
        validator = LayoutsContainerValidator(structure)

        assert validator.is_id_equals_name() == result

    @staticmethod
    def test_is_valid_layout_container():
        layout = {
            'version': -1,
            'name': 'test layout container',
        }
        structure = mock_structure('', layout)
        validator = LayoutsContainerValidator(structure)
        assert not validator.is_valid_layout(validate_rn=False)


# demisto_sdk/commands/common/tests/legacy_git_tools_test.py

import os

from demisto_sdk.commands.common.constants import FileType
from demisto_sdk.commands.common.legacy_git_tools import (
    filter_changed_files, get_changed_files, get_modified_and_added_files)
from demisto_sdk.tests.constants_test import *


def test_filter_changed_files(mocker):
    """
        Given:
            - A string of git diff results
        When:
            - running filter_changed_files on the string
        Then:
            - Ensure the modified files are recognized correctly.
            - Ensure the added files are recognized correctly.
            - Ensure the renamed file is in a tup;e in the modified files.
            - Ensure modified metadata files are in the changed_meta_files and that the added one is not.
            - Ensure the added code and meta files are not in added files.
            - Ensure old format file is recognized correctly.
            - Ensure deleted file is recognized correctly.
            - Ensure ignored files are set correctly.
    """

    mocker.patch.object(os.path, 'isfile', return_value=True)
    diff_string = f"M	{VALID_INCIDENT_FIELD_PATH}\n" \
                  f"M	{VALID_PYTHON_INTEGRATION_PATH}\n" \
                  f"M	{VALID_INTEGRATION_TEST_PATH}\n" \
                  f"M	{VALID_METADATA1_PATH}\n" \
                  f"M	{VALID_CLASSIFIER_PATH}\n" \
                  f"M	{VALID_DESCRIPTION_PATH}\n" \
                  f"M	{VALID_LAYOUT_PATH}\n" \
                  f"R100	{VALID_INTEGRATION_TEST_PATH}	{VALID_INTEGRATION_TEST_PATH}\n" \
                  f"A	{VALID_PACK_IGNORE_PATH}\n" \
                  f"A	{VALID_INDICATOR_FIELD_PATH}\n" \
                  f"A	{VALID_SECRETS_IGNORE_PATH}\n" \
                  f"A	{VALID_PYTHON_INTEGRATION_PATH}\n" \
                  f"A	{VALID_INTEGRATION_TEST_PATH}\n" \
                  f"A	{VALID_DESCRIPTION_PATH}\n" \
                  f"A	{VALID_IMAGE_PATH}\n" \
                  f"A	{VALID_WIDGET_PATH}\n" \
                  f"A	{VALID_PYTHON_INTEGRATION_TEST_PATH}\n" \
                  f"A	{VALID_PIPEFILE_PATH}\n" \
                  f"A	{VALID_PIPEFILE_LOCK_PATH}\n" \
                  f"A	{VALID_README_PATH}\n" \
                  f"A	{VALID_METADATA2_PATH}\n" \
                  f"D	{VALID_SCRIPT_PATH}\n" \
                  f"D	{VALID_DASHBOARD_PATH}\n" \
                  f"A	{VALID_JSON_FILE_FOR_UNIT_TESTING}"

    modified_files_list, added_files_list, deleted_files, old_format_files, changed_meta_files, \
        ignored_files, new_packs = filter_changed_files(files_string=diff_string, print_ignored_files=True)

    # checking that modified files are recognized correctly
    assert VALID_INCIDENT_FIELD_PATH in modified_files_list
    assert VALID_CLASSIFIER_PATH in modified_files_list
    assert VALID_DESCRIPTION_PATH in modified_files_list
    assert VALID_INTEGRATION_TEST_PATH in old_format_files
    assert VALID_LAYOUT_PATH in modified_files_list

    # checking that there are no unwanted files in modified files
    assert VALID_PIPEFILE_LOCK_PATH not in modified_files_list
    assert VALID_SCRIPT_PATH not in modified_files_list

    # checking that files in tests dir are not in modified_files
    assert VALID_JSON_FILE_FOR_UNIT_TESTING not in modified_files_list

    # check that the modified code file is not there but the yml file is
    assert VALID_INTEGRATION_TEST_PATH in old_format_files
    assert VALID_PYTHON_INTEGRATION_PATH not in modified_files_list

    # check that the modified metadata file is in the changed_meta_files but the added one is not
    assert VALID_METADATA1_PATH in changed_meta_files
    assert VALID_METADATA2_PATH not in changed_meta_files

    # check that the added files are recognized correctly
    assert VALID_README_PATH in added_files_list
    assert VALID_INTEGRATION_TEST_PATH in old_format_files
    assert VALID_WIDGET_PATH in added_files_list
    assert VALID_INDICATOR_FIELD_PATH in added_files_list

    # check that the added code files and meta file are not in the added_files
    assert VALID_PYTHON_INTEGRATION_PATH not in added_files_list
    assert VALID_PYTHON_INTEGRATION_TEST_PATH not in added_files_list
    assert VALID_METADATA1_PATH not in added_files_list

    # check that non-image, pipfile, description or schema are in the ignored files and the rest are
    assert VALID_PIPEFILE_PATH not in ignored_files
    assert VALID_PIPEFILE_LOCK_PATH not in ignored_files
    assert VALID_DESCRIPTION_PATH not in ignored_files
    assert VALID_IMAGE_PATH not in ignored_files
    assert VALID_SECRETS_IGNORE_PATH in ignored_files
    assert VALID_PYTHON_INTEGRATION_TEST_PATH in ignored_files
    assert VALID_PACK_IGNORE_PATH in ignored_files

    # check recognized deleted file
    assert VALID_SCRIPT_PATH in deleted_files
    assert VALID_DASHBOARD_PATH in deleted_files


def test_staged(mocker):
    """
    Given
        - staged = True flag
        - diff on yml file
    When
        - Run get_modified_and_added_files.
    Then
        - modified files returns only the staged files using git diff.
        - modified packs only returns the staged packs.
    """

    def run_command_effect(arg):
        # if the call is to check the staged files only - return the HelloWorld integration.
        if arg == 'git diff --name-only --staged':
            return 'Packs/HelloWorld/Integrations/HelloWorld.yml'

        # else return all the files that were changed from master and their status in comparison to the master.
        else:
            return 'M\tPacks/HelloWorld/Integrations/HelloWorld.yml\nM\tPacks/BigFix/Integrations/BigFix/BigFix.yml'

    mocker.patch('demisto_sdk.commands.common.legacy_git_tools.run_command', side_effect=run_command_effect)
    mocker.patch('demisto_sdk.commands.common.legacy_git_tools.os.path.isfile', return_value=True)
    mocker.patch('demisto_sdk.commands.common.legacy_git_tools.find_type', return_value=FileType.INTEGRATION)
    mocker.patch('demisto_sdk.commands.validate.validate_manager.ValidateManager.is_old_file_format',
                 return_value=False)

    modified_files, _, _, _, modified_packs, _, _ = get_modified_and_added_files('..', 'master', staged=True,
                                                                                 is_circle=True)
    assert modified_files == {'Packs/HelloWorld/Integrations/HelloWorld.yml'}
    assert modified_packs == {'HelloWorld'}


def test_get_changed_files(mocker):
    diff_string = f"M	{VALID_LAYOUT_PATH}\n" \
                  f"R100	{VALID_INTEGRATION_TEST_PATH}	{VALID_INTEGRATION_TEST_PATH}\n" \
                  f"A	{VALID_PACK_IGNORE_PATH}\n" \
                  f"D	{VALID_SCRIPT_PATH}\n"
    mocker.patch('demisto_sdk.commands.common.legacy_git_tools.run_command', return_value=diff_string)
    expected_result = [
        {
            'name': VALID_LAYOUT_PATH,
            'status': 'M'
        },
        {
            'name': VALID_INTEGRATION_TEST_PATH,
            'status': 'R'
        },
        {
            'name': VALID_PACK_IGNORE_PATH,
            'status': 'A'
        },
        {
            'name': VALID_SCRIPT_PATH,
            'status': 'D'
        }
    ]
    assert expected_result == get_changed_files()


# demisto_sdk/commands/common/tests/lists_test.py

import json

import pytest
from mock import patch

from demisto_sdk.commands.common.constants import LISTS_DIR, PACKS_DIR
from demisto_sdk.commands.common.hook_validations.lists import ListsValidator
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator
from demisto_sdk.commands.common.tools import src_root

TEST_DATA = src_root() / 'tests' / 'test_files'
TEST_CONTENT_REPO = TEST_DATA / 'content_slim'
LIST_GOOD = TEST_CONTENT_REPO / PACKS_DIR / 'Sample01' / LISTS_DIR / 'list-checked_integrations.json'
LIST_BAD_FROM_VERSION = TEST_CONTENT_REPO / PACKS_DIR / 'Sample01' / LISTS_DIR / 'bad_from_version.json'
LIST_BAD_VERSION = TEST_CONTENT_REPO / PACKS_DIR / 'Sample01' / LISTS_DIR / 'bad_version.json'


def mock_structure(file_path=None, current_file=None, old_file=None):
    with patch.object(StructureValidator, '__init__', lambda a, b: None):
        structure = StructureValidator(file_path)
        structure.is_valid = True
        structure.scheme_name = 'list'
        structure.file_path = file_path
        file = open(file_path, "r")
        structure.current_file = json.loads(file.read())
        file.close()
        structure.old_file = old_file
        structure.prev_ver = 'master'
        structure.branch_name = ''
        structure.quite_bc = False
        return structure


class TestListValidator:

    @pytest.mark.parametrize('list_path, is_valid', [(LIST_GOOD, True), (LIST_BAD_VERSION, False),
                                                     (LIST_BAD_FROM_VERSION, False)])
    def test_is_valid_list(self, list_path, is_valid):
        """
        Given
        - A list with fromVersion of 6.5.0 and version of -1 OR A list with fromVersion of 1 and version of 1
        When
        - Validating a list
        Then
        - Return that the list is valid
        """
        structure = mock_structure(file_path=str(list_path))
        list_item = ListsValidator(structure, json_file_path=str(list_path))
        assert list_item.is_valid_list() == is_valid


# demisto_sdk/commands/common/tests/mapper_test.py

import pytest
from mock import patch

from demisto_sdk.commands.common.hook_validations.mapper import MapperValidator
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator


def mock_structure(file_path=None, current_file=None, old_file=None):
    with patch.object(StructureValidator, '__init__', lambda a, b: None):
        structure = StructureValidator(file_path)
        structure.is_valid = True
        structure.scheme_name = 'mapper'
        structure.file_path = file_path
        structure.current_file = current_file
        structure.old_file = old_file
        structure.prev_ver = 'master'
        structure.branch_name = ''
        return structure


class TestMapperValidator:

    INCOMING_MAPPER = {
        "mapping": {"0": {"internalMapping": {"Incident Field": {"simple": "Incident Field"}}}},
        "type": "mapping-incoming"
    }

    OUTGOING_MAPPER = {
        "mapping": {"0": {"internalMapping": {"Incident Field": {"simple": "Incident Field"}}}},
        "type": "mapping-outgoing"
    }

    ID_SET_WITH_INCIDENT_FIELD = {"IncidentFields": [{"name": {"name": "Incident Field"}}],
                                  "IndicatorFields": [{"name": {"name": "Incident Field"}}]}

    ID_SET_WITHOUT_INCIDENT_FIELD = {"IncidentFields": [{"name": {"name": "name"}}],
                                     "IndicatorFields": [{"name": {"name": "name"}}]}

    IS_INCIDENT_FIELD_EXIST = [
        (INCOMING_MAPPER, ID_SET_WITH_INCIDENT_FIELD, True, True),
        (INCOMING_MAPPER, ID_SET_WITHOUT_INCIDENT_FIELD, True, False),
        (OUTGOING_MAPPER, ID_SET_WITH_INCIDENT_FIELD, True, True),
        (OUTGOING_MAPPER, ID_SET_WITHOUT_INCIDENT_FIELD, True, False)
    ]

    @pytest.mark.parametrize("mapper_json, id_set_json, is_circle, expected_result", IS_INCIDENT_FIELD_EXIST)
    def test_is_incident_field_exist(self, repo, mapper_json, id_set_json, is_circle, expected_result):
        """
        Given
        - A mapper with incident fields
        - An id_set file.
        When
        - validating mapper
        Then
        - validating that incident fields exist in id_set.
        """
        repo.id_set.write_json(id_set_json)
        structure = mock_structure("", mapper_json)
        validator = MapperValidator(structure)
        assert validator.is_incident_field_exist(id_set_json, is_circle) == expected_result

    IS_MATCHING_NAME_ID_INPUT = [
        ({"id": "name", "name": "name"}, True),
        ({"id": "id_field", "name": "name_field"}, False)
    ]

    @pytest.mark.parametrize("mapper, result", IS_MATCHING_NAME_ID_INPUT)
    def test_is_name_id_equal(self, repo, mapper, result):
        """
        Given
        - A mapper with name and id
        When
        - validating mapper
        Then
        - validating that the mapper name and id are equal.
        """

        structure = mock_structure("", mapper)
        validator = MapperValidator(structure)

        assert validator.is_id_equals_name() == result


# demisto_sdk/commands/common/tests/old_release_notes_test.py

import pytest

from demisto_sdk.commands.common.hook_validations.old_release_notes import \
    OldReleaseNotesValidator


def get_validator(file_path='', diff=''):
    release_notes_validator = OldReleaseNotesValidator("")
    release_notes_validator.file_path = file_path
    release_notes_validator.release_notes_path = file_path
    release_notes_validator.release_notes = file_path
    release_notes_validator.master_diff = diff
    release_notes_validator.ignored_errors = {}
    release_notes_validator.checked_files = set()
    release_notes_validator.json_file_path = ''
    return release_notes_validator


diff_nothing = ''

diff_changed_the_same_line = 'diff --git a/Integrations/integration-VirusTotal_CHANGELOG.md b/Integrations/' \
                             'integration-VirusTotal_CHANGELOG.md\n' \
                             '\nindex cbf564679..cffab9e90 100644' \
                             '\n--- a/Integrations/integration-VirusTotal_CHANGELOG.md' \
                             '\n+++ b/Integrations/integration-VirusTotal_CHANGELOG.md' \
                             '\n@@ -1,15 +1,15 @@' \
                             '\n ## [Unreleased]' \
                             '\n-  - Added batch support for the **ip** and **url** and **domain** commands.' \
                             '\n+  - Added batch support for the **ip** and **url** and **domain** commands1.' \
                             '\n   - Fixed an issue where the DBotScore would create duplications in the ' \
                             'incident context. This effects Demisto version 5.5 and higher.' \
                             '\n ## [19.8.2] - 2019-08-22' \
                             '\n   - Added the Virus Total permanent link to the context of the following commands: ' \
                             '\n     - url' \
                             '\n     - file' \
                             '\n     - url-scan' \
                             '\n     - file-scan' \
                             '\n     - file-rescan' \
                             '\n ## [19.8.0] - 2019-08-06' \
                             '\n   - Updated outputs with new indicator fields.'

diff_removed_line = 'diff --git a/Integrations/integration-VirusTotal_CHANGELOG.md b/Integrations/' \
                    'integration-VirusTotal_CHANGELOG.md' \
                    '\nindex cbf564679..c49498c58 100644' \
                    '\n--- a/Integrations/integration-VirusTotal_CHANGELOG.md' \
                    '\n+++ b/Integrations/integration-VirusTotal_CHANGELOG.md' \
                    '\n@@ -1,15 +1,14 @@' \
                    '\n ## [Unreleased]' \
                    '\n-  - Added batch support for the **ip** and **url** and **domain** commands.' \
                    '\n-  - Fixed an issue where the DBotScore would create duplications in the incident context. ' \
                    'This effects Demisto version 5.5 and higher.' \
                    '\n+' \
                    '\n ## [19.8.2] - 2019-08-22' \
                    '\n   - Added the Virus Total permanent link to the context of the following commands: ' \
                    '\n     - url' \
                    '\n     - file' \
                    '\n     - url-scan' \
                    '\n     - file-scan' \
                    '\n     - file-rescan' \
                    '\n ## [19.8.0] - 2019-08-06' \
                    '\n   - Updated outputs with new indicator fields.'

diff_added_line = 'diff --git a/Integrations/integration-VirusTotal_CHANGELOG.md b/Integrations/i' \
                  'ntegration-VirusTotal_CHANGELOG.md\n' \
                  '\nindex cbf564679..cffab9e90 100644' \
                  '\n--- a/Integrations/integration-VirusTotal_CHANGELOG.md' \
                  '\n+++ b/Integrations/integration-VirusTotal_CHANGELOG.md' \
                  '\n@@ -1,15 +1,15 @@' \
                  '\n ## [Unreleased]' \
                  '\n-  - Added batch support for the **ip** and **url** and **domain** commands.' \
                  '\n+  - Added batch support for the **ip** and **url** and **domain** commands1.' \
                  '\n+ ' \
                  '\n   - Fixed an issue where the DBotScore would create duplications in the incident context. ' \
                  'This effects Demisto version 5.5 and higher.' \
                  '\n ## [19.8.2] - 2019-08-22' \
                  '\n   - Added the Virus Total permanent link to the context of the following commands: ' \
                  '\n     - url' \
                  '\n     - file' \
                  '\n     - url-scan' \
                  '\n     - file-scan' \
                  '\n     - file-rescan' \
                  '\n ## [19.8.0] - 2019-08-06' \
                  '\n   - Updated outputs with new indicator fields.'

diff_package = [(diff_nothing, False),
                (diff_changed_the_same_line, False),
                (diff_removed_line, False),
                (diff_added_line, True)]


@pytest.mark.parametrize('release_notes_diff, expected_result', diff_package)
def test_rn_master_diff(release_notes_diff, expected_result, mocker):
    mocker.patch.object(OldReleaseNotesValidator, '__init__', lambda a, b: None)
    validator = get_validator(diff=release_notes_diff)
    validator.suppress_print = False
    assert validator.is_release_notes_changed() == expected_result


# demisto_sdk/commands/common/tests/pack_metadata_validator_test.py

import io
import os
from typing import Dict

import pytest

from demisto_sdk.commands.common import tools
from demisto_sdk.commands.common.constants import EXCLUDED_DISPLAY_NAME_WORDS
from demisto_sdk.commands.common.hook_validations.base_validator import \
    BaseValidator
from demisto_sdk.commands.common.hook_validations.pack_unique_files import (
    PACK_METADATA_NAME, PACK_METADATA_SUPPORT,
    BlockingValidationFailureException, PackUniqueFilesValidator)
from demisto_sdk.commands.common.legacy_git_tools import git_path


class TestPackMetadataValidator:
    FILES_PATH = os.path.normpath(os.path.join(__file__, f'{git_path()}/demisto_sdk/tests', 'test_files'))

    @pytest.mark.parametrize('metadata', [os.path.join(FILES_PATH, 'pack_metadata__valid.json'),
                                          os.path.join(FILES_PATH, 'pack_metadata__valid__community.json'),
                                          ])
    def test_metadata_validator_valid(self, mocker, metadata):
        mocker.patch.object(tools, 'get_dict_from_file', return_value=({'approved_list': []}, 'json'))
        mocker.patch.object(PackUniqueFilesValidator, '_read_file_content',
                            return_value=TestPackMetadataValidator.read_file(metadata))
        mocker.patch.object(PackUniqueFilesValidator, '_is_pack_file_exists', return_value=True)

        validator = PackUniqueFilesValidator('fake')
        assert validator.validate_pack_meta_file()

    @pytest.mark.parametrize('metadata', [
        os.path.join(FILES_PATH, 'pack_metadata_invalid_price.json'),
        os.path.join(FILES_PATH, 'pack_metadata_invalid_dependencies.json'),
        os.path.join(FILES_PATH, 'pack_metadata_list_dependencies.json'),
        os.path.join(FILES_PATH, 'pack_metadata_empty_categories.json'),
        os.path.join(FILES_PATH, 'pack_metadata_invalid_category.json'),
        os.path.join(FILES_PATH, 'pack_metadata_invalid_keywords.json'),
        os.path.join(FILES_PATH, 'pack_metadata_invalid_tags.json'),
    ])
    def test_metadata_validator_invalid__non_breaking(self, mocker, metadata):
        mocker.patch.object(tools, 'get_dict_from_file', return_value=({'approved_list': []}, 'json'))
        mocker.patch.object(PackUniqueFilesValidator, '_read_file_content',
                            return_value=TestPackMetadataValidator.read_file(metadata))
        mocker.patch.object(PackUniqueFilesValidator, '_is_pack_file_exists', return_value=True)
        mocker.patch.object(BaseValidator, 'check_file_flags', return_value='')

        validator = PackUniqueFilesValidator('fake')
        assert not validator.validate_pack_meta_file()

    @pytest.mark.parametrize('metadata', [
        os.path.join(FILES_PATH, 'pack_metadata_missing_fields.json'),
        os.path.join(FILES_PATH, 'pack_metadata_list.json'),
        os.path.join(FILES_PATH, 'pack_metadata_short_name.json'),
        os.path.join(FILES_PATH, 'pack_metadata_name_start_lower.json'),
        os.path.join(FILES_PATH, 'pack_metadata_name_start_incorrect.json'),
        os.path.join(FILES_PATH, 'pack_metadata_pack_in_name.json'),
    ])
    def test_metadata_validator_invalid__breaking(self, mocker, metadata):
        """
        Given
                A pack metadata file with invalid contents that should halt validations
        When
                Calling validate_pack_meta_file
        Then
                Ensure BlockingValidationFailureException is raised
        """
        mocker.patch.object(tools, 'get_dict_from_file', return_value=({'approved_list': []}, 'json'))
        mocker.patch.object(PackUniqueFilesValidator, '_read_file_content',
                            return_value=TestPackMetadataValidator.read_file(metadata))
        mocker.patch.object(PackUniqueFilesValidator, '_is_pack_file_exists', return_value=True)
        mocker.patch.object(BaseValidator, 'check_file_flags', return_value='')

        validator = PackUniqueFilesValidator('fake')
        with pytest.raises(BlockingValidationFailureException):
            assert not validator.validate_pack_meta_file()

    VALIDATE_PACK_NAME_INPUTS = [({PACK_METADATA_NAME: 'fill mandatory field'}, False),
                                 ({PACK_METADATA_NAME: 'A'}, False),
                                 ({PACK_METADATA_NAME: 'notCapitalized'}, False),
                                 ({PACK_METADATA_NAME: 'BitcoinAbuse (Community)', PACK_METADATA_SUPPORT: 'community'},
                                  False),
                                 ({PACK_METADATA_NAME: 'BitcoinAbuse'}, True)]

    @pytest.mark.parametrize('metadata_content, expected', VALIDATE_PACK_NAME_INPUTS)
    def test_validate_pack_name(self, metadata_content: Dict, expected: bool, mocker):
        """
        Given:
        - Metadata JSON pack file content.

        When:
        - Validating if pack name is valid.

        Then:
        - Ensure expected result is returned.
        """
        validator = PackUniqueFilesValidator('fake')
        mocker.patch.object(validator, '_add_error', return_value=True)
        assert validator.validate_pack_name(metadata_content) == expected

    def test_name_does_not_contain_excluded_word(self):
        """
        Given:
        - Pack name.

        When:
        - Validating pack name does not contain excluded word.

        Then:
        - Ensure expected result is returned.
        """
        pack_name: str = 'Bitcoin Abuse'
        validator = PackUniqueFilesValidator('fake')
        assert validator.name_does_not_contain_excluded_word(pack_name)
        for excluded_word in EXCLUDED_DISPLAY_NAME_WORDS:
            invalid_pack_name: str = f'{pack_name} ({excluded_word})'
            assert not validator.name_does_not_contain_excluded_word(invalid_pack_name)

    @staticmethod
    def read_file(file_):
        with io.open(file_, mode="r", encoding="utf-8") as data:
            return data.read()

    def test_metadata_not_dict(self, mocker):
        """
        Given:
        - Metadata file whom structure is not a dict

        When:
        - Validating metadata structure.

        Then:
        - Ensure false is returned, and a BlockingValidationFailureException is raised.
        """
        mocker.patch.object(PackUniqueFilesValidator, '_read_metadata_content', return_value={'a', 'b'})
        validator = PackUniqueFilesValidator('fake')
        mocker.patch.object(validator, '_add_error')
        with pytest.raises(BlockingValidationFailureException):
            assert not validator._is_pack_meta_file_structure_valid()

    def test_metadata_validator_empty_categories(self, mocker):
        metadata = os.path.join(self.__class__.FILES_PATH, 'pack_metadata_empty_categories.json')
        mocker.patch.object(tools, 'get_dict_from_file', return_value=({'approved_list': []}, 'json'))
        mocker.patch.object(PackUniqueFilesValidator, '_read_file_content',
                            return_value=TestPackMetadataValidator.read_file(metadata))
        mocker.patch.object(PackUniqueFilesValidator, '_is_pack_file_exists', return_value=True)
        mocker.patch.object(BaseValidator, 'check_file_flags', return_value='')

        validator = PackUniqueFilesValidator('fake')
        assert not validator.validate_pack_meta_file()
        assert "[PA129] - pack_metadata.json - Missing categories" in validator.get_errors()


# demisto_sdk/commands/common/tests/pack_unique_files_test.py

import json
import os

import click
import pytest
import requests_mock
from click.testing import CliRunner
from git import GitCommandError

from demisto_sdk.__main__ import main
from demisto_sdk.commands.common import tools
from demisto_sdk.commands.common.constants import (PACK_METADATA_DESC,
                                                   PACK_METADATA_SUPPORT,
                                                   PACK_METADATA_TAGS,
                                                   PACK_METADATA_USE_CASES,
                                                   PACKS_README_FILE_NAME,
                                                   XSOAR_SUPPORT)
from demisto_sdk.commands.common.errors import Errors
from demisto_sdk.commands.common.hook_validations.base_validator import \
    BaseValidator
from demisto_sdk.commands.common.hook_validations.pack_unique_files import \
    PackUniqueFilesValidator
from demisto_sdk.commands.common.legacy_git_tools import git_path
from TestSuite.test_tools import ChangeCWD

VALIDATE_CMD = "validate"
PACK_METADATA_PARTNER = {
    "name": "test",
    "description": "test",
    "support": "partner",
    "currentVersion": "1.0.1",
    "author": "bar",
    "categories": [
        "Data Enrichment & Threat Intelligence"
    ],
    "tags": [],
    "useCases": [],
    "keywords": [],
    "price": 2,
    "email": "some@mail.com",
    "url": "https://www.paloaltonetworks.com/cortex"
}

README_INPUT_RESULTS_LIST = [
    ('', False),
    (' ', False),
    ('\t\t\n ', False),
    ('Text', True),
]


class TestPackUniqueFilesValidator:
    FILES_PATH = os.path.normpath(os.path.join(__file__, f'{git_path()}/demisto_sdk/tests', 'test_files', 'Packs'))
    FAKE_PACK_PATH = os.path.normpath(os.path.join(__file__, f'{git_path()}/demisto_sdk/tests', 'test_files',
                                                   'fake_pack'))
    FAKE_PATH_NAME = 'fake_pack'
    validator = PackUniqueFilesValidator(FAKE_PATH_NAME)
    validator.pack_path = FAKE_PACK_PATH

    def restart_validator(self):
        self.validator.pack_path = ''
        self.validator = PackUniqueFilesValidator(self.FAKE_PATH_NAME)
        self.validator.pack_path = self.FAKE_PACK_PATH

    def test_is_error_added_name_only(self):
        self.validator._add_error(('boop', '101'), 'file_name')
        assert f'{self.validator.pack_path}/file_name: [101] - boop\n' in self.validator.get_errors(True)
        assert f'{self.validator.pack_path}/file_name: [101] - boop\n' in self.validator.get_errors()
        self.validator._errors = []

    def test_is_error_added_full_path(self):
        self.validator._add_error(('boop', '101'), f'{self.validator.pack_path}/file/name')
        assert f'{self.validator.pack_path}/file/name: [101] - boop\n' in self.validator.get_errors(True)
        assert f'{self.validator.pack_path}/file/name: [101] - boop\n' in self.validator.get_errors()
        self.validator._errors = []

    def test_is_file_exist(self):
        assert self.validator._is_pack_file_exists(PACKS_README_FILE_NAME)
        assert not self.validator._is_pack_file_exists('boop')
        self.validator._errors = []

    def test_parse_file_into_list(self):
        assert ['boop', 'sade', ''] == self.validator._parse_file_into_list(PACKS_README_FILE_NAME)
        assert not self.validator._parse_file_into_list('boop')
        self.validator._errors = []

    def test_validate_pack_unique_files(self, mocker):
        mocker.patch.object(BaseValidator, 'check_file_flags', return_value='')
        mocker.patch.object(PackUniqueFilesValidator, 'validate_pack_readme_and_pack_description', return_value=True)
        mocker.patch.object(PackUniqueFilesValidator, 'validate_pack_readme_images', return_value=True)
        mocker.patch.object(tools, 'get_dict_from_file', return_value=({'approved_list': []}, 'json'))
        assert not self.validator.are_valid_files(id_set_validations=False)
        fake_validator = PackUniqueFilesValidator('fake')
        mocker.patch.object(fake_validator, '_read_metadata_content', return_value=dict())
        assert fake_validator.are_valid_files(id_set_validations=False)

    def test_validate_pack_metadata(self, mocker):
        mocker.patch.object(BaseValidator, 'check_file_flags', return_value='')
        mocker.patch.object(PackUniqueFilesValidator, 'validate_pack_readme_and_pack_description', return_value=True)
        mocker.patch.object(PackUniqueFilesValidator, 'validate_pack_readme_images', return_value=True)
        mocker.patch.object(tools, 'get_dict_from_file', return_value=({'approved_list': []}, 'json'))
        assert not self.validator.are_valid_files(id_set_validations=False)
        fake_validator = PackUniqueFilesValidator('fake')
        mocker.patch.object(fake_validator, '_read_metadata_content', return_value=dict())
        assert fake_validator.are_valid_files(id_set_validations=False)

    def test_validate_partner_contribute_pack_metadata_no_mail_and_url(self, mocker, repo):
        """
        Given
        - Partner contributed pack without email and url.

        When
        - Running validate on it.

        Then
        - Ensure validate found errors.
        """
        pack_metadata_no_email_and_url = PACK_METADATA_PARTNER.copy()
        pack_metadata_no_email_and_url['email'] = ''
        pack_metadata_no_email_and_url['url'] = ''
        mocker.patch.object(tools, 'is_external_repository', return_value=True)
        mocker.patch.object(PackUniqueFilesValidator, '_is_pack_file_exists', return_value=True)
        mocker.patch.object(PackUniqueFilesValidator, 'validate_pack_name', return_value=True)
        mocker.patch.object(PackUniqueFilesValidator, 'get_master_private_repo_meta_file', return_value=None)
        mocker.patch.object(PackUniqueFilesValidator, '_read_file_content',
                            return_value=json.dumps(pack_metadata_no_email_and_url))
        mocker.patch.object(BaseValidator, 'check_file_flags', return_value=None)
        mocker.patch.object(tools, 'get_dict_from_file', return_value=({'approved_list': []}, 'json'))
        pack = repo.create_pack('PackName')
        pack.pack_metadata.write_json(pack_metadata_no_email_and_url)
        with ChangeCWD(repo.path):
            runner = CliRunner(mix_stderr=False)
            result = runner.invoke(main, [VALIDATE_CMD, '-i', pack.path], catch_exceptions=False)
        assert 'Contributed packs must include email or url' in result.stdout

    @pytest.mark.parametrize('url, is_valid', [
        ('https://github.com/pont_to_repo', False),
        ('some_support_url', True),
        ('https://github.com/pont_to_repo/issues', True),
    ])
    def test_validate_partner_pack_metadata_url(self, mocker, repo, url, is_valid):
        """
        Given
        - Partner contributed pack with an is_valid url.

        When
        - Running validate on it.

        Then
        - Ensure validate finds errors accordingly.
        """
        pack_metadata_changed_url = PACK_METADATA_PARTNER.copy()
        pack_metadata_changed_url['url'] = url

        mocker.patch.object(tools, 'is_external_repository', return_value=True)
        mocker.patch.object(PackUniqueFilesValidator, '_is_pack_file_exists', return_value=True)
        mocker.patch.object(PackUniqueFilesValidator, 'validate_pack_name', return_value=True)
        mocker.patch.object(PackUniqueFilesValidator, 'get_master_private_repo_meta_file', return_value=None)
        mocker.patch.object(PackUniqueFilesValidator, '_read_file_content',
                            return_value=json.dumps(pack_metadata_changed_url))
        mocker.patch.object(BaseValidator, 'check_file_flags', return_value=None)
        mocker.patch.object(tools, 'get_dict_from_file', return_value=({'approved_list': []}, 'json'))
        pack = repo.create_pack('PackName')
        pack.pack_metadata.write_json(pack_metadata_changed_url)
        with ChangeCWD(repo.path):
            runner = CliRunner(mix_stderr=False)
            result = runner.invoke(main, [VALIDATE_CMD, '-i', pack.path], catch_exceptions=False)

        error_text = 'The metadata URL leads to a GitHub repo instead of a support page.'
        if is_valid:
            assert error_text not in result.stdout
        else:
            assert error_text in result.stdout

    def test_validate_partner_contribute_pack_metadata_price_change(self, mocker, repo):
        """
        Given
        - Partner contributed pack where price has changed.

        When
        - Running validate on it.

        Then
        - Ensure validate found errors.
        """
        pack_metadata_price_changed = PACK_METADATA_PARTNER.copy()
        pack_metadata_price_changed['price'] = 3
        mocker.patch.object(tools, 'is_external_repository', return_value=True)
        mocker.patch.object(PackUniqueFilesValidator, '_is_pack_file_exists', return_value=True)
        mocker.patch.object(PackUniqueFilesValidator, 'validate_pack_name', return_value=True)
        mocker.patch.object(PackUniqueFilesValidator, 'get_master_private_repo_meta_file',
                            return_value=PACK_METADATA_PARTNER)
        mocker.patch.object(PackUniqueFilesValidator, '_read_file_content',
                            return_value=json.dumps(pack_metadata_price_changed))
        mocker.patch.object(BaseValidator, 'check_file_flags', return_value=None)
        mocker.patch.object(tools, 'get_dict_from_file', return_value=({'approved_list': []}, 'json'))
        pack = repo.create_pack('PackName')
        pack.pack_metadata.write_json(pack_metadata_price_changed)
        with ChangeCWD(repo.path):
            runner = CliRunner(mix_stderr=False)
            result = runner.invoke(main, [VALIDATE_CMD, '-i', pack.path], catch_exceptions=False)
        assert 'The pack price was changed from 2 to 3 - revert the change' in result.stdout

    def test_check_timestamp_format(self):
        """
        Given
        - timestamps in various formats.

        When
        - Running check_timestamp_format on them.

        Then
        - Ensure True for iso format and False for any other format.
        """
        fake_validator = PackUniqueFilesValidator('fake')
        good_format_timestamp = '2020-04-14T00:00:00Z'
        missing_z = '2020-04-14T00:00:00'
        missing_t = '2020-04-14 00:00:00Z'
        only_date = '2020-04-14'
        with_hyphen = '2020-04-14T00-00-00Z'
        assert fake_validator.check_timestamp_format(good_format_timestamp)
        assert not fake_validator.check_timestamp_format(missing_t)
        assert not fake_validator.check_timestamp_format(missing_z)
        assert not fake_validator.check_timestamp_format(only_date)
        assert not fake_validator.check_timestamp_format(with_hyphen)

    def test_validate_pack_dependencies_invalid_id_set(self, mocker, repo):
        """
        Given
        - An invalid id set error being raised

        When
        - Running validate_pack_dependencies.

        Then
        - Ensure that the validation fails and that the invalid id set error is printed.
        """
        self.restart_validator()

        def error_raising_function(*args, **kwargs):
            raise ValueError("Couldn't find any items for pack 'PackID'. make sure your spelling is correct.")

        mocker.patch(
            'demisto_sdk.commands.common.hook_validations.pack_unique_files.get_core_pack_list',
            side_effect=error_raising_function
        )
        assert not self.validator.validate_pack_dependencies()
        assert Errors.invalid_id_set()[0] in self.validator.get_errors()

    def test_validate_core_pack_dependencies(self):
        """
        Given
        - A list of non-core packs

        When
        - Running validate_core_pack_dependencies.

        Then
        - Ensure that the validation fails and that the invalid core pack dependencies error is printed.
        """
        self.restart_validator()
        dependencies_packs = {'dependency_pack_1': {'mandatory': True, 'display_name': 'dependency pack 1'},
                              'dependency_pack_2': {'mandatory': False, 'display_name': 'dependency pack 2'},
                              'dependency_pack_3': {'mandatory': True, 'display_name': 'dependency pack 3'}}

        assert not self.validator.validate_core_pack_dependencies(dependencies_packs)
        assert Errors.invalid_core_pack_dependencies('fake_pack', ['dependency_pack_1', 'dependency_pack_3'])[0] \
            in self.validator.get_errors()

    def test_validate_pack_dependencies_skip_id_set_creation(self, capsys):
        """
        Given
        -  skip_id_set_creation flag set to true.
        -  No id_set file exists

        When
        - Running validate_pack_dependencies.

        Then
        - Ensure that the validation passes and that the skipping message is printed.
        """
        self.restart_validator()
        self.validator.skip_id_set_creation = True
        res = self.validator.validate_pack_dependencies()
        self.validator.skip_id_set_creation = False  # reverting to default for next tests
        assert res
        assert "No first level dependencies found" in capsys.readouterr().out

    @pytest.mark.parametrize('usecases, is_valid, branch_usecases', [
        ([], True, []),
        (['Phishing', 'Malware'], True, []),
        (['NonApprovedUsecase', 'Case Management'], False, []),
        (['NewUseCase'], True, ['NewUseCase']),
        (['NewUseCase1, NewUseCase2'], False, ['NewUseCase1'])
    ])
    def test_is_approved_usecases(self, repo, usecases, is_valid, branch_usecases, mocker):
        """
        Given:
            - Case A: Pack without usecases
            - Case B: Pack with approved usecases (Phishing and Malware)
            - Case C: Pack with non-approved usecase (NonApprovedUsecase) and approved usecase (Case Management)
            - Case D: Pack with approved usecase (NewUseCase) located in my branch only
            - Case E: Pack with non-approved usecase (NewUseCase2) and approved usecase (NewUseCase1)
            located in my branch only

        When:
            - Validating approved usecases

        Then:
            - Case A: Ensure validation passes as there are no usecases to verify
            - Case B: Ensure validation passes as both usecases are approved
            - Case C: Ensure validation fails as it contains a non-approved usecase (NonApprovedUsecase)
                      Verify expected error is printed
            - Case D: Ensure validation passes as usecase is approved on the same branch
            - Case E: Ensure validation fails as it contains a non-approved usecase (NewUseCase2)
                      Verify expected error is printed
        """
        self.restart_validator()
        pack_name = 'PackName'
        pack = repo.create_pack(pack_name)
        pack.pack_metadata.write_json({
            PACK_METADATA_USE_CASES: usecases,
            PACK_METADATA_SUPPORT: XSOAR_SUPPORT,
            PACK_METADATA_TAGS: []
        })
        mocker.patch.object(tools, 'is_external_repository', return_value=False)
        mocker.patch.object(tools, 'get_dict_from_file', return_value=({'approved_list': branch_usecases}, 'json'))
        self.validator.pack_path = pack.path

        with ChangeCWD(repo.path):
            assert self.validator._is_approved_usecases() == is_valid
            if not is_valid:
                assert 'The pack metadata contains non approved usecases:' in self.validator.get_errors()

    @pytest.mark.parametrize('tags, is_valid, branch_tags', [
        ([], True, []),
        (['Machine Learning', 'Spam'], True, []),
        (['NonApprovedTag', 'GDPR'], False, []),
        (['NewTag'], True, ['NewTag']),
        (['NewTag1, NewTag2'], False, ['NewTag1'])
    ])
    def test_is_approved_tags(self, repo, tags, is_valid, branch_tags, mocker):
        """
        Given:
            - Case A: Pack without tags
            - Case B: Pack with approved tags (Machine Learning and Spam)
            - Case C: Pack with non-approved tags (NonApprovedTag) and approved tags (GDPR)
            - Case D: Pack with approved tags (NewTag) located in my branch only
            - Case E: Pack with non-approved tags (NewTag) and approved tags (NewTag)
            located in my branch only
        When:
            - Validating approved tags

        Then:
            - Case A: Ensure validation passes as there are no tags to verify
            - Case B: Ensure validation passes as both tags are approved
            - Case C: Ensure validation fails as it contains a non-approved tags (NonApprovedTag)
                      Verify expected error is printed
            - Case D: Ensure validation passes as tags is approved on the same branch
            - Case E: Ensure validation fails as it contains a non-approved tag (NewTag2)
                      Verify expected error is printed
        """
        self.restart_validator()
        pack_name = 'PackName'
        pack = repo.create_pack(pack_name)
        pack.pack_metadata.write_json({
            PACK_METADATA_USE_CASES: [],
            PACK_METADATA_SUPPORT: XSOAR_SUPPORT,
            PACK_METADATA_TAGS: tags
        })
        mocker.patch.object(tools, 'is_external_repository', return_value=False)
        mocker.patch.object(tools, 'get_dict_from_file', return_value=({'approved_list': branch_tags}, 'json'))
        self.validator.pack_path = pack.path

        with ChangeCWD(repo.path):
            assert self.validator._is_approved_tags() == is_valid
            if not is_valid:
                assert 'The pack metadata contains non approved tags:' in self.validator.get_errors()

    @pytest.mark.parametrize('pack_content, tags, is_valid', [
        ("none", [], True),
        ("none", ["Use Case"], False),
        ("playbook", ["Use Case"], True),
        ("incident", ["Use Case"], True),
        ("layout", ["Use Case"], True),
        ("playbook", [], True),
    ])
    def test_is_right_usage_of_usecase_tag(self, repo, pack_content, tags, is_valid):
        self.restart_validator()
        pack_name = 'PackName'
        pack = repo.create_pack(pack_name)
        pack.pack_metadata.write_json({
            PACK_METADATA_USE_CASES: [],
            PACK_METADATA_SUPPORT: XSOAR_SUPPORT,
            PACK_METADATA_TAGS: tags,
        })

        if pack_content == "playbook":
            pack.create_playbook(name="PlaybookName")
        elif pack_content == "incident":
            pack.create_incident_type(name="IncidentTypeName")
        elif pack_content == "layout":
            pack.create_layout(name="Layout")

        self.validator.pack_path = pack.path

        with ChangeCWD(repo.path):
            assert self.validator.is_right_usage_of_usecase_tag() == is_valid

    @pytest.mark.parametrize('type, is_valid', [
        ('community', True),
        ('partner', True),
        ('xsoar', True),
        ('someName', False),
        ('test', False),
        ('developer', True)
    ])
    def test_is_valid_support_type(self, repo, type, is_valid):
        """
        Given:
            - Pack with support type in the metadata file.

        When:
            - Running _is_valid_support_type.

        Then:
            - Ensure True when the support types are valid, else False with the right error message.
        """
        self.restart_validator()
        pack_name = 'PackName'
        pack = repo.create_pack(pack_name)
        pack.pack_metadata.write_json({
            PACK_METADATA_USE_CASES: [],
            PACK_METADATA_SUPPORT: type
        })

        self.validator.pack_path = pack.path

        with ChangeCWD(repo.path):
            assert self.validator._is_valid_support_type() == is_valid
            if not is_valid:
                assert 'Support field should be one of the following: xsoar, partner, developer or community.' in \
                       self.validator.get_errors()

    def test_get_master_private_repo_meta_file_running_on_master(self, mocker, repo, capsys):
        """
        Given:
            - A repo which runs on master branch

        When:
            - Running get_master_private_repo_meta_file.

        Then:
            - Ensure result is None and the appropriate skipping message is printed.
        """
        self.restart_validator()
        pack_name = 'PackName'
        pack = repo.create_pack(pack_name)
        pack.pack_metadata.write_json(PACK_METADATA_PARTNER)

        class MyRepo:
            active_branch = 'master'

        mocker.patch('demisto_sdk.commands.common.hook_validations.pack_unique_files.Repo', return_value=MyRepo)
        res = self.validator.get_master_private_repo_meta_file(str(pack.pack_metadata.path))
        assert not res
        assert "Running on master branch - skipping price change validation" in capsys.readouterr().out

    def test_get_master_private_repo_meta_file_getting_git_error(self, repo, capsys, mocker):
        """
        Given:
            - A repo which runs on non-master branch.
            - git.show command raises GitCommandError.

        When:
            - Running get_master_private_repo_meta_file.

        Then:
            - Ensure result is None and the appropriate skipping message is printed.
        """
        self.restart_validator()
        pack_name = 'PackName'
        pack = repo.create_pack(pack_name)
        pack.pack_metadata.write_json(PACK_METADATA_PARTNER)

        class MyRepo:
            active_branch = 'not-master'

            class gitClass:
                def show(self, var):
                    raise GitCommandError("A", "B")

            git = gitClass()

        mocker.patch('demisto_sdk.commands.common.hook_validations.pack_unique_files.Repo', return_value=MyRepo)
        res = self.validator.get_master_private_repo_meta_file(str(pack.pack_metadata.path))
        assert not res
        assert "Got an error while trying to connect to git" in capsys.readouterr().out

    def test_get_master_private_repo_meta_file_file_not_found(self, mocker, repo, capsys):
        """
        Given:
            - A repo which runs on non-master branch.
            - git.show command returns None.

        When:
            - Running get_master_private_repo_meta_file.

        Then:
            - Ensure result is None and the appropriate skipping message is printed.
        """
        self.restart_validator()
        pack_name = 'PackName'
        pack = repo.create_pack(pack_name)
        pack.pack_metadata.write_json(PACK_METADATA_PARTNER)

        class MyRepo:
            active_branch = 'not-master'

            class gitClass:
                def show(self, var):
                    return None

            git = gitClass()

        mocker.patch('demisto_sdk.commands.common.hook_validations.pack_unique_files.Repo', return_value=MyRepo)
        res = self.validator.get_master_private_repo_meta_file(str(pack.pack_metadata.path))
        assert not res
        assert "Unable to find previous pack_metadata.json file - skipping price change validation" in \
               capsys.readouterr().out

    @pytest.mark.parametrize('text, result', README_INPUT_RESULTS_LIST)
    def test_validate_pack_readme_file_is_not_empty_partner(self, mocker, text, result):
        """
       Given:
            - partner pack

        When:
            - Running test_validate_pack_readme_file_is_not_empty_partner.

        Then:
            - Ensure result is False for empty README.md file and True otherwise.
        """
        self.validator = PackUniqueFilesValidator(self.FAKE_PACK_PATH)
        self.validator.support = 'partner'
        mocker.patch.object(PackUniqueFilesValidator, '_read_file_content', return_value=text)
        assert self.validator.validate_pack_readme_file_is_not_empty() == result

    @pytest.mark.parametrize('text, result', README_INPUT_RESULTS_LIST)
    def test_validate_pack_readme_file_is_not_empty_use_case(self, mocker, text, result):
        """
       Given:
            - pack with use case

        When:
            - Running test_validate_pack_readme_file_is_not_empty_partner.

        Then:
            - Ensure result is False for empty README.md file and True otherwise.
        """
        self.validator = PackUniqueFilesValidator(os.path.join(self.FILES_PATH, 'CortexXDR'))
        mocker.patch.object(PackUniqueFilesValidator, '_read_file_content', return_value=text)
        assert self.validator.validate_pack_readme_file_is_not_empty() == result

    def test_validate_pack_readme_file_is_not_empty_missing_file(self):
        self.validator = PackUniqueFilesValidator(os.path.join(self.FILES_PATH, 'DummyPack'))
        assert self.validator._is_pack_file_exists(self.validator.readme_file) is False

    def test_validate_pack_readme_valid_images(self, mocker):
        """
            Given
                - A pack README file with valid absolute image paths in it.
            When
                - Run validate on pack README file
            Then
                - Ensure:
                    - Validation succeed
                    - Valid absolute image paths were not caught
        """
        from demisto_sdk.commands.common.hook_validations.readme import \
            ReadMeValidator

        self.validator = PackUniqueFilesValidator(os.path.join(self.FILES_PATH, 'DummyPack2'))
        mocker.patch.object(ReadMeValidator, 'check_readme_relative_image_paths', return_value=[])  # Test only absolute paths

        with requests_mock.Mocker() as m:
            # Mock get requests
            m.get('https://github.com/demisto/content/raw/test1.png',
                  status_code=200, text="Test1")
            m.get('https://raw.githubusercontent.com/demisto/content/raw/test1.png',
                  status_code=200, text="Test1")
            m.get('https://raw.githubusercontent.com/demisto/content/raw/test1.jpg',
                  status_code=200, text="Test1")

            result = self.validator.validate_pack_readme_images()
            errors = self.validator.get_errors()
        assert result
        assert 'please repair it:\n![Identity with High Risk Score](https://github.com/demisto/content/raw/test1.png)' not in errors
        assert 'please repair it:\n![Identity with High Risk Score](https://raw.githubusercontent.com/demisto/content/raw/test1.png)' not in errors
        assert 'please repair it:\n(https://raw.githubusercontent.com/demisto/content/raw/test1.jpg)' not in errors

    def test_validate_pack_readme_invalid_images(self):
        """
            Given
                - A pack README file with invalid absolute and relative image paths in it.
            When
                - Run validate on pack README file
            Then
                - Ensure:
                    - Validation fails
                    - Invalid relative image paths were caught correctly
                    - Invalid absolute image paths were caught correctly
        """
        self.validator = PackUniqueFilesValidator(os.path.join(self.FILES_PATH, 'DummyPack2'))

        with requests_mock.Mocker() as m:
            # Mock get requests
            m.get('https://github.com/demisto/content/raw/test1.png',
                  status_code=404, text="Test1")
            m.get('https://raw.githubusercontent.com/demisto/content/raw/test1.png',
                  status_code=404, text="Test1")
            m.get('https://raw.githubusercontent.com/demisto/content/raw/test1.jpg',
                  status_code=404, text="Test1")

            result = self.validator.validate_pack_readme_images()
            errors = self.validator.get_errors()
        assert not result
        assert 'Detected the following image relative path: ![Identity with High Risk Score](doc_files/High_Risk_User.png)' in errors
        assert 'Detected the following image relative path: ![Identity with High Risk Score](home/test1/test2/doc_files/High_Risk_User.png)' in errors
        assert 'Detected the following image relative path: (../../doc_files/Access_investigation_-_Generic_4_5.png)' in errors
        assert 'Image link was not found, either insert it or remove it:\n![Account Enrichment](Insert the link to your image here)' in errors

        assert 'please repair it:\n![Identity with High Risk Score](https://github.com/demisto/content/raw/test1.png)' in errors
        assert 'please repair it:\n![Identity with High Risk Score](https://raw.githubusercontent.com/demisto/content/raw/test1.png)' in errors
        assert 'please repair it:\n(https://raw.githubusercontent.com/demisto/content/raw/test1.jpg)' in errors
        # this path is not an image path and should not be shown.
        assert 'https://github.com/demisto/content/raw/test3.png' not in errors

    @pytest.mark.parametrize('readme_content, is_valid', [
        ('Hey there, just testing', True),
        ('This is a test. All good!', False),
    ])
    def test_pack_readme_is_different_then_pack_description(self, repo, readme_content, is_valid):
        """
        Given:
            - Case A: A unique pack readme.
            - Case B: Pack readme that is equal to pack description

        When:
            - Validating pack readme vs pack description

        Then:
            - Case A: Ensure validation passes as the pack readme and pack description are different.
            - Case B: Ensure validation fails as the pack readme is the same as the pack description.
                      Verify expected error is printed
        """
        self.restart_validator()
        pack_name = 'PackName'
        pack = repo.create_pack(pack_name)
        pack.readme.write_text(readme_content)
        pack.pack_metadata.write_json({
            PACK_METADATA_DESC: 'This is a test. All good!',
        })

        self.validator.pack_path = pack.path

        with ChangeCWD(repo.path):
            assert self.validator.validate_pack_readme_and_pack_description() == is_valid
            if not is_valid:
                assert 'README.md content is equal to pack description. ' \
                       'Please remove the duplicate description from README.md file' in self.validator.get_errors()

    def test_validate_pack_readme_and_pack_description_no_readme_file(self, repo):
        """
        Given:
            - A pack with no readme.

        When:
            - Validating pack readme vs pack description

        Then:
            - Fail on no README file and not on descrption error.
        """
        self.restart_validator()
        pack_name = 'PackName'
        pack = repo.create_pack(pack_name)
        self.validator.pack_path = pack.path

        with ChangeCWD(repo.path):
            os.remove(pack.readme.path)
            assert self.validator.validate_pack_readme_and_pack_description()
            assert '"README.md" file does not exist, create one in the root of the pack' in self.validator.get_errors()
            assert 'README.md content is equal to pack description. ' \
                   'Please remove the duplicate description from README.md file' not in self.validator.get_errors()

    def test_valid_is_pack_metadata_desc_too_long(self, repo):
        """
        Given:
            - Valid description length

        When:
            - Validating pack description length

        Then:
            - Ensure validation passes as the description field length is valid.

        """
        pack_description = 'Hey there, just testing'
        assert self.validator.is_pack_metadata_desc_too_long(pack_description) is True

    def test_invalid_is_pack_metadata_desc_too_long(self, mocker, repo):
        """
        Given:
            - Invalid description length - higher than 130

        When:
            - Validating pack description length

        Then:
            - Ensure validation passes although description field length is higher than 130
            - Ensure warning will be printed.
        """
        pack_description = 'This is will fail cause the description here is too long.' \
                           'test test test test test test test test test test test test test test test test test' \
                           ' test test test test test'
        error_desc = 'The description field of the pack_metadata.json file is longer than 130 characters.'

        mocker.patch("click.secho")

        assert self.validator.is_pack_metadata_desc_too_long(pack_description) is True
        assert error_desc in click.secho.call_args_list[0][0][0]

    def test_validate_author_image_exists_valid(self, repo):
        """
        Given:
            - Pack with partner support and author image

        When:
            - Validating if author image exists

        Then:
            - Ensure validation passes.
        """
        pack = repo.create_pack('MyPack')

        self.validator.metadata_content = {'support': 'partner'}
        self.validator.pack_path = pack.path
        author_image_path = pack.author_image.path

        with ChangeCWD(repo.path):
            res = self.validator.validate_author_image_exists()
            assert res
            assert f'Partners must provide a non-empty author image under the path {author_image_path}.' not in \
                   self.validator.get_errors()

    def test_validate_author_image_exists_invalid(self, repo):
        """
        Given:
            - Pack with partner support and no author image

        When:
            - Validating if author image exists

        Then:
            - Ensure validation fails.
        """
        pack = repo.create_pack('MyPack')

        self.validator.metadata_content = {'support': 'partner'}
        self.validator.pack_path = pack.path
        author_image_path = pack.author_image.path

        with ChangeCWD(repo.path):
            os.remove(author_image_path)
            res = self.validator.validate_author_image_exists()
            assert not res
            assert f'Partners must provide a non-empty author image under the path {author_image_path}.' in \
                   self.validator.get_errors()


# demisto_sdk/commands/common/tests/playbook_test.py

from typing import Optional

import pytest
from mock import patch

from demisto_sdk.commands.common.hook_validations.playbook import \
    PlaybookValidator
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator
from demisto_sdk.tests.constants_test import (
    CONTENT_REPO_EXAMPLE_ROOT, INVALID_PLAYBOOK_UNHANDLED_CONDITION,
    INVALID_TEST_PLAYBOOK_UNHANDLED_CONDITION)
from TestSuite.test_tools import ChangeCWD


def mock_structure(file_path=None, current_file=None, old_file=None):
    # type: (Optional[str], Optional[dict], Optional[dict]) -> StructureValidator
    with patch.object(StructureValidator, '__init__', lambda a, b: None):
        structure = StructureValidator(file_path)
        structure.is_valid = True
        structure.scheme_name = 'playbook'
        structure.file_path = file_path
        structure.current_file = current_file
        structure.old_file = old_file
        structure.prev_ver = 'master'
        structure.branch_name = ''
        return structure


class TestPlaybookValidator:
    ROLENAME_NOT_EXIST = {"id": "Intezer - scan host", "version": -1}
    ROLENAME_EXIST_EMPTY = {"id": "Intezer - scan host", "version": -1, "rolename": []}
    ROLENAME_EXIST_NON_EMPTY = {"id": "Intezer - scan host", "version": -1, "rolename": ["Administrator"]}
    IS_NO_ROLENAME_INPUTS = [
        (ROLENAME_NOT_EXIST, True),
        (ROLENAME_EXIST_EMPTY, True),
        (ROLENAME_EXIST_NON_EMPTY, False)
    ]
    CONDITION_NOT_EXIST_1 = ROLENAME_NOT_EXIST
    CONDITION_NOT_EXIST_2 = {"id": "Intezer - scan host", "version": -1, "tasks": {'1': {'type': 'not_condition'}}}
    CONDITION_EXIST_EMPTY_1 = {"id": "Intezer - scan host", "version": -1,
                               "tasks": {
                                   '1': {'type': 'not_condition'},
                                   '2': {'type': 'condition'}}
                               }
    CONDITION_EXIST_EMPTY_2 = {"id": "Intezer - scan host", "version": -1,
                               "tasks":
                                   {'1': {'type': 'condition',
                                          'nexttasks': {}}}
                               }
    CONDITION_EXIST_PARTIAL_1 = {"id": "Intezer - scan host", "version": -1,
                                 "tasks":
                                     {'1': {'type': 'condition',
                                            'conditions': [],
                                            'nexttasks': {}}}
                                 }
    CONDITION_EXIST_PARTIAL_2 = {"id": "Intezer - scan host", "version": -1,
                                 "tasks":
                                     {'1': {'type': 'condition',
                                            'conditions': [{'label': 'yes'}],
                                            'nexttasks': {'#default#': ['2']}}}
                                 }
    CONDITION_EXIST_PARTIAL_3 = {"id": "Intezer - scan host", "version": -1,
                                 "tasks":
                                     {'1': {'type': 'condition',
                                            'conditions': [{'label': 'yes'}],
                                            'nexttasks': {'#default#': []}}}
                                 }
    CONDITION_EXIST_FULL_NO_TASK_ID = {"id": "Intezer - scan host", "version": -1,
                                       "tasks":
                                           {'1': {'type': 'condition',
                                                  'conditions': [{'label': 'yes'}],
                                                  'nexttasks': {'#default#': []}}}
                                       }
    CONDITION_EXIST_FULL = {"id": "Intezer - scan host", "version": -1,
                            "tasks":
                                {'1': {'type': 'condition',
                                       'conditions': [{'label': 'yes'}],
                                       'nexttasks': {'yes': ['3']}}}}
    CONDITION_EXIST_FULL_CASE_DIF = {"id": "Intezer - scan host", "version": -1,
                                     "tasks":
                                         {'1': {'type': 'condition',
                                                'conditions': [{'label': 'YES'}],
                                                'nexttasks': {'#default#': ['2'], 'yes': ['3']}}}}
    CONDITIONAL_ASK_EXISTS_NO_REPLY_OPTS = {"id": "Intezer - scan host", "version": -1,
                                            "tasks":
                                                {'1': {'type': 'condition',
                                                       'message': {},
                                                       'nexttasks': {}}}}
    CONDITIONAL_ASK_EXISTS_NO_NXT_TASK = {"id": "Intezer - scan host", "version": -1,
                                          "tasks":
                                              {'1': {'type': 'condition',
                                                     'message': {'replyOptions': ['yes']},
                                                     'nexttasks': {}}}
                                          }
    CONDITIONAL_ASK_EXISTS_WITH_DFLT_NXT_TASK = {"id": "Intezer - scan host", "version": -1,
                                                 "tasks":
                                                     {'1': {'type': 'condition',
                                                            'message': {'replyOptions': ['yes']},
                                                            'nexttasks': {'#default#': []}}}}
    CONDITIONAL_ASK_EXISTS_WITH_NXT_TASK = {"id": "Intezer - scan host", "version": -1,
                                            "tasks":
                                                {'1': {'type': 'condition',
                                                       'message': {'replyOptions': ['yes']},
                                                       'nexttasks': {'yes': ['1']}}}
                                            }
    CONDITIONAL_ASK_EXISTS_WITH_NXT_TASK_CASE_DIF = {"id": "Intezer - scan host", "version": -1,
                                                     "tasks":
                                                         {'1': {'type': 'condition',
                                                                'message': {'replyOptions': ['yes']},
                                                                'nexttasks': {'YES': ['1']}}}}
    CONDITIONAL_TASK_EXISTS_WITH_TRUE_AND_FALSE_POSITIVE = {"id": "Intezer - scan host", "version": -1,
                                                            "tasks":
                                                                {'1': {'type': 'condition',
                                                                       'message': {'replyOptions': ['yes', 'NO']},
                                                                       'nexttasks': {"False Positive": ["29"],
                                                                                     "True Positive": ["45"]}}}}
    CONDITIONAL_SCRPT_WITHOUT_NXT_TASK = {"id": "Intezer - scan host", "version": -1,
                                          "tasks":
                                              {'1': {'type': 'condition',
                                                     'scriptName': 'testScript'}}}
    CONDITIONAL_SCRPT_WITH_DFLT_NXT_TASK = {"id": "Intezer - scan host", "version": -1,
                                            "tasks":
                                                {'1': {'type': 'condition',
                                                       'scriptName': 'testScript',
                                                       'nexttasks': {'#default#': []}}}}
    CONDITIONAL_SCRPT_WITH_MULTI_NXT_TASK = {"id": "Intezer - scan host", "version": -1,
                                             "tasks":
                                                 {'1': {'type': 'condition',
                                                        'scriptName': 'testScript',
                                                        'nexttasks': {'#default#': [], 'yes': []}}}}
    DELETECONTEXT_ALL_EXIST = {"id": "Intezer - scan host", "version": -1,
                               "tasks":
                                   {'1': {'type': 'regular',
                                          'task': {'scriptName': 'DeleteContext'},
                                          'scriptarguments': {'all': {'simple': 'yes'}}}}}
    DELETECONTEXT_WITHOUT_ALL = {"id": "Intezer - scan host", "version": -1,
                                 "tasks":
                                     {'1': {'type': 'regular',
                                            'task': {'scriptName': 'DeleteContext'},
                                            'scriptarguments': {'all': {'simple': 'no'}}}}}
    DELETECONTEXT_DOESNT_EXIST = {"id": "Intezer - scan host", "version": -1,
                                  "tasks":
                                      {'1': {'type': 'regular',
                                             'task': {'name': 'test'}}}}
    IS_DELETECONTEXT = [
        (DELETECONTEXT_ALL_EXIST, False),
        (DELETECONTEXT_WITHOUT_ALL, True),
        (DELETECONTEXT_DOESNT_EXIST, True)
    ]

    IS_CONDITIONAL_INPUTS = [
        (CONDITION_NOT_EXIST_1, True),
        (CONDITION_EXIST_EMPTY_1, True),
        (CONDITION_EXIST_EMPTY_2, True),
        (CONDITION_EXIST_PARTIAL_1, True),
        (CONDITION_EXIST_PARTIAL_2, False),
        (CONDITION_EXIST_PARTIAL_3, False),
        (CONDITION_EXIST_FULL_NO_TASK_ID, False),
        (CONDITION_EXIST_FULL, True),
        (CONDITION_EXIST_FULL_CASE_DIF, True),
        (CONDITIONAL_ASK_EXISTS_NO_REPLY_OPTS, True),
        (CONDITIONAL_ASK_EXISTS_NO_NXT_TASK, False),
        (CONDITIONAL_ASK_EXISTS_WITH_DFLT_NXT_TASK, True),
        (CONDITIONAL_ASK_EXISTS_WITH_NXT_TASK, True),
        (CONDITIONAL_ASK_EXISTS_WITH_NXT_TASK_CASE_DIF, True),
        (CONDITIONAL_TASK_EXISTS_WITH_TRUE_AND_FALSE_POSITIVE, True),
        (CONDITIONAL_SCRPT_WITHOUT_NXT_TASK, False),
        (CONDITIONAL_SCRPT_WITH_DFLT_NXT_TASK, False),
        (CONDITIONAL_SCRPT_WITH_MULTI_NXT_TASK, True),
    ]

    TASKS_NOT_EXIST = ROLENAME_NOT_EXIST
    NEXT_TASKS_NOT_EXIST_1 = {"id": "Intezer - scan host", "version": -1, "starttaskid": "1",
                              "tasks": {'1': {'type': 'not_condition'}}}
    NEXT_TASKS_NOT_EXIST_2 = {"id": "Intezer - scan host", "version": -1, "starttaskid": "1",
                              "tasks": {
                                  '1': {'type': 'title'},
                                  '2': {'type': 'condition'}}
                              }
    NEXT_TASKS_INVALID_EXIST_1 = {"id": "Intezer - scan host", "version": -1, "starttaskid": "1",
                                  "tasks": {
                                      '1': {'type': 'title', 'nexttasks': {'next': ['3']}},
                                      '2': {'type': 'condition'}}
                                  }
    NEXT_TASKS_INVALID_EXIST_2 = {"id": "Intezer - scan host", "version": -1, "starttaskid": "1",
                                  "tasks": {
                                      '1': {'type': 'title', 'nexttasks': {'next': ['3']}},
                                      '2': {'type': 'condition'},
                                      '3': {'type': 'condition'}}
                                  }
    NEXT_TASKS_VALID_EXIST_1 = {"id": "Intezer - scan host", "version": -1, "starttaskid": "1",
                                "tasks": {
                                    '1': {'type': 'title', 'nexttasks': {'next': ['2', '3']}},
                                    '2': {'type': 'condition'},
                                    '3': {'type': 'condition'}}
                                }
    NEXT_TASKS_VALID_EXIST_2 = {"id": "Intezer - scan host", "version": -1, "starttaskid": "1",
                                "tasks": {
                                    '1': {'type': 'title', 'nexttasks': {'next': ['2']}},
                                    '2': {'type': 'condition', 'nexttasks': {'next': ['3']}},
                                    '3': {'type': 'condition'}}
                                }

    IS_INSTANCE_EXISTS = {"id": "Intezer - scan host", "version": -1, "starttaskid": "1",
                          "tasks": {
                              '1': {'type': 'title', 'nexttasks': {'next': ['2']},
                                    'scriptarguments': {'using': {'simple': 'tst.instance'}}},
                              '2': {'type': 'condition', 'nexttasks': {'next': ['3']}},
                              '3': {'type': 'condition'}}
                          }

    IS_INSTANCE_DOESNT_EXISTS = {"id": "Intezer - scan host", "version": -1, "starttaskid": "1",
                                 "tasks": {
                                     '1': {'type': 'title', 'nexttasks': {'next': ['2', '3']}},
                                     '2': {'type': 'condition'},
                                     '3': {'type': 'condition'}}
                                 }

    IS_USING_INSTANCE = [
        (IS_INSTANCE_EXISTS, False),
        (IS_INSTANCE_DOESNT_EXISTS, True),
    ]

    IS_ROOT_CONNECTED_INPUTS = [
        (TASKS_NOT_EXIST, True),
        (NEXT_TASKS_NOT_EXIST_1, True),
        (NEXT_TASKS_NOT_EXIST_2, False),
        (NEXT_TASKS_INVALID_EXIST_1, False),
        (NEXT_TASKS_INVALID_EXIST_2, False),
        (NEXT_TASKS_VALID_EXIST_1, True),
        (NEXT_TASKS_VALID_EXIST_2, True),
    ]

    PLAYBOOK_JSON_VALID_SCRIPT_ID = {
        "tasks": {"0": {"task": {"script": "scriptId1"}},
                  "1": {"task": {"script": "scriptId2"}}}}
    ID_SET_VALID_SCRIPT_ID = {"scripts": [{"scriptId1": {"name": "name"}}, {"scriptId2": {"name": "name"}}]}

    PLAYBOOK_JSON_INVALID_SCRIPT_ID = {
        "tasks": {"0": {"task": {"script": "scriptId1"}},
                  "1": {"task": {"script": "scriptId2"}}}}
    ID_SET_INVALID_SCRIPT_ID = {"scripts": [{"scriptId2": {"name": "name"}}]}

    PLAYBOOK_JSON_VALID_SCRIPT_NAME = {
        "tasks": {"0": {"task": {"scriptName": "scriptName1"}},
                  "1": {"task": {"scriptName": "scriptName2"}}}}
    ID_SET_VALID_SCRIPT_NAME = {
        "scripts": [{"scriptId1": {"name": "scriptName1"}}, {"scriptId2": {"name": "scriptName2"}}]}

    PLAYBOOK_JSON_INVALID_SCRIPT_NAME = {
        "tasks": {"0": {"task": {"scriptName": "scriptName1"}},
                  "1": {"task": {"scriptName": "scriptName2"}}}}
    ID_SET_INVALID_SCRIPT_NAME = {
        "scripts": [{"scriptId1": {"name": "scriptName3"}}, {"scriptId2": {"name": "scriptName1"}}]}

    IS_SCRIPT_ID_VALID = [
        (PLAYBOOK_JSON_VALID_SCRIPT_ID, ID_SET_VALID_SCRIPT_ID, True),
        (PLAYBOOK_JSON_INVALID_SCRIPT_ID, ID_SET_INVALID_SCRIPT_ID, False),
        (PLAYBOOK_JSON_VALID_SCRIPT_NAME, ID_SET_VALID_SCRIPT_NAME, True),
        (PLAYBOOK_JSON_INVALID_SCRIPT_NAME, ID_SET_INVALID_SCRIPT_NAME, False),
    ]

    PlAYBOOK_JSON_VALID_TASKID = {
        "0": {"task": {"id": "8bff5d33-9554-4ab9-833c-cc0c0d5fdfd8"},
              "taskid": "8bff5d33-9554-4ab9-833c-cc0c0d5fdfd8"},
        "1": {"task": {"id": "106b8f2e-5106-4857-82ac-122450af4893"},
              "taskid": "106b8f2e-5106-4857-82ac-122450af4893"}
    }

    PlAYBOOK_JSON_INVALID_TASKID = {
        "0": {"task": {"id": "1"},
              "taskid": "1"},
        "1": {"task": {"id": "106b8f2e-5106-4857-82ac-122450af4893"},
              "taskid": "106b8f2e-5106-4857-82ac-122450af4893"}
    }

    PLAYBOOK_JSON_ID_EQUALS_TASKID = {
        "0": {"task": {"id": "8bff5d33-9554-4ab9-833c-cc0c0d5fdfd8"},
              "taskid": "8bff5d33-9554-4ab9-833c-cc0c0d5fdfd8"},
        "1": {"task": {"id": "106b8f2e-5106-4857-82ac-122450af4893"},
              "taskid": "106b8f2e-5106-4857-82ac-122450af4893"}
    }

    PLAYBOOK_JSON_ID_NOT_EQUAL_TO_TASKID = {
        "0": {"task": {"id": "8bff5d33-9554-4ab9-833c-cc0c0d5fdfd8"},
              "taskid": "106b8f2e-5106-4857-82ac-122450af4893"},
        "1": {"task": {"id": "106b8f2e-5106-4857-82ac-122450af4893"},
              "taskid": "8bff5d33-9554-4ab9-833c-cc0c0d5fdfd8"}
    }

    PLAYBOOK_JSON_INDICATORS_INPUT_VALID = {
        'inputs': [
            {'playbookInputQuery': {'queryEntity': 'indicators'}}
        ],
        'quiet': True,
        'tasks': {
            "0": {'quietmode': 0}
        }
    }

    PLAYBOOK_JSON_INDICATORS_INPUT_INVALID_QUIET = {
        'inputs': [
            {'playbookInputQuery': {'queryEntity': 'indicators'}}
        ],
        'quiet': False,
        'tasks': {
            "0": {'quietmode': 1}
        }
    }

    PLAYBOOK_JSON_INDICATORS_INPUT_INVALID_QUIET_TASK = {
        'inputs': [
            {'playbookInputQuery': {'queryEntity': 'indicators'}}
        ],
        'quiet': True,
        'tasks': {
            "0": {'quietmode': 0},
            "1": {'quietmode': 2}
        }
    }

    PLAYBOOK_JSON_INDICATORS_INPUT_INVALID_ON_ERROR = {
        'inputs': [
            {'playbookInputQuery': {'queryEntity': 'indicators'}}
        ],
        'quiet': True,
        'tasks': {
            "0": {'quietmode': 0},
            "1": {'quietmode': 1, 'continueonerror': True}
        }
    }
    IS_VALID_INDICATORS_INPUT = [
        (PLAYBOOK_JSON_INDICATORS_INPUT_VALID, True),
        (PLAYBOOK_JSON_INDICATORS_INPUT_INVALID_QUIET, False),
        (PLAYBOOK_JSON_INDICATORS_INPUT_INVALID_QUIET_TASK, False),
        (PLAYBOOK_JSON_INDICATORS_INPUT_INVALID_ON_ERROR, False),

    ]

    IS_ID_UUID = [
        (PlAYBOOK_JSON_VALID_TASKID, True),
        (PlAYBOOK_JSON_INVALID_TASKID, False)
    ]

    IS_TASK_ID_EQUALS_ID = [
        (PLAYBOOK_JSON_ID_EQUALS_TASKID, True),
        (PLAYBOOK_JSON_ID_NOT_EQUAL_TO_TASKID, False)
    ]

    DEPRECATED_VALID = {"deprecated": True, "description": "Deprecated. Use the XXXX playbook instead."}
    DEPRECATED_VALID2 = {"deprecated": True, "description": "Deprecated. Feodo Tracker no longer supports this feed "
                                                            "No available replacement."}
    DEPRECATED_VALID3 = {"deprecated": True, "description": "Deprecated. The playbook uses an unsupported scraping"
                                                            " API. Use Proofpoint Protection Server v2 playbook"
                                                            " instead."}

    DEPRECATED_INVALID_DESC = {"deprecated": True, "description": "Deprecated."}
    DEPRECATED_INVALID_DESC2 = {"deprecated": True, "description": "Use the ServiceNow playbook to manage..."}
    DEPRECATED_INVALID_DESC3 = {"deprecated": True, "description": "Deprecated. The playbook uses an unsupported"
                                                                   " scraping API."}
    DEPRECATED_INPUTS = [
        (DEPRECATED_VALID, True),
        (DEPRECATED_VALID2, True),
        (DEPRECATED_VALID3, True),
        (DEPRECATED_INVALID_DESC, False),
        (DEPRECATED_INVALID_DESC2, False),
        (DEPRECATED_INVALID_DESC3, False)
    ]

    CONDITIONAL_SCRPT_WITH_DFLT_NXT_TASK = {"id": "Intezer - scan host", "version": -1,
                                            "tasks":
                                                {'1': {'type': 'condition',
                                                       'scriptName': 'testScript',
                                                       'nexttasks': {'#default#': []}}}}

    CONDITIONAL_SCRPT_WITH_NO_DFLT_NXT_TASK = {"id": "Intezer - scan host", "version": -1,
                                               "tasks":
                                               {'1': {'type': 'condition',
                                                      'scriptName': 'testScript',
                                                      'nexttasks': {'1': []}}}}

    CONDITION_TASK_WITH_ELSE = {'1': {'type': 'condition',
                                      'scriptName': 'testScript',
                                      'nexttasks': {'#default#': []}}}

    CONDITION_TASK_WITHOUT_ELSE = {'1': {'type': 'condition',
                                         'scriptName': 'testScript',
                                                       'nexttasks': {'1': []}}}
    IS_ELSE_IN_CONDITION_TASK = [(CONDITIONAL_SCRPT_WITH_NO_DFLT_NXT_TASK.get('tasks').get('1'), False),
                                 (CONDITIONAL_SCRPT_WITH_DFLT_NXT_TASK.get('tasks').get('1'), True)]

    @pytest.mark.parametrize("playbook_json, id_set_json, expected_result", IS_SCRIPT_ID_VALID)
    def test_playbook_script_id(self, mocker, playbook, repo, playbook_json, id_set_json, expected_result):
        """

        Given
        - A playbook with scrips ids or script names
        - An id_set file.

        When
        - validating playbook

        Then
        - In case script id or script name don't exist in id_set , prints a warning.
        """
        playbook.yml.write_dict(playbook_json)
        repo.id_set.write_json(id_set_json)
        structure = mock_structure("", playbook_json)
        validator = PlaybookValidator(structure)
        assert validator.is_script_id_valid(id_set_json) == expected_result

    @pytest.mark.parametrize("playbook_json, expected_result", IS_NO_ROLENAME_INPUTS)
    def test_is_added_required_fields(self, playbook_json, expected_result):
        structure = mock_structure("", playbook_json)
        validator = PlaybookValidator(structure)
        assert validator.is_no_rolename() is expected_result

    @pytest.mark.parametrize("playbook_json, expected_result", IS_CONDITIONAL_INPUTS)
    def test_is_condition_branches_handled(self, playbook_json, expected_result):
        structure = mock_structure("", playbook_json)
        validator = PlaybookValidator(structure)
        assert validator.is_condition_branches_handled() is expected_result

    @pytest.mark.parametrize("playbook_json, expected_result", IS_ROOT_CONNECTED_INPUTS)
    def test_is_root_connected_to_all_tasks(self, playbook_json, expected_result):
        structure = mock_structure("", playbook_json)
        validator = PlaybookValidator(structure)
        assert validator.is_root_connected_to_all_tasks() is expected_result

    @pytest.mark.parametrize("playbook_path, expected_result", [(INVALID_TEST_PLAYBOOK_UNHANDLED_CONDITION, True),
                                                                (INVALID_PLAYBOOK_UNHANDLED_CONDITION, False)])
    def test_skipping_test_playbooks(self, mocker, playbook_path, expected_result):
        """
            Given
            - A playbook

            When
            - The playbook has unhandled condition in it

            Then
            -  Ensure the unhandled condition is ignored if it's a test playbook
            -  Ensure validation fails if it's a not test playbook
        """
        with ChangeCWD(CONTENT_REPO_EXAMPLE_ROOT):
            structure = StructureValidator(file_path=playbook_path)
            validator = PlaybookValidator(structure)
            mocker.patch.object(validator, 'is_script_id_valid', return_value=True)

            assert validator.is_valid_playbook() is expected_result

    @pytest.mark.parametrize("playbook_json, expected_result", IS_DELETECONTEXT)
    def test_is_delete_context_all_in_playbook(self, playbook_json, expected_result):
        """
        Given
        - A playbook

        When
        - The playbook have deleteContext script use with all=yes

        Then
        -  Ensure that the validation fails when all=yes arg exists.
        """
        structure = mock_structure("", playbook_json)
        validator = PlaybookValidator(structure)
        assert validator.is_delete_context_all_in_playbook() is expected_result

    @pytest.mark.parametrize("playbook_json, expected_result", IS_USING_INSTANCE)
    def test_is_using_instance(self, playbook_json, expected_result):
        """
        Given
        - A playbook

        When
        - The playbook has a using specific instance.
        - The playbook doestnt have using in it.

        Then
        - Ensure validation fails if it's a not test playbook
        - Ensure that the validation passes if no using usage.
        """
        structure = mock_structure("", playbook_json)
        validator = PlaybookValidator(structure)
        assert validator.is_using_instance() is expected_result

    @pytest.mark.parametrize("playbook_json, expected_result", IS_ID_UUID)
    def test_is_id_uuid(self, playbook_json, expected_result):
        """
        Given
        - A playbook

        When
        - The playbook include taskid and inside task field an id that are both from uuid type.
        - The playbook include taskid and inside task field an id that are not both from uuid type.

        Then
        - Ensure validation passes if the taskid field and the id inside task field are both from uuid type
        - Ensure validation fails if the taskid field and the id inside task field are one of them not from uuid type
        """
        structure = mock_structure("", playbook_json)
        validator = PlaybookValidator(structure)
        validator._is_id_uuid() is expected_result

    @pytest.mark.parametrize("playbook_json, expected_result", IS_TASK_ID_EQUALS_ID)
    def test_is_taskid_equals_id(self, playbook_json, expected_result):
        """
        Given
        - A playbook

        When
        - The playbook include taskid and inside task field an id that are both have the same value.
        - The playbook include taskid and inside task field an id that are different values.

        Then
        - Ensure validation passes if the taskid field and the id inside task field have the same value
        - Ensure validation fails if the taskid field and the id inside task field are have different value
        """
        structure = mock_structure("", playbook_json)
        validator = PlaybookValidator(structure)
        validator._is_taskid_equals_id() is expected_result

    @pytest.mark.parametrize("current, answer", DEPRECATED_INPUTS)
    def test_is_valid_deprecated_playbook(self, current, answer):
        """
        Given
            1. A deprecated playbook with a valid description according to 'deprecated regex' (including the replacement
               playbook name).
            2. A deprecated playbook with a valid description according to the 'deprecated no replacement regex'.
            3. A deprecated playbook with a valid description according to 'deprecated regex' (including the replacement
               playbook name, and the reason for deprecation.).
            4. A deprecated playbook with an invalid description that isn't according to the 'deprecated regex'
               (doesn't include a replacement playbook name, or declare there isn't a replacement).
            5. A deprecated playbook with an invalid description that isn't according to the 'deprecated regex'
               (doesn't start with the phrase: 'Deprecated.').
            6. A deprecated playbook with an invalid description that isn't according to the 'deprecated regex'
               (Includes the reason for deprecation, but doesn't include a replacement playbook name,
               or declare there isn't a replacement).
        When
            - running is_valid_as_deprecated.

        Then
            - a playbook with an invalid description will be errored.
        """
        structure = mock_structure("", current)
        validator = PlaybookValidator(structure)
        validator.current_file = current
        assert validator.is_valid_as_deprecated() is answer

    @pytest.mark.parametrize("playbook_json, expected_result", [(CONDITIONAL_SCRPT_WITH_NO_DFLT_NXT_TASK, True)])
    def test_verify_all_conditional_tasks_has_else_path(self, playbook_json, expected_result):
        """
        Given
            - A playbook with a condition without a default task

        When
            - Running Validate playbook

        Then
            - Function returns true as this is an ignored error.
        """
        structure = mock_structure("", playbook_json)
        validator = PlaybookValidator(structure)
        assert validator.verify_condition_tasks_has_else_path() is expected_result

    @pytest.mark.parametrize("playbook_task_json, expected_result", IS_ELSE_IN_CONDITION_TASK)
    def test_verify_else_for_conditions_task(self, playbook_task_json, expected_result):
        """
        Given
            - A playbook condition task with a default task
            - A playbook condition task without a default task

        When
            - Running Validate playbook

        Then
            - Return True if the condition task has default path , else false
        """
        structure = mock_structure("", playbook_task_json)
        validator = PlaybookValidator(structure)
        assert validator._is_else_path_in_condition_task(task=playbook_task_json) is expected_result

    def test_name_contains_the_type(self, pack):
        """
        Given
            - An playbook with a name that contains the word "playbook".
        When
            - running name_not_contain_the_type.
        Then
            - Ensure the validate failed.
        """
        playbook = pack.create_playbook(yml={"name": "test_playbook"})

        with ChangeCWD(pack.repo_path):
            structure_validator = StructureValidator(playbook.yml.path)
            validator = PlaybookValidator(structure_validator)

            assert not validator.name_not_contain_the_type()

    def test_name_does_not_contains_the_type(self, pack):
        """
        Given
            - An playbook with a name that does not contains the "playbook" string.
        When
            - running name_not_contain_the_type.
        Then
            - Ensure the validate passes.
        """
        playbook = pack.create_playbook(yml={"name": "test"})

        with ChangeCWD(pack.repo_path):
            structure_validator = StructureValidator(playbook.yml.path)
            validator = PlaybookValidator(structure_validator)

            assert validator.name_not_contain_the_type()

    @pytest.mark.parametrize("playbook_json, expected_result", IS_VALID_INDICATORS_INPUT)
    def test_is_valid_with_indicators_input(self, playbook_json, expected_result):
        """
        Given
        - A playbook

        When
        - The playbook with input from indicators, includes all valid fields.
        - The playbook with input from indicators, is not set on quietmode.
        - The playbook with input from indicators, one of the tasks does not have quiet mode on.
        -The playbook with input from indicators, one of the tasks continues on error

        Then
        - Ensure validation passes.
        - Ensure validation fails.
        - Ensure validation fails.
        - Ensure validation fails.
        """
        structure = mock_structure("", playbook_json)
        validator = PlaybookValidator(structure)
        assert validator.is_valid_with_indicators_input() is expected_result


# demisto_sdk/commands/common/tests/pre_process_rule_test.py

from demisto_sdk.commands.common.hook_validations.pre_process_rule import \
    PreProcessRuleValidator


class TestPreProcessRuleValidator:

    def test_get_field_name(self):
        """
        Given
        - A context field name
        When
        - The fieldname can be surrounded by ${}
        Then
        - Return the field name itself
        """

        assert 'foo1' == PreProcessRuleValidator.get_field_name('foo1')
        assert 'foo2' == PreProcessRuleValidator.get_field_name('${foo2')
        assert 'foo3' == PreProcessRuleValidator.get_field_name('foo3}')
        assert 'foo4' == PreProcessRuleValidator.get_field_name('${foo4}')


# demisto_sdk/commands/common/tests/readme_test.py

import glob
import io
import os
import sys

import pytest
import requests_mock

from demisto_sdk.commands.common.git_util import GitUtil
from demisto_sdk.commands.common.hook_validations.readme import ReadMeValidator
from demisto_sdk.commands.common.legacy_git_tools import git_path
from TestSuite.test_tools import ChangeCWD

VALID_MD = f'{git_path()}/demisto_sdk/tests/test_files/README-valid.md'
INVALID_MD = f'{git_path()}/demisto_sdk/tests/test_files/README-invalid.md'
INVALID2_MD = f'{git_path()}/demisto_sdk/tests/test_files/README-invalid2.md'
INVALID3_MD = f'{git_path()}/demisto_sdk/tests/test_files/README-short-invalid.md'
IMAGES_MD = f'{git_path()}/demisto_sdk/tests/test_files/README-images.md'
EMPTY_MD = f'{git_path()}/demisto_sdk/tests/test_files/README-empty.md'
FAKE_INTEGRATION_README = f'{git_path()}/demisto_sdk/tests/test_files/fake_integration/fake_README.md'

README_INPUTS = [
    (VALID_MD, True),
    (INVALID_MD, False),
    (INVALID2_MD, False),
    (INVALID3_MD, False),
    (EMPTY_MD, True)
]

MDX_SKIP_NPM_MESSAGE = 'Required npm modules are not installed. To run this test you must run "npm install" ' \
                       'in the root of the project.'


@pytest.mark.parametrize("current, answer", README_INPUTS)
def test_is_file_valid(mocker, current, answer):
    readme_validator = ReadMeValidator(current)
    valid = readme_validator.are_modules_installed_for_verify(readme_validator.content_path)
    if not valid:
        pytest.skip('skipping mdx test. ' + MDX_SKIP_NPM_MESSAGE)
        return
    with requests_mock.Mocker() as m:
        # Mock get requests
        m.get('https://github.com/demisto/content/blob/123/Packs/AutoFocus/doc_files/AutoFocusPolling.png',
              status_code=200, text="Test1")
        m.get('https://github.com/demisto/content/blob/123/Packs/FeedOffice365/doc_files/test.png',
              status_code=200, text="Test2")
        m.get('https://github.com/demisto/content/raw/123/Packs/valid/doc_files/test.png',
              status_code=200, text="Test3")
        mocker.patch.dict(os.environ, {'DEMISTO_README_VALIDATION': 'yes', 'DEMISTO_MDX_CMD_VERIFY': 'yes'})
        assert readme_validator.is_valid_file() is answer
        assert not ReadMeValidator._MDX_SERVER_PROCESS


@pytest.mark.parametrize("current, answer", README_INPUTS)
def test_is_file_valid_mdx_server(mocker, current, answer):
    readme_validator = ReadMeValidator(current)
    valid = readme_validator.are_modules_installed_for_verify(readme_validator.content_path)
    if not valid:
        pytest.skip('skipping mdx server test. ' + MDX_SKIP_NPM_MESSAGE)
        return
    mocker.patch.dict(os.environ, {'DEMISTO_README_VALIDATION': 'yes'})
    assert readme_validator.is_valid_file() is answer
    assert ReadMeValidator._MDX_SERVER_PROCESS is not None
    ReadMeValidator.stop_mdx_server()


def test_are_modules_installed_for_verify_false_res(tmp_path):
    r = str(tmp_path / "README.md")
    with open(r, 'w') as f:
        f.write('Test readme')
    readme_validator = ReadMeValidator(r)
    # modules will be missing in tmp_path
    assert not readme_validator.are_modules_installed_for_verify(tmp_path)


def test_is_image_path_valid():
    """
    Given
        - A README file with 2 invalid images paths in it.
    When
        - Run validate on README file
    Then
        - Ensure:
            - Validation fails
            - Both images paths were caught correctly
            - Valid image path was not caught
            - An alternative paths were suggested
    """
    captured_output = io.StringIO()
    sys.stdout = captured_output  # redirect stdout.
    images_paths = ["https://github.com/demisto/content/blob/123/Packs/AutoFocus/doc_files/AutoFocusPolling.png",
                    "https://github.com/demisto/content/blob/123/Packs/FeedOffice365/doc_files/test.png",
                    "https://github.com/demisto/content/raw/123/Packs/valid/doc_files/test.png"]
    alternative_images_paths = [
        "https://github.com/demisto/content/raw/123/Packs/AutoFocus/doc_files/AutoFocusPolling.png",
        "https://github.com/demisto/content/raw/123/Packs/FeedOffice365/doc_files/test.png"]
    readme_validator = ReadMeValidator(INVALID_MD)
    result = readme_validator.is_image_path_valid()
    sys.stdout = sys.__stdout__  # reset stdout.
    assert not result
    assert images_paths[0] and alternative_images_paths[0] in captured_output.getvalue()
    assert images_paths[1] and alternative_images_paths[1] in captured_output.getvalue()
    assert images_paths[2] not in captured_output.getvalue()


@pytest.mark.parametrize("file_input, missing_section",
                         [("## Troubleshooting\n## OtherSection", "Troubleshooting"),
                          ("## Troubleshooting", "Troubleshooting"),
                          ("## Troubleshooting\n\n---\n## OtherSection", "Troubleshooting"),
                          ("## Use Cases\n\n----------\n## OtherSection", "Use Cases"),
                          ("## Additional Information\n\n## OtherSection", "Additional Information"),
                          ("## Known Limitations\n\n----------\n", "Known Limitations")])
def test_unvalid_verify_no_empty_sections(integration, capsys, file_input, missing_section):
    """
    Given
        - Empty sections in different forms
    When
        - Run validate on README file
    Then
        - Ensure no empty sections from the SECTIONS list
    """

    integration.readme.write(file_input)
    readme_path = integration.readme.path

    with ChangeCWD(integration.repo_path):
        readme_validator = ReadMeValidator(readme_path)
        result = readme_validator.verify_no_empty_sections()

        stdout, _ = capsys.readouterr()
        section_error = f'{missing_section} is empty, please elaborate or delete the section.'

        assert not result
        assert section_error in stdout


@pytest.mark.parametrize("file_input",
                         ["## Troubleshooting\n## OtherSection\n## Additional Information\n\n## OtherSection\n##"])
def test_combined_unvalid_verify_no_empty_sections(integration, capsys, file_input):
    """
    Given
        - Couple of empty sections
    When
        - Run validate on README file
    Then
        - Ensure no empty sections from the SECTIONS list
    """

    integration.readme.write(file_input)
    readme_path = integration.readme.path

    with ChangeCWD(integration.repo_path):
        readme_validator = ReadMeValidator(readme_path)
        result = readme_validator.verify_no_empty_sections()

        stdout, _ = capsys.readouterr()
        error = 'Failed verifying README.md Error Message is: Troubleshooting is empty, please elaborate or delete the' \
                ' section.\nAdditional Information is empty, please elaborate or delete the section.'

        assert not result
        assert error in stdout


@pytest.mark.parametrize("file_input",
                         ["## Troubleshooting\ninput",
                          "## Troubleshooting\n\n---\ninput",
                          "## Use Cases\n\n----------\ninput",
                          "## Additional Information\n\ninput",
                          "## Additional Information\n\n### OtherSection",
                          "## Known Limitations\n\n----------\ninput"])
def test_valid_sections(integration, file_input):
    """
    Given
        - Valid sections in different forms from SECTIONS
    When
        - Run validate on README file
    Then
        - Ensure no empty sections from the SECTIONS list
    """

    integration.readme.write(file_input)
    readme_path = integration.readme.path
    readme_validator = ReadMeValidator(readme_path)
    result = readme_validator.verify_no_empty_sections()

    assert result


@pytest.mark.parametrize("file_input, section",
                         [("##### Required Permissions\n**FILL IN REQUIRED PERMISSIONS HERE**\n##### Base Command",
                           'FILL IN REQUIRED PERMISSIONS HERE'),
                          ("##### Required Permissions **FILL IN REQUIRED PERMISSIONS HERE**\n##### Base Command",
                           'FILL IN REQUIRED PERMISSIONS HERE'),
                          ("##### Required Permissions FILL IN REQUIRED PERMISSIONS HERE",
                           'FILL IN REQUIRED PERMISSIONS HERE'),
                          ("##### Required Permissions FILL IN REQUIRED PERMISSIONS HERE",
                           'FILL IN REQUIRED PERMISSIONS HERE'),
                          ("This integration was integrated and tested with version xx of integration v2",
                           'version xx'),
                          ("##Dummy Integration\n this integration is for getting started and learn how to build an "
                           "integration. some extra text here",
                           'getting started and learn how to build an integration')])
def test_verify_no_default_sections_left(integration, capsys, file_input, section):
    """
    Given
        - Readme that contains sections that are created as default and need to be changed
    When
        - Run validate on README file
    Then
        - Ensure no default sections in the readme file
    """
    integration.readme.write(file_input)
    readme_path = integration.readme.path

    with ChangeCWD(integration.repo_path):
        readme_validator = ReadMeValidator(readme_path)
        result = readme_validator.verify_no_default_sections_left()

        stdout, _ = capsys.readouterr()
        section_error = f'Replace "{section}" with a suitable info.'
        assert not result
        assert section_error in stdout


ERROR_FOUND_CASES = [
    ([f'{FAKE_INTEGRATION_README} - [RM102]'], [], False),
    ([], [f'{FAKE_INTEGRATION_README} - [RM102]'], False),
    ([], [], True),
]


@pytest.mark.parametrize("readme_fake_path, readme_text",
                         [('/HelloWorld/README.md', 'getting started and learn how to build an integration')])
def test_readme_ignore(integration, readme_fake_path, readme_text):
    """
    Check that packs in ignore list are ignored.
       Given
            - README path of ignore pack
        When
            - Run validate on README of ignored pack
        Then
            - Ensure validation ignored the pack
    """
    integration.readme.write(readme_text)
    readme_path = integration.readme.path
    readme_validator = ReadMeValidator(readme_path)
    # change the pack path to readme_fake_path
    from pathlib import Path
    readme_validator.file_path = Path(readme_fake_path)
    readme_validator.pack_path = readme_validator.file_path.parent

    result = readme_validator.verify_no_default_sections_left()
    assert result


@pytest.mark.parametrize("errors_found, errors_ignore, expected", ERROR_FOUND_CASES)
def test_context_only_runs_once_when_error_exist(mocker, integration, errors_found, errors_ignore, expected):
    """
        Given
            - README that contains changes and YML file
        When
            - Run validate on README file and YML
        Then
            - Ensure validation only run once, either for YML or for README
        """
    readme_validator = ReadMeValidator(FAKE_INTEGRATION_README)
    mocker.patch.object(ReadMeValidator, '_get_error_lists',
                        return_value=(errors_found, errors_ignore))

    result = readme_validator.is_context_different_in_yml()
    assert result == expected


DIFFERENCE_CONTEXT_RESULTS_CASE = [
    ({'zoom-create-user': {'only in yml': {'Zoom.User.id'}, 'only in readme': set()}}, False),
    # case path exists only in yml
    ({'zoom-list-users': {'only in yml': set(), 'only in readme': {'Zoom.User.last_name', 'Zoom.User.first_name'}}},
     False),  # case path exists only in readme
    ({}, True),  # case no changes were found
]


@pytest.mark.parametrize("difference_found, expected", DIFFERENCE_CONTEXT_RESULTS_CASE)
def test_context_difference_created_is_valid(mocker, difference_found, expected):
    """
    Given
        - README that contains changes and YML file
    When
        - Run validate on README file and YML
    Then
        - Ensure the difference context is correct
    """
    mocker.patch('demisto_sdk.commands.common.hook_validations.readme.compare_context_path_in_yml_and_readme',
                 return_value=difference_found)
    readme_validator = ReadMeValidator(FAKE_INTEGRATION_README)
    handle_error_mock = mocker.patch.object(ReadMeValidator, 'handle_error')
    valid = readme_validator.is_context_different_in_yml()
    assert valid == expected
    if not valid:
        handle_error_mock.assert_called()
    else:
        handle_error_mock.assert_not_called()


def test_invalid_short_file(capsys):
    """
    Given
        - Non empty Readme with less than 30 chars.
    When
        - Running validate on README file
    Then
        - Ensure verify on Readme fails
    """
    readme_validator = ReadMeValidator(INVALID3_MD)
    result = readme_validator.verify_readme_is_not_too_short()
    stdout, _ = capsys.readouterr()
    short_readme_error = 'Your Pack README is too small (29 chars). Please move its content to the pack ' \
                         'description or add more useful information to the Pack README. ' \
                         'Pack README files are expected to include a few sentences about the pack and/or images.'
    assert not result
    assert short_readme_error in stdout


def test_demisto_in_readme(repo):
    """
        Given
            - An integration README contains the word 'Demisto'.

        When
            - Running verify_demisto_in_readme_content.

        Then
            - Ensure that the validation fails.
    """

    pack = repo.create_pack('PackName')
    integration = pack.create_integration('IntName')

    readme_path = glob.glob(os.path.join(os.path.dirname(integration.yml.path), '*README.md'))[0]

    with open(readme_path, 'w') as f:
        f.write('This checks if we have the word Demisto in the README.')

    with ChangeCWD(repo.path):
        readme_validator = ReadMeValidator(integration.readme.path)

        assert not readme_validator.verify_demisto_in_readme_content()


def test_demisto_not_in_readme(repo):
    """
        Given
            - An integration README without the word 'Demisto'.

        When
            - Running verify_demisto_in_readme_content.

        Then
            - Ensure that the validation passes.
    """

    pack = repo.create_pack('PackName')
    integration = pack.create_integration('IntName')

    readme_path = glob.glob(os.path.join(os.path.dirname(integration.yml.path), '*README.md'))[0]

    with open(readme_path, 'w') as f:
        f.write('This checks if we have the word XSOAR in the README.')

    readme_validator = ReadMeValidator(integration.readme.path)

    assert readme_validator.verify_demisto_in_readme_content()


def test_verify_template_not_in_readme(repo):
    """
        Given
            - An integration README contains the generic sentence '%%FILL HERE%%'.

        When
            - Running verify_template_not_in_readme.

        Then
            - Ensure that the validation fails.
    """

    pack = repo.create_pack('PackName')
    integration = pack.create_integration('IntName')

    readme_path = glob.glob(os.path.join(os.path.dirname(integration.yml.path), '*README.md'))[0]

    with open(readme_path, 'w') as f:
        f.write('This checks if we have the sentence %%FILL HERE%% in the README.')

    with ChangeCWD(repo.path):
        readme_validator = ReadMeValidator(integration.readme.path)

        assert not readme_validator.verify_template_not_in_readme()


def test_verify_readme_image_paths(mocker):
    """
    Given
        - A README file (not pack README) with valid/invalid relative image
         paths and valid/invalid absolute image paths in it.
    When
        - Run validate on README file
    Then
        - Ensure:
            - Validation fails
            - Image paths were caught correctly
            - Valid paths are not caught
    """
    captured_output = io.StringIO()
    sys.stdout = captured_output  # redirect stdout.

    readme_validator = ReadMeValidator(IMAGES_MD)
    mocker.patch.object(GitUtil, 'get_current_working_branch', return_value='branch_name')
    with requests_mock.Mocker() as m:
        # Mock get requests
        m.get('https://github.com/demisto/test1.png',
              status_code=404, text="Test1")
        m.get('https://github.com/demisto/content/raw/test2.png',
              status_code=404, text="Test2")
        m.get('https://github.com/demisto/test3.png',
              status_code=200, text="Test3")

        result = readme_validator.verify_readme_image_paths()
    sys.stdout = sys.__stdout__  # reset stdout.
    captured_output = captured_output.getvalue()
    assert not result
    assert 'The following image relative path is not valid, please recheck it:\n' \
           '![Identity with High Risk Score](../../default.png)' in captured_output
    assert 'The following image relative path is not valid, please recheck it:\n' \
           '![Identity with High Risk Score](default.png)' not in captured_output
    assert 'Branch name was found in the URL, please change it to the commit hash:\n' \
           '![branch in url]' in captured_output
    assert 'Branch name was found in the URL, please change it to the commit hash:\n' \
           '![commit hash in url]' not in captured_output
    assert 'please repair it:\n' \
           '![Identity with High Risk Score](https://github.com/demisto/test1.png)' in captured_output
    assert 'please repair it:\n(https://github.com/demisto/content/raw/test2.png)' in captured_output
    assert 'please repair it:\n' \
           '![Identity with High Risk Score](https://github.com/demisto/test3.png)' \
           not in captured_output


# demisto_sdk/commands/common/tests/release_notes_config_test.py

from typing import Optional

import pytest

from demisto_sdk.commands.common.hook_validations.release_notes_config import \
    ReleaseNotesConfigValidator


class TestReleaseNotesConfigValidator:
    HAS_CORRESPONDING_RN_FILE_INPUTS = [('Some RN text', True), (None, False)]

    @pytest.mark.parametrize('rn_text, expected', HAS_CORRESPONDING_RN_FILE_INPUTS)
    def test_has_corresponding_rn_file(self, pack, rn_text: Optional[str], expected: bool):
        """
        Given:
        - Config file for RN.

        When:
        - Checking if config file is valid:
        Case a: Config file has corresponding RN file.
        Case b: Config file does not have corresponding RN file.

        Then: Ensure expected bool is returned:
        Case a: Ensure true is returned.
        Case b: Ensure false is returned.
        """
        if rn_text:
            with open(f'{pack.path}/ReleaseNotes/1_0_1.md', 'w') as f:
                f.write(rn_text)
        rn_config_validator = ReleaseNotesConfigValidator(f'{pack.path}/ReleaseNotes/1_0_1.json')
        assert rn_config_validator.has_corresponding_rn_file() == expected

    @pytest.mark.parametrize('rn_text, expected', HAS_CORRESPONDING_RN_FILE_INPUTS)
    def test_is_valid_file(self, pack, rn_text: Optional[str], expected: bool):
        """
        Given:
        - Config file for RN.

        When:
        - Checking if config file is valid:
        Case a: Config file has corresponding RN file.
        Case b: Config file does not have corresponding RN file.

        Then: Ensure expected bool is returned:
        Case a: Ensure true is returned.
        Case b: Ensure false is returned.
        """
        if rn_text:
            with open(f'{pack.path}/ReleaseNotes/1_0_1.md', 'w') as f:
                f.write(rn_text)
        rn_config_validator = ReleaseNotesConfigValidator(f'{pack.path}/ReleaseNotes/1_0_1.json')
        assert rn_config_validator.is_file_valid() == expected


# demisto_sdk/commands/common/tests/release_notes_test.py

import os

import pytest

from demisto_sdk.commands.common.constants import FileType
from demisto_sdk.commands.common.hook_validations.release_notes import \
    ReleaseNotesValidator
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator
from demisto_sdk.commands.common.legacy_git_tools import git_path
from demisto_sdk.commands.update_release_notes.update_rn import UpdateRN


def get_validator(file_path='', modified_files=None, added_files=None):
    release_notes_validator = ReleaseNotesValidator("")
    release_notes_validator.release_notes_file_path = os.path.join(FILES_PATH, 'CortexXDR')
    release_notes_validator.release_notes_path = file_path
    release_notes_validator.latest_release_notes = file_path
    release_notes_validator.modified_files = modified_files
    release_notes_validator.added_files = added_files
    release_notes_validator.pack_name = 'CortexXDR'
    release_notes_validator.file_types_that_should_not_appear_in_rn = {
        FileType.TEST_SCRIPT, FileType.TEST_PLAYBOOK, FileType.README, FileType.RELEASE_NOTES, None}
    release_notes_validator.ignored_errors = {}
    release_notes_validator.checked_files = set()
    release_notes_validator.json_file_path = ''
    release_notes_validator.pack_path = 'Path/CortexXDR'
    release_notes_validator.suppress_print = False
    return release_notes_validator


FILES_PATH = os.path.normpath(os.path.join(__file__, f'{git_path()}/demisto_sdk/tests', 'test_files', 'Packs'))
nothing_in_rn = ''
rn_not_filled_out = '%%UPDATE_RN%%'
rn_filled_out = 'This are sample release notes'
diff_package = [(nothing_in_rn, False),
                (rn_not_filled_out, False),
                (rn_filled_out, True)]


@pytest.mark.parametrize('release_notes, expected_result', diff_package)
def test_rn_master_diff(release_notes, expected_result, mocker):
    """
    Given
    - Case 1: Empty release notes.
    - Case 2: Not filled out release notes.
    - Case 3: Valid release notes

    When
    - Running validation on release notes.

    Then
    - Ensure validation correctly identifies valid release notes.
    - Case 1: Should return the prompt "Please complete the release notes found at: {path}" and
              return False
    - Case 2: Should return the prompt "Please finish filling out the release notes found at: {path}" and
              return False
    - Case 3: Should print nothing and return True
    """
    mocker.patch.object(ReleaseNotesValidator, '__init__', lambda a, b: None)
    ReleaseNotesValidator.ignored_errors = []
    validator = get_validator(release_notes)
    assert validator.is_file_valid() == expected_result


def test_init():
    """
    Given
    - Release notes file path

    When
    - Running validation on release notes.

    Then
    - Ensure init returns valid file path and release notes contents.
    """
    filepath = os.path.join(FILES_PATH, 'CortexXDR', 'ReleaseNotes', '1_1_1.md')
    release_notes_validator = ReleaseNotesValidator(filepath, pack_name='test')
    release_notes_validator.release_notes_file_path = 'demisto_sdk/tests/test_files/Packs/CortexXDR/ReleaseNotes/' \
                                                      '1_1_1.md'
    assert release_notes_validator.release_notes_path == filepath
    assert release_notes_validator.latest_release_notes == '### Test'


NOT_FILLED_OUT_RN = '''
### Incident Types
#### Cortex XDR Incident
- %%UPDATE_RN%%

### Incident Fields
#### XDR Alerts
- %%UPDATE_RN%%

### Integrations
#### Palo Alto Networks Cortex XDR - Investigation and Response
- %%UPDATE_RN%%

### Scripts
#### EntryWidgetNumberHostsXDR
- %%UPDATE_RN%%
'''
FILLED_OUT_RN = '''
### Classifiers
#### dummy classifier
- Test

### Dashboards
#### dashboard-sample_packs_new2.json
-Test

### Incident Types
#### Cortex XDR Incident
- Test

### Incident Fields
#### XDR Alerts
- Test

### Integrations
#### Palo Alto Networks Cortex XDR - Investigation and Response
- Test

### Layouts
#### details-Cortex_XDR_Incident
- Test

### Scripts
#### EntryWidgetNumberHostsXDR
- Test

### Playbooks
#### Cortex XDR Incident Handling
- test
'''

TEST_RELEASE_NOTES_TEST_BANK_1 = [
    ('', False),  # Completely Empty
    ('### Integrations\n#### HelloWorld\n- Grammar correction for code '  # Missing Items
     'description.\n\n### Scripts\n#### HelloWorldScript \n- Grammar correction for '
     'code description. ', False),
    (NOT_FILLED_OUT_RN, False),
    (FILLED_OUT_RN, True)

]
MODIFIED_FILES = [
    os.path.join(FILES_PATH, 'CortexXDR', 'Integrations/PaloAltoNetworks_XDR/PaloAltoNetworks_XDR.yml'),
    os.path.join(FILES_PATH, 'CortexXDR', 'Integrations/PaloAltoNetworks_XDR/PaloAltoNetworks_XDR_image.png'),
    os.path.join(FILES_PATH, 'CortexXDR', 'IncidentTypes/Cortex_XDR_Incident.json'),
    os.path.join(FILES_PATH, 'CortexXDR', 'IncidentFields/XDR_Alerts.json'),
    os.path.join(FILES_PATH, 'CortexXDR', 'Scripts/EntryWidgetNumberHostsXDR/EntryWidgetNumberHostsXDR.yml'),
    os.path.join(FILES_PATH, 'CortexXDR', 'README.md'),
    os.path.join(FILES_PATH, 'CortexXDR', 'Playbooks/Cortex_XDR_Incident_Handling.yml'),
    os.path.join(FILES_PATH, 'CortexXDR', 'Layouts/details-Cortex_XDR_Incident.json'),
    os.path.join(FILES_PATH, 'CortexXDR', 'Classifiers/classifier-to-test.json'),
    os.path.join(FILES_PATH, 'CortexXDR', 'Dashboards/dashboard-sample_packs_new2.json')
]
ADDED_FILES = [
    os.path.join(FILES_PATH, 'CortexXDR', 'Playbooks/Cortex_XDR_Incident_Handling.yml'),
    os.path.join(FILES_PATH, 'CortexXDR', 'Integrations/PaloAltoNetworks_XDR/PaloAltoNetworks_XDR_description.md'),
    os.path.join(FILES_PATH, 'CortexXDR', 'ReleaseNotes/1_0_0.md'),
    os.path.join(FILES_PATH, 'CortexXDR', 'README.md'),
]


@pytest.mark.parametrize('release_notes, complete_expected_result', TEST_RELEASE_NOTES_TEST_BANK_1)
def test_are_release_notes_complete(release_notes, complete_expected_result, mocker):
    """
    Given
    - Case 1: Empty release notes.
    - Case 2: Not filled out release notes.
    - Case 3: Valid release notes

    When
    - Running validation on release notes.

    Then
    - Ensure validation correctly identifies valid release notes.
    - Case 1: Should return the prompt "Please complete the release notes found at: {path}" and
              return False
    - Case 2: Should return the prompt "Please finish filling out the release notes found at: {path}" and
              return False
    - Case 3: Should print nothing and return True
    """
    mocker.patch.object(ReleaseNotesValidator, '__init__', lambda a, b: None)
    mocker.patch.object(StructureValidator, 'scheme_of_file_by_path', return_value='integration')
    mocker.patch.object(UpdateRN, 'get_master_version', return_value='0.0.0')
    validator = get_validator(release_notes, MODIFIED_FILES)
    assert validator.are_release_notes_complete() == complete_expected_result


MODIFIED_FILES_INVALID = [
    os.path.join(FILES_PATH, 'CortexXDR', 'Integrations/PaloAltoNetworks_XDR/PaloAltoNetworks_XDR.yml'),
    os.path.join(FILES_PATH, 'CortexXDR', 'IncidentTypes/Cortex_XDR_Incident.json'),
    os.path.join(FILES_PATH, 'CortexXDR', 'IncidentFields/XDR_Alerts.json'),
    os.path.join(FILES_PATH, 'CortexXDR', 'Scripts/EntryWidgetNumberHostsXDR/EntryWidgetNumberHostsXDR.yml'),
    os.path.join(FILES_PATH, 'CortexXDR', 'TestPlaybooks/Cortex_XDR.yml'),
    os.path.join(FILES_PATH, 'CortexXDR', '.secrets-ignore'),
    os.path.join(FILES_PATH, 'CortexXDR', 'Playbooks/Cortex_XDR_Incident_Handling.yml'),
    os.path.join(FILES_PATH, 'CortexXDR', 'Layouts/details-Cortex_XDR_Incident.json'),
    os.path.join(FILES_PATH, 'CortexXDR', 'Classifiers/classifier-to-test.json'),
    os.path.join(FILES_PATH, 'CortexXDR', 'Dashboards/dashboard-sample_packs_new2.json')
]


@pytest.mark.parametrize('release_notes, complete_expected_result', TEST_RELEASE_NOTES_TEST_BANK_1)
def test_are_release_notes_complete_invalid_file_type(release_notes, complete_expected_result, mocker):
    """
    Given
    - Case 1: Empty release notes.
    - Case 2: Not filled out release notes.
    - Case 3: Valid release notes

    When
    - Running validation on release notes.

    Then
    - Ensure validation correctly identifies valid release notes.
    - Case 1: Should return the prompt "Please complete the release notes found at: {path}" and
              return False
    - Case 2: Should return the prompt "Please finish filling out the release notes found at: {path}" and
              return False
    - Case 3: Should print nothing and return True
    """
    mocker.patch.object(ReleaseNotesValidator, '__init__', lambda a, b: None)
    mocker.patch.object(StructureValidator, 'scheme_of_file_by_path', return_value='integration')
    mocker.patch.object(UpdateRN, 'get_master_version', return_value='0.0.0')
    validator = get_validator(release_notes, MODIFIED_FILES_INVALID)
    assert validator.are_release_notes_complete() == complete_expected_result


TEST_RELEASE_NOTES_TEST_BANK_ADDED = [
    ('', False),  # Completely Empty
    ('### Integrations\n#### HelloWorld\n- Grammar correction for code '  # Missing Items
     'description.\n\n### Scripts\n#### HelloWorldScript\n- Grammar correction for '
     'code description. ', False),
    (NOT_FILLED_OUT_RN, False),
    (FILLED_OUT_RN, True)

]


@pytest.mark.parametrize('release_notes, complete_expected_result', TEST_RELEASE_NOTES_TEST_BANK_ADDED)
def test_are_release_notes_complete_added(release_notes, complete_expected_result, mocker):
    """
    Given
    - Case 1: Empty release notes.
    - Case 2: Not filled out release notes.
    - Case 3: Valid release notes

    When
    - Running validation on release notes.

    Then
    - Ensure validation correctly identifies valid release notes.
    - Case 1: Should return the prompt "Please complete the release notes found at: {path}" and
              return False
    - Case 2: Should return the prompt "Please finish filling out the release notes found at: {path}" and
              return False
    - Case 3: Should print nothing and return True
    """
    mocker.patch.object(ReleaseNotesValidator, '__init__', lambda a, b: None)
    mocker.patch.object(StructureValidator, 'scheme_of_file_by_path', return_value='integration')
    mocker.patch.object(UpdateRN, 'get_master_version', return_value='0.0.0')
    validator = get_validator(release_notes, MODIFIED_FILES, ADDED_FILES)
    assert validator.are_release_notes_complete() == complete_expected_result


def test_are_release_notes_complete_renamed_file(mocker):
    """
    Given
    -
    When
    - Running validation on release notes.
    Then
    - Ensure validation correctly identifies valid release notes.
    -
    """
    renamed_file = [
        (os.path.join(FILES_PATH, 'CortexXDR', 'Integrations/PaloAltoNetworks_XDR/PaloAltoNetworks_XDR_old.yml'),
         os.path.join(FILES_PATH, 'CortexXDR', 'Integrations/PaloAltoNetworks_XDR/PaloAltoNetworks_XDR.yml'))
    ]
    release_notes = """
    ### Integrations
    #### Palo Alto Networks Cortex XDR - Investigation and Response
    - Test
    """
    mocker.patch.object(ReleaseNotesValidator, '__init__', lambda a, b: None)
    mocker.patch.object(StructureValidator, 'scheme_of_file_by_path', return_value='integration')
    mocker.patch.object(UpdateRN, 'get_master_version', return_value='0.0.0')
    validator = get_validator(release_notes, renamed_file)
    assert validator.are_release_notes_complete()


def test_are_release_notes_complete_file_pack_contained_in_file_name_different_pack(mocker, repo):
    """
    Given:
    - Modified file whose name contains the checked pack name.
        checked pack: CortexXDR.
        modified file pack name: FeedCortexXDR.

    When:
    - Validation CortexXDR pack.

    Then:
    - Ensure validation returns true.
    """
    mocker.patch.object(ReleaseNotesValidator, '__init__', lambda a, b: None)
    mocker.patch.object(StructureValidator, 'scheme_of_file_by_path', return_value='integration')
    mocker.patch.object(UpdateRN, 'get_master_version', return_value='0.0.0')
    pack = repo.create_pack('FeedCortexXDR')
    integration_outside_pack = pack.create_integration(name='FeedCortexXDR')
    integration_outside_pack.create_default_integration('FeedCortexXDR')
    validator = get_validator('', modified_files=[integration_outside_pack.yml.path])
    assert validator.are_release_notes_complete()


def test_are_release_notes_complete_rn_config(pack):
    """
    Given:
    - Release notes config file.

    When:
    - Checking if it should have an entry in RN.

    Then:
    - Ensure it is not checked and release notes return valid response.
    """
    rn = pack.create_release_notes('1_0_1', is_bc=True)
    validator = ReleaseNotesValidator(rn.path, modified_files=[rn.path.replace('md', 'json')],
                                      pack_name=os.path.basename(pack.path))
    assert validator.are_release_notes_complete()


def test_are_release_notes_with_author_image(mocker, repo):
    """
    Given:
    - Added/modified author image.

    When:
    - Validating RN are complete.

    Then:
    - Ensure File is skipped from check.
    """
    mocker.patch.object(ReleaseNotesValidator, '__init__', lambda a, b: None)
    mocker.patch.object(StructureValidator, 'scheme_of_file_by_path', return_value='integration')
    mocker.patch.object(UpdateRN, 'get_master_version', return_value='0.0.0')
    pack = repo.create_pack('CortexXDR')
    integration_outside_pack = pack.create_integration(name='CortexXDR')
    integration_outside_pack.create_default_integration('CortexXDR')
    validator = get_validator('', modified_files=[pack.author_image.path])
    assert validator.are_release_notes_complete()


TEST_RELEASE_NOTES_TEST_BANK_2 = [
    ('', False),  # Completely Empty
    ('### Integrations\n#### HelloWorld\n- Grammar correction for code '  # Missing Items
     'description.\n\n### Scripts\n#### HelloWorldScript\n- Grammar correction for '
     'code description. ', True),
    (NOT_FILLED_OUT_RN, False),
    (FILLED_OUT_RN, True),
    ('<!-- Test -->', False),
    ('<!-- Test --> #### Integrations\n##### Some Integration', True)

]


@pytest.mark.parametrize('release_notes, filled_expected_result', TEST_RELEASE_NOTES_TEST_BANK_2)
def test_has_release_notes_been_filled_out(release_notes, filled_expected_result, mocker):
    """
    Given
    - Case 1: Empty release notes.
    - Case 2: Not filled out release notes.
    - Case 3: Valid release notes
    - Case 4: Release notes with all items excluded.
    - Case 5: Release notes with exclusion, but also with filled out section.

    When
    - Running validation on release notes.

    Then
    - Ensure validation correctly identifies valid release notes.
    - Case 1: Should return the prompt "Please complete the release notes found at: {path}" and
              return True
    - Case 2: Should return the prompt "Please finish filling out the release notes found at: {path}" and
              return False
    - Case 3: Should print nothing and return True
    - Case 4: Should return the prompt "Your release notes file is empty" and return False
    - Case 5: Should print nothing and return True
    """
    mocker.patch.object(ReleaseNotesValidator, '__init__', lambda a, b: None)
    mocker.patch.object(StructureValidator, 'scheme_of_file_by_path', return_value='integration')
    mocker.patch.object(UpdateRN, 'get_master_version', return_value='0.0.0')
    validator = get_validator(release_notes, MODIFIED_FILES)
    assert validator.has_release_notes_been_filled_out() == filled_expected_result


# demisto_sdk/commands/common/tests/reputation_test.py

import pytest

from demisto_sdk.commands.common.hook_validations.reputation import \
    ReputationValidator
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator

data_is_valid_version = [
    (-1, True),
    (0, False),
    (1, False),
]


@pytest.mark.parametrize('version, is_valid', data_is_valid_version)
def test_is_valid_version(version, is_valid):
    structure = StructureValidator("")
    structure.current_file = {"version": version}
    validator = ReputationValidator(structure)
    assert validator.is_valid_version() == is_valid, f'is_valid_version({version}) returns {not is_valid}.'


data_is_valid_expiration = [
    (0, True),
    (500, True),
    (-1, False),
    ("not_valid", False)
]


@pytest.mark.parametrize('expiration, is_valid', data_is_valid_expiration)
def test_is_valid_expiration(expiration, is_valid):
    structure = StructureValidator("")
    structure.current_file = {"fromVersion": "5.5.0", "expiration": expiration}
    validator = ReputationValidator(structure)
    assert validator.is_valid_expiration() == is_valid, f'is_valid_expiration({expiration})' \
                                                        f' returns {not is_valid}.'


data_is_id_equals_details = [
    ("CIDR", "CIDR", True),
    ("CIDR", "CIDR2", False)
]


@pytest.mark.parametrize('id_, details, is_valid', data_is_id_equals_details)
def test_is_id_equals_details(id_, details, is_valid):
    structure = StructureValidator("")
    structure.current_file = {"id": id_, "details": details}
    validator = ReputationValidator(structure)
    assert validator.is_id_equals_details() == is_valid, f'is_id_equals_details({id_}, {details})' \
        f' returns {not is_valid}.'


data_is_valid_id = [
    ("CIDR", True),
    ("host_test", True),
    ("ipv4&ipv6", True),
    ("ipv4 ipv6", True),
    ("ipv4-ipv6", False),
    ("ipv4*ipv6", False),
]


@pytest.mark.parametrize('id_, is_valid', data_is_valid_id)
def test_is_valid_id_field(id_, is_valid):
    structure = StructureValidator("")
    structure.current_file = {"id": id_}
    validator = ReputationValidator(structure)
    assert validator.is_valid_indicator_type_id() == is_valid


data_is_empty_id_and_details = [
    ("CIDR", "CIDR", True),
    ("CIDR", "", False),
    ("", "CIDR", False),
]


@pytest.mark.parametrize('id_, details, is_valid', data_is_empty_id_and_details)
def test_is_id_and_details_empty(id_, details, is_valid):
    structure = StructureValidator("")
    structure.current_file = {"id": id_, "details": details}
    validator = ReputationValidator(structure)
    assert validator.is_required_fields_empty() == is_valid


# demisto_sdk/commands/common/tests/script_test.py

import pytest
from mock import patch

from demisto_sdk.commands.common.hook_validations.script import ScriptValidator
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator
from TestSuite.test_tools import ChangeCWD


def get_validator(current_file=None, old_file=None, file_path=""):
    with patch.object(StructureValidator, '__init__', lambda a, b: None):
        structure = StructureValidator("")
        structure.current_file = current_file
        structure.old_file = old_file
        structure.file_path = file_path
        structure.is_valid = True
        structure.prev_ver = 'master'
        structure.branch_name = ''
        structure.quite_bc = False
        validator = ScriptValidator(structure)
        validator.old_script = old_file
        validator.current_script = current_file
    return validator


class TestScriptValidator:
    BASE_DOCKER_IMAGE = {
        'dockerimage': '1.0.0'
    }

    CHANGED_DOCKER_IMAGE = {
        'dockerimage': 'test_updated'
    }
    NO_DOCKER_IMAGE = {
        'no': 'dockerimage'
    }

    UPDATED_DOCKER_IMAGE = {
        'dockerimage': '1.0.1'
    }

    INPUTS_DOCKER_IMAGES = [
        (BASE_DOCKER_IMAGE, NO_DOCKER_IMAGE, True),
        (BASE_DOCKER_IMAGE, CHANGED_DOCKER_IMAGE, True),
        (BASE_DOCKER_IMAGE, BASE_DOCKER_IMAGE, False),
        (NO_DOCKER_IMAGE, CHANGED_DOCKER_IMAGE, True),
        (BASE_DOCKER_IMAGE, UPDATED_DOCKER_IMAGE, True),
    ]

    SANE_DOC_PATH = 'Scripts/SaneDocReport/SaneDocReport.yml'
    SANE_DOC_SUBTYPE = {
        "type": "python",
        "subtype": "python3"
    }

    SAND_DOC_CHANGED_SUBTYPE = {
        "type": "python",
        "subtype": "python2"
    }
    """
    INPUTS_SANE_DOCS_IMAGES = [
        (SANE_DOC_PATH, SANE_DOC_SUBTYPE, SAND_DOC_CHANGED_SUBTYPE, True, False),
        (SANE_DOC_PATH, BASE_DOCKER_IMAGE, UPDATED_DOCKER_IMAGE, False, True)
    ]

    @pytest.mark.parametrize('path, current_file, old_file, answer_subtype, answer_backwards', INPUTS_SANE_DOCS_IMAGES)
    def test_sane_docs(self, path, current_file, old_file, answer_subtype, answer_baclwards):
        structure = StructureValidator(file_path=path)
        validator = ScriptValidator(structure)
        validator.current_file = current_file
        validator.old_file = old_file

        assert validator.is_changed_subtype() is answer_subtype
        assert validator.is_backward_compatible() is answer_baclwards
    """

    CONTEXT_OLD = {
        'outputs': [
            {
                'contextPath': 'test1'
            },
            {
                'contextPath': 'test2'
            }
        ]
    }

    CONTEXT_NEW = {
        'outputs': [
            {
                'contextPath': 'test1'
            }
        ]
    }

    CONTEXT_CHANGED = {
        'outputs': [
            {
                'contextPath': 'test2'
            }
        ]
    }
    CONTEXT_MULTI_OLD = {
        'outputs': [
            {
                'contextPath': 'test1'
            },
            {
                'contextPath': 'test2'
            }
        ]
    }

    CONTEXT_MULTI_NEW = {
        'outputs': [
            {
                'contextPath': 'test2'
            },
            {
                'contextPath': 'test1'
            }
        ]
    }
    CONTEXT_EMPTY_OUTPUTS = {
        'outputs': None
    }
    INPUTS_CONTEXT_PATHS = [
        (CONTEXT_NEW, CONTEXT_OLD, True),
        (CONTEXT_OLD, CONTEXT_NEW, False),
        (CONTEXT_CHANGED, CONTEXT_OLD, True),
        (CONTEXT_OLD, CONTEXT_CHANGED, False),
        (CONTEXT_MULTI_NEW, CONTEXT_OLD, False),
        (CONTEXT_NEW, CONTEXT_NEW, False),
        (CONTEXT_NEW, CONTEXT_MULTI_NEW, True),
        (CONTEXT_MULTI_NEW, CONTEXT_NEW, False),
        (CONTEXT_EMPTY_OUTPUTS, CONTEXT_EMPTY_OUTPUTS, False)
    ]

    @pytest.mark.parametrize('current_file, old_file, answer', INPUTS_CONTEXT_PATHS)
    def test_deleted_context_path(self, current_file, old_file, answer):
        validator = get_validator(current_file, old_file)
        assert validator.is_context_path_changed() is answer
        validator.structure_validator.quite_bc = True
        assert validator.is_context_path_changed() is False

    OLD_ARGS = {
        'args': [
            {
                'name': 'test1'
            }
        ]
    }
    CURRENT_ARGS = {
        'args': [
            {
                'name': 'test1'
            },
            {
                'name': 'test2'
            }
        ]
    }
    MOVED_ARG = {
        'args': [
            {
                'name': 'test2'
            },
            {
                'name': 'test1'
            }
        ]
    }
    OLD_MULTI_ARGS = {
        'args': [
            {
                'name': 'test1'
            },
            {
                'name': 'test2'
            }
        ]
    }

    CURRENT_MULTI_ARGS = {
        'args': [
            {
                'name': 'test1'
            },
            {
                'name': 'test2'
            }
        ]
    }

    ADDED_MULTI_ARGS = {
        'args': [
            {
                'name': 'test2'
            },
            {
                'name': 'test1'
            },
            {
                'name': 'test3'
            }
        ]
    }
    INPUTS_ARGS_CHANGED = [
        (CURRENT_ARGS, OLD_ARGS, False),
        (MOVED_ARG, OLD_ARGS, False),
        (CURRENT_MULTI_ARGS, OLD_MULTI_ARGS, False),
        (ADDED_MULTI_ARGS, OLD_MULTI_ARGS, False),
        (OLD_MULTI_ARGS, ADDED_MULTI_ARGS, True)
    ]

    @pytest.mark.parametrize('current_file, old_file, answer', INPUTS_ARGS_CHANGED)
    def test_is_arg_changed(self, current_file, old_file, answer):
        validator = get_validator(current_file, old_file)
        assert validator.is_arg_changed() is answer
        validator.structure_validator.quite_bc = True
        assert validator.is_arg_changed() is False

    DUP_1 = {
        'args': [
            {
                'name': 'test1'
            },
            {
                'name': 'test1'
            }
        ]
    }
    NO_DUP = {
        'args': [
            {
                'name': 'test1'
            },
            {
                'name': 'test2'
            }
        ]
    }
    INPUTS_DUPLICATES = [
        (DUP_1, True),
        (NO_DUP, False)
    ]

    @pytest.mark.parametrize('current_file, answer', INPUTS_DUPLICATES)
    def test_is_there_duplicates_args(self, current_file, answer):
        validator = get_validator(current_file)
        assert validator.is_there_duplicates_args() is answer
        validator.structure_validator.quite_bc = True
        assert validator.is_there_duplicates_args() is False

    REQUIRED_ARGS_BASE = {
        'args': [
            {
                'name': 'test',
                'required': False
            }
        ]
    }

    REQUIRED_ARGS_TRUE = {
        'args': [
            {
                'name': 'test',
                'required': True
            }
        ]
    }
    INPUTS_REQUIRED_ARGS = [
        (REQUIRED_ARGS_BASE, REQUIRED_ARGS_BASE, False),
        (REQUIRED_ARGS_TRUE, REQUIRED_ARGS_BASE, True),
        (REQUIRED_ARGS_TRUE, REQUIRED_ARGS_TRUE, False),
        (REQUIRED_ARGS_BASE, REQUIRED_ARGS_TRUE, False)
    ]

    @pytest.mark.parametrize('current_file, old_file, answer', INPUTS_REQUIRED_ARGS)
    def test_is_added_required_args(self, current_file, old_file, answer):
        validator = get_validator(current_file, old_file)
        assert validator.is_added_required_args() is answer
        validator.structure_validator.quite_bc = True
        assert validator.is_added_required_args() is False

    INPUT_CONFIGURATION_1 = {
        'args': [
            {
                'name': 'test',
                'required': False
            },
            {
                'name': 'test1',
                'required': True
            }
        ]
    }
    EXPECTED_CONFIGURATION_1 = {
        'test': False,
        'test1': True
    }
    INPUTS_CONFIGURATION_EXTRACTION = [
        (INPUT_CONFIGURATION_1, EXPECTED_CONFIGURATION_1)
    ]

    @pytest.mark.parametrize('script, expected', INPUTS_CONFIGURATION_EXTRACTION)
    def test_configuration_extraction(self, script, expected):
        assert ScriptValidator._get_arg_to_required_dict(script) == expected, 'Failed to extract configuration'

    PYTHON3_SUBTYPE = {
        "type": "python",
        "subtype": "python3"
    }
    PYTHON2_SUBTYPE = {
        "type": "python",
        "subtype": "python2"
    }

    BLA_BLA_SUBTYPE = {
        "type": "python",
        "subtype": "blabla"
    }
    INPUTS_SUBTYPE_TEST = [
        (PYTHON2_SUBTYPE, PYTHON3_SUBTYPE, True),
        (PYTHON3_SUBTYPE, PYTHON2_SUBTYPE, True),
        (PYTHON3_SUBTYPE, PYTHON3_SUBTYPE, False),
        (PYTHON2_SUBTYPE, PYTHON2_SUBTYPE, False)
    ]

    @pytest.mark.parametrize('current_file, old_file, answer', INPUTS_SUBTYPE_TEST)
    def test_is_changed_subtype_python(self, current_file, old_file, answer):
        validator = get_validator()
        validator.current_file = current_file
        validator.old_file = old_file
        assert validator.is_changed_subtype() is answer
        validator.structure_validator.quite_bc = True
        assert validator.is_changed_subtype() is False

    INPUTS_IS_VALID_SUBTYPE = [
        (BLA_BLA_SUBTYPE, False),
        (PYTHON2_SUBTYPE, True),
        (PYTHON3_SUBTYPE, True)
    ]

    @pytest.mark.parametrize('current_file, answer', INPUTS_IS_VALID_SUBTYPE)
    def test_is_valid_subtype(self, current_file, answer):
        validator = get_validator()
        validator.current_file = current_file
        assert validator.is_valid_subtype() is answer

    V2_VALID = {"name": "scriptnameV2", "id": "scriptnameV2"}
    V2_WRONG_DISPLAY = {"name": "scriptnamev2", "id": "scriptnamev2"}
    V2_NAME_INPUTS = [
        (V2_VALID, True),
        (V2_WRONG_DISPLAY, False),
    ]

    IS_SKIPPING_DOCKER_CHECK = [("Packs/ApiModules", False, True),
                                ("Packs/ApiModules", True, True),
                                ("Packs/Pack1", True, True)]

    @pytest.mark.parametrize("file_path, skip_docker_check, answer", IS_SKIPPING_DOCKER_CHECK)
    def test_is_docker_image_valid(self, file_path, skip_docker_check, answer):
        validator = get_validator()
        validator.file_path = file_path
        validator.skip_docker_check = skip_docker_check
        assert validator.is_docker_image_valid() is answer

    @pytest.mark.parametrize("current, answer", V2_NAME_INPUTS)
    def test_is_valid_name(self, current, answer):
        validator = get_validator()
        validator.current_file = current
        assert validator.is_valid_name() is answer

    @pytest.mark.parametrize("script_type, fromversion, res", [
        ('powershell', None, False),
        ('powershell', '4.5.0', False),
        ('powershell', '5.5.0', True),
        ('powershell', '5.5.1', True),
        ('powershell', '6.0.0', True),
        ('python', '', True),
        ('python', '4.5.0', True),
    ])
    def test_valid_pwsh(self, script_type, fromversion, res):
        current = {
            "type": script_type,
            "fromversion": fromversion,
        }
        validator = get_validator()
        validator.current_file = current
        assert validator.is_valid_pwsh() == res

    def test_valid_script_file_path(self):
        """
        Given
            - A script with valid file path.
        When
            - running is_valid_script_file_path.
        Then
            - a script with a valid file path is valid.
        """

        validator = get_validator()
        validator.file_path = 'Packs/AbuseDB/Scripts/script-AbuseIPDBPopulateIndicators.yml'

        assert validator.is_valid_script_file_path()

        validator.file_path = 'Packs/AWS-EC2/Scripts/AwsEC2GetPublicSGRules/AwsEC2GetPublicSGRules.yml'

        assert validator.is_valid_script_file_path()

    def test_invalid_script_file_path(self, mocker):
        """
        Given
            - A script with invalid file path.
        When
            - running is_valid_script_file_path.
        Then
            - a script with a invalid file path is invalid.
        """

        validator = get_validator()
        validator.file_path = 'Packs/AbuseDB/Scripts/AbuseIPDBPopulateIndicators.yml'
        mocker.patch.object(validator, "handle_error", return_value=True)

        assert not validator.is_valid_script_file_path()

        validator.file_path = 'Packs/AWS-EC2/Scripts/AwsEC2GetPublicSGRules/Aws.yml'
        mocker.patch.object(validator, "handle_error", return_value=True)

        assert not validator.is_valid_script_file_path()

    NO_INCIDENT_INPUT = [
        ({"args": [{"name": "arg1"}]}, True),
        ({"args": [{"name": "incident_arg"}]}, False)
    ]

    @pytest.mark.parametrize("content, answer", NO_INCIDENT_INPUT)
    def test_no_incident_in_core_pack(self, content, answer):
        """
        Given
            - A script with args names.
        When
            - running no_incident_in_core_pack.
        Then
            - validate that args' names do not contain the word incident.
        """

        validator = get_validator(content)
        assert validator.no_incident_in_core_pack() is answer
        assert validator.is_valid is answer

    def test_folder_name_without_separators(self, pack):
        """
        Given
            - An script without separators in folder name.
        When
            - running check_separators_in_folder.
        Then
            - Ensure the validate passes.
        """

        script = pack.create_script('myScr')

        structure_validator = StructureValidator(script.yml.path)
        validator = ScriptValidator(structure_validator)

        assert validator.check_separators_in_folder()

    def test_files_names_without_separators(self, pack):
        """
        Given
            - An script without separators in files names.
        When
            - running check_separators_in_files.
        Then
            - Ensure the validate passes.
        """

        script = pack.create_script('myScr')

        structure_validator = StructureValidator(script.yml.path)
        validator = ScriptValidator(structure_validator)

        assert validator.check_separators_in_files()

    def test_folder_name_with_separators(self, pack):
        """
        Given
            - An script with separators in folder name.
        When
            - running check_separators_in_folder.
        Then
            - Ensure the validate failed.
        """
        with ChangeCWD(pack.repo_path):
            script = pack.create_script('my_Scr')

            structure_validator = StructureValidator(script.yml.path)
            validator = ScriptValidator(structure_validator)

            assert not validator.check_separators_in_folder()

    def test_files_names_with_separators(self, pack):
        """
        Given
            - An script with separators in files names.
        When
            - running check_separators_in_files.
        Then
            - Ensure the validate failed.
        """
        with ChangeCWD(pack.repo_path):
            script = pack.create_script('my_Int')

            structure_validator = StructureValidator(script.yml.path)
            validator = ScriptValidator(structure_validator)

            assert not validator.check_separators_in_files()

    DEPRECATED_VALID = {"deprecated": True, "comment": "Deprecated. Use the XXXX script instead."}
    DEPRECATED_VALID2 = {"deprecated": True, "comment": "Deprecated. Feodo Tracker no longer supports this feed "
                                                        "No available replacement."}
    DEPRECATED_VALID3 = {"deprecated": True, "comment": "Deprecated. The script uses an unsupported scraping "
                                                        "API. Use Proofpoint Protection Server v2 script instead."}
    DEPRECATED_INVALID_DESC = {"deprecated": True, "comment": "Deprecated."}
    DEPRECATED_INVALID_DESC2 = {"deprecated": True, "comment": "Use the ServiceNow script to manage..."}
    DEPRECATED_INVALID_DESC3 = {"deprecated": True, "comment": "Deprecated. The script uses an unsupported scraping"
                                                               " API."}
    DEPRECATED_INPUTS = [
        (DEPRECATED_VALID, True),
        (DEPRECATED_VALID2, True),
        (DEPRECATED_VALID3, True),
        (DEPRECATED_INVALID_DESC, False),
        (DEPRECATED_INVALID_DESC2, False),
        (DEPRECATED_INVALID_DESC3, False)
    ]

    @pytest.mark.parametrize("current, answer", DEPRECATED_INPUTS)
    def test_is_valid_deprecated_script(self, current, answer):
        """
        Given
            1. A deprecated script with a valid description according to 'deprecated regex' (including the replacement
               script name).
            2. A deprecated script with a valid description according to the 'deprecated no replacement regex'.
            3. A deprecated script with a valid description according to 'deprecated regex' (including the replacement
               script name, and the reason for deprecation.).
            4. A deprecated script with an invalid description that isn't according to the 'deprecated regex'
               (doesn't include a replacement script name, or declare there isn't a replacement).
            5. A deprecated script with an invalid description that isn't according to the 'deprecated regex'
               (doesn't start with the phrase: 'Deprecated.').
            6. A deprecated script with an invalid description that isn't according to the 'deprecated regex'
               (Includes the reason for deprecation, but doesn't include a replacement script name,
               or declare there isn't a replacement).
        When
            - running is_valid_as_deprecated.

        Then
            - a script with an invalid description will be errored.
        """
        validator = get_validator(current_file=current)
        assert validator.is_valid_as_deprecated() is answer

    def test_name_contains_the_type(self, pack):
        """
        Given
            - An script with a name that contains the word "script".
        When
            - running name_not_contain_the_type.
        Then
            - Ensure the validate failed.
        """

        script = pack.create_script(yml={"name": "test_script"})

        with ChangeCWD(pack.repo_path):
            structure_validator = StructureValidator(script.yml.path)
            validator = ScriptValidator(structure_validator)

            assert not validator.name_not_contain_the_type()

    def test_name_does_not_contains_the_type(self, pack):
        """
        Given
            - An script with a name that does not contains the "script" string.
        When
            - running name_not_contain_the_type.
        Then
            - Ensure the validate passes.
        """

        script = pack.create_script(yml={"name": "test"})

        structure_validator = StructureValidator(script.yml.path)
        validator = ScriptValidator(structure_validator)
        assert validator.name_not_contain_the_type()

    def test_runas_is_dbtrole(self, pack):
        """
        Given
            - A script with runas = DBotRole.
        When
            - running runas_is_not_dbtrole.
        Then
            - Ensure the validate failed.
        """

        script = pack.create_script(yml={"runas": "DBotRole"})

        with ChangeCWD(pack.repo_path):
            structure_validator = StructureValidator(script.yml.path)
            validator = ScriptValidator(structure_validator)

            assert not validator.runas_is_not_dbtrole()

    def test_runas_is_not_dbtrole(self, pack):
        """
        Given
            - A script without runas.
        When
            - running runas_is_not_dbtrole.
        Then
            - Ensure the validate passes.
        """

        script = pack.create_script(yml={})

        with ChangeCWD(pack.repo_path):
            structure_validator = StructureValidator(script.yml.path)
            validator = ScriptValidator(structure_validator)

            assert validator.runas_is_not_dbtrole()


# demisto_sdk/commands/common/tests/structure_test.py

import json
import os
from os.path import isfile
from shutil import copyfile
from typing import List, Tuple

import pytest
from ruamel.yaml import YAML

from demisto_sdk.commands.common.constants import (
    CODE_FILES_REGEX, PACKAGE_YML_FILE_REGEX,
    PACKS_CLASSIFIER_JSON_5_9_9_REGEX, PACKS_CLASSIFIER_JSON_REGEX,
    PACKS_DASHBOARD_JSON_REGEX, PACKS_INCIDENT_FIELD_JSON_REGEX,
    PACKS_INCIDENT_TYPE_JSON_REGEX, PACKS_INTEGRATION_NON_SPLIT_YML_REGEX,
    PACKS_INTEGRATION_PY_REGEX, PACKS_INTEGRATION_TEST_PY_REGEX,
    PACKS_INTEGRATION_YML_REGEX, PACKS_LAYOUT_JSON_REGEX,
    PACKS_LAYOUTS_CONTAINER_JSON_REGEX, PACKS_MAPPER_JSON_REGEX,
    PACKS_SCRIPT_PY_REGEX, PACKS_SCRIPT_TEST_PLAYBOOK,
    PACKS_SCRIPT_TEST_PY_REGEX, PACKS_SCRIPT_YML_REGEX,
    PACKS_WIDGET_JSON_REGEX, PLAYBOOK_README_REGEX, PLAYBOOK_YML_REGEX,
    TEST_PLAYBOOK_YML_REGEX)
from demisto_sdk.commands.common.hook_validations.base_validator import \
    BaseValidator
from demisto_sdk.commands.common.hook_validations.structure import (
    StructureValidator, checked_type_by_reg)
from demisto_sdk.tests.constants_test import (
    DASHBOARD_TARGET, DIR_LIST, INCIDENT_FIELD_TARGET,
    INDICATORFIELD_EXACT_SCHEME, INDICATORFIELD_EXTRA_FIELDS,
    INDICATORFIELD_MISSING_AND_EXTRA_FIELDS, INDICATORFIELD_MISSING_FIELD,
    INTEGRATION_TARGET, INVALID_DASHBOARD_PATH, INVALID_INTEGRATION_ID_PATH,
    INVALID_INTEGRATION_YML_1, INVALID_INTEGRATION_YML_2,
    INVALID_INTEGRATION_YML_3, INVALID_INTEGRATION_YML_4,
    INVALID_INTEGRATION_YML_5, INVALID_LAYOUT_CONTAINER_PATH,
    INVALID_LAYOUT_PATH, INVALID_PLAYBOOK_ID_PATH, INVALID_PLAYBOOK_PATH,
    INVALID_REPUTATION_FILE, INVALID_WIDGET_PATH, LAYOUT_TARGET,
    LAYOUTS_CONTAINER_TARGET, PLAYBOOK_PACK_TARGET, PLAYBOOK_TARGET,
    VALID_DASHBOARD_PATH, VALID_INTEGRATION_ID_PATH,
    VALID_INTEGRATION_TEST_PATH, VALID_LAYOUT_CONTAINER_PATH,
    VALID_LAYOUT_PATH, VALID_PLAYBOOK_ARCSIGHT_ADD_DOMAIN_PATH,
    VALID_PLAYBOOK_ID_PATH, VALID_REPUTATION_FILE, VALID_TEST_PLAYBOOK_PATH,
    VALID_WIDGET_PATH, WIDGET_TARGET)
from TestSuite.json_based import JSONBased
from TestSuite.pack import Pack
from TestSuite.test_tools import ChangeCWD


class TestStructureValidator:
    INPUTS_TARGETS = [
        LAYOUTS_CONTAINER_TARGET,
        LAYOUT_TARGET,
        DASHBOARD_TARGET,
        WIDGET_TARGET,
        PLAYBOOK_TARGET,
        INTEGRATION_TARGET,
        INCIDENT_FIELD_TARGET,
        PLAYBOOK_PACK_TARGET,
    ]
    CREATED_DIRS = list()  # type: List

    @classmethod
    def setup_class(cls):
        # checking that the files in the test are not exists so they won't overwrites.
        for target in cls.INPUTS_TARGETS:
            if isfile(target) is True:
                pytest.fail(f"{target} File in tests already exists!")
        # Creating directory for tests if they're not exists

        for directory in DIR_LIST:
            if not os.path.exists(directory):
                cls.CREATED_DIRS.append(directory)
                os.makedirs(directory)

    @classmethod
    def teardown_class(cls):
        for target in cls.INPUTS_TARGETS:
            if isfile(target) is True:
                os.remove(target)
        for directory in cls.CREATED_DIRS:
            if os.path.exists(directory):
                os.rmdir(directory)

    SCHEME_VALIDATION_INPUTS = [
        (VALID_TEST_PLAYBOOK_PATH, 'playbook', True, "Found a problem in the scheme although there is no problem"),
        (VALID_PLAYBOOK_ARCSIGHT_ADD_DOMAIN_PATH, 'playbook', True,
         "Found a problem in the scheme although there is no problem"),
        (INVALID_PLAYBOOK_PATH, 'playbook', False, "Found no problem in the scheme although there is a problem"),
    ]

    @pytest.mark.parametrize("path, scheme, answer, error", SCHEME_VALIDATION_INPUTS)
    def test_scheme_validation_playbook(self, path, scheme, answer, error, mocker):
        mocker.patch.object(StructureValidator, 'scheme_of_file_by_path', return_value=scheme)
        validator = StructureValidator(file_path=path)
        assert validator.is_valid_scheme() is answer, error

    SCHEME_VALIDATION_INDICATORFIELDS = [
        (INDICATORFIELD_EXACT_SCHEME, INCIDENT_FIELD_TARGET, True),
        (INDICATORFIELD_EXTRA_FIELDS, INCIDENT_FIELD_TARGET, False),
        (INDICATORFIELD_MISSING_FIELD, INCIDENT_FIELD_TARGET, False),
        (INDICATORFIELD_MISSING_AND_EXTRA_FIELDS, INCIDENT_FIELD_TARGET, False)
    ]

    @pytest.mark.parametrize("path, scheme, answer", SCHEME_VALIDATION_INDICATORFIELDS)
    def test_scheme_validation_indicatorfield(self, path, scheme, answer, mocker):
        validator = StructureValidator(file_path=path, predefined_scheme='incidentfield')
        assert validator.is_valid_scheme() is answer

    SCHEME_VALIDATION_REPUTATION = [
        (VALID_REPUTATION_FILE, True),
        (INVALID_REPUTATION_FILE, False)
    ]

    @pytest.mark.parametrize("path, answer", SCHEME_VALIDATION_REPUTATION)
    def test_scheme_validation_reputation(self, path, answer):
        validator = StructureValidator(file_path=path, predefined_scheme='reputation')
        assert validator.is_valid_scheme() is answer

    INPUTS_VALID_FROM_VERSION_MODIFIED = [
        (VALID_TEST_PLAYBOOK_PATH, INVALID_PLAYBOOK_PATH, False),
        (INVALID_PLAYBOOK_PATH, VALID_PLAYBOOK_ID_PATH, False),
        (INVALID_PLAYBOOK_PATH, INVALID_PLAYBOOK_PATH, True)
    ]

    @pytest.mark.parametrize('path, old_file_path, answer', INPUTS_VALID_FROM_VERSION_MODIFIED)
    def test_fromversion_update_validation_yml_structure(self, path, old_file_path, answer):
        validator = StructureValidator(file_path=path)
        with open(old_file_path) as f:
            validator.old_file = YAML(typ='safe', pure=True).load(f)
            assert validator.is_valid_fromversion_on_modified() is answer

    INPUTS_IS_ID_MODIFIED = [
        (INVALID_PLAYBOOK_PATH, VALID_PLAYBOOK_ID_PATH, True, "Didn't find the id as updated in file"),
        (VALID_PLAYBOOK_ID_PATH, VALID_PLAYBOOK_ID_PATH, False, "Found the ID as changed although it is not")
    ]

    @pytest.mark.parametrize("current_file, old_file, answer, error", INPUTS_IS_ID_MODIFIED)
    def test_is_id_modified(self, current_file, old_file, answer, error):
        validator = StructureValidator(file_path=current_file)
        with open(old_file) as f:
            validator.old_file = YAML(typ='safe', pure=True).load(f)
            assert validator.is_id_modified() is answer, error

    POSITIVE_ERROR = "Didn't find a slash in the ID even though it contains a slash."
    NEGATIVE_ERROR = "found a slash in the ID even though it not contains a slash."
    INPUTS_IS_FILE_WITHOUT_SLASH = [
        (VALID_INTEGRATION_ID_PATH, True, POSITIVE_ERROR),
        (INVALID_INTEGRATION_ID_PATH, False, NEGATIVE_ERROR),
        (VALID_PLAYBOOK_ID_PATH, True, POSITIVE_ERROR),
        (INVALID_PLAYBOOK_ID_PATH, False, NEGATIVE_ERROR)

    ]

    @pytest.mark.parametrize('path, answer, error', INPUTS_IS_FILE_WITHOUT_SLASH)
    def test_integration_file_with_valid_id(self, path, answer, error):
        validator = StructureValidator(file_path=path)
        assert validator.is_file_id_without_slashes() is answer, error

    INPUTS_IS_PATH_VALID = [
        ("Packs/Test/Reports/report-sade.json", True),
        ("Notinregex/report-sade.json", False),
        ("Packs/Test/Integrations/Cymon/Cymon.yml", True),
    ]

    @pytest.mark.parametrize('path, answer', INPUTS_IS_PATH_VALID)
    def test_is_valid_file_path(self, path, answer, mocker):
        mocker.patch.object(StructureValidator, "load_data_from_file", return_value=None)
        structure = StructureValidator(path)
        structure.scheme_name = None
        assert structure.is_valid_file_path() is answer

    INPUTS_IS_VALID_FILE = [
        (VALID_LAYOUT_PATH, LAYOUT_TARGET, True),
        (INVALID_LAYOUT_PATH, LAYOUT_TARGET, False),
        (VALID_LAYOUT_CONTAINER_PATH, LAYOUTS_CONTAINER_TARGET, True),
        (INVALID_LAYOUT_CONTAINER_PATH, LAYOUTS_CONTAINER_TARGET, False),
        (INVALID_WIDGET_PATH, WIDGET_TARGET, False),
        (VALID_WIDGET_PATH, WIDGET_TARGET, True),
        (VALID_DASHBOARD_PATH, DASHBOARD_TARGET, True),
        (INVALID_DASHBOARD_PATH, DASHBOARD_TARGET, False),
        (VALID_TEST_PLAYBOOK_PATH, PLAYBOOK_TARGET, True),
        (VALID_INTEGRATION_TEST_PATH, INTEGRATION_TARGET, True),
        (INVALID_PLAYBOOK_PATH, INTEGRATION_TARGET, False),
    ]  # type: List[Tuple[str, str, bool]]

    @pytest.mark.parametrize('source, target, answer', INPUTS_IS_VALID_FILE)
    def test_is_file_valid(self, source, target, answer, mocker):
        mocker.patch.object(BaseValidator, 'check_file_flags', return_value='')
        try:
            copyfile(source, target)
            structure = StructureValidator(target)
            assert structure.is_valid_file() is answer
        finally:
            os.remove(target)

    pykwalify_error_1 = " - Cannot find required key \'category\'. Path: \'\'.: Path: \'/\'>'"
    expected_error_1 = 'Missing the field "category" in root'
    pykwalify_error_2 = " - Cannot find required key \'id\'. Path: \'/commonfields\'.: Path: \'/\'>'"
    expected_error_2 = 'Missing the field "id" in Path: \'commonfields\''
    pykwalify_error_3 = " - Cannot find required key \'description\'. Path: \'/script/commands/0/arguments/0\'.: " \
                        "Path: \'/\'>'"
    expected_error_3 = 'Missing the field "description" in Path: \'script\'-> \'commands\'-> ' \
                       '\'integrationTest-search-vulnerabilities\'-> \'arguments\'-> \'top-priority\''
    pykwalify_error_4 = " - Cannot find required key \'description\'. Path: \'/script/commands/5/outputs/0\'.: " \
                        "Path: \'/\'>'"
    expected_error_4 = 'Missing the field "description" in Path: ' \
                       '\'script\'-> \'commands\'-> \'integrationTest-get-connectors\'-> ' \
                       '\'outputs\'-> \'integrationTest.ConnectorsList.ID\''
    pykwalify_error_5 = " - Key 'ok' was not defined. Path: '/configuration/0'"
    expected_error_5 = 'The field "ok" in path \'configuration\'-> \'url\' was not defined in the scheme'
    pykwalify_error_6 = "- Enum 'Network Securitys' does not exist. " \
                        "Path: '/category' Enum: ['Analytics & SIEM', 'Utilities', 'Messaging']."
    expected_error_6 = 'The value "Network Securitys" in \'category\' is invalid ' \
                       '- legal values include: \'Analytics & SIEM\', \'Utilities\', \'Messaging\''

    TEST_ERRORS = [
        (INVALID_INTEGRATION_YML_1, 'integration', pykwalify_error_1, expected_error_1),
        (INVALID_INTEGRATION_YML_2, 'integration', pykwalify_error_2, expected_error_2),
        (INVALID_INTEGRATION_YML_3, 'integration', pykwalify_error_3, expected_error_3),
        (INVALID_INTEGRATION_YML_4, 'integration', pykwalify_error_4, expected_error_4),
        (INVALID_INTEGRATION_YML_4, 'integration', pykwalify_error_5, expected_error_5),
        (INVALID_INTEGRATION_YML_4, 'integration', pykwalify_error_6, expected_error_6),
    ]  # type: List[Tuple[str,str,str, str]]

    @pytest.mark.parametrize('path, scheme , error, correct', TEST_ERRORS)
    def test_print_error_line(self, path, scheme, error, correct, mocker):
        mocker.patch.object(StructureValidator, 'scheme_of_file_by_path', return_value=scheme)
        structure = StructureValidator(file_path=path)
        err = structure.parse_error_line(error)
        assert correct in err[0]

    def test_check_for_spaces_in_file_name(self, mocker):
        mocker.patch.object(StructureValidator, "handle_error", return_value='Not-non-string')
        file_with_spaces = "Packs/pack/Classifiers/space in name"
        file_without_spaces = "Packs/pack/Classifiers/no-space-in-name"
        structure = StructureValidator(file_path=file_with_spaces)
        assert structure.check_for_spaces_in_file_name() is False

        structure = StructureValidator(file_path=file_without_spaces)
        assert structure.check_for_spaces_in_file_name() is True

    def test_is_valid_file_extension(self, mocker):
        mocker.patch.object(StructureValidator, "handle_error", return_value='Not-non-string')
        mocker.patch.object(StructureValidator, 'load_data_from_file', return_value="")
        image = "image.png"
        yml_file = "yml_file.yml"
        json_file = "json_file.json"
        md_file = "md_file.md"
        non_valid_file = "not_valid.py"
        no_extension = "no_ext"

        structure = StructureValidator(file_path=image)
        assert structure.is_valid_file_extension()

        structure = StructureValidator(file_path=yml_file)
        assert structure.is_valid_file_extension()

        structure = StructureValidator(file_path=json_file)
        assert structure.is_valid_file_extension()

        structure = StructureValidator(file_path=md_file)
        assert structure.is_valid_file_extension()

        structure = StructureValidator(file_path=non_valid_file)
        assert not structure.is_valid_file_extension()

        structure = StructureValidator(file_path=no_extension)
        assert not structure.is_valid_file_extension()

    def test_is_field_with_open_ended(self, pack: Pack):
        from demisto_sdk.commands.common.hook_validations.incident_field import \
            TypeFields
        field_content = {
            'cliName': 'sanityname',
            'name': 'sanity name',
            'id': 'incident',
            'content': True,
            'type': TypeFields.IncidentFieldTypeMultiSelect.value,
            'openEnded': True
        }
        incident_field: JSONBased = pack.create_incident_field(
            'incident-field-test',
            content=field_content
        )
        structure = StructureValidator(incident_field.path)
        assert structure.is_valid_scheme()

    def test_is_indicator_with_open_ended(self, pack: Pack):
        from demisto_sdk.commands.common.hook_validations.incident_field import \
            TypeFields
        field_content = {
            'cliName': 'sanityname',
            'name': 'sanity name',
            'id': 'incident',
            'content': True,
            'type': TypeFields.IncidentFieldTypeMultiSelect.value,
            'openEnded': True
        }
        incident_field: JSONBased = pack.create_indicator_field(
            'incident-field-test',
            content=field_content
        )
        structure = StructureValidator(incident_field.path)
        assert structure.is_valid_scheme()

    @pytest.mark.parametrize('is_feed', (True, False))
    @pytest.mark.parametrize('missing_field',
                             ('isFeed', 'selectedFeeds', 'isAllFeeds', 'name', 'id', 'fromVersion', 'playbookId'))
    def test_job_missing_field(self, repo, capsys, is_feed: bool, missing_field: str):
        """
        Given
                A Job object in a repo, with one of the required fields missing
        When
                Validating the file
        Then
                Ensure the structure validator raises a suitable error
        """
        pack = repo.create_pack()
        job = pack.create_job(is_feed=is_feed, name='job_name')
        job.remove(missing_field)

        validator = StructureValidator(job.path, is_new_file=True)
        with ChangeCWD(repo.path):
            assert not validator.is_valid_file()
        captured = capsys.readouterr().out
        assert f'Missing the field "{missing_field}" in root' in captured

    def test_invalid_yml(self, capsys):
        """
        Given
                An integration yml file, with duplicate field "display: Fetch indicators"

        When
                Validating the file
        Then
                Ensure the structure validator raises a suitable error
        """
        validator = StructureValidator(file_path=INVALID_INTEGRATION_YML_5,
                                       predefined_scheme='integration')
        exception = f"[ERROR]: {INVALID_INTEGRATION_YML_5}: [ST113] - There is problem with the yml file. The error: while constructing a mapping\n" \
                    f"  in \"{INVALID_INTEGRATION_YML_5}\", line 6, column 3\n" \
                    f"found duplicate key \"display\" with value \"Fetch indicators\" (original value: \"Fetch indicators\")\n" \
                    f"  in \"{INVALID_INTEGRATION_YML_5}\", line 8, column 3\n\n" \
                    f"To suppress this check see:\n" \
                    f"    http://yaml.readthedocs.io/en/latest/api.html#duplicate-keys\n\n" \
                    f"Duplicate keys will become an error in future releases, and are errors\n" \
                    f"by default when using the new API.\n\n"
        assert not validator.is_valid_yml()
        err_msg = capsys.readouterr()
        assert exception in err_msg


class TestGetMatchingRegex:
    INPUTS = [
        ('Packs/XDR/Playbooks/XDR.yml', [PLAYBOOK_YML_REGEX, TEST_PLAYBOOK_YML_REGEX],
         PLAYBOOK_YML_REGEX),
        ('Packs/XDR/NoMatch/XDR.yml', [PLAYBOOK_YML_REGEX, TEST_PLAYBOOK_YML_REGEX], False)
    ]

    @pytest.mark.parametrize("string_to_match, regexes, answer", INPUTS)
    def test_get_matching_regex(self, string_to_match, regexes, answer):
        assert checked_type_by_reg(string_to_match, regexes, True) == answer

    test_packs_regex_params = [
        (['Packs/XDR/Integrations/XDR/XDR.yml', 'Packs/XDR/Scripts/Random/Random.yml'],
         ['Packs/Integrations/XDR/XDR_test.py', 'Packs/Scripts/Random/Random.py'],
         [PACKAGE_YML_FILE_REGEX]),
        (['Packs/XDR/Integrations/XDR/XDR.py'],
         ['Packs/Integrations/XDR/XDR_test.py', 'Packs/Sade/Integrations/XDR/test_yarden.py'],
         [PACKS_INTEGRATION_PY_REGEX]),
        (['Packs/XDR/Integrations/XDR/XDR.yml'], ['Packs/Integrations/XDR/XDR_test.py'], [PACKS_INTEGRATION_YML_REGEX]),
        (['Packs/Sade/Integrations/XDR/XDR_test.py'], ['Packs/Sade/Integrations/yarden.py'],
         [PACKS_INTEGRATION_TEST_PY_REGEX]),

        (['Packs/XDR/Scripts/Random/Random.yml'], ['Packs/Scripts/Random/Random.py'], [PACKS_SCRIPT_YML_REGEX]),
        (['Packs/XDR/Scripts/Random/Random.py'], ['Packs/Scripts/Random/Random_test.py'], [PACKS_SCRIPT_PY_REGEX]),
        (['Packs/XDR/Scripts/Random/Random_test.py'], ['Packs/Sade/Scripts/test_yarden.pt'],
         [PACKS_SCRIPT_TEST_PY_REGEX]),
        (['Packs/XDR/Playbooks/XDR.yml'], ['Packs/Playbooks/XDR/XDR_test.py'], [PLAYBOOK_YML_REGEX]),
        (['Packs/XDR/TestPlaybooks/playbook.yml'], ['Packs/TestPlaybooks/nonpb.xml'], [TEST_PLAYBOOK_YML_REGEX]),
        (['Packs/Sade/Classifiers/classifier-yarden.json'], ['Packs/Sade/Classifiers/classifier-yarden-json.txt'],
         [PACKS_CLASSIFIER_JSON_REGEX]),
        (['Packs/Sade/Classifiers/classifier-test_5_9_9.json'],
         ['Packs/Sade/Classifiers/classifier-test_5_9_9-json.txt'],
         [PACKS_CLASSIFIER_JSON_5_9_9_REGEX]),
        (['Packs/Sade/Classifiers/classifier-mapper-test.json'], ['Packs/Sade/Classifiers/classifier-mapper-test.txt'],
         [PACKS_MAPPER_JSON_REGEX]),
        (
            ['Packs/Sade/Dashboards/yarden.json'], ['Packs/Sade/Dashboards/yarden-json.txt'], [PACKS_DASHBOARD_JSON_REGEX]),
        (['Packs/Sade/IncidentTypes/yarden.json'], ['Packs/Sade/IncidentTypes/yarden-json.txt'],
         [PACKS_INCIDENT_TYPE_JSON_REGEX]),
        (['Packs/Sade/Widgets/yarden.json'], ['Packs/Sade/Widgets/yarden-json.txt'], [PACKS_WIDGET_JSON_REGEX]),
        (['Packs/Sade/Layouts/yarden.json'], ['Packs/Sade/Layouts/yarden_json.yml'], [PACKS_LAYOUT_JSON_REGEX]),
        (['Packs/Sade/Layouts/layoutscontainer-test.json'], ['Packs/Sade/Layouts/yarden_json.yml'],
         [PACKS_LAYOUTS_CONTAINER_JSON_REGEX]),
        (['Packs/Sade/IncidentFields/yarden.json'], ['Packs/Sade/IncidentFields/yarden-json.txt'],
         [PACKS_INCIDENT_FIELD_JSON_REGEX]),
        (
            ['Packs/XDR/Playbooks/playbook-Test.yml', 'Packs/XDR/Playbooks/Test.yml'],
            ['Packs/XDR/Playbooks/playbook-Test_CHANGELOG.md'],
            [PLAYBOOK_YML_REGEX]
        ),
        (
            ['Packs/OpenPhish/Integrations/integration-OpenPhish.yml'],
            ['Packs/OpenPhish/Integrations/OpenPhish/OpenPhish.yml'],
            [PACKS_INTEGRATION_NON_SPLIT_YML_REGEX]
        ),
        (
            ['Packs/OpenPhish/Playbooks/playbook-Foo_README.md'],
            ['Packs/OpenPhish/Playbooks/playbook-Foo_README.yml'],
            [PLAYBOOK_README_REGEX]
        ),
        (
            ['Packs/DeveloperTools/TestPlaybooks/script-CallTableToMarkdown.yml'],
            ['Packs/DeveloperTools/TestPlaybooks/CallTableToMarkdown.yml'],
            [PACKS_SCRIPT_TEST_PLAYBOOK]
        ),
        (
            ['Packs/DeveloperTools/TestPlaybooks/CallTableToMarkdown.yml'],
            ['Packs/DeveloperTools/TestPlaybooks/script-CallTableToMarkdown.yml'],
            [TEST_PLAYBOOK_YML_REGEX]
        ),
        (
            ['Packs/SomeScript/Scripts/ScriptName/ScriptName.ps1',
             'Packs/SomeIntegration/Integrations/IntegrationName/IntegrationName.ps1',
             'Packs/SomeScript/Scripts/ScriptName/ScriptName.py',
             'Packs/SomeIntegration/Integrations/IntegrationName/IntegrationName.py'],
            ['Packs/SomeScript/Scripts/ScriptName/ScriptName.Tests.ps1',
             'Packs/SomeIntegration/Integrations/IntegrationName/IntegrationName.Tests.ps1',
             'Packs/SomeScript/Scripts/ScriptName/ScriptName.yml',
             'Packs/SomeScript/Scripts/ScriptName/NotTheSameScriptName.ps1',
             'Packs/SomeIntegration/Integrations/IntegrationName/NotTheSameIntegrationName.ps1',
             'Packs/SomeScript/Scripts/ScriptName/ScriptName_test.py',
             'Packs/SomeIntegration/Integrations/IntegrationName/IntegrationName_test.py',
             'Packs/SomeScript/Scripts/ScriptName/NotTheSameScriptName.py',
             'Packs/SomeIntegration/Integrations/IntegrationName/NotTheSameIntegrationName.py'
             ],
            CODE_FILES_REGEX
        )
    ]

    @pytest.mark.parametrize('acceptable,non_acceptable,regex', test_packs_regex_params)
    def test_packs_regex(self, acceptable, non_acceptable, regex):
        for test_path in acceptable:
            assert checked_type_by_reg(test_path, compared_regexes=regex)

        for test_path in non_acceptable:
            assert not checked_type_by_reg(test_path, compared_regexes=regex)

    RELEASE_NOTES_CONFIG_SCHEME_INPUTS = [(dict(), True),
                                          ({'breakingChanges': True}, True),
                                          ({'breakingChanges': False}, True),
                                          ({'breakingChanges': True, 'breakingChangesNotes': 'BC'}, True),
                                          ({'breakingChanges': False, 'breakingChangesNotes': 'BC'}, True),
                                          ({'breakingChanges': 'true', 'breakingChangesNotes': 'BC'}, False),
                                          ({'breakingChanges': True, 'breakingChangesNotes': True}, False)]

    @pytest.mark.parametrize('release_notes_config, expected', RELEASE_NOTES_CONFIG_SCHEME_INPUTS)
    def test_release_notes_config_scheme(self, tmpdir, release_notes_config: dict, expected: bool):
        file_path: str = f'{tmpdir}/1_0_1.json'
        with open(file_path, 'w') as f:
            f.write(json.dumps(release_notes_config))
        validator = StructureValidator(file_path=file_path, predefined_scheme='releasenotesconfig')
        assert validator.is_valid_scheme() is expected


# demisto_sdk/commands/common/tests/tools_test.py

import glob
import os
import shutil
from pathlib import Path
from typing import List, Union

import git
import pytest
import requests

from demisto_sdk.commands.common import tools
from demisto_sdk.commands.common.constants import (
    DEFAULT_CONTENT_ITEM_TO_VERSION, INTEGRATIONS_DIR, LAYOUTS_DIR, PACKS_DIR,
    PACKS_PACK_IGNORE_FILE_NAME, PLAYBOOKS_DIR, SCRIPTS_DIR,
    TEST_PLAYBOOKS_DIR, FileType)
from demisto_sdk.commands.common.legacy_git_tools import git_path
from demisto_sdk.commands.common.tools import (
    LOG_COLORS, arg_to_list, compare_context_path_in_yml_and_readme,
    filter_files_by_type, filter_files_on_pack, filter_packagify_changes,
    find_type, get_code_lang, get_current_repo, get_dict_from_file,
    get_entity_id_by_entity_type, get_entity_name_by_entity_type,
    get_file_displayed_name, get_file_version_suffix_if_exists,
    get_files_in_dir, get_ignore_pack_skipped_tests, get_last_release_version,
    get_last_remote_release_version, get_latest_release_notes_text,
    get_pack_metadata, get_relative_path_from_packs_dir,
    get_release_note_entries, get_release_notes_file_path, get_ryaml,
    get_test_playbook_id, get_to_version, has_remote_configured,
    is_origin_content_repo, is_pack_path, is_uuid, retrieve_file_ending,
    run_command_os, server_version_compare, to_kebab_case)
from demisto_sdk.tests.constants_test import (IGNORED_PNG,
                                              INDICATORFIELD_EXTRA_FIELDS,
                                              SOURCE_FORMAT_INTEGRATION_COPY,
                                              VALID_BETA_INTEGRATION_PATH,
                                              VALID_DASHBOARD_PATH,
                                              VALID_GENERIC_DEFINITION_PATH,
                                              VALID_GENERIC_FIELD_PATH,
                                              VALID_GENERIC_MODULE_PATH,
                                              VALID_GENERIC_TYPE_PATH,
                                              VALID_INCIDENT_FIELD_PATH,
                                              VALID_INCIDENT_TYPE_PATH,
                                              VALID_INTEGRATION_TEST_PATH,
                                              VALID_LAYOUT_PATH, VALID_MD,
                                              VALID_PLAYBOOK_ID_PATH,
                                              VALID_REPUTATION_FILE,
                                              VALID_SCRIPT_PATH,
                                              VALID_WIDGET_PATH)
from demisto_sdk.tests.test_files.validate_integration_test_valid_types import (
    LAYOUT, MAPPER, OLD_CLASSIFIER, REPUTATION)
from TestSuite.pack import Pack
from TestSuite.playbook import Playbook
from TestSuite.repo import Repo
from TestSuite.test_tools import ChangeCWD


class TestGenericFunctions:
    PATH_TO_HERE = f'{git_path()}/demisto_sdk/tests/test_files/'
    FILE_PATHS = [
        (os.path.join(PATH_TO_HERE, 'fake_integration.yml'), tools.get_yaml),
        (os.path.join(PATH_TO_HERE, 'fake_json.json'), tools.get_json)
    ]

    @pytest.mark.parametrize('file_path, func', FILE_PATHS)
    def test_get_file(self, file_path, func):
        assert func(file_path)

    @pytest.mark.parametrize('dir_path', ['demisto_sdk', f'{git_path()}/demisto_sdk/tests/test_files'])
    def test_get_yml_paths_in_dir(self, dir_path):
        yml_paths, first_yml_path = tools.get_yml_paths_in_dir(dir_path, error_msg='')
        yml_paths_test = glob.glob(os.path.join(dir_path, '*yml'))
        assert sorted(yml_paths) == sorted(yml_paths_test)
        if yml_paths_test:
            assert first_yml_path == yml_paths_test[0]
        else:
            assert not first_yml_path

    data_test_get_dict_from_file = [
        (VALID_REPUTATION_FILE, True, 'json'),
        (VALID_SCRIPT_PATH, True, 'yml'),
        ('test', True, None),
        (None, True, None),
        ('invalid-path.json', False, None)
    ]

    @pytest.mark.parametrize('path, raises_error, _type', data_test_get_dict_from_file)
    def test_get_dict_from_file(self, path, raises_error, _type):
        output = get_dict_from_file(str(path), raises_error=raises_error)[1]
        assert output == _type, f'get_dict_from_file({path}) returns: {output} instead {_type}'

    data_test_find_type = [
        (VALID_DASHBOARD_PATH, FileType.DASHBOARD),
        (VALID_INCIDENT_FIELD_PATH, FileType.INCIDENT_FIELD),
        (VALID_INCIDENT_TYPE_PATH, FileType.INCIDENT_TYPE),
        (INDICATORFIELD_EXTRA_FIELDS, FileType.INDICATOR_FIELD),
        (VALID_INTEGRATION_TEST_PATH, FileType.INTEGRATION),
        (VALID_LAYOUT_PATH, FileType.LAYOUT),
        (VALID_PLAYBOOK_ID_PATH, FileType.PLAYBOOK),
        (VALID_REPUTATION_FILE, FileType.REPUTATION),
        (VALID_SCRIPT_PATH, FileType.SCRIPT),
        (VALID_WIDGET_PATH, FileType.WIDGET),
        (VALID_GENERIC_TYPE_PATH, FileType.GENERIC_TYPE),
        (VALID_GENERIC_FIELD_PATH, FileType.GENERIC_FIELD),
        (VALID_GENERIC_MODULE_PATH, FileType.GENERIC_MODULE),
        (VALID_GENERIC_DEFINITION_PATH, FileType.GENERIC_DEFINITION),
        (IGNORED_PNG, None),
        ('', None),
        ('Author_image.png', FileType.AUTHOR_IMAGE),
    ]

    @pytest.mark.parametrize('path, _type', data_test_find_type)
    def test_find_type(self, path, _type):
        output = find_type(str(path))
        assert output == _type, f'find_type({path}) returns: {output} instead {_type}'

    def test_find_type_ignore_sub_categories(self):
        output = find_type(VALID_BETA_INTEGRATION_PATH)
        assert output == FileType.BETA_INTEGRATION, \
            f'find_type({VALID_BETA_INTEGRATION_PATH}) returns: {output} instead {FileType.BETA_INTEGRATION}'

        output = find_type(VALID_BETA_INTEGRATION_PATH, ignore_sub_categories=True)
        assert output == FileType.INTEGRATION, \
            f'find_type({VALID_BETA_INTEGRATION_PATH}) returns: {output} instead {FileType.INTEGRATION}'

    def test_find_type_no_file(self):
        """
        Given
        - A non existing file path.

        When
        - Running find_type.

        Then
        - Ensure None is returned
        """
        madeup_path = 'some/path'
        output = find_type(madeup_path)
        assert not output

    test_path_md = [
        VALID_MD
    ]

    @pytest.mark.parametrize('path', test_path_md)
    def test_filter_packagify_changes(self, path):
        modified, added, removed = filter_packagify_changes(modified_files=[], added_files=[], removed_files=[path])
        assert modified == []
        assert added == set()
        assert removed == [VALID_MD]

    test_content_path_on_pack = [
        ('AbuseDB',
         {'Packs/AbuseDB/Integrations/AbuseDB/AbuseDB.py', 'Packs/Another_pack/Integrations/example/example.py'})
    ]

    @pytest.mark.parametrize('pack, file_paths_list', test_content_path_on_pack)
    def test_filter_files_on_pack(self, pack, file_paths_list):
        """
        Given
        - Set of files and pack name.
        When
        - Want to filter the list by specific pack.
        Then:
        - Ensure the set of file paths contains only files located in the given pack.
        """
        files_paths = filter_files_on_pack(pack, file_paths_list)
        assert files_paths == {'Packs/AbuseDB/Integrations/AbuseDB/AbuseDB.py'}

    for_test_filter_files_by_type = [
        ({VALID_INCIDENT_FIELD_PATH, VALID_PLAYBOOK_ID_PATH}, [FileType.PLAYBOOK], {VALID_INCIDENT_FIELD_PATH}),
        ({VALID_INCIDENT_FIELD_PATH, VALID_INCIDENT_TYPE_PATH}, [],
         {VALID_INCIDENT_FIELD_PATH, VALID_INCIDENT_TYPE_PATH}),
        (set(), [FileType.PLAYBOOK], set())
    ]

    @pytest.mark.parametrize('files, types, output', for_test_filter_files_by_type)
    def test_filter_files_by_type(self, files, types, output, mocker):
        """
        Given
        - Sets of content files and file types to skip.
        When
        - Want to filter the lists by file typs.
        Then:
        - Ensure the list returned Whiteout the files to skip.
        """
        mocker.patch('demisto_sdk.commands.common.tools.is_file_path_in_pack', return_value='True')
        files = filter_files_by_type(files, types)

        assert files == output

    @pytest.mark.parametrize('path, output', [('demisto.json', 'json'), ('wow', '')])
    def test_retrieve_file_ending(self, path, output):
        assert retrieve_file_ending(path) == output

    @pytest.mark.parametrize('data, entity, output', [
        ({'script': {'type': 'javascript'}}, INTEGRATIONS_DIR, 'javascript'),
        ({'type': 'javascript'}, SCRIPTS_DIR, 'javascript'),
        ({}, LAYOUTS_DIR, '')
    ])
    def test_get_code_lang(self, data, entity, output):
        assert get_code_lang(data, entity) == output

    def test_camel_to_snake(self):
        snake = tools.camel_to_snake('CamelCase')

        assert snake == 'camel_case'


class TestGetRemoteFile:
    content_repo = 'demisto/content'

    def test_get_remote_file_sanity(self):
        hello_world_yml = tools.get_remote_file(
            'Packs/HelloWorld/Integrations/HelloWorld/HelloWorld.yml',
            github_repo=self.content_repo
        )
        assert hello_world_yml
        assert hello_world_yml['commonfields']['id'] == 'HelloWorld'

    def test_get_remote_file_content_sanity(self):
        hello_world_py = tools.get_remote_file(
            'Packs/HelloWorld/Integrations/HelloWorld/HelloWorld.py',
            return_content=True,
            github_repo=self.content_repo
        )
        assert hello_world_py

    def test_get_remote_file_content(self):
        hello_world_py = tools.get_remote_file(
            'Packs/HelloWorld/Integrations/HelloWorld/HelloWorld.py',
            return_content=True,
            github_repo=self.content_repo
        )
        hello_world_text = hello_world_py.decode()
        assert isinstance(hello_world_py, bytes)
        assert hello_world_py
        assert 'main()' in hello_world_text
        assert hello_world_text.startswith('"""HelloWorld Integration for Cortex XSOAR (aka Demisto)')

    def test_get_remote_file_origin(self):
        hello_world_yml = tools.get_remote_file(
            'Packs/HelloWorld/Integrations/HelloWorld/HelloWorld.yml',
            'master',
            github_repo=self.content_repo
        )
        assert hello_world_yml
        assert hello_world_yml['commonfields']['id'] == 'HelloWorld'

    def test_get_remote_file_tag(self):
        gmail_yml = tools.get_remote_file(
            'Integrations/Gmail/Gmail.yml',
            '19.10.0',
            github_repo=self.content_repo
        )
        assert gmail_yml
        assert gmail_yml['commonfields']['id'] == 'Gmail'

    def test_get_remote_file_origin_tag(self):
        gmail_yml = tools.get_remote_file(
            'Integrations/Gmail/Gmail.yml',
            'origin/19.10.0',
            github_repo=self.content_repo
        )
        assert gmail_yml
        assert gmail_yml['commonfields']['id'] == 'Gmail'

    def test_get_remote_file_invalid(self):
        invalid_yml = tools.get_remote_file(
            'Integrations/File/File.yml',
            '19.10.0',
            github_repo=self.content_repo
        )
        assert not invalid_yml

    def test_get_remote_file_invalid_branch(self):
        invalid_yml = tools.get_remote_file(
            'Integrations/Gmail/Gmail.yml',
            'NoSuchBranch',
            github_repo=self.content_repo
        )
        assert not invalid_yml

    def test_get_remote_file_invalid_origin_branch(self):
        invalid_yml = tools.get_remote_file(
            'Integrations/Gmail/Gmail.yml',
            'origin/NoSuchBranch',
            github_repo=self.content_repo
        )
        assert not invalid_yml

    def test_get_remote_md_file_origin(self):
        hello_world_readme = tools.get_remote_file(
            'Packs/HelloWorld/README.md',
            'master',
            github_repo=self.content_repo
        )
        assert hello_world_readme == {}

    def test_should_file_skip_validation_negative(self):
        should_skip = tools.should_file_skip_validation(
            'Packs/HelloWorld/Integrations/HelloWorld/search_alerts.json'
        )
        assert not should_skip

    SKIPPED_FILE_PATHS = [
        'some_text_file.txt',
        'pack_metadata.json',
        'testdata/file.json',
        'test_data/file.json',
        'data_test/file.json',
        'testcommandsfunctions/file.json',
        'testhelperfunctions/file.json',
        'StixDecodeTest/file.json',
        'TestCommands/file.json',
        'SetGridField_test/file.json',
        'IPNetwork_test/file.json',
        'test-data/file.json'
        'some_file/integration_DESCRIPTION.md'
        'some_file/integration_CHANGELOG.md'
        'some_file/integration_unified.md'
    ]

    @pytest.mark.parametrize("file_path", SKIPPED_FILE_PATHS)
    def test_should_file_skip_validation_positive(self, file_path):
        should_skip = tools.should_file_skip_validation(file_path)
        assert should_skip


class TestGetRemoteFileLocally:
    REPO_NAME = 'example_repo'
    FILE_NAME = 'somefile.json'
    FILE_CONTENT = '{"id": "some_file"}'

    def setup_method(self):
        # create local git repo
        example_repo = git.Repo.init(self.REPO_NAME)
        example_repo.git.checkout('-b', 'origin/master')
        with open(os.path.join(self.REPO_NAME, self.FILE_NAME), 'w+') as somefile:
            somefile.write(self.FILE_CONTENT)
        example_repo.git.add(self.FILE_NAME)
        example_repo.git.config('user.email', 'automatic@example.com')
        example_repo.git.config('user.name', 'AutomaticTest')
        example_repo.git.commit('-m', 'test_commit', '-a')
        example_repo.git.checkout('-b', 'master')

    def test_get_file_from_master_when_in_private_repo(self, mocker):
        mocker.patch.object(tools, 'is_external_repository', return_value=True)

        class Response:
            ok = False

        mocker.patch.object(requests, 'get', return_value=Response)
        mocker.patch.object(os, 'getenv', return_value=False)
        some_file_json = tools.get_remote_file(os.path.join(self.REPO_NAME, self.FILE_NAME),
                                               github_repo=self.REPO_NAME)
        assert some_file_json
        assert some_file_json['id'] == 'some_file'

    def teardown_method(self):
        shutil.rmtree(self.REPO_NAME)


class TestServerVersionCompare:
    V5 = "5.0.0"
    V0 = "0.0.0"
    EQUAL = 0
    LEFT_IS_LATER = 1
    RIGHT_IS_LATER = -1
    INPUTS = [
        (V0, V5, RIGHT_IS_LATER),
        (V5, V0, LEFT_IS_LATER),
        (V5, V5, EQUAL),
        ("4.5.0", "4.5", EQUAL)
    ]

    @pytest.mark.parametrize("left, right, answer", INPUTS)
    def test_server_version_compare(self, left, right, answer):
        assert server_version_compare(left, right) == answer


def test_pascal_case():
    res = tools.pascal_case("PowerShell Remoting")
    assert res == "PowerShellRemoting"
    res = tools.pascal_case("good life")
    assert res == "GoodLife"
    res = tools.pascal_case("good_life-here v2")
    assert res == "GoodLifeHereV2"


def test_capital_case():
    res = tools.capital_case("PowerShell Remoting")
    assert res == "PowerShell Remoting"
    res = tools.capital_case("good life")
    assert res == "Good Life"
    res = tools.capital_case("good_life-here v2")
    assert res == "Good_life-here V2"
    res = tools.capital_case("")
    assert res == ""


class TestPrintColor:
    def test_print_color(self, mocker):
        mocker.patch('builtins.print')

        tools.print_color('test', LOG_COLORS.GREEN)

        print_args = print.call_args[0][0]
        assert print_args == u'{}{}{}'.format(LOG_COLORS.GREEN, 'test', LOG_COLORS.NATIVE)


class TestReleaseVersion:
    def test_get_last_release(self, mocker):
        mocker.patch('demisto_sdk.commands.common.tools.run_command', return_value='1.2.3\n4.5.6\n3.2.1\n20.0.0')

        tag = get_last_release_version()

        assert tag == '20.0.0'


class TestEntityAttributes:
    @pytest.mark.parametrize('data, entity', [({'commonfields': {'id': 1}}, INTEGRATIONS_DIR),
                                              ({'typeId': 1}, LAYOUTS_DIR), ({'id': 1}, PLAYBOOKS_DIR)])
    def test_get_entity_id_by_entity_type(self, data, entity):
        assert get_entity_id_by_entity_type(data, entity) == 1

    @pytest.mark.parametrize('data, entity', [({'typeId': 'wow'}, LAYOUTS_DIR),
                                              ({'name': 'wow'}, LAYOUTS_DIR),
                                              ({'name': 'wow'}, PLAYBOOKS_DIR)])
    def test_get_entity_name_by_entity_type(self, data, entity):
        assert get_entity_name_by_entity_type(data, entity) == 'wow'


class TestGetFilesInDir:
    def test_project_dir_is_file(self):
        project_dir = 'demisto_sdk/commands/download/downloader.py'
        assert get_files_in_dir(project_dir, ['py']) == [project_dir]

    def test_not_recursive(self):
        project_dir = 'demisto_sdk/commands/download'
        files = [f'{project_dir}/__init__.py', f'{project_dir}/downloader.py', f'{project_dir}/README.md']
        assert sorted(get_files_in_dir(project_dir, ['py', 'md'], False)) == sorted(files)

    def test_recursive(self):
        integrations_dir = 'demisto_sdk/commands/download/tests/tests_env/content/Packs/TestPack/Integrations'
        integration_instance_dir = f'{integrations_dir}/TestIntegration'
        files = [f'{integration_instance_dir}/TestIntegration.py',
                 f'{integration_instance_dir}/TestIntegration_testt.py']
        assert sorted(get_files_in_dir(integrations_dir, ['py'])) == sorted(files)

    def test_recursive_pack(self):
        pack_dir = 'demisto_sdk/commands/download/tests/tests_env/content/Packs/TestPack'
        files = [f'{pack_dir}/Integrations/TestIntegration/TestIntegration.py',
                 f'{pack_dir}/Integrations/TestIntegration/TestIntegration_testt.py',
                 f'{pack_dir}/Scripts/TestScript/TestScript.py']
        assert sorted(get_files_in_dir(pack_dir, ['py'])) == sorted(files)


run_command_os_inputs = [
    ('ls', os.getcwd()),
    ('ls', Path(os.getcwd()))
]


@pytest.mark.parametrize('command, cwd', run_command_os_inputs)
def test_run_command_os(command, cwd):
    """Tests a simple command, to check if it works
    """
    stdout, stderr, return_code = run_command_os(
        command,
        cwd=cwd
    )
    assert 0 == return_code
    assert stdout
    assert not stderr


class TestGetFile:
    def test_get_ryaml(self):
        file_data = get_ryaml(SOURCE_FORMAT_INTEGRATION_COPY)
        assert file_data
        assert file_data.get('name') is not None


def test_get_latest_release_notes_text_invalid():
    """
    Given
    - Invalid release notes

    When
    - Running validation on release notes.

    Then
    - Ensure None is returned
    """
    PATH_TO_HERE = f'{git_path()}/demisto_sdk/tests/test_files/'
    file_path = os.path.join(PATH_TO_HERE, 'empty-RN.md')
    assert get_latest_release_notes_text(file_path) is None


def test_get_release_notes_file_path_valid():
    """
    Given
    - Valid release notes path

    When
    - Running validation on release notes.

    Then
    - Ensure valid file path is returned
    """
    filepath = '/SomePack/1_1_1.md'
    assert get_release_notes_file_path(filepath) == filepath


def test_get_release_notes_file_path_invalid():
    """
    Given
    - Invalid release notes path

    When
    - Running validation on release notes.

    Then
    - Ensure None is returned
    """
    filepath = '/SomePack/1_1_1.json'
    assert get_release_notes_file_path(filepath) is None


remote_testbank = [
    ('origin  https://github.com/turbodog/content.git', False),
    ('upstream  https://github.com/demisto/content.git', True)
]


@pytest.mark.parametrize('git_value, response', remote_testbank)
def test_has_remote(mocker, git_value, response):
    """
    While: Testing if the remote upstream contains demisto/content
    Given:
      1. Origin string not containing demisto/content
      2. Upstream string containing demisto/content
    Expects:
      1. Test condition fails
      2. Test condition passes
    :param git_value: Git string from `git remotes -v`
    """
    mocker.patch('demisto_sdk.commands.common.tools.run_command', return_value=git_value)
    test_remote = has_remote_configured()
    assert response == test_remote


origin_testbank = [
    ('origin  https://github.com/turbodog/content.git', False),
    ('origin  https://github.com/demisto/content.git', True)
]


@pytest.mark.parametrize('git_value, response', origin_testbank)
def test_origin_content(mocker, git_value, response):
    """
    While: Testing if the remote origin contains demisto/content
    Given:
      1. Origin string not containing demisto/content
      2. Origin string containing demisto/content
    Expects:
      1. Test condition fails
      2. Test condition passes
    :param git_value: Git string from `git remotes -v`
    """
    mocker.patch('demisto_sdk.commands.common.tools.run_command', return_value=git_value)
    test_remote = is_origin_content_repo()
    assert response == test_remote


def test_get_ignore_pack_tests__no_pack():
    """
    Given
    - Pack that doesn't exist
    When
    - Collecting packs' ignored tests - running `get_ignore_pack_tests()`
    Then:
    - returns an empty set
    """
    nonexistent_pack = 'NonexistentFakeTestPack'
    ignore_test_set = get_ignore_pack_skipped_tests(nonexistent_pack, {nonexistent_pack}, {})
    assert len(ignore_test_set) == 0


def test_get_ignore_pack_tests__no_ignore_pack(tmpdir):
    """
    Given
    - Pack doesn't have .pack-ignore file
    When
    - Collecting packs' ignored tests - running `get_ignore_pack_tests()`
    Then:
    - returns an empty set
    """
    fake_pack_name = 'FakeTestPack'

    # prepare repo
    repo = Repo(tmpdir)
    repo_path = Path(repo.path)
    pack = Pack(repo_path / PACKS_DIR, fake_pack_name, repo)
    pack_ignore_path = os.path.join(pack.path, PACKS_PACK_IGNORE_FILE_NAME)

    # remove .pack-ignore if exists
    if os.path.exists(pack_ignore_path):
        os.remove(pack_ignore_path)

    ignore_test_set = get_ignore_pack_skipped_tests(fake_pack_name, {fake_pack_name}, {})
    assert len(ignore_test_set) == 0


def test_get_ignore_pack_tests__test_not_ignored(tmpdir):
    """
    Given
    - Pack have .pack-ignore file
    - There are no skipped tests in .pack-ignore
    When
    - Collecting packs' ignored tests - running `get_ignore_pack_tests()`
    Then:
    - returns an empty set
    """
    fake_pack_name = 'FakeTestPack'

    # prepare repo
    repo = Repo(tmpdir)
    repo_path = Path(repo.path)
    pack = Pack(repo_path / PACKS_DIR, fake_pack_name, repo)
    pack_ignore_path = os.path.join(pack.path, PACKS_PACK_IGNORE_FILE_NAME)

    # prepare .pack-ignore
    open(pack_ignore_path, 'a').close()

    ignore_test_set = get_ignore_pack_skipped_tests(fake_pack_name, {fake_pack_name}, {})
    assert len(ignore_test_set) == 0


def test_get_ignore_pack_tests__ignore_test(tmpdir, mocker):
    """
    Given
    - Pack have .pack-ignore file
    - There are skipped tests in .pack-ignore
    - Set of modified packs.
    When
    - Collecting packs' ignored tests - running `get_ignore_pack_tests()`
    Then:
    - returns a list with the skipped tests
    """
    fake_pack_name = 'FakeTestPack'
    fake_test_name = 'FakeTestPlaybook'
    expected_id = 'SamplePlaybookTest'

    # prepare repo
    repo = Repo(tmpdir)
    packs_path = Path(repo.path) / PACKS_DIR
    pack = Pack(packs_path, fake_pack_name, repo)
    test_playbook_path = packs_path / fake_pack_name / TEST_PLAYBOOKS_DIR
    test_playbook = Playbook(test_playbook_path, fake_test_name, repo, is_test_playbook=True)
    pack_ignore_path = os.path.join(pack.path, PACKS_PACK_IGNORE_FILE_NAME)

    # prepare .pack-ignore
    with open(pack_ignore_path, 'a') as pack_ignore_f:
        pack_ignore_f.write("[file:TestIntegration.yml]\nignore=IN126\n\n"
                            f"[file:{test_playbook.name}]\nignore=auto-test")

    # prepare mocks
    mocker.patch.object(tools, "get_pack_ignore_file_path", return_value=pack_ignore_path)
    mocker.patch.object(os.path, "join", return_value=str(test_playbook_path / (test_playbook.name + ".yml")))
    mocker.patch.object(tools, "get_test_playbook_id", return_value=('SamplePlaybookTest', 'FakeTestPack'))

    ignore_test_set = get_ignore_pack_skipped_tests(fake_pack_name, {fake_pack_name}, {})
    assert len(ignore_test_set) == 1
    assert expected_id in ignore_test_set


def test_get_ignore_pack_tests__ignore_missing_test(tmpdir, mocker):
    """
    Given
    - Pack have .pack-ignore file
    - There are skipped tests in .pack-ignore
    - The tests are missing from the content pack
    When
    - Collecting packs' ignored tests - running `get_ignore_pack_tests()`
    Then:
    - returns a list with the skipped tests
    """
    fake_pack_name = 'FakeTestPack'
    fake_test_name = 'FakeTestPlaybook.yml'

    # prepare repo
    repo = Repo(tmpdir)
    packs_path = Path(repo.path) / PACKS_DIR
    pack = Pack(packs_path, fake_pack_name, repo)
    test_playbook_path = packs_path / fake_pack_name / TEST_PLAYBOOKS_DIR
    pack_ignore_path = os.path.join(pack.path, PACKS_PACK_IGNORE_FILE_NAME)

    # prepare .pack-ignore
    with open(pack_ignore_path, 'a') as pack_ignore_f:
        pack_ignore_f.write("[file:TestIntegration.yml]\nignore=IN126\n\n"
                            f"[file:{fake_test_name}]\nignore=auto-test")

    # prepare mocks
    mocker.patch.object(tools, "get_pack_ignore_file_path", return_value=pack_ignore_path)
    mocker.patch.object(os.path, "join", return_value=str(test_playbook_path / fake_test_name))
    mocker.patch.object(tools, "get_test_playbook_id", return_value=(None, 'FakeTestPack'))

    ignore_test_set = get_ignore_pack_skipped_tests(fake_pack_name, {fake_pack_name}, {})
    assert len(ignore_test_set) == 0


@pytest.mark.parametrize(argnames="arg, expected_result",
                         argvalues=[["a1,b2,c3", ['a1', 'b2', 'c3']],
                                    ["[\"a1\",\"b2\",\"c3\"]", ["a1", "b2", "c3"]],
                                    [['a1', 'b2', 'c3'], ['a1', 'b2', 'c3']],
                                    ["", []],
                                    [[], []]
                                    ])
def test_arg_to_list(arg: Union[List[str], str], expected_result: List[str]):
    """
    Given
    - String or list of strings.
    Case a: comma-separated string.
    Case b: a string representing a list.
    Case c: python list.
    Case d: empty string.
    Case e: empty list.

    When
    - Convert given string to list of strings, for example at unify.add_contributors_support.

    Then:
    - Ensure a Python list is returned with the relevant values.
    """
    func_result = arg_to_list(arg=arg, separator=",")
    assert func_result == expected_result


V2_VALID = {"display": "integrationname v2", "name": "integrationname v2", "id": "integrationname v2"}
V2_WRONG_DISPLAY = {"display": "integrationname V2", "name": "integrationname v2", "id": "integrationname V2"}
NOT_V2_VIA_DISPLAY_NOR_NAME = {"display": "integrationname", "name": "integrationv2name", "id": "integrationv2name"}
NOT_V2_VIA_DISPLAY = {"display": "integrationname", "name": "integrationname v2", "id": "integrationv2name"}
NOT_V2_VIA_NAME = {"display": "integrationname V2", "name": "integrationname", "id": "integrationv2name"}
V3_VALID = {"display": "integrationname v3", "name": "integrationname v3", "id": "integrationname v3"}
V3_WRONG_DISPLAY = {"display": "integrationname V3", "name": "integrationname v3", "id": "integrationname V3"}
NOT_V3_VIA_DISPLAY_NOR_NAME = {"display": "integrationname", "name": "integrationv3name", "id": "integrationv3name"}
NOT_V3_VIA_DISPLAY = {"display": "integrationname", "name": "integrationname v3", "id": "integrationv3name"}
NOT_V3_VIA_NAME = {"display": "integrationname V3", "name": "integrationname", "id": "integrationv3Gname"}
GET_FILE_VERSION_SUFFIX_IF_EXISTS_NAME_INPUTS = [
    (V2_VALID, '2'),
    (V2_WRONG_DISPLAY, '2'),
    (NOT_V2_VIA_DISPLAY_NOR_NAME, None),
    (NOT_V2_VIA_NAME, None),
    (NOT_V2_VIA_DISPLAY, '2'),
    (V3_VALID, '3'),
    (V3_WRONG_DISPLAY, '3'),
    (NOT_V3_VIA_DISPLAY_NOR_NAME, None),
    (NOT_V3_VIA_NAME, None),
    (NOT_V3_VIA_DISPLAY, '3')
]


@pytest.mark.parametrize("current, answer", GET_FILE_VERSION_SUFFIX_IF_EXISTS_NAME_INPUTS)
def test_get_file_version_suffix_if_exists_via_name(current, answer):
    assert get_file_version_suffix_if_exists(current) is answer


GET_FILE_VERSION_SUFFIX_IF_EXIST_INPUTS = [
    (V2_VALID, '2'),
    (V2_WRONG_DISPLAY, '2'),
    (NOT_V2_VIA_DISPLAY, None),
    (NOT_V2_VIA_NAME, '2'),
    (NOT_V2_VIA_DISPLAY_NOR_NAME, None),
    (V3_VALID, '3'),
    (V3_WRONG_DISPLAY, '3'),
    (NOT_V3_VIA_DISPLAY, None),
    (NOT_V3_VIA_NAME, '3'),
    (NOT_V3_VIA_DISPLAY_NOR_NAME, None),
]


@pytest.mark.parametrize("current, answer", GET_FILE_VERSION_SUFFIX_IF_EXIST_INPUTS)
def test_get_file_version_suffix_if_exists_via_display(current, answer):
    assert get_file_version_suffix_if_exists(current, check_in_display=True) is answer


def test_test_get_file_version_suffix_if_exists_no_name_and_no_display():
    """
    Given:
    - 'current_file': Dict representing YML data of an integration or script.

    When:
    - Invalid dict given, not containing display and name values.

    Then:
    - Ensure None is returned.
    """
    assert get_file_version_suffix_if_exists(dict(), check_in_display=True) is None
    assert get_file_version_suffix_if_exists(dict(), check_in_display=False) is None


def test_get_to_version_with_to_version(repo):
    pack = repo.create_pack('Pack')
    integration = pack.create_integration('INT', yml={'toversion': '4.5.0'})
    with ChangeCWD(repo.path):
        to_ver = get_to_version(integration.yml.path)

        assert to_ver == '4.5.0'


def test_get_to_version_no_to_version(repo):
    pack = repo.create_pack('Pack')
    integration = pack.create_integration('INT', yml={})
    with ChangeCWD(repo.path):
        to_ver = get_to_version(integration.yml.path)

        assert to_ver == DEFAULT_CONTENT_ITEM_TO_VERSION


def test_get_file_displayed_name__integration(repo):
    """
    Given
    - The path to an integration.

    When
    - Running get_file_displayed_name.

    Then:
    - Ensure the returned name is the display field.
    """
    pack = repo.create_pack('MyPack')
    integration = pack.create_integration('MyInt')
    integration.create_default_integration()
    yml_content = integration.yml.read_dict()
    yml_content['display'] = 'MyDisplayName'
    integration.yml.write_dict(yml_content)
    with ChangeCWD(repo.path):
        display_name = get_file_displayed_name(integration.yml.path)
        assert display_name == 'MyDisplayName'


def test_get_file_displayed_name__script(repo):
    """
    Given
    - The path to a script.

    When
    - Running get_file_displayed_name.

    Then:
    - Ensure the returned name is the name field.
    """
    pack = repo.create_pack('MyPack')
    script = pack.create_script('MyScr')
    script.create_default_script()
    yml_content = script.yml.read_dict()
    yml_content['name'] = 'MyDisplayName'
    script.yml.write_dict(yml_content)
    with ChangeCWD(repo.path):
        display_name = get_file_displayed_name(script.yml.path)
        assert display_name == 'MyDisplayName'


def test_get_file_displayed_name__playbook(repo):
    """
    Given
    - The path to a playbook.

    When
    - Running get_file_displayed_name.

    Then:
    - Ensure the returned name is the name field.
    """
    pack = repo.create_pack('MyPack')
    playbook = pack.create_playbook('MyPlay')
    playbook.create_default_playbook()
    yml_content = playbook.yml.read_dict()
    yml_content['name'] = 'MyDisplayName'
    playbook.yml.write_dict(yml_content)
    with ChangeCWD(repo.path):
        display_name = get_file_displayed_name(playbook.yml.path)
        assert display_name == 'MyDisplayName'


def test_get_file_displayed_name__mapper(repo):
    """
    Given
    - The path to a mapper.

    When
    - Running get_file_displayed_name.

    Then:
    - Ensure the returned name is the name field.
    """
    pack = repo.create_pack('MyPack')
    mapper = pack.create_mapper('MyMap', content=MAPPER)
    json_content = mapper.read_json_as_dict()
    json_content['name'] = 'MyDisplayName'
    mapper.write_json(json_content)
    with ChangeCWD(repo.path):
        display_name = get_file_displayed_name(mapper.path)
        assert display_name == 'MyDisplayName'


def test_get_file_displayed_name__old_classifier(repo):
    """
    Given
    - The path to an old classifier.

    When
    - Running get_file_displayed_name.

    Then:
    - Ensure the returned name is the brandName field.
    """
    pack = repo.create_pack('MyPack')
    old_classifier = pack.create_classifier('MyClas', content=OLD_CLASSIFIER)
    json_content = old_classifier.read_json_as_dict()
    json_content['brandName'] = 'MyDisplayName'
    old_classifier.write_json(json_content)
    with ChangeCWD(repo.path):
        display_name = get_file_displayed_name(old_classifier.path)
        assert display_name == 'MyDisplayName'


def test_get_file_displayed_name__layout(repo):
    """
    Given
    - The path to a layout.

    When
    - Running get_file_displayed_name.

    Then:
    - Ensure the returned name is the TypeName field.
    """
    pack = repo.create_pack('MyPack')
    layout = pack.create_layout('MyLay', content=LAYOUT)
    json_content = layout.read_json_as_dict()
    json_content['TypeName'] = 'MyDisplayName'
    layout.write_json(json_content)
    with ChangeCWD(repo.path):
        display_name = get_file_displayed_name(layout.path)
        assert display_name == 'MyDisplayName'


def test_get_file_displayed_name__reputation(repo):
    """
    Given
    - The path to a reputation.

    When
    - Running get_file_displayed_name.

    Then:
    - Ensure the returned name is the id field.
    """
    pack = repo.create_pack('MyPack')
    reputation = pack._create_json_based('MyRep', content=REPUTATION, prefix='reputation')
    json_content = reputation.read_json_as_dict()
    json_content['id'] = 'MyDisplayName'
    reputation.write_json(json_content)
    with ChangeCWD(repo.path):
        display_name = get_file_displayed_name(reputation.path)
        assert display_name == 'MyDisplayName'


def test_get_file_displayed_name__image(repo):
    """
    Given
    - The path to an image.

    When
    - Running get_file_displayed_name.

    Then:
    - Ensure the returned name is the file name.
    """
    pack = repo.create_pack('MyPack')
    integration = pack.create_integration('MyInt')
    integration.create_default_integration()
    with ChangeCWD(repo.path):
        display_name = get_file_displayed_name(integration.image.path)
        assert display_name == os.path.basename(integration.image.rel_path)


def test_get_pack_metadata(repo):
    """
    Given
    - The path to some file in the repo.

    When
    - Running get_pack_metadata.

    Then:
    - Ensure the returned pack metadata of the file's pack.
    """
    metadata_json = {"name": "MyPack", "support": "xsoar", "currentVersion": "1.1.0"}

    pack = repo.create_pack('MyPack')
    pack_metadata = pack.pack_metadata
    pack_metadata.update(metadata_json)

    result = get_pack_metadata(pack.path)

    assert metadata_json == result


def test_get_last_remote_release_version(requests_mock):
    """
    When
    - Get latest release tag from remote pypi api

    Then:
    - Ensure the returned version is as expected
    """
    os.environ['DEMISTO_SDK_SKIP_VERSION_CHECK'] = ''
    os.environ['CI'] = ''
    expected_version = '1.3.8'
    requests_mock.get(r"https://pypi.org/pypi/demisto-sdk/json", json={'info': {'version': expected_version}})
    assert get_last_remote_release_version() == expected_version


IS_PACK_PATH_INPUTS = [('Packs/BitcoinAbuse', True),
                       ('Packs/BitcoinAbuse/Layouts', False),
                       ('Packs/BitcoinAbuse/Classifiers', False),
                       ('Unknown', False)]


@pytest.mark.parametrize('input_path, expected', IS_PACK_PATH_INPUTS)
def test_is_pack_path(input_path: str, expected: bool):
    """
    Given:
        - 'input_path': Path to some file or directory

    When:
        - Checking whether pack is to a pack directory.

    Then:
        - Ensure expected boolean is returned.

    """
    assert is_pack_path(input_path) == expected


@pytest.mark.parametrize('s, is_valid_uuid', [
    ('', False),
    ('ffc9fbb0-1a73-448c-89a8-fe979e0f0c3e', True),
    ('somestring', False)
])
def test_is_uuid(s, is_valid_uuid):
    """
    Given:
        - Case A: Empty string
        - Case B: Valid UUID
        - Case C: Invalid UUID

    When:
        - Checking if the string is a valid UUID

    Then:
        - Case A: False as it is an empty string
        - Case B: True as it is a valid UUID
        - Case C: False as it is a string which is not a valid UUID
    """
    if is_valid_uuid:
        assert is_uuid(s)
    else:
        assert not is_uuid(s)


def test_get_relative_path_from_packs_dir():
    """
    Given:
        - 'input_path': Path to some file or directory

    When:
        - Running get_relative_path_from_packs_dir

    Then:
        - Ensure that:
          - If it is an absolute path to a pack related object - it returns the relative path from Packs dir.
          - If it is a relative path from Packs dir or an unrelated path - return the path unchanged.

    """
    abs_path = '/Users/who/dev/demisto/content/Packs/Accessdata/Integrations/Accessdata/Accessdata.yml'
    rel_path = 'Packs/Accessdata/Integrations/Accessdata/Accessdata.yml'
    unrelated_path = '/Users/who/dev/demisto'

    assert get_relative_path_from_packs_dir(abs_path) == rel_path
    assert get_relative_path_from_packs_dir(rel_path) == rel_path
    assert get_relative_path_from_packs_dir(unrelated_path) == unrelated_path


@pytest.mark.parametrize('version,expected_result', [
    ('1.3.8', ['* Updated the **secrets** command to work on forked branches.']),
    ('1.3', [])
])
def test_get_release_note_entries(version, expected_result):
    """
    Given:
        - Version of the demisto-sdk.

    When:
        - Running get_release_note_entries.

    Then:
        - Ensure that the result as expected.
    """

    assert get_release_note_entries(version) == expected_result


def test_suppress_stdout(capsys):
    """
        Given:
            - Messages to print.

        When:
            - Printing a message inside the suppress_stdout context manager.
            - Printing message after the suppress_stdout context manager is used.
        Then:
            - Ensure that messages are not printed to console while suppress_stdout is enabled.
            - Ensure that messages are printed to console when suppress_stdout is disabled.
    """
    print('You can see this')
    captured = capsys.readouterr()
    assert captured.out == 'You can see this\n'
    with tools.suppress_stdout():
        print('You cannot see this')
        captured = capsys.readouterr()
    assert captured.out == ''
    print('And you can see this again')
    captured = capsys.readouterr()
    assert captured.out == 'And you can see this again\n'


def test_suppress_stdout_exception(capsys):
    """
        Given:
            - Messages to print.

        When:
            - Performing an operation which throws an exception inside the suppress_stdout context manager.
            - Printing something after the suppress_stdout context manager is used.
        Then:
            - Ensure that the context manager do not not effect exception handling.
            - Ensure that messages are printed to console when suppress_stdout is disabled.

    """
    with pytest.raises(Exception) as excinfo:
        with tools.suppress_stdout():
            2 / 0
    assert str(excinfo.value) == 'division by zero'
    print('After error prints are enabled again.')
    captured = capsys.readouterr()
    assert captured.out == 'After error prints are enabled again.\n'


def test_compare_context_path_in_yml_and_readme_non_vs_code_format_valid():
    """
        Given:
            - a yml for an integration.
            - a valid readme in a non-vs code format

        When:
            - running compare_context_path_in_yml_and_readme.

        Then:
            - Ensure that no differences are found.
    """
    yml_dict = {"script": {
        "commands": [
            {
                "name": "servicenow-create-ticket",
                "outputs": [
                    {
                        "contextPath": "ServiceNow.Ticket.ID",
                        "description": "Ticket ID.",
                        "type": "string"
                    },
                    {
                        "contextPath": "ServiceNow.Ticket.OpenedBy",
                        "description": "Ticket opener ID.",
                        "type": "string"
                    }
                ]
            }
        ]
    }}
    readme_content = "### servicenow-create-ticket\n" \
                     "***\n" \
                     "Creates new ServiceNow ticket.\n\n\n" \
                     "#### Base Command\n\n" \
                     "`servicenow-create-ticket`\n" \
                     "#### Input\n\n" \
                     "| **Argument Name** | **Description** | **Required** |\n" \
                     "| --- | --- | --- |\n" \
                     "| short_description | Short description of the ticket. | Optional |\n\n\n" \
                     " #### Context Output\n\n" \
                     "| **Path** | **Type** | **Description** |\n" \
                     "| --- | --- | --- |\n" \
                     "| ServiceNow.Ticket.ID | string | ServiceNow ticket ID. |\n" \
                     "| ServiceNow.Ticket.OpenedBy | string | ServiceNow ticket opener ID. |\n"

    diffs = compare_context_path_in_yml_and_readme(yml_dict, readme_content)
    assert not diffs


def test_compare_context_path_in_yml_and_readme_non_vs_code_format_invalid():
    """
        Given:
            - a yml for an integration.
            - an invalid readme in a non-vs code format

        When:
            - running compare_context_path_in_yml_and_readme.

        Then:
            - Ensure that differences are found.
    """
    yml_dict = {"script": {
        "commands": [
            {
                "name": "servicenow-create-ticket",
                "outputs": [
                    {
                        "contextPath": "ServiceNow.Ticket.ID",
                        "description": "Ticket ID.",
                        "type": "string"
                    },
                    {
                        "contextPath": "ServiceNow.Ticket.OpenedBy",
                        "description": "Ticket opener ID.",
                        "type": "string"
                    }
                ]
            }
        ]
    }}
    readme_content = "### servicenow-create-ticket\n" \
                     "***\n" \
                     "Creates new ServiceNow ticket.\n\n\n" \
                     "#### Base Command\n\n" \
                     "`servicenow-create-ticket`\n" \
                     "#### Input\n\n" \
                     "| **Argument Name** | **Description** | **Required** |\n" \
                     "| --- | --- | --- |\n" \
                     "| short_description | Short description of the ticket. | Optional |\n\n\n" \
                     " #### Context Output\n\n" \
                     "| **Path** | **Type** | **Description** |\n" \
                     "| --- | --- | --- |\n" \
                     "| ServiceNow.Ticket.ID | string | ServiceNow ticket ID. |\n"

    diffs = compare_context_path_in_yml_and_readme(yml_dict, readme_content)
    assert 'ServiceNow.Ticket.OpenedBy' in diffs.get('servicenow-create-ticket').get('only in yml')


def test_compare_context_path_in_yml_and_readme_vs_code_format_valid():
    """
        Given:
            - a yml for an integration.
            - a valid readme in a vs code format

        When:
            - running compare_context_path_in_yml_and_readme.

        Then:
            - Ensure that no differences are found.
    """
    yml_dict = {"script": {
        "commands": [
            {
                "name": "servicenow-create-ticket",
                "outputs": [
                    {
                        "contextPath": "ServiceNow.Ticket.ID",
                        "description": "Ticket ID.",
                        "type": "string"
                    },
                    {
                        "contextPath": "ServiceNow.Ticket.OpenedBy",
                        "description": "Ticket opener ID.",
                        "type": "string"
                    }
                ]
            }
        ]
    }}
    readme_content = "### servicenow-create-ticket\n" \
                     "***\n" \
                     "Creates new ServiceNow ticket.\n\n\n" \
                     "#### Base Command\n\n" \
                     "`servicenow-create-ticket`\n" \
                     "#### Input\n\n" \
                     "| **Argument Name** | **Description**                  | **Required** |\n" \
                     "| ----------------- | -------------------------------- | ------------ |\n" \
                     "| short_description | Short description of the ticket. | Optional     |\n\n\n" \
                     " #### Context Output\n\n" \
                     "| **Path**                   | **Type** | **Description**              |\n" \
                     "| -------------------------- | -------- | ---------------------------- |\n" \
                     "| ServiceNow.Ticket.ID       | string   | ServiceNow ticket ID.        |\n" \
                     "| ServiceNow.Ticket.OpenedBy | string   | ServiceNow ticket opener ID. |\n"

    diffs = compare_context_path_in_yml_and_readme(yml_dict, readme_content)
    assert not diffs


def test_compare_context_path_in_yml_and_readme_vs_code_format_invalid():
    """
        Given:
            - a yml for an integration.
            - an invalid readme in a vs code format

        When:
            - running compare_context_path_in_yml_and_readme.

        Then:
            - Ensure that differences are found.
    """
    yml_dict = {"script": {
        "commands": [
            {
                "name": "servicenow-create-ticket",
                "outputs": [
                    {
                        "contextPath": "ServiceNow.Ticket.ID",
                        "description": "Ticket ID.",
                        "type": "string"
                    },
                    {
                        "contextPath": "ServiceNow.Ticket.OpenedBy",
                        "description": "Ticket opener ID.",
                        "type": "string"
                    }
                ]
            }
        ]
    }}
    readme_content = "### servicenow-create-ticket\n" \
                     "***\n" \
                     "Creates new ServiceNow ticket.\n\n\n" \
                     "#### Base Command\n\n" \
                     "`servicenow-create-ticket`\n" \
                     "#### Input\n\n" \
                     "| **Argument Name** | **Description**                  | **Required** |\n" \
                     "| ----------------- | -------------------------------- | ------------ |\n" \
                     "| short_description | Short description of the ticket. | Optional     |\n\n\n" \
                     " #### Context Output\n\n" \
                     "| **Path**             | **Type** | **Description**       |\n" \
                     "| -------------------- | -------- | --------------------- |\n" \
                     "| ServiceNow.Ticket.ID | string   | ServiceNow ticket ID. |\n"

    diffs = compare_context_path_in_yml_and_readme(yml_dict, readme_content)
    assert 'ServiceNow.Ticket.OpenedBy' in diffs.get('servicenow-create-ticket').get('only in yml')


def test_get_definition_name():
    """
    Given
    - The path to a generic field/generic type file.

    When
    - the file has a connected generic definition

    Then:
    - Ensure the returned name is the connected definitions name.
    """

    pack_path = f'{git_path()}/demisto_sdk/tests/test_files/generic_testing'
    field_path = pack_path + "/GenericFields/Object/genericfield-Sample.json"
    type_path = pack_path + "/GenericTypes/Object/generictype-Sample.json"

    assert tools.get_definition_name(field_path, pack_path) == 'Object'
    assert tools.get_definition_name(type_path, pack_path) == 'Object'


def test_gitlab_ci_yml_load():
    """
        Given:
            - a yml file with gitlab ci data

        When:
            - trying to load it to the sdk  - like via find_type

        Then:
            - Ensure that the load does not fail.
            - Ensure the file has no identification
    """
    test_file = f'{git_path()}/demisto_sdk/tests/test_files/gitlab_ci_test_file.yml'
    try:
        res = find_type(test_file)
    except Exception:
        # if we got here an error has occurred when trying to load the file
        assert False

    assert res is None


IRON_BANK_CASES = [
    ({'tags': []}, False),  # case no tags
    ({'tags': ['iron bank']}, False),  # case some other tags than "Iron Bank"
    ({'tags': ['Iron Bank', 'other_tag']}, True),  # case Iron Bank tag exist
    ({}, False)  # case no tags
]


@pytest.mark.parametrize('metadata, expected', IRON_BANK_CASES)
def test_is_iron_bank_pack(mocker, metadata, expected):
    mocker.patch.object(tools, 'get_pack_metadata', return_value=metadata)
    res = tools.is_iron_bank_pack('example_path')
    assert res == expected


def test_get_test_playbook_id():
    """
    Given:
        - A list of test playbooks from id_set
        - Test playbook file name

    When:
        - trying to get the pack and name of the test playbook - via running get_test_playbook_id command

    Then:
        - Ensure that the currect pack name returned.
        - Ensure that the currect test name returned.

    """
    test_playbook_id_set = [
        {
            "HelloWorld-Test": {
                "name": "HelloWorld-Test",
                "file_path": "Packs/HelloWorld/TestPlaybooks/playbook-HelloWorld-Test.yml",
                "fromversion": "5.0.0",
                "implementing_scripts": [
                    "HelloWorldScript",
                    "DeleteContext",
                    "FetchFromInstance"
                ],
                "command_to_integration": {
                    "helloworld-say-hello": "",
                    "helloworld-search-alerts": ""
                },
                "pack": "HelloWorld"
            }
        },
        {
            "HighlightWords_Test": {
                "name": "HighlightWords - Test",
                "file_path": "Packs/CommonScripts/TestPlaybooks/playbook-HighlightWords_-_Test.yml",
                "implementing_scripts": [
                    "VerifyHumanReadableContains",
                    "HighlightWords"
                ],
                "pack": "CommonScripts"
            }
        },
        {
            "HTTPListRedirects - Test SSL": {
                "name": "HTTPListRedirects - Test SSL",
                "file_path": "Packs/CommonScripts/TestPlaybooks/playbook-HTTPListRedirects_-_Test_SSL.yml",
                "implementing_scripts": [
                    "PrintErrorEntry",
                    "HTTPListRedirects",
                    "DeleteContext"
                ],
                "pack": "CommonScripts"
            }
        }]

    test_name = 'playbook-HelloWorld-Test.yml'
    test_playbook_name, test_playbook_pack = get_test_playbook_id(test_playbook_id_set, test_name)
    assert test_playbook_name == 'HelloWorld-Test'
    assert test_playbook_pack == 'HelloWorld'


@pytest.mark.parametrize(
    'url, expected_name',
    [
        ('ssh://git@github.com/demisto/content-dist.git', ('github.com', 'demisto', 'content-dist')),
        ('git@github.com:demisto/content-dist.git', ('github.com', 'demisto', 'content-dist')),
        ('https://github.com/demisto/content-dist.git', ('github.com', 'demisto', 'content-dist')),
        ('https://github.com/demisto/content-dist', ('github.com', 'demisto', 'content-dist')),
        ('https://code.pan.run/xsoar/content-dist', ('code.pan.run', 'xsoar', 'content-dist')),  # gitlab
        ('https://code.pan.run/xsoar/content-dist.git', ('code.pan.run', 'xsoar', 'content-dist')),
        ('https://gitlab-ci-token:token@code.pan.run/xsoar/content-dist.git', ('code.pan.run', 'xsoar', 'content-dist'))
    ]
)
def test_get_current_repo(mocker, url, expected_name):
    import giturlparse
    mocker.patch.object(giturlparse, 'parse', return_value=giturlparse.parse(url))
    name = get_current_repo()
    assert name == expected_name


KEBAB_CASES = [('Scan File', 'scan-file'),
               ('Scan File-', 'scan-file'),
               ('Scan.File', 'scan-file'),
               ('*scan,file', 'scan-file'),
               ('Scan     File', 'scan-file'),
               ('Scan - File', 'scan-file'),
               ('Scan-File', 'scan-file'),
               ('Scan- File', 'scan-file'),
               ('Scan -File', 'scan-file')]


@pytest.mark.parametrize('input_str, output_str', KEBAB_CASES)
def test_to_kebab_case(input_str, output_str):
    assert to_kebab_case(input_str) == output_str


# demisto_sdk/commands/common/tests/update_id_set_test.py

import json
import logging
import os
import sys
import tempfile
import unittest
from pathlib import Path

import pytest

import demisto_sdk.commands.common.update_id_set as uis
from demisto_sdk.commands.common.constants import (DEFAULT_JOB_FROM_VERSION,
                                                   JOBS_DIR, FileType)
from demisto_sdk.commands.common.legacy_git_tools import git_path
from demisto_sdk.commands.common.update_id_set import (
    find_duplicates, get_classifier_data, get_dashboard_data,
    get_fields_by_script_argument,
    get_filters_and_transformers_from_complex_value,
    get_filters_and_transformers_from_playbook, get_general_data,
    get_generic_field_data, get_generic_module_data, get_generic_type_data,
    get_incident_fields_by_playbook_input, get_incident_type_data,
    get_indicator_type_data, get_layout_data, get_layoutscontainer_data,
    get_mapper_data, get_pack_metadata_data, get_playbook_data,
    get_report_data, get_script_data, get_values_for_keys_recursively,
    get_widget_data, has_duplicate, merge_id_sets, process_general_items,
    process_incident_fields, process_integration, process_jobs, process_script,
    re_create_id_set, should_skip_item_by_mp)
from TestSuite.utils import IsEqualFunctions

TESTS_DIR = f'{git_path()}/demisto_sdk/tests'


class TestPacksMetadata:
    METADATA_WITH_XSOAR_SUPPORT = {
        'name': 'Pack1',
        'support': 'xsoar',
        'currentVersion': '1.0.0',
        'author': 'Cortex XSOAR',
        'tags': ['Alerts'],
        'useCases': ['Case Management'],
        'categories': ['Endpoint'],
        'marketplaces': ['xsoar', 'marketplacev2'],
    }

    METADATA_WITH_PARTNER_SUPPORT = {
        'name': 'Pack1',
        'support': 'partner',
        'currentVersion': '1.0.0',
        'author': 'Some Partner',
        'tags': ['Alerts'],
        'useCases': ['Case Management'],
        'categories': ['Endpoint'],
        'marketplaces': ['xsoar', 'marketplacev2'],
    }

    METADATA_WITH_COMMUNITY_SUPPORT = {
        'name': 'Pack1',
        'support': 'community',
        'currentVersion': '1.0.0',
        'author': 'Someone',
        'tags': ['Alerts'],
        'useCases': ['Case Management'],
        'categories': ['Endpoint'],
        'marketplaces': ['xsoar', 'marketplacev2'],
    }

    TEST_PACK = [
        (METADATA_WITH_XSOAR_SUPPORT, 'Cortex XSOAR', 'certified'),
        (METADATA_WITH_PARTNER_SUPPORT, 'Some Partner', 'certified'),
        (METADATA_WITH_COMMUNITY_SUPPORT, 'Someone', ''),
    ]

    @staticmethod
    @pytest.mark.parametrize('metadata_file_content, author, certification', TEST_PACK)
    def test_process_metadata(mocker, repo, metadata_file_content, author, certification):
        """
        Given
            - A pack_metadata file for Pack1
        When
            - parsing pack metadata files
        Then
            - parsing all the data from file successfully
        """
        mocker.patch.object(uis, 'get_pack_name', return_value='Pack1')
        mocker.patch.object(uis, 'get_mp_types_from_metadata_by_item', return_value=['xsoar', 'marketplacev2'])

        pack = repo.create_pack("Pack1")
        pack.pack_metadata.write_json(metadata_file_content)

        res = get_pack_metadata_data(pack.pack_metadata.path, print_logs=False)
        result = res.get('Pack1')

        assert 'name' in result.keys()
        assert result.get('name') == 'Pack1'
        assert result.get('current_version') == '1.0.0'
        assert result.get('author') == author
        assert result.get('certification') == certification
        assert result.get('tags') == ['Alerts']
        assert result.get('use_cases') == ['Case Management']
        assert result.get('categories') == ['Endpoint']
        assert result.get('marketplaces') == ['xsoar', 'marketplacev2']

    @staticmethod
    def test_process_metadata__marketplace_mismatch(mocker):
        """
        Given
            - Pack metadata

        When
            - parsing pack files when there is a mismatch between the item's marketplaces and the current run marketplace.

        Then
            - return empty dict
        """
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=True)
        res = get_pack_metadata_data('', print_logs=False)
        assert res == {}

    @staticmethod
    @pytest.mark.parametrize('print_logs', [True, False])
    def test_process_packs_success(mocker, capsys, repo, print_logs):
        """
        Given
            - A pack metadata file path.
            - Whether to print information to log.
        When
            - Parsing pack metadata files.
        Then
            - Verify output to logs.
        """
        mocker.patch.object(uis, 'get_pack_name', return_value='Pack1')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        pack = repo.create_pack("Pack1")
        pack.pack_metadata.write_json({
            'name': 'Pack',
            'currentVersion': '1.0.0',
            'author': 'Cortex XSOAR',
            'support': 'xsoar',
            'tags': ['Alerts'],
            'useCases': ['Case Management'],
            'categories': ['Endpoint'],
            'marketplaces': ['xsoar', 'marketplacev2']
        })
        pack_metadata_path = pack.pack_metadata.path
        res = get_pack_metadata_data(pack_metadata_path, print_logs)

        captured = capsys.readouterr()

        assert res['Pack1']['name'] == 'Pack'
        assert res['Pack1']['current_version'] == '1.0.0'
        assert res['Pack1']['author'] == 'Cortex XSOAR'
        assert res['Pack1']['tags'] == ['Alerts']
        assert res['Pack1']['use_cases'] == ['Case Management']
        assert res['Pack1']['categories'] == ['Endpoint']
        assert res['Pack1']['certification'] == 'certified'
        assert res['Pack1']['marketplaces'] == ['xsoar', 'marketplacev2']

        assert (f'adding {pack_metadata_path} to id_set' in captured.out) == print_logs

    @staticmethod
    def test_process_packs_exception_thrown(capsys, mocker):
        """
        Given
            - A pack metadata file path.
        When
            - Parsing pack metadata files.
        Then
            - Handle the exceptions gracefully.
        """
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)
        with pytest.raises(FileNotFoundError):
            get_pack_metadata_data('Pack_Path', True)
        captured = capsys.readouterr()

        assert 'Failed to process Pack_Path, Error:' in captured.out


class TestDuplicates:
    MOCKED_DATA = [
        (
            [
                {
                    "BluecatAddressManager": {
                        "name": "BluecatAddressManager",
                        "file_path": "Integrations/BluecatAddressManager/BluecatAddressManager.yml",
                        "fromversion": "5.0.0"
                    }
                },
                {
                    "BluecatAddressManager": {
                        "name": "BluecatAddressManager",
                        "file_path": "Integrations/BluecatAddressManager/BluecatAddressManager.yml",
                        "fromversion": "5.0.0"
                    }
                }
            ], 'BluecatAddressManager', True
        ),
        (
            [
                {
                    "BluecatAddressManager": {
                        "name": "BluecatAddressManager",
                        "file_path": "Integrations/BluecatAddressManager/BluecatAddressManager.yml",
                        "fromversion": "5.0.0"
                    }
                },
                {
                    "BluecatAddressManager": {
                        "name": "BluecatAddressManager",
                        "file_path": "Integrations/BluecatAddressManager/BluecatAddressManager.yml",
                        "fromversion": "3.1.0",
                        "toversion": "4.0.0"
                    }
                }
            ], 'BluecatAddressManager', False
        ),
        (
            [
                {
                    'Test3': {
                        'name': 'Test3',
                        'file_path': 'A',
                        'fromversion': '3.0.0',
                        'toversion': '3.6.0',
                    }
                },
                {
                    'Test3': {
                        'name': 'Test3',
                        'file_path': 'B',
                        'fromversion': '3.5.0',
                        'toversion': '4.5.0',
                    }
                },
                {
                    'Test3': {
                        'name': 'Test3',
                        'file_path': 'C',
                        'fromversion': '3.5.2',
                        'toversion': '3.5.4',
                    }
                },
                {
                    'Test3': {
                        'name': 'Test3',
                        'file_path': 'D',
                        'fromversion': '4.5.0',
                    },
                },
            ], 'Test3', True
        ),
    ]

    @staticmethod
    @pytest.mark.parametrize('first_pack, second_pack, first_source, second_source, expected', [
        ('pack1', 'pack2', ('github.com', 'demisto', 'repo1'), ('github.com', 'demisto', 'repo2'), True),
        ('pack1', 'pack2', ('github.com', 'demisto', 'repo1'), ('github.com', 'demisto', 'repo1'), True),
        ('pack1', 'pack1', ('github.com', 'demisto', 'repo1'), ('github.com', 'demisto', 'repo2'), True),
        ('pack1', 'pack1', ('github.com', 'demisto', 'repo1'), ('github.com', 'demisto', 'repo1'), False),
        ('pack1', 'pack1', ('github.com', 'demisto', 'repo1'), ('code.pan.run', 'xsoar', 'repo1'), False),
        ('pack1', 'pack1', ('github.com', 'demisto', 'repo1'), ('code.pan.run', 'xsoar', 'repo2'), True)

    ])
    def test_has_duplicate(first_pack, second_pack, first_source, second_source, expected):
        """
        Given
            - id_set.json with two duplicate layouts of the same type (details), their versions also overrides.
            They are considered duplicates because they have the same name (typeID), their versions override, and they
            are the same kind (details).
            If the pack and source is the same - there are duplicates, otherwise they are not

        When
            - checking for duplicate

        Then
            - Ensure duplicates found depending on the pack and source
        """
        id_set = {
            'Layouts': []
        }
        id_set['Layouts'].append({
            'urlRep': {
                'typeID': 'urlRep',
                'fromVersion': '5.0.0',
                'kind': 'Details',
                'path': 'Layouts/layout-details-urlrep.json',
                'pack': first_pack,
                'source': first_source
            }
        })

        id_set['Layouts'].append({
            'urlRep': {
                'typeID': 'urlRep',
                'kind': 'Details',
                'path': 'Layouts/layout-details-urlrep2.json',
                'pack': second_pack,
                'source': second_source
            }
        })

        # Check for duplicates for existing id-sets,
        # Will not be considered as duplicate when two entities come from the same source
        has_duplicates = has_duplicate(id_set['Layouts'], 'urlRep', 'Layouts', False, is_create_new=False)
        assert has_duplicates == expected

        # Check for duplicates for a new id-set,
        # In this case all the examples above should be considered as duplicates
        assert has_duplicate(id_set['Layouts'], 'urlRep', 'Layouts', False, is_create_new=True),\
            "if it's a new pack it is always a duplicate"

    @staticmethod
    def test_has_no_duplicate():
        """
        Given
            - id_set.json with two non duplicate layouts. They have different kind

        When
            - checking for duplicate

        Then
            - Ensure duplicates not found
        """
        id_set = {
            'Layouts': []
        }
        id_set['Layouts'].append({
            'urlRep': {
                'typeID': 'urlRep',
                'kind': 'Details',
                'path': 'Layouts/layout-details-urlrep.json'
            }
        })

        id_set['Layouts'].append({
            'urlRep': {
                'typeID': 'urlRep',
                'kind': 'edit',
                'path': 'Layouts/layout-edit-urlrep.json'
            }
        })

        has_duplicates = has_duplicate(id_set['Layouts'], 'urlRep', 'Layouts', False)
        assert has_duplicates is False


class TestIntegrations:
    INTEGRATION_DATA = {
        "Dummy Integration": {
            "name": "Dummy Integration",
            "file_path": TESTS_DIR + "/test_files/DummyPack/Integrations/DummyIntegration/DummyIntegration.yml",
            "fromversion": "4.1.0",
            "source": ['github.com', 'demisto', 'demisto-sdk'],
            "docker_image": "demisto/python3:3.7.4.977",
            "commands": ['xdr-get-incidents',
                         'xdr-get-incident-extra-data',
                         'xdr-update-incident',
                         'xdr-insert-parsed-alert',
                         'xdr-insert-cef-alerts',
                         'xdr-isolate-endpoint',
                         'xdr-unisolate-endpoint',
                         'xdr-get-endpoints',
                         'xdr-get-distribution-versions',
                         'xdr-create-distribution',
                         'xdr-get-distribution-url',
                         'xdr-get-create-distribution-status',
                         'xdr-get-audit-management-logs',
                         'xdr-get-audit-agent-reports'],
            "api_modules": "HTTPFeedApiModule",
            "classifiers": "dummy-classifier",
            "incident_types": "dummy-incident-type",
            "indicator_fields": "CommonTypes",
            "indicator_types": "CommonTypes",
            "mappers": [
                "dummy-mapper-in",
                "dummy-mapper-out"
            ]
        }
    }

    UNIFIED_INTEGRATION_DATA = {
        "Dummy Integration": {
            "name": "Dummy Integration",
            "file_path": TESTS_DIR + "/test_files/DummyPack/Integrations/integration-DummyIntegration.yml",
            "fromversion": "4.1.0",
            "source": ['github.com', 'demisto', 'demisto-sdk'],
            "commands": ['xdr-get-incidents',
                         'xdr-get-incident-extra-data',
                         'xdr-update-incident',
                         'xdr-insert-parsed-alert',
                         'xdr-insert-cef-alerts',
                         'xdr-isolate-endpoint',
                         'xdr-unisolate-endpoint',
                         'xdr-get-endpoints',
                         'xdr-get-distribution-versions',
                         'xdr-create-distribution',
                         'xdr-get-distribution-url',
                         'xdr-get-create-distribution-status',
                         'xdr-get-audit-management-logs',
                         'xdr-get-audit-agent-reports'],
            "api_modules": "HTTPFeedApiModule",
            "classifiers": "dummy-classifier",
            "incident_types": "dummy-incident-type",
            "indicator_fields": "CommonTypes",
            "indicator_types": "CommonTypes",
            "mappers": [
                "dummy-mapper-in",
                "dummy-mapper-out"
            ]
        }
    }

    def test_process_integration__sanity(self, mocker):
        """
        Given
            - A valid script package folder located at Packs/DummyPack/Scripts/DummyScript.

        When
            - parsing script files

        Then
            - integration data will be collected properly
        """
        non_unified_file_path = os.path.join(TESTS_DIR, 'test_files',
                                             'DummyPack', 'Integrations', 'DummyIntegration')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)
        res = process_integration(non_unified_file_path, True)
        assert len(res) == 1
        non_unified_integration_data = res[0]

        unified_file_path = os.path.join(TESTS_DIR, 'test_files',
                                         'DummyPack', 'Integrations', 'integration-DummyIntegration.yml')

        res = process_integration(unified_file_path, True)
        assert len(res) == 1
        unified_integration_data = res[0]

        test_pairs = [
            (non_unified_integration_data, TestIntegrations.INTEGRATION_DATA),
            (unified_integration_data, TestIntegrations.UNIFIED_INTEGRATION_DATA)
        ]

        for returned, constant in test_pairs:
            assert IsEqualFunctions.is_lists_equal(list(returned.keys()), list(constant.keys()))

            const_data = constant.get('Dummy Integration')
            returned_data = returned.get('Dummy Integration')

            assert IsEqualFunctions.is_dicts_equal(returned_data, const_data)

    @staticmethod
    def test_process_integration__exception(mocker):
        """
        Given
            - An invalid "integration" file located at invalid_file_structures where commonfields object is not a dict.

        When
            - parsing integration files

        Then
            - an exception will be raised
        """
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)
        test_file_path = os.path.join(TESTS_DIR, 'test_files', 'invalid_file_structures', 'integration.yml')
        with pytest.raises(Exception):
            process_integration(test_file_path, True)

    @staticmethod
    def test_process_integration__marketplace_mismatch(mocker):
        """
        Given
            - An integration file

        When
            - parsing item files when there is a mismatch between the item's marketplaces and the current run marketplace.

        Then
            - return empty list
        """
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=True)
        test_file_path = os.path.join(TESTS_DIR, 'test_files', 'invalid_file_structures', 'integration.yml')
        res = process_integration(test_file_path, print_logs=False)
        assert res == []


class TestScripts:
    SCRIPT_DATA = {
        "DummyScript": {
            "name": "DummyScript",
            "file_path": TESTS_DIR + "/test_files/DummyPack/Scripts/DummyScript.yml",
            "source": ['github.com', 'demisto', 'demisto-sdk'],
            "fromversion": "5.0.0",
            "docker_image": "demisto/python3:3.7.3.286",
            "tests": [
                "No test - no need to test widget"
            ]
        }
    }

    PACK_SCRIPT_DATA = {
        "DummyScript": {
            "name": "DummyScript",
            "source": ['github.com', 'demisto', 'demisto-sdk'],
            "docker_image": "demisto/python3:3.8.2.6981",
            "pack": "DummyPack",
            "file_path": TESTS_DIR + "/test_files/Packs/DummyPack/Scripts/DummyScript/DummyScript.yml",
        }
    }

    @staticmethod
    def test_get_script_data():
        """
        Test for getting the script data
        """
        file_path = TESTS_DIR + '/test_files/DummyPack/Scripts/DummyScript.yml'
        data = get_script_data(file_path)

        assert IsEqualFunctions.is_lists_equal(list(data.keys()), list(TestScripts.SCRIPT_DATA.keys()))

        const_data = TestScripts.SCRIPT_DATA.get('DummyScript')
        returned_data = data.get('DummyScript')

        assert IsEqualFunctions.is_dicts_equal(returned_data, const_data)

    @staticmethod
    def test_process_script__sanity_package(mocker):
        """
        Given
            - An invalid "script" file located at invalid_file_structures where commonfields object is not a dict.

        When
            - parsing script files

        Then
            - an exception will be raised
        """
        test_file_path = os.path.join(TESTS_DIR, 'test_files',
                                      'Packs', 'DummyPack', 'Scripts', 'DummyScript')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)
        res = process_script(test_file_path, True)
        assert len(res) == 1
        data = res[0]

        assert IsEqualFunctions.is_lists_equal(list(data.keys()), list(TestScripts.PACK_SCRIPT_DATA.keys()))

        const_data = TestScripts.PACK_SCRIPT_DATA.get('DummyScript')
        returned_data = data.get('DummyScript')

        assert IsEqualFunctions.is_dicts_equal(returned_data, const_data)

    @staticmethod
    def test_process_script__marketplace_mismatch(mocker):
        """
        Given
            - A script file

        When
            - parsing script files when there is a mismatch between the item's marketplaces and the current run marketplace.

        Then
            - return empty list
        """
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=True)
        test_file_path = os.path.join(TESTS_DIR, 'test_files', 'invalid_file_structures', 'integration.yml')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)
        res = process_script(test_file_path, print_logs=False)
        assert res == []

    @staticmethod
    def test_process_script__exception(mocker):
        """
        Given
            - An invalid "script" file located at invalid_file_structures where commonfields object is not a dict.

        When
            - parsing script files

        Then
            - an exception will be raised
        """
        test_file_path = os.path.join(TESTS_DIR, 'test_files', 'invalid_file_structures', 'script.yml')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        with pytest.raises(Exception):
            process_script(test_file_path, True)


class TestPlaybooks:
    PLAYBOOK_DATA = {
        "name": "Dummy Playbook",
        "file_path": TESTS_DIR + "/test_files/DummyPack/Playbooks/DummyPlaybook.yml",
        "source": ['github.com', 'demisto', 'demisto-sdk'],
        "fromversion": "4.5.0",
        "filters": ["isEqualString"],
        "transformers": ["uniq"],
        "implementing_scripts": [
            "XDRSyncScript",
            "StopScheduledTask",
        ],
        "implementing_playbooks": [
            "Calculate Severity - Standard",
            "Palo Alto Networks - Malware Remediation",
        ],
        "command_to_integration": {
            "xdr-update-incident": "",
            "autofocus-sample-analysis": ""
        },
        "tests": [
            "No Test"
        ],
        "skippable_tasks": [
            "StopScheduledTask",
            "autofocus-sample-analysis"
        ]
    }

    @staticmethod
    def test_get_playbook_data():
        """
        Test for getting the playbook data
        """
        file_path = TESTS_DIR + '/test_files/DummyPack/Playbooks/DummyPlaybook.yml'
        data = get_playbook_data(file_path)['Dummy Playbook']

        assert IsEqualFunctions.is_dicts_equal(data, TestPlaybooks.PLAYBOOK_DATA)

    @staticmethod
    def test_get_playbook_data_2():
        """
        Given
            - A playbook file called playbook-with-incident-fields.yml

        When
            - parsing playbook files

        Then
            - parsing all the data from file successfully
        """
        test_dir = f'{git_path()}/demisto_sdk/commands/create_id_set/tests/test_data/playbook-with-incident-fields.yml'
        result = get_playbook_data(test_dir)
        result = result.get('Arcsight - Get events related to the Case')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'implementing_scripts' in result.keys()
        assert 'command_to_integration' in result.keys()
        assert 'tests' in result.keys()
        assert 'incident_fields' in result.keys()
        assert 'indicator_fields' in result.keys()

    @staticmethod
    def test_get_playbook_data_no_fields(mocker):
        """
        Given
            - A playbook file called playbook-no-incident-fields.yml without any
                incident or indicator fields that it depends on.

        When
            - parsing playbook files

        Then
            - parsing all the data from file successfully
        """
        test_dir = f'{git_path()}/demisto_sdk/commands/create_id_set/tests/test_data/playbook-no-incident-fields.yml'
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        result = get_playbook_data(test_dir)
        result = result.get('Arcsight - Get events related to the Case')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'implementing_scripts' in result.keys()
        assert 'command_to_integration' in result.keys()
        assert 'tests' in result.keys()
        assert 'incident_fields' not in result.keys()
        assert 'indicator_fields' not in result.keys()

    @staticmethod
    def test_process_playbook__exception(mocker):
        """
        Given
            - An invalid "playbook" file located at invalid_file_structures where tasks object is not a dict.

        When
            - parsing playbook files

        Then
            - an exception will be raised
        """
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)
        test_file_path = os.path.join(TESTS_DIR, 'test_files', 'invalid_file_structures', 'playbook.yml')
        with pytest.raises(Exception):
            process_general_items(test_file_path, True, (FileType.PLAYBOOK,), get_playbook_data)

    @staticmethod
    def test_get_playbook_data_bad_graph():
        """
        Given
            - A playbook file called playbook-invalid-bad-graph.yml:
                - task 1 point to non-existing task
                - task 2 is not connected

        When
            - parsing playbook files

        Then
            - parsing flow graph from file successfully (only tasks 0 and 1 will be in the graph)
        """
        test_file_path = os.path.join(TESTS_DIR, 'test_files', 'playbook-invalid-bad-graph.yml')
        result = get_playbook_data(test_file_path)
        playbook_data = result.get('InvalidPlaybook-BadGraph', {})
        assert playbook_data.get('name') == 'InvalidPlaybook-BadGraph'
        assert playbook_data.get('command_to_integration', {}).get('ip') == ''
        assert playbook_data.get('command_to_integration', {}).get('domain') == ''
        assert 'domain' in playbook_data.get('skippable_tasks', [])
        assert 'ip' not in playbook_data.get('skippable_tasks', [])

    @staticmethod
    def test_get_playbook_data_bad_graph_2():
        """
        Given
            - A playbook file called playbook-invalid-bad-graph_2.yml:
                - starttaskid=5 but task 5 does not exist

        When
            - parsing playbook files

        Then
            - parsing flow graph from file successfully (no actual tasks will be in the graph)
        """
        test_file_path = os.path.join(TESTS_DIR, 'test_files', 'playbook-invalid-bad-graph_2.yml')
        result = get_playbook_data(test_file_path)
        playbook_data = result.get('InvalidPlaybook-BadGraph', {})
        assert playbook_data.get('name') == 'InvalidPlaybook-BadGraph'
        assert playbook_data.get('command_to_integration', {}).get('ip') == ''
        assert playbook_data.get('command_to_integration', {}).get('domain') == ''
        # domain task is marked as skippable so it will be included regardless to the graph.
        assert 'domain' in playbook_data.get('skippable_tasks', [])
        assert len(playbook_data.get('skippable_tasks', [])) == 1

    @staticmethod
    def test_get_filters_from_playbook_tasks():
        """
        Given
        - playbook with one task and 3 filters: isEqualString, isEqualString and StringContainsArray

        When
        - parsing filters from the playbook

        Then
        - parsing 2 filters successfully
        - isEqualString filter shows once

        """
        data = {'tasks': {'0': {'scriptarguments': {'value': {'complex': {'filters': [[{'operator': 'isEqualString'}],
                                                                                      [{'operator': 'isEqualString'}],
                                                                                      [{
                                                                                          'operator': 'StringContainsArray'}]
                                                                                      ]}}}}}}
        _, filters = get_filters_and_transformers_from_playbook(data)
        assert len(filters) == 2
        assert 'isEqualString' in filters
        assert 'StringContainsArray' in filters

    @staticmethod
    def test_get_transformers_from_playbook_tasks():
        """
        Given
        - playbook with one task and 3 transformers: Length, Length and toUpperCase

        When
        - parsing transformers from the playbook

        Then
        - parsing 2 transformers successfully
        - Length transformer shows once

        """
        data = {'tasks': {'0': {'scriptarguments': {'value': {'complex': {'transformers': [{'operator': 'toUpperCase'},
                                                                                           {'operator': 'Length'},
                                                                                           {'operator': 'Length'}
                                                                                           ]}}}}}}
        transformers, _ = get_filters_and_transformers_from_playbook(data)
        assert len(transformers) == 2
        assert 'toUpperCase' in transformers
        assert 'Length' in transformers

    @staticmethod
    def test_get_transformers_from_playbook_condition_task():
        """
        Given
        - playbook with one condition task with toUpperCase transformer

        When
        - parsing transformers from the playbook

        Then
        - parsing toUpperCase transformer successfully

        """
        data = {'tasks': {'0': {'type': 'condition', 'conditions': [
            {'condition': [[{'left': {'value': {'complex': {'transformers': [{'operator': 'toUpperCase'}
                                                                             ]}}}}]]}]}}}
        transformers, _ = get_filters_and_transformers_from_playbook(data)
        assert transformers == ['toUpperCase']

    @staticmethod
    def test_get_transformers_and_filters_from_playbook_two_conditions_task():
        """
        Given
        - playbook with one task that contains 2 conditions: one with filter and one with transformer

        When
        - parsing transformers and filters from the playbook

        Then
        - parsing toUpperCase transformer successfully
        - parsing isEqualString filter successfully


        """
        data = {'tasks': {'0': {'type': 'condition', 'conditions': [
            {'condition': [[{'left': {'value': {'complex': {'filters': [[{'operator': 'isEqualString'}]
                                                                        ]}}}}]]},
            {'condition': [[{'right': {'value': {'complex': {'transformers': [{'operator': 'toUpperCase'}
                                                                              ]}}}}]]}]}}}

        transformers, filters = get_filters_and_transformers_from_playbook(data)
        assert transformers == ['toUpperCase']
        assert filters == ['isEqualString']

    @staticmethod
    def test_get_transformers_from_playbook_inputs(mocker):
        """
        Given
        - playbook with 2 inputs that using Length and toUpperCase transformers

        When
        - parsing transformers from the playbook inputs

        Then
        - parsing 2 transformers successfully

        """
        data = {'inputs': [{'value': {'complex': {'transformers': [{'operator': 'toUpperCase'}
                                                                   ]}}},
                           {'value': {'complex': {'transformers': [{'operator': 'Length'}
                                                                   ]}}}]}
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        transformers, _ = get_filters_and_transformers_from_playbook(data)
        assert len(transformers) == 2
        assert 'toUpperCase' in transformers
        assert 'Length' in transformers


class TestLayouts:
    @staticmethod
    def test_process_layouts__sanity(mocker):
        """
        Given
            - A layout file called layout-to-test.json

        When
            - parsing layout files

        Then
            - parsing all the data from file successfully
        """
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', 'layout-to-test.json')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        res = process_general_items(test_file, True, (FileType.LAYOUT,), get_layout_data)
        assert len(res) == 1
        result = res[0]
        result = result.get('urlRep')
        assert 'kind' in result.keys()
        assert 'name' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'toversion' in result.keys()
        assert 'file_path' in result.keys()
        assert 'typeID' in result.keys()
        assert 'incident_and_indicator_types' in result.keys()
        assert 'incident_and_indicator_fields' in result.keys()

    @staticmethod
    def test_process_layouts__no_incident_types_and_fields(mocker):
        """
        Given
            - A layout file called layout-to-test.json that doesnt have related incident fields and indicator fields

        When
            - parsing layout files

        Then
            - parsing all the data from file successfully
        """
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', 'layout-to-test-no-types-fields.json')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        res = process_general_items(test_file, False, (FileType.LAYOUT,), get_layout_data)
        assert len(res) == 1
        result = res[0]
        result = result.get('urlRep')
        assert 'kind' in result.keys()
        assert 'name' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'toversion' in result.keys()
        assert 'file_path' in result.keys()
        assert 'typeID' in result.keys()
        assert 'incident_and_indicator_types' in result.keys()
        assert 'incident_and_indicator_fields' not in result.keys()

    @staticmethod
    def test_process_layoutscontainer__sanity(mocker):
        """
        Given
            - A layoutscontainer file called layoutscontainer-to-test.json
        When
            - parsing layoutscontainer files
        Then
            - parsing all the data from file successfully
        """
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', 'layoutscontainer-to-test.json')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        res = process_general_items(test_file, True, (FileType.LAYOUTS_CONTAINER,), get_layoutscontainer_data)
        assert len(res) == 1
        result = res[0]
        result = result.get('layouts_container_test')
        assert 'detailsV2' in result.keys()
        assert 'name' in result.keys()
        assert 'group' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'toversion' in result.keys()
        assert 'file_path' in result.keys()
        assert 'incident_and_indicator_types' in result.keys()
        assert 'incident_and_indicator_fields' in result.keys()


class TestIncidentFields:
    @staticmethod
    def test_process_incident_fields__sanity(mocker):
        """
        Given
            - An incident field file called incidentfield-to-test.json

        When
            - parsing incident field files

        Then
            - parsing all the data from file successfully
        """
        test_dir = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                'test_data', 'incidentfield-to-test.json')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        res = process_incident_fields(test_dir, True, [])
        assert len(res) == 1
        result = res[0]
        result = result.get('incidentfield-test')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'toversion' in result.keys()
        assert 'incident_types' in result.keys()
        assert 'scripts' in result.keys()

    @staticmethod
    def test_process_incident_fields__no_types_scripts(mocker):
        """
        Given
            - An incident field file called incidentfield-to-test-no-types_scripts.json with no script or incident type
            related to it

        When
            - parsing incident field files

        Then
            - parsing all the data from file successfully
        """
        test_dir = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                'test_data', 'incidentfield-to-test-no-types_scripts.json')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        res = process_incident_fields(test_dir, True, [])
        assert len(res) == 1
        result = res[0]
        result = result.get('incidentfield-test')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'toversion' in result.keys()
        assert 'incident_types' not in result.keys()
        assert 'scripts' not in result.keys()

    @staticmethod
    def test_process_incident_fields__marketplace_mismatch(mocker):
        """
        Given
            - An incident field file
        When
            - parsing An incident field files when there is a mismatch between the item's marketplaces and the current run marketplace.
        Then
            - return empty list
        """
        test_dir = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                'test_data', 'incidentfield-to-test-no-types_scripts.json')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=True)
        res = process_incident_fields(test_dir, True, [])
        assert res == []


class TestIndicatorType:
    @staticmethod
    def test_process_indicator_type__sanity(mocker):
        """
        Given
            - An indicator type file called reputation-indicatortype.json.

        When
            - parsing indicator type files

        Then
            - parsing all the data from file successfully
        """
        test_dir = f'{git_path()}/demisto_sdk/commands/create_id_set/tests/test_data/reputation-indicatortype.json'
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        result = get_indicator_type_data(test_dir, [{'integration': {'commands': ['ip']}}])
        result = result.get('indicator-type-dummy')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'integrations' in result.keys()
        assert 'scripts' in result.keys()
        assert "dummy-script" in result.get('scripts')
        assert "dummy-script-2" in result.get('scripts')
        assert "dummy-script-3" in result.get('scripts')

    @staticmethod
    def test_get_indicator_type_data_no_integration_no_scripts():
        """
        Given
            - An indicator type file called reputation-indicatortype_no_script_no_integration.json without any
                integrations or scripts that it depends on.

        When
            - parsing indicator type files

        Then
            - parsing all the data from file successfully
        """
        test_dir = f'{git_path()}/demisto_sdk/commands/create_id_set/tests/test_data/' \
                   f'reputation-indicatortype_no_script_no_integration.json'

        result = get_indicator_type_data(test_dir, [])
        result = result.get('indicator-type-dummy')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'integrations' not in result.keys()
        assert 'scripts' not in result.keys()


class TestIncidentTypes:
    @staticmethod
    def test_get_incident_type_data__sanity():
        """
        Given
            - An incident type file called incidenttype-to-test.json

        When
            - parsing incident type files

        Then
            - parsing all the data from file successfully
        """
        test_file = f'{git_path()}/demisto_sdk/commands/create_id_set/tests/test_data/incidenttype-to-test.json'

        res = get_incident_type_data(test_file)
        result = res.get('dummy incident type')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'playbooks' in result.keys()
        assert 'scripts' in result.keys()

    @staticmethod
    def test_get_incident_type_data__no_playbooks_scripts():
        """
        Given
            - An incident type file called incidenttype-to-test-no-playbook-script.json with no script or playbook
            related to it

        When
            - parsing incident type files

        Then
            - parsing all the data from file successfully
        """
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set',
                                 'tests', 'test_data', 'incidenttype-to-test-no-playbook-script.json')

        res = get_incident_type_data(test_file)
        result = res.get('dummy incident type')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'playbooks' not in result.keys()
        assert 'scripts' not in result.keys()


class TestClassifiers:
    @staticmethod
    def test_process_classifiers__no_types_scripts(mocker):
        """
        Given
            - An classifier file called classifier-to-test-no-incidenttypes.json with incident type
            related to it

        When
            - parsing classifier files

        Then
            - parsing all the data from file successfully
        """
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', 'classifier-to-test-no-incidenttypes.json')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        res = get_classifier_data(test_file)
        result = res.get('dummy classifier')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'incident_types' not in result.keys()
        assert 'incident_fields' not in result.keys()


class TestMappers:
    @staticmethod
    def test_process_mappers__sanity():
        """
        Given
            - A mapper file called classifier-mapper-to-test.json

        When
            - parsing mapper files

        Then
            - parsing all the data from file successfully
        """
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', 'classifier-mapper-to-test.json')

        res = get_mapper_data(test_file)
        result = res.get('dummy mapper')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'incident_types' in result.keys()
        assert 'incident_fields' in result.keys()
        assert 'dummy incident type' in result['incident_types']
        assert 'dummy incident type 1' in result['incident_types']
        assert 'dummy incident type 2' in result['incident_types']
        assert 'dummy incident field' in result['incident_fields']
        assert 'dummy incident field 1' in result['incident_fields']
        assert 'dummy incident field 2' in result['incident_fields']
        assert 'dummy incident field 3' in result['incident_fields']
        assert 'occurred' not in result['incident_fields']

    @staticmethod
    def test_process_mappers__no_types_fields():
        """
        Given
            - An mapper file called classifier-mapper-to-test-no-types-fields.json with incident type
            related to it

        When
            - parsing mapper files

        Then
            - parsing all the data from file successfully
        """
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', 'classifier-mapper-to-test-no-types-fields.json')

        res = get_mapper_data(test_file)
        result = res.get('dummy mapper')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'incident_types' not in result.keys()
        assert 'incident_fields' not in result.keys()

    @staticmethod
    def test_process_mappers__outgoing_mapper(mocker):
        """
        Given
            - A mapper file called ServiceNow-outgoing-mapper with incident fields related to it

        When
            - running get_mapper_data

        Then
            - Validating parsing all the incident fields from the simple key.
        """
        outgoing_mapper_snow = {
            "defaultIncidentType": "ServiceNow Ticket",
            "description": "Maps outgoing ServiceNow incident Fields.",
            "feed": False,
            "fromVersion": "6.0.0",
            "id": "ServiceNow-outgoing-mapper",
            "mapping": {
                "ServiceNow Ticket": {
                    "dontMapEventToLabels": False,
                    "internalMapping": {
                        "category": {
                            "complex": None,
                            "simple": "servicenowcategory"
                        },
                        "closed_at": {
                            "complex": {
                                "accessor": "",
                                "filters": [
                                    [
                                        {
                                            "ignoreCase": False,
                                            "left": {
                                                "isContext": True,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "closed"
                                                }
                                            },
                                            "operator": "isAfter",
                                            "right": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "0001-01-01T00:00:00Z"
                                                }
                                            }
                                        }
                                    ]
                                ],
                                "root": "closed",
                                "transformers": []
                            },
                            "simple": ""
                        },
                        "description": {
                            "complex": None,
                            "simple": "details"
                        },
                        "escalation": {
                            "complex": None,
                            "simple": "servicenowescalation"
                        },
                        "impact": {
                            "complex": None,
                            "simple": "servicenowimpact"
                        },
                        "notify": {
                            "complex": None,
                            "simple": "servicenownotify"
                        },
                        "priority": {
                            "complex": None,
                            "simple": "servicenowpriority"
                        },
                        "resolved_at": {
                            "complex": {
                                "accessor": "",
                                "filters": [
                                    [
                                        {
                                            "ignoreCase": False,
                                            "left": {
                                                "isContext": True,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "closed"
                                                }
                                            },
                                            "operator": "isAfter",
                                            "right": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "0001-01-01T00:00:00Z"
                                                }
                                            }
                                        }
                                    ]
                                ],
                                "root": "closed",
                                "transformers": []
                            },
                            "simple": ""
                        },
                        "severity": {
                            "complex": {
                                "accessor": "",
                                "filters": [],
                                "root": "severity",
                                "transformers": [
                                    {
                                        "args": {
                                            "limit": {
                                                "isContext": False,
                                                "value": None
                                            },
                                            "replaceWith": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "3 - Low"
                                                }
                                            },
                                            "toReplace": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "0"
                                                }
                                            }
                                        },
                                        "operator": "replace"
                                    },
                                    {
                                        "args": {
                                            "limit": {
                                                "isContext": False,
                                                "value": None
                                            },
                                            "replaceWith": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "3 - Low"
                                                }
                                            },
                                            "toReplace": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "0.5"
                                                }
                                            }
                                        },
                                        "operator": "replace"
                                    },
                                    {
                                        "args": {
                                            "limit": {
                                                "isContext": False,
                                                "value": None
                                            },
                                            "replaceWith": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "3 - Low"
                                                }
                                            },
                                            "toReplace": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "1"
                                                }
                                            }
                                        },
                                        "operator": "replace"
                                    },
                                    {
                                        "args": {
                                            "limit": {
                                                "isContext": False,
                                                "value": None
                                            },
                                            "replaceWith": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "2 - Medium"
                                                }
                                            },
                                            "toReplace": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "2"
                                                }
                                            }
                                        },
                                        "operator": "replace"
                                    },
                                    {
                                        "args": {
                                            "limit": {
                                                "isContext": False,
                                                "value": None
                                            },
                                            "replaceWith": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "1 - High"
                                                }
                                            },
                                            "toReplace": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "3"
                                                }
                                            }
                                        },
                                        "operator": "replace"
                                    },
                                    {
                                        "args": {
                                            "limit": {
                                                "isContext": False,
                                                "value": None
                                            },
                                            "replaceWith": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "1 - High"
                                                }
                                            },
                                            "toReplace": {
                                                "isContext": False,
                                                "value": {
                                                    "complex": None,
                                                    "simple": "4"
                                                }
                                            }
                                        },
                                        "operator": "replace"
                                    }
                                ]
                            },
                            "simple": ""
                        },
                        "short_description": {
                            "complex": None,
                            "simple": "name"
                        },
                        "sla_due": {
                            "complex": None,
                            "simple": "remediationsla.dueDate"
                        },
                        "state": {
                            "complex": None,
                            "simple": "servicenowstate"
                        },
                        "subcategory": {
                            "complex": None,
                            "simple": "subcategory"
                        },
                        "urgency": {
                            "complex": None,
                            "simple": "servicenowurgency"
                        },
                        "work_start": {
                            "complex": None,
                            "simple": "timetoassignment.startDate"
                        }
                    }
                }
            },
            "name": "ServiceNow - Outgoing Mapper",
            "type": "mapping-outgoing",
            "version": -1
        }
        mocker.patch("demisto_sdk.commands.common.tools.get_file", return_value=outgoing_mapper_snow)

        mapper = get_mapper_data('')
        mapper_data = mapper.get('ServiceNow-outgoing-mapper')
        assert mapper_data.get('name') == 'ServiceNow - Outgoing Mapper'
        assert mapper_data.get('fromversion') == '6.0.0'
        assert mapper_data.get('incident_types') == ['ServiceNow Ticket']
        assert set(mapper_data.get('incident_fields')) == {
            'closed', 'servicenowescalation', 'servicenowurgency', 'subcategory', 'servicenownotify',
            'servicenowcategory', 'remediationsla.dueDate', 'servicenowstate', 'timetoassignment.startDate',
            'servicenowimpact', 'servicenowpriority'}

    @staticmethod
    def test_process_mappers__complex_value():
        """
        Given
            - An mapper file called classifier-mapper-to-test-complex-value.json with one transformer and one filter

        When
            - parsing mapper files

        Then
            - parsing one filter and one transformer from file successfully
        """
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', 'classifier-mapper-to-test-complex-value.json')

        res = get_mapper_data(test_file)
        result = res.get('dummy mapper')
        transformers = result.get('transformers')
        filters = result.get('filters')
        assert len(transformers) == 1
        assert 'toUpperCase' in transformers
        assert len(filters) == 1
        assert 'isEqualString' in filters


class TestWidget:
    @staticmethod
    def test_process_widget__with_script():
        """
        Given
            - A widget file called widget-with-scripts.json

        When
            - parsing widget files

        Then
            - parsing all the data from file successfully
        """
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', 'widget-with-scripts.json')

        res = get_widget_data(test_file)
        result = res.get('dummy_widget')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'scripts' in result.keys()
        assert 'dummy_script' in result['scripts']

    @staticmethod
    def test_process_widget__no_script():
        """
        Given
            - A widget file called widget-no-scripts.json

        When
            - parsing widget files

        Then
            - parsing all the data from file successfully
        """
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', 'widget-no-scripts.json')

        res = get_widget_data(test_file)
        result = res.get('dummy_widget')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'scripts' not in result.keys()


class TestDashboard:
    DASHBOARD_WITH_SCRIPT = {
        "id": "dummy_dashboard",
        "layout": [
            {
                "widget": {
                    "category": "",
                    "dataType": "scripts",
                    "fromServerVersion": "",
                    "id": "dummy_widget",
                    "name": "dummy_dashboard",
                    "query": "dummy_script",
                    "toServerVersion": "",
                },
            }
        ],
        "name": "dummy_dashboard",
        "fromVersion": "6.0.0",
    }

    DASHBOARD_NO_SCRIPT = {

        "id": "dummy_dashboard",
        "layout": [
            {
                "widget": {
                    "category": "",
                    "dataType": "indicators",
                    "fromServerVersion": "",
                    "id": "dummy_widget",
                    "name": "dummy_dashboard",
                    "packID": "",
                    "toServerVersion": "",
                    "widgetType": "table"
                },
            }
        ],
        "name": "dummy_dashboard",
        "fromVersion": "6.0.0",
    }

    @staticmethod
    def test_process_dashboard__with_script(repo):
        """
        Given
            - A dashboard file called dashboard-with-scripts.json

        When
            - parsing dashboard files

        Then
            - parsing all the data from file successfully
        """
        pack = repo.create_pack("Pack1")
        dashboard = pack.create_dashboard('dummy_dashboard')
        dashboard.update(TestDashboard.DASHBOARD_WITH_SCRIPT)

        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', dashboard.path)

        res = get_dashboard_data(test_file)
        result = res.get('dummy_dashboard')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'scripts' in result.keys()
        assert 'dummy_script' in result['scripts']

    @staticmethod
    def test_process_dashboard__no_script(repo):
        """
        Given
            - A dashboard file called dashboard-no-scripts.json

        When
            - parsing dashboard files

        Then
            - parsing all the data from file successfully
        """
        pack = repo.create_pack("Pack1")
        dashboard = pack.create_dashboard('dummy_dashboard')
        dashboard.update(TestDashboard.DASHBOARD_NO_SCRIPT)

        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', dashboard.path)

        res = get_dashboard_data(test_file)
        result = res.get('dummy_dashboard')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'scripts' not in result.keys()


class TestReport:
    REPORT_WITH_SCRIPT = {
        "id": "dummy_report",
        "modified": "2020-09-23T07:54:57.783240299Z",
        "startDate": "0001-01-01T00:00:00Z",
        "name": "dummy_report",
        "dashboard": {
            "id": "dummy_report",
            "version": 0,
            "name": "dummy_report",
            "layout": [
                {
                    "id": "dummy_report",
                    "widget": {
                        "id": "dummy_report",
                        "version": 1,
                        "modified": "2020-09-09T14:02:27.423018192Z",
                        "name": "dummy_widget",
                        "dataType": "scripts",
                        "query": "dummy_script",
                    }
                }
            ]
        },
        "fromVersion": "6.0.0",
    }

    REPORT_NO_SCRIPT = {
        "id": "dummy_report",
        "name": "dummy_report",
        "dashboard": {
            "id": "dummy_report",
            "name": "dummy_report",
            "layout": [
                {
                    "id": "dummy_report",
                    "widget": {
                        "id": "dummy_report",
                        "name": "dummy_widget",
                        "dataType": "indicators",
                    }
                }
            ]
        },
        "fromVersion": "6.0.0",
    }

    @staticmethod
    def test_process_report__with_script(repo):
        """
        Given
            - A report file called report-with-scripts.json

        When
            - parsing report files

        Then
            - parsing all the data from file successfully
        """
        pack = repo.create_pack("Pack1")
        report = pack.create_report('dummy_report')
        report.update(TestReport.REPORT_WITH_SCRIPT)
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', report.path)

        res = get_report_data(test_file)
        result = res.get('dummy_report')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'scripts' in result.keys()
        assert 'dummy_script' in result['scripts']

    @staticmethod
    def test_process_report__no_script(repo):
        """
        Given
            - A report file called report-no-scripts.json

        When
            - parsing report files

        Then
            - parsing all the data from file successfully
        """
        pack = repo.create_pack("Pack1")
        report = pack.create_report('dummy_report')
        report.update(TestReport.REPORT_NO_SCRIPT)
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', report.path)

        res = get_report_data(test_file)
        result = res.get('dummy_report')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'scripts' not in result.keys()


class TestGenericFunctions:
    @staticmethod
    def test_process_general_items__sanity(mocker):
        """
        Given
            - A classifier file called classifier-to-test.json

        When
            - parsing classifier files

        Then
            - parsing all the data from file successfully
        """
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', 'classifier-to-test.json')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        res = process_general_items(test_file, True, (FileType.CLASSIFIER,), get_classifier_data)
        assert len(res) == 1
        result = res[0]
        result = result.get('dummy classifier')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'incident_types' in result.keys()
        assert 'dummy incident type' in result['incident_types']
        assert 'dummy incident type 2' in result['incident_types']
        assert 'dummy incident type 3' in result['incident_types']

    @staticmethod
    def test_process_general_items__marketplace_mismatch(mocker):
        """
        Given
            - An item file

        When
            - parsing item files when there is a mismatch between the item's marketplaces and the current run marketplace.

        Then
            - return empty list
        """
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=True)
        test_file = os.path.join(git_path(), 'demisto_sdk', 'commands', 'create_id_set', 'tests',
                                 'test_data', 'classifier-to-test.json')
        res = process_general_items(test_file, True, (FileType.CLASSIFIER,), get_classifier_data)
        assert res == []

    @staticmethod
    def test_process_general_items__exception(mocker):
        """
        Given
            - An invalid "dashboard" file located at invalid_file_structures where id is a list so it can't be used
                as a dict key.

        When
            - parsing dashboard files

        Then
            - an exception will be raised
        """
        test_file_path = os.path.join(TESTS_DIR, 'test_files', 'invalid_file_structures', 'dashboard.json')
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        with pytest.raises(Exception):
            process_general_items(test_file_path, True, (FileType.DASHBOARD,), get_general_data)

    @staticmethod
    def test_get_values_for_keys_recursively():
        """
        Given
            - A list of keys to extract their values from a dict

        When
            - Extracting data from nested elements in the json

        Then
            - Extracting the values from all the levels of nesting in the json
        """

        test_dict = {
            'id': 1,
            'nested': {
                'x1': 1,
                'x2': 'x2',
                'x3': False,
                'x4': [
                    {
                        'x1': 2,
                        'x2': 55
                    },
                    {
                        'x3': 1,
                        'x2': True
                    }
                ]
            },
            'x2': 4.0
        }

        test_keys = ['x1', 'x2', 'x3']

        expected = {
            'x1': [1, 2],
            'x2': ['x2', 55, True, 4.0],
            'x3': [False, 1]
        }

        assert expected == get_values_for_keys_recursively(test_dict, test_keys)

    INPUT_WITH_INCIDENT_FIELD_SIMPLE = {
        "key": "AlertID",
        "value": {
            "simple": "${incident.field_name}"
        },
        "required": False
    }

    INPUT_WITH_INCIDENT_FIELD_COMPLEX1 = {
        "key": "AlertID",
        "value": {
            "complex": {
                "root": "incident",
                "accessor": "field_name"
            }
        },
        "required": False
    }

    INPUT_WITH_INCIDENT_FIELD_COMPLEX2 = {
        "key": "AlertID",
        "value": {
            "complex": {
                "root": "incident.field_name",
                "accessor": "username"
            }
        },
        "required": False
    }

    INPUT_SIMPLE_WITHOUT_INCIDENT_FIELD = {
        "key": "AlertID",
        "value": {
            "simple": "${not_incident.field_name}"
        },
        "required": False
    }

    INPUT_COMPLEX_WITHOUT_INCIDENT_FIELD = {
        "key": "AlertID",
        "value": {
            "complex": {
                "root": "something",
                "accessor": "username"
            }
        },
        "required": False
    }

    INPUTS = [
        (INPUT_WITH_INCIDENT_FIELD_SIMPLE, True),
        (INPUT_WITH_INCIDENT_FIELD_COMPLEX1, True),
        (INPUT_WITH_INCIDENT_FIELD_COMPLEX2, True),
        (INPUT_SIMPLE_WITHOUT_INCIDENT_FIELD, False),
        (INPUT_COMPLEX_WITHOUT_INCIDENT_FIELD, False)
    ]

    @staticmethod
    @pytest.mark.parametrize('playbook_input, are_there_incident_fields', INPUTS)
    def test_get_incident_fields_by_playbook_input(playbook_input, are_there_incident_fields):
        """
        Given
            - A list of playbook inputs

        When
            - Searching for dependent incident fields

        Then
            -  Finding all dependent incident fields in the input
        """

        result = get_incident_fields_by_playbook_input(playbook_input.get('value'))
        if are_there_incident_fields:
            assert "field_name" in result
        else:
            assert result == set()

    EXAMPLE_TASK_WITH_SIMPLE_SCRIPT_ARGUMENTS = {
        "id": "ID",
        "scriptarguments": {
            "field_name": {
                "simple": "${inputs.IndicatorTagName}"
            }
        }
    }

    EXAMPLE_TASK_WITH_CUSTOM_FIELDS_SCRIPT_ARGUMENTS = {
        "id": "ID",
        "scriptarguments": {
            "customFields": {
                "simple": '[{"field_name":"${inputs.IndicatorTagName}"}]'
            }
        }
    }

    TASK_INPUTS = [
        # EXAMPLE_TASK_WITH_SIMPLE_SCRIPT_ARGUMENTS,
        EXAMPLE_TASK_WITH_CUSTOM_FIELDS_SCRIPT_ARGUMENTS
    ]

    @staticmethod
    @pytest.mark.parametrize('task', TASK_INPUTS)
    def test_get_fields_by_script_argument(task):
        """
        Given
            - A list of playbook tasks

        When
            - Searching for dependent incident fields in the task script arguments

        Then
            - Finding all dependent incident fields in the task
        """

        result = get_fields_by_script_argument(task)
        assert "field_name" in result


class TestFlow(unittest.TestCase):
    WIDGET_DATA = {
        "id": "temp-widget-dup-check",
        "version": -1,
        "fromVersion": "3.5.0",
        "name": "check duplicate",
        "dataType": "incidents",
        "widgetType": "pie"
    }

    REPORT_DATA = {
        "id": "temp-report-dup-check",
        "name": "Critical and High incidents",
        "description": "All critical and high severity incidents that may need the analyst attention.",
        "fromVersion": "3.5.0"
    }

    CLASSIFIER_DATA = {
        "id": "dup_check-classifier",
        "version": -1,
        "modified": "2018-05-21T12:41:29.542577629Z",
        "defaultIncidentType": "",
        "brandName": "dup_check-classifier-name"
    }

    LAYOUT_DATA = {
        "TypeName": "layout-dup-check-type-name",
        "kind": "details",
        "fromVersion": "5.0.0",
        "layout": {
            "TypeName": "",
            "id": "layout-dup-check-id",
            "kind": "details",
            "modified": "2019-09-01T12:25:49.808989+03:00",
            "name": "",
            "system": False
        },
        "name": "my-layout",
        "typeId": "layout-dup-check-id",
        "version": -1
    }

    DASHBOARD_DATA = {
        "id": "dup-check-dashbaord",
        "version": -1,
        "fromVersion": "4.0.0",
        "description": "",
        "name": "My Dashboard",
    }

    DASHBOARD_DATA2 = {
        "id": "dup-check-dashbaord",
        "version": -1,
        "fromVersion": "4.0.0",
        "description": "",
        "name": "My Dashboard2",
    }

    INCIDENT_FIELD_DATA = {
        "cliName": "accountid",
        "description": "",
        "fieldCalcScript": "",
        "group": 0,
        "id": "incident_account_field_dup_check",
        "name": "Account ID",
        "fromVersion": "5.0.0"
    }

    # TODO: unskip
    @pytest.mark.skip
    def test_find_duplicates(self):
        sys.path.insert(1, os.getcwd())
        # Make the script run from tests dir
        os.chdir(os.path.abspath(os.path.join(os.path.dirname(__file__), TESTS_DIR)))

        # create duplicate report
        temp_report = tempfile.NamedTemporaryFile(mode="w+", prefix='report-',  # disable-secrets-detection
                                                  suffix='.json', dir='Reports')  # disable-secrets-detection
        json.dump(TestFlow.REPORT_DATA, temp_report)
        temp_report.flush()
        os.fsync(temp_report.fileno())
        temp_report2 = tempfile.NamedTemporaryFile(mode="w+", prefix='report-',  # disable-secrets-detection
                                                   suffix='.json', dir='Reports')  # disable-secrets-detection
        json.dump(TestFlow.REPORT_DATA, temp_report2)
        temp_report2.flush()
        os.fsync(temp_report2.fileno())

        # create duplicate Widgets
        temp_widget = tempfile.NamedTemporaryFile(mode="w+", prefix='widget-',  # disable-secrets-detection
                                                  suffix='.json', dir='Widgets')  # disable-secrets-detection
        json.dump(TestFlow.WIDGET_DATA, temp_widget)
        temp_widget.flush()
        os.fsync(temp_widget.fileno())
        temp_widget2 = tempfile.NamedTemporaryFile(mode="w+", prefix='widget-',  # disable-secrets-detection
                                                   suffix='.json', dir='Widgets')  # disable-secrets-detection
        json.dump(TestFlow.WIDGET_DATA, temp_widget2)
        temp_widget2.flush()
        os.fsync(temp_widget2.fileno())

        # create duplicate Classifier
        temp_classifier = tempfile.NamedTemporaryFile(mode="w+", prefix='classifier-',  # disable-secrets-detection
                                                      suffix='.json', dir='Classifiers')  # disable-secrets-detection
        json.dump(TestFlow.WIDGET_DATA, temp_classifier)
        temp_classifier.flush()
        os.fsync(temp_classifier.fileno())
        temp_classifier2 = tempfile.NamedTemporaryFile(mode="w+", prefix='classifier-',  # disable-secrets-detection
                                                       suffix='.json', dir='Classifiers')  # disable-secrets-detection
        json.dump(TestFlow.WIDGET_DATA, temp_classifier2)
        temp_classifier2.flush()
        os.fsync(temp_classifier2.fileno())

        # create duplicate Layout
        temp_layout = tempfile.NamedTemporaryFile(mode="w+", prefix='layout-',  # disable-secrets-detection
                                                  suffix='.json', dir='Layouts')  # disable-secrets-detection
        json.dump(TestFlow.LAYOUT_DATA, temp_layout)
        temp_layout.flush()
        os.fsync(temp_layout.fileno())
        temp_layout2 = tempfile.NamedTemporaryFile(mode="w+", prefix='layout-', suffix='.json',
                                                   # disable-secrets-detection
                                                   dir='Packs/CortexXDR/Layouts')  # disable-secrets-detection
        json.dump(TestFlow.LAYOUT_DATA, temp_layout2)
        temp_layout2.flush()
        os.fsync(temp_layout2.fileno())

        # create duplicate Dashboard
        temp_dashboard = tempfile.NamedTemporaryFile(mode="w+", prefix='dashboard-',  # disable-secrets-detection
                                                     suffix='.json', dir='Dashboards')  # disable-secrets-detection
        json.dump(TestFlow.DASHBOARD_DATA, temp_dashboard)
        temp_dashboard.flush()
        os.fsync(temp_dashboard.fileno())
        temp_dashboard2 = tempfile.NamedTemporaryFile(mode="w+", prefix='dashboard-',  # disable-secrets-detection
                                                      suffix='.json', dir='Dashboards')  # disable-secrets-detection
        json.dump(TestFlow.DASHBOARD_DATA2, temp_dashboard2)
        temp_dashboard2.flush()
        os.fsync(temp_dashboard2.fileno())

        # create one incident type field and one indicator type field with same data
        temp_incident_field = tempfile.NamedTemporaryFile(mode='w+', prefix='incidentfield-',
                                                          # disable-secrets-detection
                                                          suffix='.json',
                                                          dir='IncidentFields')  # disable-secrets-detection
        json.dump(TestFlow.INCIDENT_FIELD_DATA, temp_incident_field)
        temp_incident_field.flush()
        os.fsync(temp_incident_field.fileno())
        temp_indicator_field = tempfile.NamedTemporaryFile(mode='w+', prefix='incidentfield-',
                                                           # disable-secrets-detection
                                                           suffix='.json', dir='IndicatorFields')
        json.dump(TestFlow.INCIDENT_FIELD_DATA, temp_indicator_field)
        temp_indicator_field.flush()
        os.fsync(temp_indicator_field.fileno())

        # create temporary file for id_set
        temp_id_set = tempfile.NamedTemporaryFile(mode="w+", prefix='temp_id_set-',  # disable-secrets-detection
                                                  suffix='.json', dir='Tests/scripts')  # disable-secrets-detection
        json_path = temp_id_set.name

        re_create_id_set(json_path, ['Reports', 'Layouts', 'Widgets', 'Classifiers', 'Dashboards',
                                     'IndicatorFields', 'IncidentFields'])
        with open(json_path) as json_file:
            data = json.load(json_file)
            dup_data = find_duplicates(data)
            assert any('temp-widget-dup-check' in i for i in dup_data)
            assert any('temp-report-dup-check' in i for i in dup_data)
            assert any('temp-widget-dup-check' in i for i in dup_data)
            assert any('dup-check-dashbaord' in i for i in dup_data)
            assert any('layout-dup-check-id' in i for i in dup_data)
            assert any('incident_account_field_dup_check' in i for i in dup_data)


class TestGenericFields:
    @staticmethod
    def test_process_generic_fields(pack):
        """
        Given
            - A generic field file

        When
            - parsing generic field files

        Then
            - parsing all the data from file successfully
        """

        field_data = {
            "cliName": "operatigsystem",
            "id": "id",
            "name": "Operating System",
            "definitionId": "assets",
            "fromVersion": "6.5.0",
            "associatedTypes": ["Asset Type"]}

        generic_types_list = [{
            "Asset Type": {
                "name": "Asset Type",
                "file_path": "path/path",
                "fromversion": "6.5.0",
                "pack": "ObjectsExample",
                "definitionId": "assets",
                "layout": "Workstation Layout"
            }
        }]

        generic_field = pack.create_generic_field('test-generic-field')
        generic_field.write_json(field_data)
        test_dir = generic_field.path

        result = get_generic_field_data(test_dir, generic_types_list=generic_types_list)
        result = result.get('id')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'definitionId' in result.keys()
        assert 'generic_types' in result.keys()


class TestGenericType:

    @staticmethod
    def test_get_generic_type_data(pack):
        """
        Given
            - A generic type file

        When
            - parsing object type files

        Then
            - parsing all the data from file successfully
        """

        object_type = pack.create_generic_module('test-object-type')
        object_type.write_json(
            {"id": "type-id", "name": "type-name", "fromVersion": "version", "definitionId": "Assets",
             "layout": "layout"})
        test_dir = object_type.path

        result = get_generic_type_data(test_dir)
        result = result.get('type-id')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'layout' in result.keys()
        assert 'definitionId' in result.keys()


class TestGenericDefinition:

    @staticmethod
    def test_get_generic_definition_data(pack):
        """
        Given
            - A generic definition file

        When
            - parsing definition type files

        Then
            - parsing all the data from file successfully
        """

        object_type = pack.create_generic_definition('test-generic-definition')
        object_type.write_json(
            {"id": "type-id", "name": "type-name", "fromVersion": "version", "auditable": False})
        test_dir = object_type.path

        result = get_general_data(test_dir)
        result = result.get('type-id')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'pack' in result.keys()


class TestGenericModule:
    @staticmethod
    def test_get_generic_module_data(repo):
        """
        Given
            - A generic module file

        When
            - parsing generic generic module files

        Then
            - parsing all the data from file successfully
        """

        module_data = {"id": "id",
                       "version": -1,
                       "name": "Vulnerability Management",
                       "fromVersion": "6.5.0",
                       "definitionIds": ["assets"],
                       "views": [{
                           "name": "Vulnerability Management",
                           "title": "Risk Base Vulnerability Management",
                           "tabs": [{
                               "name": "Assets",
                               "newButtonDefinitionId": "assets",
                               "dashboard": {
                                   "id": "assets_dashboard",
                                   "version": -1,
                                   "fromDate": "0001-01-01T00:00:00Z",
                                   "toDate": "0001-01-01T00:00:00Z",
                                   "name": "Assets Dashboard",
                                   "prevName": "Assets Dashboard", }}]}]}

        pack = repo.create_pack('pack')
        generic_module = pack.create_generic_module('test-generic-module')
        generic_module.write_json(module_data)
        test_dir = generic_module.path

        result = get_generic_module_data(test_dir)
        result = result.get('id')
        assert 'name' in result.keys()
        assert 'file_path' in result.keys()
        assert 'fromversion' in result.keys()
        assert 'definitionIds' in result.keys()
        assert 'views' in result.keys()
        assert 'pack' in result.keys()


class TestJob:
    @staticmethod
    @pytest.mark.parametrize('print_logs', (True, False))
    @pytest.mark.parametrize('is_feed', (True, False))
    def test_process_jobs(capsys, repo, is_feed: bool, print_logs: bool, mocker):
        """
        Given
            - A repo with a job object.
            - Whether to print logs.
        When
            - Parsing job files.
        Then
            - Verify output to logs.
        """
        pack = repo.create_pack()
        job_details = 'job details'
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)
        job = pack.create_job(is_feed, details=job_details)
        res = process_jobs(job.path, print_logs)

        captured = capsys.readouterr()
        assert len(res) == 1
        datum = res[0][job.pure_name]
        assert datum['name'] == job.pure_name
        path = Path(datum['file_path'])
        assert path.name == f'job-{job.pure_name}.json'
        assert path.exists()
        assert path.is_file()
        assert path.suffix == '.json'
        assert path.parts[-2] == JOBS_DIR
        assert path.parts[-3] == pack.name

        assert datum['fromversion'] == DEFAULT_JOB_FROM_VERSION
        assert datum['pack'] == pack.name
        assert datum['details'] == job_details
        assert datum['selectedFeeds'] == []

        assert (f'adding {job.path} to id_set' in captured.out) == print_logs

    @staticmethod
    @pytest.mark.parametrize('is_feed', (True, False))
    def test_process_jobs_non_job_extension(capsys, repo, is_feed: bool, mocker):
        """
        Given
            - A file that isn't a valid Job (wrong filetype)
            - Whether to print logs.
        When
            - Parsing job files.
        Then
            - Verify output to logs.
        """
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        pack = repo.create_pack()
        job = pack.create_job(is_feed)
        job_path = Path(job.path)
        new_path = job_path.rename(job_path.with_suffix('.yml'))
        res = process_jobs(str(new_path), False)
        assert not res

    @staticmethod
    @pytest.mark.parametrize('print_logs', (True, False))
    @pytest.mark.parametrize('is_feed', (True, False))
    def test_process_jobs_file_nonexistent(capsys, repo, is_feed: bool, print_logs: bool, mocker):
        """
        Given
            - A file that isn't a valid Job (missing file)
            - Whether to print logs.
        When
            - Parsing job files.
        Then
            - Verify output to logs.
        """
        mocker.patch.object(uis, 'should_skip_item_by_mp', return_value=False)

        pack = repo.create_pack()
        job = pack.create_job(is_feed)
        job_json_path = Path(job.path)
        job_json_path_as_yml = job_json_path.with_suffix('.yml')

        job_json_path.rename(job_json_path_as_yml)
        with pytest.raises(FileNotFoundError):
            assert not process_jobs(str(job_json_path), print_logs)
        assert f"failed to process job {job_json_path}" in capsys.readouterr().out


def test_merge_id_sets(tmp_path):
    """
    Given
    - two id_set files
    - id_sets don't contain duplicate items

    When
    - merged

    Then
    - ensure the output id_set contains items from both id_sets
    - ensure no duplicates found
    """
    tmp_dir = tmp_path / "somedir"
    tmp_dir.mkdir()

    first_id_set = {
        'playbooks': [
            {
                'playbook_foo1': {

                }
            }
        ],
        'integrations': [
            {
                'integration_foo1': {

                }
            }
        ],
        'Packs': {
            'pack_foo1': {

            }
        }
    }

    second_id_set = {
        'playbooks': [
            {
                'playbook_foo2': {

                }
            }
        ],
        'integrations': [
            {
                'integration_foo2': {

                }
            }
        ],
        'Packs': {
            'pack_foo2': {

            }
        }
    }

    output_id_set, duplicates = merge_id_sets(first_id_set, second_id_set)

    assert output_id_set.get_dict() == {
        'playbooks': [
            {
                'playbook_foo1': {

                }
            },
            {
                'playbook_foo2': {

                }
            }
        ],
        'integrations': [
            {
                'integration_foo1': {

                }
            },
            {
                'integration_foo2': {

                }
            }
        ],
        'Packs': {
            'pack_foo1': {

            },
            'pack_foo2': {

            }
        }
    }

    assert not duplicates


def test_merged_id_sets_with_duplicates(caplog):
    """
    Given
    - first_id_set.json
    - second_id_set.json
    - they both has the same script ScriptFoo

    When
    - merged

    Then
    - ensure output id_set contains items from both id_sets
    - ensure merge fails
    - ensure duplicate ScriptFoo found

    """
    caplog.set_level(logging.DEBUG)

    first_id_set = {
        'playbooks': [
            {
                'playbook_foo1': {
                    'name': 'playbook_foo1'
                }
            }
        ],
        'scripts': [
            {
                'ScriptFoo': {
                    'name': 'ScriptFoo',
                    'pack': 'ScriptFoo1'
                }
            }
        ]
    }

    second_id_set = {
        'playbooks': [
            {
                'playbook_foo2': {
                    'name': 'playbook_foo2'
                }
            }
        ],
        'scripts': [
            {
                'ScriptFoo': {
                    'name': 'ScriptFoo',
                    'pack': 'ScriptFoo2'
                }
            }
        ]
    }

    output_id_set, duplicates = merge_id_sets(first_id_set, second_id_set)

    assert output_id_set is None
    assert duplicates == ['ScriptFoo']


def test_merged_id_sets_with_legal_duplicates(caplog):
    """
    Given
    - first_id_set.json
    - second_id_set.json
    - they both have the same playbook

    When
    - merged

    Then
    - ensure merge fails
    - ensure duplicate playbook_foo1 found

    """
    caplog.set_level(logging.DEBUG)

    first_id_set = {
        'playbooks': [
            {
                'playbook_foo1': {
                    'name': 'playbook_foo1',
                    'pack': 'foo_1',
                    "source": ['github.com', 'demisto', 'repo1'],
                }
            }
        ],
        'scripts': [
            {
                'Script_Foo1': {
                    'name': 'ScriptFoo',
                    'pack': 'foo_1',
                    "source": ['github.com', 'demisto', 'repo1'],
                }
            }
        ]
    }

    second_id_set = {
        'playbooks': [
            {
                'playbook_foo1': {
                    'name': 'playbook_foo1',
                    'pack': 'foo_1',
                    "source": ['github.com', 'demisto', 'repo2'],
                }
            }
        ],
        'scripts': []
    }

    output_id_set, duplicates = merge_id_sets(first_id_set, second_id_set)

    assert output_id_set is None
    assert duplicates == ['playbook_foo1']


def test_get_filters_and_transformers_from_complex_value():
    """
    Given
    - complex value with 3 transformers: Length, Length and toUpperCase
      and 3 filters: isEqualString, isEqualString and StringContainsArray

    When
    - parsing transformers and filters from the value

    Then
    - parsing 2 transformers successfully
    - Length transformer shows once
    - parsing 2 filters successfully
    - isEqualString filter shows once

    """

    data = {'transformers': [{'operator': 'toUpperCase'},
                             {'operator': 'Length'},
                             {'operator': 'Length'}],
            'filters': [[{'operator': 'isEqualString'}],
                        [{'operator': 'isEqualString'}],
                        [{'operator': 'StringContainsArray'}]]}
    transformers, filters = get_filters_and_transformers_from_complex_value(data)
    assert len(transformers) == 2
    assert len(filters) == 2
    assert 'toUpperCase' in transformers
    assert 'Length' in transformers
    assert 'isEqualString' in filters
    assert 'StringContainsArray' in filters


def test_should_skip_item_by_mp(mocker):
    """
    Given
    - path of content item, the current marketplace this id set is generated for.
    When
    - when creating the id set, checking a content item that is 'xsoar only' if it should be part of the mpV2
     id set.
    Then
    - return True since this item should be skipped.

    """
    import demisto_sdk.commands.common.update_id_set as uis
    mocker.patch.object(uis, 'get_mp_types_from_metadata_by_item', return_value=['xsoar'])
    pack_path = os.path.join(TESTS_DIR, 'test_files', 'DummyPackXsoarMPOnly')
    script_path = os.path.join(TESTS_DIR, 'test_files', 'DummyPackScriptIsXsoarOnly', 'Scripts', 'DummyScript')
    res1 = should_skip_item_by_mp(pack_path, 'mpv2')
    res2 = should_skip_item_by_mp(script_path, 'mpv2')
    assert res1
    assert res2


# demisto_sdk/commands/common/tests/widget_test.py

from typing import Optional

import pytest
from mock import patch

from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator
from demisto_sdk.commands.common.hook_validations.widget import WidgetValidator


def mock_structure(file_path=None, current_file=None, old_file=None, quite_bc=False):
    # type: (Optional[str], Optional[dict], Optional[dict]) -> StructureValidator
    with patch.object(StructureValidator, '__init__', lambda a, b: None):
        structure = StructureValidator(file_path)
        structure.is_valid = True
        structure.scheme_name = 'widget'
        structure.file_path = file_path
        structure.current_file = current_file
        structure.old_file = old_file
        structure.prev_ver = 'master'
        structure.branch_name = ''
        structure.quite_bc = quite_bc
        return structure


@pytest.mark.parametrize('current_file, answer', [({'dataType': 'metrics', 'fromVersion': '6.2.0'}, True),
                                                  ({'dataType': 'metrics', 'fromVersion': '5.5.0'}, False),
                                                  ({'dataType': 'incidents', 'fromVersion': '6.2.0'}, True)])
def test_is_valid_fromversion(current_file, answer):
    """
    Given:
        A widget validator with dataType and fromVersion fields.

    When:
        Running is_valid_fromversion.

    Then:
        Ensure that the answer as expected.
    """
    structure = mock_structure()
    widget_validator = WidgetValidator(structure)

    widget_validator.current_file = current_file

    assert widget_validator._is_valid_fromversion() == answer


# demisto_sdk/commands/common/tests/xsoar_config_file_test.py

from typing import Iterator, List

import pytest
from jsonschema import ValidationError

from demisto_sdk.commands.common.hook_validations.xsoar_config_json import \
    XSOARConfigJsonValidator


def test_schema_file_correct_path():
    """
    Given:
        Run of xsoar_config.json validator.
    When:
        Initiation of the XSOARConfigJsonValidator object.
    Then:
        Validate that the __init__ method finds the schema in the expected path.
    """
    validator = XSOARConfigJsonValidator('./')
    assert 'demisto_sdk/commands/common/schemas/xsoar_config.json' in validator.schema_path


class TestCreateSchemaValidationResultsTable:
    @staticmethod
    def errors_iterator(errors_list: List) -> Iterator[ValidationError]:
        for error in errors_list:
            yield error

    def test_create_schema_validation_results_table_no_errors(self):
        """
        Given:
            No errors were found in the xsoar_config.json file.
        When:
            Validating the file schema.
        Then:
            Validate that no output is provided by the function.
        """
        generator = self.errors_iterator([])
        _, errors_found = XSOARConfigJsonValidator.create_schema_validation_results_table(generator)
        assert not errors_found

    def test_create_schema_validation_results_table_one_error(self):
        """
        Given:
            One error was found in the xsoar_config.json file.
        When:
            Validating the file schema.
        Then:
            Validate that the table has the right data.
        """
        generator = self.errors_iterator([ValidationError('One Error')])
        errors_table, errors_found = XSOARConfigJsonValidator.create_schema_validation_results_table(generator)

        assert errors_found
        assert 'Error Message' in errors_table.field_names

        errors_table_string = errors_table.get_string()
        assert """+---+---------------+
|   | Error Message |
+---+---------------+
| 0 |   One Error   |
+---+---------------+""" == errors_table_string

    def test_create_schema_validation_results_table_multiple_errors(self):
        """
        Given:
            Multiple errors were found in the xsoar_config.json file.
        When:
            Validating the file schema.
        Then:
            Validate that the table has the right data.
        """
        generator = self.errors_iterator([ValidationError('One Error'), ValidationError('Error #2')])
        errors_table, errors_found = XSOARConfigJsonValidator.create_schema_validation_results_table(generator)

        assert errors_found
        assert 'Error Message' in errors_table.field_names

        errors_table_string = errors_table.get_string()
        assert """+---+---------------+
|   | Error Message |
+---+---------------+
| 0 |   One Error   |
| 1 |    Error #2   |
+---+---------------+""" == errors_table_string


class TestSchemaValidation:
    def test_valid_file_content(self):
        """
        Given:
            Valid configuration file.
        When:
            Validating the file schema.
        Then:
            Validates verification returns that the file is valid.
        """
        file_content = {
            "custom_packs": [
                {
                    "id": "id1",
                    "url": "url1"
                }
            ],
            "marketplace_packs": [
                {
                    "id": "id1",
                    "version": "*"
                }
            ],
            "lists": [
                {
                    "name": "List #1",
                    "value": "Value #1"
                }
            ],
            "jobs": [
                {
                    "type": "Unclassified",
                    "name": "name1",
                    "playbookId": "playbook1",
                    "scheduled": True,
                    "recurrent": True,
                    "cronView": True,
                    "cron": "0 10,15 * * *",
                    "startDate": "2021-01-07T15:10:04.000Z",
                    "endingDate": "2021-01-07T15:10:04.000Z",
                    "endingType": "never",
                    "timezoneOffset": -120,
                    "timezone": "Asia/Jerusalem",
                    "shouldTriggerNew": True,
                    "closePrevRun": True
                }
            ]
        }

        validator = XSOARConfigJsonValidator('./')
        # it gets False from failing load the configuration file, which is expected here.
        validator._is_valid = True
        validator.configuration_json = file_content

        is_valid = validator.is_valid_xsoar_config_file()

        assert is_valid

    def test_invalid_file_bad_root_section(self):
        """
        Given:
            Invalid configuration file which has a bad root section.
        When:
            Validating the file schema.
        Then:
            Validates verification returns that the file is invalid.
        """
        file_content = {
            "unexpected_section": [],
            "custom_packs": [
                {
                    "id": "id1",
                    "url": "url1"
                }
            ],
            "marketplace_packs": [
                {
                    "id": "id1",
                    "version": "*"
                }
            ],
            "lists": [
                {
                    "name": "List #1",
                    "value": "Value #1"
                }
            ],
            "jobs": [
                {
                    "type": "Unclassified",
                    "name": "name1",
                    "playbookId": "playbook1",
                    "scheduled": True,
                    "recurrent": True,
                    "cronView": True,
                    "cron": "0 10,15 * * *",
                    "startDate": "2021-01-07T15:10:04.000Z",
                    "endingDate": "2021-01-07T15:10:04.000Z",
                    "endingType": "never",
                    "timezoneOffset": -120,
                    "timezone": "Asia/Jerusalem",
                    "shouldTriggerNew": True,
                    "closePrevRun": True
                }
            ]
        }

        validator = XSOARConfigJsonValidator('./')
        # it gets False from failing load the configuration file, which is expected here.
        validator._is_valid = True
        validator.configuration_json = file_content

        is_valid = validator.is_valid_xsoar_config_file()

        assert not is_valid

    @pytest.mark.parametrize('key1, key2, key3, key4', [
        ('bad', 'id', 'name', 'name'),
        ('id', 'bad', 'name', 'name'),
        ('id', 'id', 'bad', 'name'),
        ('id', 'id', 'name', 'bad'),
    ])
    def test_invalid_file_bad_keys(self, key1, key2, key3, key4):
        """
        Given:
            Invalid configuration file which has a bad key in one of the sections.
        When:
            Validating the file schema.
        Then:
            Validates verification returns that the file is invalid.
        """
        file_content = {
            "custom_packs": [
                {
                    key1: "id1",
                    "url": "url1"
                }
            ],
            "marketplace_packs": [
                {
                    key2: "id1",
                    "version": "*"
                }
            ],
            "lists": [
                {
                    key3: "List #1",
                    "value": "Value #1"
                }
            ],
            "jobs": [
                {
                    "type": "Unclassified",
                    key4: "name1",
                    "playbookId": "playbook1",
                    "scheduled": True,
                    "recurrent": True,
                    "cronView": True,
                    "cron": "0 10,15 * * *",
                    "startDate": "2021-01-07T15:10:04.000Z",
                    "endingDate": "2021-01-07T15:10:04.000Z",
                    "endingType": "never",
                    "timezoneOffset": -120,
                    "timezone": "Asia/Jerusalem",
                    "shouldTriggerNew": True,
                    "closePrevRun": True
                }
            ]
        }

        validator = XSOARConfigJsonValidator('./')
        # it gets False from failing load the configuration file, which is expected here.
        validator._is_valid = True
        validator.configuration_json = file_content

        is_valid = validator.is_valid_xsoar_config_file()

        assert not is_valid


# demisto_sdk/commands/common/tests/field_validator_test.py
from distutils.version import LooseVersion

import pytest
from mock import patch

from demisto_sdk.commands.common.hook_validations.field_base_validator import (
    FieldBaseValidator, GroupFieldTypes)
from demisto_sdk.commands.common.hook_validations.structure import \
    StructureValidator

INDICATOR_GROUP_NUMBER = 2
INCIDENT_GROUP_NUMBER = 0


class TestFieldValidator:
    NAME_SANITY_FILE = {
        'cliName': 'sanityname',
        'name': 'sanity name',
        'id': 'incident',
        'content': True,
    }

    BAD_NAME_1 = {
        'cliName': 'sanityname',
        'name': 'Incident',
        'content': True,
    }

    BAD_NAME_2 = {
        'cliName': 'sanityname',
        'name': 'case',
        'content': True,
    }

    BAD_NAME_3 = {
        'cliName': 'sanityname',
        'name': 'Playbook',
        'content': True,
    }

    GOOD_NAME_4 = {
        'cliName': 'sanityname',
        'name': 'Alerting feature',
        'content': True,
    }

    BAD_NAME_5 = {
        'cliName': 'sanity name',
        'name': 'INciDeNts',
        'content': True,
    }

    INPUTS_NAMES = [
        (NAME_SANITY_FILE, False),
        (BAD_NAME_1, True),
        (BAD_NAME_2, True),
        (BAD_NAME_3, True),
        (GOOD_NAME_4, False),
        (BAD_NAME_5, True)
    ]

    @pytest.mark.parametrize('current_file, answer', INPUTS_NAMES)
    def test_is_valid_name_sanity(self, current_file, answer):
        import os
        import sys
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = FieldBaseValidator(structure, set(), set())
            validator.current_file = current_file

            with open("file", 'w') as temp_out:
                old_stdout = sys.stdout
                sys.stdout = temp_out
                validator.is_valid_name()
                sys.stdout = old_stdout

            with open('file', 'r') as temp_out:
                output = temp_out.read()
                assert ('IF100' in str(output)) is answer
            # remove the temp file
            os.system('rm -rf file')

    CONTENT_1 = {
        'content': True
    }

    CONTENT_BAD_1 = {
        'content': False
    }

    CONTENT_BAD_2 = {
        'something': True
    }

    INPUTS_FLAGS = [
        (CONTENT_1, True),
        (CONTENT_BAD_1, False),
        (CONTENT_BAD_2, False)
    ]

    @pytest.mark.parametrize('current_file, answer', INPUTS_FLAGS)
    def test_is_valid_content_flag_sanity(self, current_file, answer):
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = FieldBaseValidator(structure, set(), set())
            validator.current_file = current_file
            assert validator.is_valid_content_flag() is answer

    SYSTEM_FLAG_1 = {
        'system': False,
        'content': True,
    }

    SYSTEM_FLAG_BAD_1 = {
        'system': True,
        'content': True,
    }

    INPUTS_SYSTEM_FLAGS = [
        (SYSTEM_FLAG_1, True),
        (SYSTEM_FLAG_BAD_1, False)
    ]

    @pytest.mark.parametrize('current_file, answer', INPUTS_SYSTEM_FLAGS)
    def test_is_valid_system_flag_sanity(self, current_file, answer):
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = FieldBaseValidator(structure, set(), set())
            validator.current_file = current_file
            assert validator.is_valid_system_flag() is answer

    VALID_CLINAMES_AND_GROUPS = [
        ("validind", GroupFieldTypes.INCIDENT_FIELD),
        ("validind", GroupFieldTypes.EVIDENCE_FIELD),
        ("validind", GroupFieldTypes.INDICATOR_FIELD)
    ]

    @pytest.mark.parametrize("cliname, group", VALID_CLINAMES_AND_GROUPS)
    def test_is_cliname_is_builtin_key(self, cliname, group):
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            current_file = {"cliName": cliname, "group": group}
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = FieldBaseValidator(structure, set(), set())
            validator.current_file = current_file
            assert validator.is_cli_name_is_builtin_key()

    INVALID_CLINAMES_AND_GROUPS = [
        ("id", GroupFieldTypes.INCIDENT_FIELD),
        ("id", GroupFieldTypes.EVIDENCE_FIELD),
        ("id", GroupFieldTypes.INDICATOR_FIELD)
    ]

    @pytest.mark.parametrize("cliname, group", INVALID_CLINAMES_AND_GROUPS)
    def test_is_cli_name_is_builtin_key_invalid(self, cliname, group):
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            current_file = {"cliName": cliname, "group": group}
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = FieldBaseValidator(structure, set(), {'id'})
            validator.current_file = current_file
            assert not validator.is_cli_name_is_builtin_key()

    VALID_CLINAMES = [
        "agoodid",
        "anot3erg00did",
    ]

    @pytest.mark.parametrize("cliname", VALID_CLINAMES)
    def test_matching_cliname_regex(self, cliname):
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            current_file = {"cliName": cliname}
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = FieldBaseValidator(structure, set(), set())
            validator.current_file = current_file
            assert validator.is_matching_cli_name_regex()

    INVALID_CLINAMES = [
        "invalid cli",
        "invalid_cli",
        "invalid$$cli",
        "לאסליטוב",
    ]

    @pytest.mark.parametrize("cliname", INVALID_CLINAMES)
    def test_matching_cliname_regex_invalid(self, cliname):
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            current_file = {"cliName": cliname}
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = FieldBaseValidator(structure, set(), set())
            validator.current_file = current_file
            assert not validator.is_matching_cli_name_regex()

    @pytest.mark.parametrize("cliname, group", VALID_CLINAMES_AND_GROUPS)
    def test_is_valid_cliname(self, cliname, group):
        current_file = {"cliName": cliname, "group": group}
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = FieldBaseValidator(structure, set(), set())
            validator.current_file = current_file
            assert validator.is_valid_cli_name()

    @pytest.mark.parametrize("cliname, group", INVALID_CLINAMES_AND_GROUPS)
    def test_is_valid_cli_name_invalid(self, cliname, group):
        current_file = {"cliName": cliname, "group": group}
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = FieldBaseValidator(structure, set(), {'id'})
            validator.current_file = current_file
            assert not validator.is_valid_cli_name()

    data_is_valid_version = [
        (-1, True),
        (0, False),
        (1, False),
    ]

    @pytest.mark.parametrize('version, is_valid', data_is_valid_version)
    def test_is_valid_version(self, version, is_valid):
        structure = StructureValidator("")
        structure.current_file = {"version": version}
        validator = FieldBaseValidator(structure, set(), set())
        assert validator.is_valid_version() == is_valid, f'is_valid_version({version}) returns {not is_valid}.'

    IS_FROM_VERSION_CHANGED_NO_OLD = {}  # type: dict[any, any]
    IS_FROM_VERSION_CHANGED_OLD = {"fromVersion": "5.0.0"}
    IS_FROM_VERSION_CHANGED_NEW = {"fromVersion": "5.0.0"}
    IS_FROM_VERSION_CHANGED_NO_NEW = {}  # type: dict[any, any]
    IS_FROM_VERSION_CHANGED_NEW_HIGHER = {"fromVersion": "5.5.0"}
    IS_CHANGED_FROM_VERSION_INPUTS = [
        (IS_FROM_VERSION_CHANGED_NO_OLD, IS_FROM_VERSION_CHANGED_NO_OLD, False),
        (IS_FROM_VERSION_CHANGED_NO_OLD, IS_FROM_VERSION_CHANGED_NEW, True),
        (IS_FROM_VERSION_CHANGED_OLD, IS_FROM_VERSION_CHANGED_NEW, False),
        (IS_FROM_VERSION_CHANGED_NO_OLD, IS_FROM_VERSION_CHANGED_NO_NEW, False),
        (IS_FROM_VERSION_CHANGED_OLD, IS_FROM_VERSION_CHANGED_NEW_HIGHER, True),
    ]

    @pytest.mark.parametrize("current_from_version, old_from_version, answer", IS_CHANGED_FROM_VERSION_INPUTS)
    def test_is_changed_from_version(self, current_from_version, old_from_version, answer):
        structure = StructureValidator("")
        structure.old_file = old_from_version
        structure.current_file = current_from_version
        validator = FieldBaseValidator(structure, set(), set())
        assert validator.is_changed_from_version() is answer
        structure.quite_bc = True
        assert validator.is_changed_from_version() is False

    data_required = [
        (True, False),
        (False, True),
    ]

    @pytest.mark.parametrize('required, is_valid', data_required)
    def test_is_valid_required(self, required, is_valid):
        structure = StructureValidator("")
        structure.current_file = {"required": required}
        validator = FieldBaseValidator(structure, set(), set())
        assert validator.is_valid_required() == is_valid, f'is_valid_required({required})' \
                                                          f' returns {not is_valid}.'

    data_is_changed_type = [
        ('shortText', 'shortText', False),
        ('shortText', 'longText', True),
        ('number', 'number', False),
        ('shortText', 'number', True),
        ('timer', 'timer', False),
        ('timer', 'number', True),
        ('timer', 'shortText', True),
        ('singleSelect', 'singleSelect', False),
        ('singleSelect', 'shortText', True)
    ]

    @pytest.mark.parametrize('current_type, old_type, is_valid', data_is_changed_type)
    def test_is_changed_type(self, current_type, old_type, is_valid):
        structure = StructureValidator("")
        structure.current_file = {"type": current_type}
        structure.old_file = {"type": old_type}
        validator = FieldBaseValidator(structure, set(), set())
        assert validator.is_changed_type() == is_valid, f'is_changed_type({current_type}, {old_type})' \
                                                        f' returns {not is_valid}.'
        structure.quite_bc = True
        assert validator.is_changed_type() is False

    FIELD_NAME1 = {
        'name': 'pack name field',
    }
    FIELD_NAME2 = {
        'name': 'pack prefix field',
    }
    FIELD_NAME3 = {
        'name': 'field',
    }
    PACK_METADATA1 = {'name': 'pack name', 'itemPrefix': ['pack prefix']}
    PACK_METADATA2 = {'name': 'pack name'}

    INPUTS_NAMES2 = [
        (FIELD_NAME1, PACK_METADATA1, False),
        (FIELD_NAME1, PACK_METADATA2, True),
        (FIELD_NAME2, PACK_METADATA1, True),
        (FIELD_NAME2, PACK_METADATA2, False),
        (FIELD_NAME3, PACK_METADATA1, False)
    ]

    @pytest.mark.parametrize('current_file,pack_metadata, answer', INPUTS_NAMES2)
    def test_is_valid_name_prefix(self, current_file, pack_metadata, answer, mocker):
        """
            Given
            - A set of indicator fields

            When
            - Running is_valid_incident_field_name_prefix on it.

            Then
            - Ensure validate fails when the field name does not start with the pack name prefix.
        """
        from demisto_sdk.commands.common.hook_validations import \
            field_base_validator
        with patch.object(StructureValidator, '__init__', lambda a, b: None):
            structure = StructureValidator("")
            structure.current_file = current_file
            structure.old_file = None
            structure.file_path = "random_path"
            structure.is_valid = True
            structure.prev_ver = 'master'
            structure.branch_name = ''
            validator = FieldBaseValidator(structure, set(), set())
            validator.current_file = current_file
            mocker.patch.object(field_base_validator, 'get_pack_metadata', return_value=pack_metadata)
            assert validator.is_valid_field_name_prefix() == answer

    IS_VALID_FROM_VERSION_FIELD = [(LooseVersion('5.5.0'), '5.5.0', True),
                                   (LooseVersion('5.5.0'), '6.0.0', True),
                                   (LooseVersion('6.0.0'), '6.0.0', True),
                                   (LooseVersion('6.0.0'), '6.1.0', True),
                                   (LooseVersion('6.2.0'), '6.0.0', False),
                                   (LooseVersion('6.5.0'), '6.0.0', False),
                                   (LooseVersion('6.5.0'), '6.0.0', False)]

    @pytest.mark.parametrize('min_version, from_version, expected', IS_VALID_FROM_VERSION_FIELD)
    def test_is_valid_from_version_field(self, pack, min_version: LooseVersion, from_version: str, expected: bool):
        """
        Given
        - A field.

        When
        - Validating the expected version is meeting the expected minimal version.

        Then
        - Ensure the expected bool is returned according to whether the condition above is satisfied.
        """
        indicator_field = pack.create_indicator_field('incident_1', {'type': 'html', 'fromVersion': from_version})
        structure = StructureValidator(indicator_field.path)
        validator = FieldBaseValidator(structure, set(), set())
        assert validator.is_valid_from_version_field(min_version, reason_for_min_version='') == expected

    def test_validate_no_empty_selected_values_value_incident(self, pack):
        """
        Given
        - An incident field.

        When
        - Validating its selectValues do no contain empty values.

        Then
        - Ensure false is returned.
        """
        incident_field = pack.create_incident_field('incident_1', {'type': 'some-type', 'cliName': 'testincident',
                                                                   'version': -1, 'fromVersion': '5.0.0',
                                                                   'content': True, 'group': INCIDENT_GROUP_NUMBER,
                                                                   'selectValues': [""]})
        structure = StructureValidator(incident_field.path)
        validator = FieldBaseValidator(structure, {'some-type'}, set())
        assert not validator.does_not_have_empty_select_values()

    def test_validate_no_empty_selected_values_value_indicator(self, pack):
        """
        Given
        - An indicator field.

        When
        - Validating its selectValues do no contain empty values.

        Then
        - Ensure false is returned.
        """
        indicator_field = pack.create_indicator_field('ind_1', {'type': 'some-type', 'cliName': 'testindicator',
                                                                'version': -1, 'fromVersion': '5.0.0',
                                                                'content': True, 'group': INDICATOR_GROUP_NUMBER,
                                                                'selectValues': [""]})
        structure = StructureValidator(indicator_field.path)
        validator = FieldBaseValidator(structure, {'some-type'}, set())
        assert not validator.does_not_have_empty_select_values()
