Mahmoud Al-Ayyoub

193 papers A* 1B 6C 20Misc 3Journal 101Unranked 62
YearRankTypeTitle / Venue / Authors
2025 J jnl
Lang. Resour. Evaluation
Rasha Obeidat, Yara Alharahsheh, Mahmoud Al-Ayyoub, Maram Gharaibeh
2025 J jnl
IEEE Access
Lima Hamad, Qutaiba Ananzeh, Malik Salameh, Abdalrahman Salem, Mohammad Alshboul, Mahmoud Al-Ayyoub
2025 J jnl
Comput.
Roweida Mohammed, Inad Aljarrah, Mahmoud Al-Ayyoub, Ali Fadel
2025 J jnl
IEEE Access
Samah Al-Zaro, Mahmoud Al-Ayyoub, Osama Daifallah Al-Khaleel
2024 J jnl
Wirel. Networks
Ahmad A. Ababneh, Yaser Jararweh, Mahmoud Al-Ayyoub
2024 J jnl
Comput.
Qanita Bani Baker, Ruba A. Al-Hussien, Mahmoud Al-Ayyoub
2024 J jnl
Cogn. Comput.
Rasha Obeidat, Marwa Al-Harbi, Mahmoud Al-Ayyoub, Luay Alawneh
2023 conf
ACIT
Ahmed Mohamed Kamal, Mahmoud Al-Ayyoub, Mohamed Deriche
2023 J jnl
Int. J. Speech Technol.
Ftoon Abu Shaqra, Rehab Duwairi, Mahmoud Al-Ayyoub
2023 J jnl
Simul. Model. Pract. Theory
Adam Kolides, Alyna Nawaz, Anshu Rathor, Denzel Beeman, Muzammil Hashmi, Sana Fatima, David Berdik, Mahmoud Al-Ayyoub, Yaser Jararweh
2023 J jnl
Int. J. Speech Technol.
Suhad Al-Issa, Mahmoud Al-Ayyoub, Osama Daifallah Al-Khaleel, Nouh Sabri Elmitwally
2023 J jnl
Simul. Model. Pract. Theory
Mohammad A. Alsmirat, Ethar El-Qawasmeh, Mahmoud Al-Ayyoub, Yaser Jararweh
2023 J jnl
Clust. Comput.
Tarek Kanan, Ala Mughaid, Riyad Al-Shalabi, Mahmoud Al-Ayyoub, Mohammad W. Elbes, Odai Sadaqa
2023 J jnl
Soft Comput.
Farah Shatnawi, Malak Abdullah, Mahmoud Hammad, Mahmoud Al-Ayyoub
2023 J jnl
Neural Comput. Appl.
Malak Abdullah, Mahmoud Al-Ayyoub, Saif AlRawashdeh, Farah Shatnawi
2023 conf
MCNA
Suhad Al-Issa, Mohammad A. Alshboul, Mahmoud Al-Ayyoub
2023 conf
IDSTA
Ruba Kharsa, Rouaa Alzoubi, Mohammad A. Alsmirat, Mahmoud Al-Ayyoub
2023 J jnl
J. Supercomput.
Emran Al-Bashabsheh, Ahmad Alaiad, Mahmoud Al-Ayyoub, Othman Beni-Yonis, Raed Abu Zitar, Laith Abualigah
2023 conf
MCNA
Shadi AlZu'bi, Omar Badarneh, Bilal Hawashin, Mahmoud Al-Ayyoub, Nouh Alhindawi, Kholoud Alsmearat
2023 J jnl
J. Ambient Intell. Humaniz. Comput.
Luay Alawneh, Mahmoud Al-Ayyoub, Ziad A. Al-Sharif, Ahmed Shatnawi
2023 conf
ISPR (2)
Marwa Al-Harbi, Rasha Obeidat, Mahmoud Al-Ayyoub, Luay Alawneh
2023 J jnl
Int. J. Inf. Sec.
Luay Alawneh, Mohammad Al-Zinati, Mahmoud Al-Ayyoub
2022 J jnl
Int. J. Adv. Intell. Paradigms
Mohammad A. Alsmirat, Khaled Alawneh, Mahmoud Al-Ayyoub, Mays Alshaikhli
2022 J jnl
IEEE Access
Mohammad A. Alsmirat, Nusaiba Al-Mnayyis, Mahmoud Al-Ayyoub, Asma'A Al-Mnayyis
2022 J jnl
Knowl. Based Syst.
Erik Cambria, Akshi Kumar, Mahmoud Al-Ayyoub, Newton Howard
2022 J jnl
ACM Trans. Asian Low Resour. Lang. Inf. Process.
Ali Fadel, Ibraheem Tuffaha, Mahmoud Al-Ayyoub
2022 J jnl
Multim. Tools Appl.
Raffi Al-Qurran, Mahmoud Al-Ayyoub, Ali M. Shatnawi
2022 conf
SemEval@NAACL
Malak Abdullah, Dalya Faraj, Safa Swedat, Jumana Khrais, Mahmoud Al-Ayyoub
2022 J jnl
Comput. J.
Bashar Al-Shboul, Abdullateef Rabab'ah, Mahmoud Al-Ayyoub, Yaser Jararweh, Thar Baker
2022 conf
IDSTA
Hafed Zarzour, Faiz Maazouzi, Mohammad Al-Zinati, Amjad Nusayr, Mohammad A. Alsmirat, Mahmoud Al-Ayyoub, Yaser Jararweh
2022 conf
SemEval@NAACL
Mohammad Habash, Yahya Daqour, Malak Abdullah, Mahmoud Al-Ayyoub
2021 conf
SNAMS
Fatima Alsharadgah, Abdallah Khreishah, Mahmoud Al-Ayyoub, Yaser Jararweh, Guanxiong Liu, Issa Khalil, Muhannad Almutiry, Nasir Saeed
2021 J jnl
PeerJ Comput. Sci.
Ayat Abedalla, Malak Abdullah, Mahmoud Al-Ayyoub, Elhadj Benkhelifa
2021 J jnl
J. Ambient Intell. Humaniz. Comput.
Luay Alawneh, Tamam Alsarhan, Mohammad Al-Zinati, Mahmoud Al-Ayyoub, Yaser Jararweh, Hongtao Lu
2021 B conf
ICTAI
Bashar Talafha, Muhy Eddin Za'ter, Samer Suleiman, Mahmoud Al-Ayyoub, Mohammed N. Al-Kabi
2021 J jnl
Comput. Electr. Eng.
Ashwaq Khalil, Moath H. A. Jarrah, Mahmoud Al-Ayyoub, Yaser Jararweh
2021 J jnl
Pattern Recognit. Lett.
Aisha Al-Sadi, Mahmoud Al-Ayyoub, Yaser Jararweh, Fumie Costen
2021 J jnl
CoRR
Bashar Talafha, Muhy Eddin Za'ter, Samer Suleiman, Mahmoud Al-Ayyoub, Mohammed N. Al-Kabi
2020 B conf
IJCNN
Ayat Abedalla, Malak Abdullah, Mahmoud Al-Ayyoub, Elhadj Benkhelifa
2020 conf
PerCom Workshops
Luay Alawneh, Belal Mohsen, Mohammad Al-Zinati, Ahmed Shatnawi, Mahmoud Al-Ayyoub
2020 conf
SNAMS
Hafed Zarzour, Bashar Al-Shboul, Mahmoud Al-Ayyoub, Yaser Jararweh
2020 J jnl
Clust. Comput.
Luay Alawneh, Mohammed A. Shehab, Mahmoud Al-Ayyoub, Yaser Jararweh, Ziad A. Al-Sharif
2020 J jnl
J. Univers. Comput. Sci.
Mohammad Muhairat, Shadi AlZu'bi, Bilal Hawashin, Mohammad W. Elbes, Mahmoud Al-Ayyoub
2020 J jnl
Future Gener. Comput. Syst.
Yaser Jararweh, Mahmoud Al-Ayyoub, Du'a Al-Zoubi, Elhadj Benkhelifa
2020 conf
ASIST
Mohammed Nour AlRashdan, Malak Abdullah, Mahmoud Al-Ayyoub, Yaser Jararweh
2020 J jnl
Concurr. Comput. Pract. Exp.
Yaser Jararweh, Mohammed A. Shehab, Qussai Yaseen, Mahmoud Al-Ayyoub
2020 conf
WMT@EMNLP
Roweida Mohammed, Mahmoud Al-Ayyoub, Malak Abdullah
2020 conf
SemEval@COLING
Ali Fadel, Mahmoud Al-Ayyoub, Erik Cambria
2020 conf
FIRE
Ali Fadel, Husam Musleh, Ibraheem Tuffaha, Mahmoud Al-Ayyoub, Yaser Jararweh, Elhadj Benkhelifa, Paolo Rosso
2020 conf
FIRE (Working Notes)
Ali Fadel, Husam Musleh, Ibraheem Tuffaha, Mahmoud Al-Ayyoub, Yaser Jararweh, Elhadj Benkhelifa, Paolo Rosso
2020 J jnl
Pattern Recognit. Lett.
Shadi AlZu'bi, Mohammed A. Shehab, Mahmoud Al-Ayyoub, Yaser Jararweh, Brij B. Gupta
2020 J jnl
CoRR
Abdalraheem Alsmadiv, Shadi AlZu'bi, Mahmoud Al-Ayyoub, Yaser Jararweh
2020 conf
IDSTA
Ali Fadel, Ibraheem Tuffaha, Mahmoud Al-Ayyoub, Yaser Jararweh
2020 J jnl
CoRR
Ayat Abedalla, Malak Abdullah, Mahmoud Al-Ayyoub, Elhadj Benkhelifa
2020 conf
CLEF (Working Notes)
Aisha Al-Sadi, Hana' Al-Theiabat, Mahmoud Al-Ayyoub
2020 J jnl
Inf. Process. Manag.
Wael Farhan, Bashar Talafha, Analle Abuammar, Ruba Jaikat, Mahmoud Al-Ayyoub, Ahmad Bisher Tarakji, Anas Toma
2019 conf
SNAMS
Shadi AlZu'bi, Abdalraheem Alsmadiv, Sokyna Al-Qatawneh, Mahmoud Al-Ayyoub, Bilal Hawashin, Yaser Jararweh
2019 J jnl
Inf. Process. Manag.
Mahmoud Al-Ayyoub, Abed Allah Khamaiseh, Yaser Jararweh, Mohammed Naji Al-Kabi
2019 J jnl
CoRR
Ghadeer Al-Bdour, Raffi Al-Qurran, Mahmoud Al-Ayyoub, Ali M. Shatnawi
2019 J jnl
Inf. Process. Manag.
Yaser Jararweh, Mahmoud Al-Ayyoub, Elhadj Benkhelifa
2019 J jnl
CoRR
Ali Fadel, Ibraheem Tuffaha, Bara' Al-Jawarneh, Mahmoud Al-Ayyoub
2019 conf
IEEE SENSORS
Tamam Alsarhan, Luay Alawneh, Mohammad Al-Zinati, Mahmoud Al-Ayyoub
2019 J jnl
Multim. Tools Appl.
Mahmoud Al-Ayyoub, Esra'a Alawneh, Yaser Jararweh, Mohammad Al-Smadi, Brij B. Gupta
2019 J jnl
Int. J. Commun. Networks Inf. Secur.
Alaa Al-Nusirat, Feras Hanandeh, Mohammad Kamel Kharabsheh, Mahmoud Al-Ayyoub, Nahla Al-dhfairi
2019 J jnl
Inf. Process. Manag.
Mohammad Al-Smadi, Mahmoud Al-Ayyoub, Yaser Jararweh, Omar Qawasmeh
2019 C conf
AICCSA
Yousef Shatnawi, Mohammad A. Alsmirat, Mahmoud Al-Ayyoub
2019 J jnl
Multim. Tools Appl.
Mohammad A. Alsmirat, Fatima Abdalla Al-Alem, Mahmoud Al-Ayyoub, Yaser Jararweh, Brij B. Gupta
2019 J jnl
Comput. Electr. Eng.
Mahmoud Al-Ayyoub, Mohammed Nasser Al-Andoli, Yaser Jararweh, Mohammad Al-Smadi, Brij B. Gupta
2019 J jnl
Multim. Tools Appl.
Yaser Jararweh, Mahmoud Al-Ayyoub, Maged Fakirah, Luay Alawneh, Brij B. Gupta
2019 conf
AffCon@AAAI
Bashar Talafha, Mahmoud Al-Ayyoub
2019 conf
CLEF (Working Notes)
Aisha Al-Sadi, Bashar Talafha, Mahmoud Al-Ayyoub, Yaser Jararweh, Fumie Costen
2019 conf
SNAMS
Ayat Abedalla, Ali Fadel, Ibraheem Tuffaha, Hani Al-Omari, Mohammad Omari, Malak Abdullah, Mahmoud Al-Ayyoub
2019 conf
SNAMS
Shadi AlZu'bi, Omar Badarneh, Bilal Hawashin, Mahmoud Al-Ayyoub, Nouh Alhindawi, Yaser Jararweh
2019 conf
WAT@EMNLP-IJCNLP
Ali Fadel, Ibraheem Tuffaha, Bara' Al-Jawarneh, Mahmoud Al-Ayyoub
2019 J jnl
CoRR
Ali Fadel, Ibraheem Tuffaha, Bara' Al-Jawarneh, Mahmoud Al-Ayyoub
2019 J jnl
CoRR
Wegdan A. Hussien, Mahmoud Al-Ayyoub, Yahya M. Tashtoush, Mohammed Al-Kabi
2019 C conf
AICCSA
Bashar Talafha, Mahmoud Al-Ayyoub, Analle Abuammar, Yaser Jararweh
2019 conf
ANT/EDI40
Ftoon Abu Shaqra, Rehab Duwairi, Mahmoud Al-Ayyoub
2019 conf
WANLP@ACL 2019
Bashar Talafha, Ali Fadel, Mahmoud Al-Ayyoub, Yaser Jararweh, Mohammad Al-Smadi, Patrick Juola
2019 J jnl
CoRR
Ali Fadel, Ibraheem Tuffaha, Mahmoud Al-Ayyoub
2019 conf
FiCloud
Ftoon Abu Shaqra, Rehab Duwairi, Mahmoud Al-Ayyoub
2019 J jnl
Clust. Comput.
Ali M. Shatnawi, Yathrip AlZahouri, Mohammed A. Shehab, Yaser Jararweh, Mahmoud Al-Ayyoub
2019 J jnl
Ad Hoc Sens. Wirel. Networks
Nour Abu Riala, Yaser Jararweh, Mahmoud Al-Ayyoub, Haythem Bany Salameh
2019 conf
SNAMS
Izzat Alsmadi, Mahmoud Al-Ayyoub, Mohammad A. Alsmirat, Yaser Jararweh
2019 C conf
AICCSA
Bashar Talafha, Mohammad Al-Smadi, Mahmoud Al-Ayyoub, Yaser Jararweh, Patrick Juola
2019 J jnl
Int. J. Mach. Learn. Cybern.
Mohammad Al-Smadi, Bashar Talafha, Mahmoud Al-Ayyoub, Yaser Jararweh
2018 C conf
AICCSA
Hana' Al-Theiabat, Mahmoud Al-Ayyoub, Mohammad A. Alsmirat, Monther Aldwairi
2018 conf
SNAMS
Shadi AlZu'bi, Bilal Hawashin, Mohammad EIBes, Mahmoud Al-Ayyoub
2018 J jnl
Multim. Tools Appl.
Mahmoud Al-Ayyoub, Shadi AlZu'bi, Yaser Jararweh, Mohammed A. Shehab, Brij Bhooshan Gupta
2018 J jnl
J. Ambient Intell. Humaniz. Comput.
Mahmoud Al-Ayyoub, Bassam Arkok, Yaser Jararweh
2018 J jnl
Digit. Investig.
Raddad Abooraig, Shadi AlZu'bi, Tarek Kanan, Bilal Hawashin, Mahmoud Al-Ayyoub, Ismail Hmeidi
2018 conf
INFOCOM Workshops
Mohammad Wardat, Mahmoud Al-Ayyoub, Yaser Jararweh, Abdallah A. Khreishah
2018 J jnl
Multim. Tools Appl.
Qussai Yaseen, Monther Aldwairi, Yaser Jararweh, Mahmoud Al-Ayyoub, Brij B. Gupta
2018 J jnl
Comput. Electr. Eng.
Jafar Albadarneh, Yaser Jararweh, Mahmoud Al-Ayyoub, Ramon dos Reis Fontes, Mohammad Al-Smadi, Christian Esteve Rothenberg
2018 J jnl
J. Comput. Sci.
Mohammad Al-Smadi, Omar Qawasmeh, Mahmoud Al-Ayyoub, Yaser Jararweh, Brij B. Gupta
2018 J jnl
J. Comput. Sci.
Mahmoud Al-Ayyoub, Aya Nuseir, Kholoud Alsmearat, Yaser Jararweh, Brij B. Gupta
2018 conf
SNAMS
Maad Ebrahim, Mahmoud Al-Ayyoub, Mohammad A. Alsmirat
2018 J jnl
Sustain. Comput. Informatics Syst.
Yaser Jararweh, Manar Bani Issa, Mustafa Daraghmeh, Mahmoud Al-Ayyoub, Mohammad A. Alsmirat
2018 conf
ICSC
Omar Badarneh, Mahmoud Al-Ayyoub, Nouh Alhindawi, Lo'ai Ali Tawalbeh, Yaser Jararweh
2018 J jnl
Simul. Model. Pract. Theory
Luay Alawneh, Emad Rawashdeh, Mahmoud Al-Ayyoub, Yaser Jararweh
2018 conf
CLEF (Working Notes)
Bashar Talafha, Mahmoud Al-Ayyoub
2018 J jnl
Trans. Emerg. Telecommun. Technol.
Qussai Yaseen, Firas AlBalas, Yaser Jararweh, Mahmoud Al-Ayyoub
2018 conf
ACIT
Raffi Al-Qurran, Mahmoud Al-Ayyoub, Ali M. Shatnawi
2018 J jnl
Future Gener. Comput. Syst.
Mahmoud Al-Ayyoub, Muneera Al-Quraan, Yaser Jararweh, Elhadj Benkhelifa, Salim Hariri
2018 J jnl
Appl. Soft Comput.
Mahmoud Al-Ayyoub, Abdullateef Rabab'ah, Yaser Jararweh, Mohammed Naji Al-Kabi, Brij Bhooshan Gupta
2018 C conf
AICCSA
Yousef Shatnawi, Mohammad A. Alsmirat, Mahmoud Al-Ayyoub, Monther Aldwairi
2018 J jnl
Int. Arab J. Inf. Technol.
Mahmoud Al-Ayyoub, Nour Alhuda Damer, Ismail Hmeidi
2017 J jnl
Simul. Model. Pract. Theory
Ala Darabseh, Mahmoud Al-Ayyoub, Yaser Jararweh, Elhadj Benkhelifa, Mladen A. Vouk, Andy J. Rindos
2017 J jnl
Multim. Tools Appl.
Mohammad A. Alsmirat, Yaser Jararweh, Mahmoud Al-Ayyoub, Mohammed A. Shehab, Brij Bhooshan Gupta
2017 J jnl
J. Supercomput.
Mohammed A. Shehab, Mahmoud Al-Ayyoub, Yaser Jararweh, Moath H. A. Jarrah
2017 J jnl
Int. J. Web Inf. Syst.
Mahmoud Al-Ayyoub, Ahmed Alwajeeh, Ismail Hmeidi
2017 J jnl
J. Inf. Secur. Appl.
Kholoud Alsmearat, Mahmoud Al-Ayyoub, Riyad Al-Shalabi, Ghassan Kanaan
2017 C conf
AICCSA
Mohammad A. Alsmirat, Ethar El-Qawasmeh, Mahmoud Al-Ayyoub, Nour Alhuda Damer, Yaser Jararweh
2017 conf
FMEC
Siyakha N. Mthunzi, Elhadj Benkhelifa, Yaser Jararweh, Mahmoud Al-Ayyoub
2017 conf
FMEC
Ahmad A. Ababneh, Mahmoud Al-Ayyoub, Yaser Jararweh, Mohammad A. Alsmirat
2017 J jnl
Pers. Ubiquitous Comput.
Yaser Jararweh, Mahmoud Al-Ayyoub, Muneera Al-Quraan, Lo'ai Ali Tawalbeh, Elhadj Benkhelifa
2017 conf
EUSPN/ICTH
Shadi AlZu'bi, Mahmoud Al-Ayyoub, Yaser Jararweh, Mohammed A. Shehab
2017 J jnl
J. Ambient Intell. Humaniz. Comput.
Mahmoud Al-Ayyoub, Yaser Jararweh, Abdullateef Rabab'ah, Monther Aldwairi
2017 J jnl
J. Univers. Comput. Sci.
Mahmoud Al-Ayyoub, Huda Al-Sarhan, Majd Al-So'ud, Mohammad Al-Smadi, Yaser Jararweh
2017 J jnl
Int. Arab J. Inf. Technol.
Aya Nuseir, Mahmoud Al-Ayyoub, Mohammed Al-Kabi, Ghassan G. Kanaan, Riyad Al-Shalabi
2017 J jnl
Multim. Tools Appl.
Moath H. A. Jarrah, Muneera Al-Quraan, Yaser Jararweh, Mahmoud Al-Ayyoub
2017 J jnl
Inf. Process. Manag.
Mohammad Al-Smadi, Zain Jaradat, Mahmoud Al-Ayyoub, Yaser Jararweh
2017 conf
SDS
Jafar Albadarneh, Yaser Jararweh, Mahmoud Al-Ayyoub, Mohammad Al-Smadi, Ramon dos Reis Fontes
2017 J jnl
Comput. J.
Yaser Jararweh, Mohammad A. Alsmirat, Mahmoud Al-Ayyoub, Elhadj Benkhelifa, Ala Darabseh, Brij Bhooshan Gupta, Ahmad Doulat
2017 J jnl
Ann. des Télécommunications
Yaser Jararweh, Mahmoud Al-Ayyoub, Lo'ai Tawalbeh, Ala Darabseh, Houbing Song
2017 B conf
MASS
Manar Bani Issa, Mustafa Daraghmeh, Yaser Jararweh, Mahmoud Al-Ayyoub, Mohammad A. Alsmirat, Elhadj Benkhelifa
2016 conf
FAS*W@SASO/ICCAC
Qussai Yaseen, Firas AlBalas, Yaser Jararweh, Mahmoud Al-Ayyoub
2016 conf
ICMCS
Mahmoud Al-Ayyoub, Shadi M. AlZu'bi, Yaser Jararweh, Mohammad A. Alsmirat
2016 C conf
AICCSA
Duaa Ekhtoom, Mahmoud Al-Ayyoub, Mohammed I. Al-Saleh, Mohammad A. Alsmirat, Ismail Hmeidi
2016 J jnl
Int. J. Web Inf. Syst.
Ismail Hmeidi, Mahmoud Al-Ayyoub, Nizar A. Mahyoub, Mohammed A. Shehab
2016 J jnl
Int. Arab J. Inf. Technol.
Mohammed Al-Kabi, Mahmoud Al-Ayyoub, Izzat Alsmadi, Heider Wahsheh
2016 J jnl
J. Univers. Comput. Sci.
Mohammad Al-Smadi, Mahmoud Al-Ayyoub, Huda Al-Sarhan, Yaser Jararweh
2016 conf
ICITST
Mohammad Al-Smadi, Omar Qawasmeh, Bashar Talafha, Mahmoud Al-Ayyoub, Yaser Jararweh, Elhadj Benkhelifa
2016 C conf
AICCSA
Abdullateef Rabab'ah, Mahmoud Al-Ayyoub, Yaser Jararweh, Monther Aldwairi
2016 C conf
AICCSA
Mohammed Naji Al-Kabi, Areej A. Al-Qwaqenah, Amal H. Gigieh, Kholoud Alsmearat, Mahmoud Al-Ayyoub, Izzat Mahmoud Alsmadi
2016 conf
ANT/SEIT
Muhannad Quwaider, Mahmoud Al-Ayyoub, Yaser Jararweh
2016 J jnl
Ann. des Télécommunications
Qutaibah Althebyan, Qussai Yaseen, Yaser Jararweh, Mahmoud Al-Ayyoub
2016 C conf
AICCSA
Mahmoud Al-Ayyoub, Qussai Yaseen, Mohammed A. Shehab, Yaser Jararweh, Firas AlBalas, Elhadj Benkhelifa
2016 conf
MobileCloud
Elhadj Benkhelifa, Thomas Welsh, Lo'ai Ali Tawalbeh, Abdallah Khreishah, Yaser Jararweh, Mahmoud Al-Ayyoub
2016 conf
FNC/MobiSPC
Elhadj Benkhelifa, Thomas Welsh, Lo'ai Tawalbeh, Yaser Jararweh, Mahmoud Al-Ayyoub
2016 C conf
AICCSA
Fatima Abdalla Al-Alem, Mohammad A. Alsmirat, Mahmoud Al-Ayyoub
2016 C conf
AICCSA
Shadi AlZu'bi, Mohammed A. Shehab, Mahmoud Al-Ayyoub, Elhadj Benkhelifa, Yaser Jararweh
2016 conf
FiCloud Workshops
Muneera Al-Quraan, Mahmoud Al-Ayyoub, Yaser Jararweh, Lo'ai Tawalbeh, Elhadj Benkhelifa
2016 conf
FiCloud
Ala Darabseh, Nikolaos M. Freris, Yaser Jararweh, Mahmoud Al-Ayyoub
2016 conf
IC2E Workshops
Yaser Jararweh, Ahmad Doulat, Ala Darabseh, Mohammad A. Alsmirat, Mahmoud Al-Ayyoub, Elhadj Benkhelifa
2016 conf
SemEval@NAACL-HLT
Maria Pontiki, Dimitris Galanis, Haris Papageorgiou, Ion Androutsopoulos, Suresh Manandhar, Mohammad Al-Smadi, Mahmoud Al-Ayyoub, Yanyan Zhao, Bing Qin, Orphée De Clercq, Véronique Hoste, Marianna Apidianaki, Xavier Tannier, Natalia V. Loukachevitch, Evgeniy V. Kotelnikov, Núria Bel, Salud María Jiménez-Zafra, Gülsen Eryigit
2016 J jnl
Future Gener. Comput. Syst.
Yaser Jararweh, Mahmoud Al-Ayyoub, Ala Darabseh, Elhadj Benkhelifa, Mladen A. Vouk, Andy J. Rindos
2016 conf
ICT
Yaser Jararweh, Ahmad Doulat, Omar AlQudah, Ejaz Ahmed, Mahmoud Al-Ayyoub, Elhadj Benkhelifa
2016 J jnl
Int. J. Cloud Comput.
Mahmoud Al-Ayyoub, Mustafa Daraghmeh, Yaser Jararweh, Qutaibah Althebyan
2016 conf
FiCloud Workshops
Mohammed Nasser Al-Andoli, Mahmoud Al-Ayyoub, Mohammad Al-Smadi, Yaser Jararweh, Elhadj Benkhelifa
2016 J jnl
Int. J. Inf. Technol. Web Eng.
Mohammad Al-Smadi, Islam Obaidat, Mahmoud Al-Ayyoub, Rami Mohawesh, Yaser Jararweh
2016 conf
ICITST
Abdullateef Rabab'ah, Mahmoud Al-Ayyoub, Mohammed A. Shehab, Yaser Jararweh, Bernard J. Jansen
2016 J jnl
J. Syst. Softw.
Mahmoud Al-Ayyoub, Yaser Jararweh, Ahmad Doulat, Haythem Ahmad Bany Salameh, Ahmad Al Abed Al Aziz, Mohammad A. Alsmirat, Abdallah Khreishah
2015 J jnl
J. Supercomput.
Mahmoud Al-Ayyoub, Ansam M. Abu-Dalo, Yaser Jararweh, Moath H. A. Jarrah, Mohammad Al-Sa'd
2015 J jnl
Inf. Syst.
Moath H. A. Jarrah, Manar Jaradat, Yaser Jararweh, Mahmoud Al-Ayyoub, Abdelkader Bousselham
2015 C conf
AICCSA
Maged Fakirah, Mohammed A. Shehab, Yaser Jararweh, Mahmoud Al-Ayyoub
2015 J jnl
J. Inf. Sci.
Ismail Hmeidi, Mahmoud Al-Ayyoub, Nawaf A. Abdulla, Abdalrahman A. Almodawar, Raddad Abooraig, Nizar A. Mahyoub
2015 C conf
AICCSA
Kholoud Alsmearat, Mohammed A. Shehab, Mahmoud Al-Ayyoub, Riyad Al-Shalabi, Ghassan Kanaan
2015 J jnl
Concurr. Comput. Pract. Exp.
Qutaibah Althebyan, Yaser Jararweh, Qussai Yaseen, Omar AlQudah, Mahmoud Al-Ayyoub
2015 conf
ANT/SEIT
Muhannad Quwaider, Yaser Jararweh, Mahmoud Al-Ayyoub, Rehab Duwairi
2015 J jnl
Int. J. Soc. Netw. Min.
Mahmoud Al-Ayyoub, Safa Bani Essa, Izzat Alsmadi
2015 J jnl
Clust. Comput.
Mahmoud Al-Ayyoub, Yaser Jararweh, Mustafa Daraghmeh, Qutaibah Althebyan
2015 J jnl
Simul. Model. Pract. Theory
Mahmoud Al-Ayyoub, Mohammad Wardat, Yaser Jararweh, Abdallah Khreishah
2015 conf
FiCloud
Mahmoud Al-Ayyoub, Yaser Jararweh, Lo'ai Ali Tawalbeh, Elhadj Benkhelifa, Anas Basalamah
2015 conf
FiCloud
Ala Darabseh, Mahmoud Al-Ayyoub, Yaser Jararweh, Elhadj Benkhelifa, Mladen A. Vouk, Andy J. Rindos
2015 J jnl
J. Ambient Intell. Humaniz. Comput.
Yaser Jararweh, Mahmoud Al-Ayyoub, Ala Darabseh, Elhadj Benkhelifa, Mladen A. Vouk, Andy J. Rindos
2015 conf
ICC Workshops
Ala Darabseh, Mahmoud Al-Ayyoub, Yaser Jararweh, Elhadj Benkhelifa, Mladen A. Vouk, Andy J. Rindos
2015 B conf
IC2E
Ala Darabseh, Mahmoud Al-Ayyoub, Yaser Jararweh, Elhadj Benkhelifa, Mladen A. Vouk, Andy J. Rindos
2015 J jnl
Int. J. Grid High Perform. Comput.
Yaser Jararweh, Mahmoud Al-Ayyoub, Ahmad Doulat, Ahmad Al Abed Al Aziz, Haythem Bany Salameh, Abdallah Khreishah
2015 C conf
AICCSA
Yaser Jararweh, Ala Darabseh, Mahmoud Al-Ayyoub, Abdelkader Bousselham, Elhadj Benkhelifa
2015 conf
FNC/MobiSPC
Manar Jaradat, Moath H. A. Jarrah, Abdelkader Bousselham, Yaser Jararweh, Mahmoud Al-Ayyoub
2015 Misc conf
UCC
Mohammad Al-Smadi, Mahmoud Al-Ayyoub, Huda Al-Sarhan, Yaser Jararweh
2015 Misc conf
UCC
Jafar Albadarneh, Bashar Talafha, Mahmoud Al-Ayyoub, Belal Zaqaibeh, Mohammad Al-Smadi, Yaser Jararweh, Elhadj Benkhelifa
2014 C conf
AICCSA
Kholoud Alsmearat, Mahmoud Al-Ayyoub, Muneer O. Bani Yassein
2014 J jnl
Int. J. Big Data Intell.
Nawaf A. Abdulla, Mahmoud Al-Ayyoub, Mohammed Naji Al-Kabi
2014 C conf
AICCSA
Kholoud Alsmearat, Mahmoud Al-Ayyoub, Riyad Al-Shalabi
2014 conf
FiCloud
Nawaf A. Abdulla, Roa'a Majdalawi, Salwa Mohammed, Mahmoud Al-Ayyoub, Mohammed Al-Kabi
2014 J jnl
Simul. Model. Pract. Theory
Yaser Jararweh, Moath H. A. Jarrah, Mazen Kharbutli, Zakarea Alshara, Mohammed Noraden Alsaleh, Mahmoud Al-Ayyoub
2014 Misc conf
UCC
Bassam Arkok, Mahmoud Al-Ayyoub, Yaser Jararweh, Izzat Alsmadi, Elhadj Benkhelifa
2014 C conf
AICCSA
Haneen Ta'amneh, Ehsan Abu Keshek, Manar Bani Issa, Mahmoud Al-Ayyoub, Yaser Jararweh
2014 conf
FiCloud
Mosab Faqeeh, Nawaf A. Abdulla, Mahmoud Al-Ayyoub, Yaser Jararweh, Muhannad Quwaider
2014 B conf
IC2E
Yaser Jararweh, Mahmoud Al-Ayyoub, Ahmad Doulat, Ahmad Al Abed Al Aziz, Haythem Bany Salameh, Abdallah Khreishah
2014 B conf
WiMob
Ahmad Doulat, Ahmad Al Abed Al Aziz, Mahmoud Al-Ayyoub, Yaser Jararweh, Haythem Bany Salameh, Abdallah Khreishah
2014 C conf
AICCSA
Yaser Jararweh, Izzat Alsmadi, Mahmoud Al-Ayyoub, Darrel Jenerette
2014 conf
FiCloud
Mohammad Wardat, Mahmoud Al-Ayyoub, Yaser Jararweh, Abdallah Khreishah
2014 C conf
AICCSA
Mosab Faqeeh, Mahmoud Al-Ayyoub, Mohammad Wardat, Ismail Hmeidi, Yaser Jararweh
2014 J jnl
Int. J. Inf. Technol. Web Eng.
Nawaf A. Abdulla, Nizar A. Ahmed, Mohammed A. Shehab, Mahmoud Al-Ayyoub, Mohammed Naji Al-Kabi, Saleh Y. Al-Rifai
2014 J jnl
IEEE/ACM Trans. Netw.
Mahmoud Al-Ayyoub, Himanshu Gupta
2013 conf
ICITST
Mohammed Naji Al-Kabi, Nawaf A. Abdulla, Mahmoud Al-Ayyoub
2013 J jnl
J. Multim. Process. Technol.
Mahmoud Al-Ayyoub, Ismail Hmeidi, Haya Rababah
2011 conf
Computer Vision and Image Analysis of Art
Mahmoud Al-Ayyoub, Mohammad T. Irfan, David G. Stork
2011 A* conf
INFOCOM
Mahmoud Al-Ayyoub, Himanshu Gupta
2010 J jnl
IEEE Trans. Mob. Comput.
Mahmoud Al-Ayyoub, Himanshu Gupta
tests/unit/test_apk_extractors.py
← Index tests/unit/test_apk_extractors.py python
"""
Unit tests for APK extractors.

Tests tag(), get_clickhouse_table(), extract(), and prepare_export_data()
for all 8 APK extractors. Uses mocked androguard APK objects.
"""
import pytest
import os
import hashlib
import tempfile
import zipfile
from dataclasses import asdict
from unittest.mock import MagicMock, patch, PropertyMock

pytestmark = [pytest.mark.unit, pytest.mark.apk]


# ============================================================================
# Helpers
# ============================================================================

def _create_fake_apk(tmp_path, files=None):
    """Create a minimal valid ZIP file that pretends to be an APK.

    Args:
        tmp_path: Directory to write the fake APK into.
        files: Dict of {filename: content_bytes} to include in the ZIP.

    Returns:
        Path to the fake APK file.
    """
    apk_path = os.path.join(tmp_path, "test.apk")
    default_files = {
        "AndroidManifest.xml": b"<manifest/>",
        "classes.dex": b"dex\n035\x00" + b"\x00" * 100,
        "res/drawable/icon.png": b"\x89PNG" + b"\x00" * 50,
    }
    if files:
        default_files.update(files)

    with zipfile.ZipFile(apk_path, "w") as zf:
        for name, data in default_files.items():
            zf.writestr(name, data)
    return apk_path


def _make_mock_apk():
    """Create a mock androguard APK object with common return values."""
    mock_apk = MagicMock()
    mock_apk.get_package.return_value = "com.example.test"
    mock_apk.get_app_name.return_value = "Test App"
    mock_apk.get_androidversion_code.return_value = "10"
    mock_apk.get_androidversion_name.return_value = "1.2.3"
    mock_apk.get_min_sdk_version.return_value = "21"
    mock_apk.get_target_sdk_version.return_value = "33"
    mock_apk.get_effective_target_sdk_version.return_value = "33"
    mock_apk.get_main_activity.return_value = "com.example.test.MainActivity"
    mock_apk.get_attribute_value.return_value = None
    mock_apk.get_libraries.return_value = []
    mock_apk.get_files.return_value = [
        "AndroidManifest.xml", "classes.dex",
        "res/drawable/icon.png", "lib/armeabi-v7a/libnative.so",
        "assets/data.bin",
    ]
    mock_apk.get_permissions.return_value = [
        "android.permission.INTERNET",
        "android.permission.CAMERA",
    ]
    mock_apk.get_declared_permissions.return_value = {}
    mock_apk.get_activities.return_value = ["com.example.test.MainActivity"]
    mock_apk.get_services.return_value = ["com.example.test.MyService"]
    mock_apk.get_receivers.return_value = ["com.example.test.BootReceiver"]
    mock_apk.get_providers.return_value = []
    mock_apk.get_uses_implied_permission_list.return_value = []
    mock_apk.is_valid_APK.return_value = True
    mock_apk.get_certificates.return_value = []
    mock_apk.get_certificates_v1.return_value = []
    mock_apk.get_certificates_v2.return_value = []
    mock_apk.get_certificates_v3.return_value = []
    mock_apk.get_signature_names.return_value = []
    mock_apk.is_signed_v1.return_value = True
    mock_apk.is_signed_v2.return_value = True
    mock_apk.is_signed_v3.return_value = False
    return mock_apk


@pytest.fixture
def fake_apk_env(tmp_path):
    """Create a fake APK file and mock APK object."""
    apk_path = _create_fake_apk(str(tmp_path))
    mock_apk = _make_mock_apk()
    mock_logger = MagicMock()
    mock_exporters = [MagicMock()]
    return apk_path, mock_apk, mock_logger, mock_exporters


# ============================================================================
# APKFeaturesExtractor
# ============================================================================

class TestAPKFeaturesExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_features"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_features"

    def test_extract_returns_dataclass(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor
        from redb.models.dataclasses import APKFeatures

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()

        assert isinstance(result, APKFeatures)
        assert result.package_name == "com.example.test"
        assert result.app_name == "Test App"
        assert result.version_code == 10
        assert result.version_name == "1.2.3"
        assert result.min_sdk_version == 21
        assert result.target_sdk_version == 33
        assert result.main_activity == "com.example.test.MainActivity"

    def test_extract_detects_native_code(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result.has_native_code is True
        assert result.has_assets is True

    def test_extract_invalid_apk_returns_none(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.is_valid_APK.return_value = False
        # APKExtractor sets self.apk to the mock_apk directly; _is_valid_apk checks self.apk is not None
        # For invalid APK, set apk to None
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=None)
        # Override _is_valid_apk to simulate parsing failure
        ext.apk = None
        result = ext.extract()
        assert result is None

    def test_extract_handles_missing_version_code(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.get_androidversion_code.return_value = None
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result.version_code == 0

    def test_prepare_export_data_clickhouse(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        ext.extract()
        result = ext.prepare_export_data("ClickHouseExporter")
        assert result is not None
        data, column_names, column_type_names = result
        assert "sha256" in column_names
        assert "package_name" in column_names
        assert "analysis_date" in column_names
        assert len(column_names) == len(column_type_names)
        assert len(data) == 1  # One row
        assert len(data[0]) == len(column_names)

    def test_prepare_export_data_no_extract(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        # Don't call extract() first
        result = ext.prepare_export_data("ClickHouseExporter")
        assert result is None


# ============================================================================
# APKManifestExtractor
# ============================================================================

class TestAPKManifestExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_manifest import APKManifestExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKManifestExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_manifest"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_manifest import APKManifestExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKManifestExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_manifest"

    def test_extract_returns_dataclass(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_manifest import APKManifestExtractor
        from redb.models.dataclasses import APKManifest

        apk_path, mock_apk, logger, exporters = fake_apk_env
        # Setup intent filter mocking
        mock_apk.get_intent_filters.return_value = {}
        ext = APKManifestExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()

        assert isinstance(result, APKManifest)
        assert result.activity_count == 1
        assert result.service_count == 1
        assert result.receiver_count == 1
        assert result.provider_count == 0

    def test_extract_counts_components(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_manifest import APKManifestExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.get_activities.return_value = ["A1", "A2", "A3"]
        mock_apk.get_services.return_value = ["S1", "S2"]
        mock_apk.get_intent_filters.return_value = {}
        ext = APKManifestExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result.activity_count == 3
        assert result.service_count == 2


# ============================================================================
# APKPermissionsExtractor
# ============================================================================

class TestAPKPermissionsExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_permissions import APKPermissionsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKPermissionsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_permissions"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_permissions import APKPermissionsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKPermissionsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_permissions"

    def test_extract_returns_dataclass(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_permissions import APKPermissionsExtractor
        from redb.models.dataclasses import APKPermissions

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKPermissionsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()

        assert isinstance(result, APKPermissions)
        assert result.total_permission_count == 2
        assert "android.permission.INTERNET" in result.permissions
        assert "android.permission.CAMERA" in result.permissions

    def test_extract_classifies_dangerous(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_permissions import APKPermissionsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKPermissionsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        # CAMERA is dangerous, INTERNET is normal
        assert result.dangerous_permission_count >= 1
        assert "android.permission.CAMERA" in result.dangerous_permissions

    def test_extract_no_permissions(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_permissions import APKPermissionsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.get_permissions.return_value = []
        ext = APKPermissionsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result.total_permission_count == 0
        assert result.dangerous_permission_count == 0


# ============================================================================
# APKSignatureExtractor
# ============================================================================

class TestAPKSignatureExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_signature import APKSignatureExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKSignatureExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_signature"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_signature import APKSignatureExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKSignatureExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_signature"

    def test_extract_unsigned_apk(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_signature import APKSignatureExtractor
        from redb.models.dataclasses import APKCodeSigningInfo

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.is_signed_v1.return_value = False
        mock_apk.is_signed_v2.return_value = False
        mock_apk.is_signed_v3.return_value = False
        ext = APKSignatureExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()

        assert isinstance(result, APKCodeSigningInfo)

    def test_extract_detects_signature_schemes(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_signature import APKSignatureExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.is_signed_v1.return_value = True
        mock_apk.is_signed_v2.return_value = True
        mock_apk.is_signed_v3.return_value = True
        ext = APKSignatureExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert 1 in result.signature_scheme_versions
        assert 2 in result.signature_scheme_versions
        assert 3 in result.signature_scheme_versions

    def test_certificate_subject_uses_short_names(self, fake_apk_env):
        """Ensure subject/issuer use RFC 4514 short names (CN=, O=) not long names."""
        from unittest.mock import MagicMock
        from asn1crypto import x509
        from redb.extractors.apk_extractors.apk_signature import APKSignatureExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env

        # Build a real asn1crypto Name so the conversion is exercised
        subject_name = x509.Name.build({
            'country_name': 'US',
            'state_or_province_name': 'California',
            'organization_name': 'Android',
            'common_name': 'Android Debug',
        })

        mock_cert = MagicMock()
        mock_cert.subject = subject_name
        mock_cert.issuer = subject_name
        mock_cert.serial_number = 12345
        mock_cert.not_valid_before = '2020-01-01 00:00:00'
        mock_cert.not_valid_after = '2030-01-01 00:00:00'
        mock_cert.hash_algo = 'sha256'
        mock_cert.public_key.bit_size = 2048
        mock_cert.dump.return_value = b'fake_cert_der_data'

        mock_apk.is_signed_v1.return_value = False
        mock_apk.is_signed_v2.return_value = True
        mock_apk.is_signed_v3.return_value = False
        mock_apk.get_certificates_v2.return_value = [mock_cert]

        ext = APKSignatureExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()

        assert result.apk_signer_subject is not None
        assert 'CN=' in result.apk_signer_subject
        assert 'O=' in result.apk_signer_subject
        assert 'Common Name' not in result.apk_signer_subject
        assert 'Organization' not in result.apk_signer_subject

        # Also check the certificate list
        assert len(result.x509_certificates) == 1
        cert = result.x509_certificates[0]
        assert 'CN=' in cert.subject
        assert 'Common Name' not in cert.subject


# ============================================================================
# APKDexExtractor
# ============================================================================

class TestAPKDexExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_dex"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_dex"

    def test_extract_returns_list(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        # Mock DEX data access
        mock_apk.get_dex_names.return_value = ["classes.dex"]
        mock_apk.get_all_dex.return_value = [b"dex\n035\x00" + b"\x00" * 100]

        # Mock the androguard DEX parser
        mock_dex_class = MagicMock()
        mock_dex_instance = MagicMock()
        mock_dex_instance.get_classes.return_value = []
        mock_dex_instance.get_methods.return_value = []
        mock_dex_instance.get_strings.return_value = []
        mock_dex_class.return_value = mock_dex_instance

        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

        with patch.dict('sys.modules', {'androguard.core.dex': MagicMock(DEX=mock_dex_class)}):
            result = ext.extract()

        assert result is not None
        assert isinstance(result, list)
        assert len(result) == 1
        assert result[0].filename == "classes.dex"

    def test_extract_computes_tlsh(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        # Need enough data for TLSH (>50 bytes of varied content)
        import os
        dex_data = os.urandom(256)
        mock_apk.get_dex_names.return_value = ["classes.dex"]
        mock_apk.get_all_dex.return_value = [dex_data]

        mock_dex_class = MagicMock()
        mock_dex_instance = MagicMock()
        mock_dex_instance.get_classes.return_value = []
        mock_dex_instance.get_methods.return_value = []
        mock_dex_instance.get_strings.return_value = []
        mock_dex_instance.get_encoded_methods.return_value = []
        mock_dex_class.return_value = mock_dex_instance

        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

        with patch.dict('sys.modules', {'androguard.core.dex': MagicMock(DEX=mock_dex_class)}):
            result = ext.extract()

        assert result is not None
        assert result[0].tlsh is not None
        assert result[0].tlsh.startswith("T1")

    def test_extract_tlsh_none_for_small_dex(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        # Too small / uniform for TLSH
        dex_data = b"\x00" * 10
        mock_apk.get_dex_names.return_value = ["classes.dex"]
        mock_apk.get_all_dex.return_value = [dex_data]

        mock_dex_class = MagicMock()
        mock_dex_instance = MagicMock()
        mock_dex_instance.get_classes.return_value = []
        mock_dex_instance.get_methods.return_value = []
        mock_dex_instance.get_strings.return_value = []
        mock_dex_instance.get_encoded_methods.return_value = []
        mock_dex_class.return_value = mock_dex_instance

        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

        with patch.dict('sys.modules', {'androguard.core.dex': MagicMock(DEX=mock_dex_class)}):
            result = ext.extract()

        assert result is not None
        # TLSH returns empty string for insufficient data, which we convert to None
        assert result[0].tlsh is None

    def test_prepare_export_clickhouse_includes_tlsh(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor
        from redb.models.dataclasses import APKDexFile

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

        # Manually set dex_files to test export
        ext.dex_files = [APKDexFile(
            filename="classes.dex",
            sha256="aa" * 32,
            class_count=10,
            method_count=50,
            string_count=100,
            tlsh="T1" + "B" * 68,
        )]

        result = ext.prepare_export_data("ClickHouseExporter")
        assert result is not None
        dex_export = result['dex']
        assert 'dex_tlsh' in dex_export['column_names']
        tlsh_idx = dex_export['column_names'].index('dex_tlsh')
        assert dex_export['data'][0][tlsh_idx] == "T1" + "B" * 68
        assert len(dex_export['column_names']) == len(dex_export['column_type_names'])

    def test_extract_no_dex_returns_none(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_dex import APKDexExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        mock_apk.get_dex_names.return_value = []
        mock_apk.get_all_dex.return_value = []
        ext = APKDexExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result is None


# ============================================================================
# APKResourceExtractor
# ============================================================================

class TestAPKResourceExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKResourceExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_resources"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKResourceExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_resources"

    def test_extract_returns_dict(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKResourceExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        # Returns dict with 'resources' key containing list of APKResource
        assert result is None or isinstance(result, dict)
        if result:
            assert "resources" in result
            assert "total_resource_count" in result
            assert isinstance(result["resources"], list)


# ============================================================================
# APKResourceExtractor — _identify_android_format
# ============================================================================

class TestIdentifyAndroidFormat:
    """Unit tests for Android-specific binary format magic detection."""

    def _make_extractor(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor
        apk_path, mock_apk, logger, exporters = fake_apk_env
        return APKResourceExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

    def test_axml_magic(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        header = b'\x03\x00\x08\x00' + b'\x00' * 12
        assert ext._identify_android_format(header) == "android_binary_xml"

    def test_arsc_magic(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        header = b'\x02\x00\x0c\x00' + b'\x00' * 12
        assert ext._identify_android_format(header) == "android_resource_table"

    def test_art_prof_magic(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        header = b'pro\x00' + b'\x00' * 12
        assert ext._identify_android_format(header) == "android_art_profile"

    def test_art_profm_magic(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        header = b'prm\x00' + b'\x00' * 12
        assert ext._identify_android_format(header) == "android_art_profile_metadata"

    def test_unknown_magic_returns_none(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        header = b'\xff\xff\xff\xff' + b'\x00' * 12
        assert ext._identify_android_format(header) is None

    def test_short_header_returns_none(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._identify_android_format(b'\x03\x00') is None
        assert ext._identify_android_format(b'') is None

    def test_png_magic_not_matched(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        header = b'\x89PNG\r\n\x1a\n' + b'\x00' * 8
        assert ext._identify_android_format(header) is None


# ============================================================================
# APKResourceExtractor — _is_suspicious_resource
# ============================================================================

class TestIsSuspiciousResource:
    """Unit tests for the layered suspicious resource classification."""

    def _make_extractor(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor
        apk_path, mock_apk, logger, exporters = fake_apk_env
        return APKResourceExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

    # ── Layer 1: Allowlisted paths ──────────────────────────────────────

    def test_allowlisted_baseline_prof_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/dexopt/baseline.prof",
            magika_type="unknown", entropy=7.9,
            android_type=None,
        ) is False

    def test_allowlisted_baseline_profm_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/dexopt/baseline.profm",
            magika_type="unknown", entropy=7.9,
            android_type=None,
        ) is False

    # ── Layer 2: Android-specific formats → always benign ───────────────

    def test_axml_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/layout/activity_main.xml",
            magika_type="gzip", entropy=6.5,
            android_type="android_binary_xml",
        ) is False

    def test_arsc_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/values/resources.arsc",
            magika_type="unknown", entropy=7.2,
            android_type="android_resource_table",
        ) is False

    def test_art_profile_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/custom/baseline.prof",
            magika_type="unknown", entropy=7.8,
            android_type="android_art_profile",
        ) is False

    def test_art_profile_metadata_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/other/baseline.profm",
            magika_type="unknown", entropy=7.5,
            android_type="android_art_profile_metadata",
        ) is False

    # ── Layer 3: Image extension / Magika type mismatch ─────────────────

    def test_fake_png_extension_with_non_image_content_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/raw/payload.png",
            magika_type="zip", entropy=7.2,
            android_type=None,
        ) is True

    def test_fake_jpg_extension_with_unknown_high_entropy_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/data.jpg",
            magika_type="unknown", entropy=6.5,
            android_type=None,
        ) is True

    def test_image_extension_mismatch_low_entropy_not_suspicious(self, fake_apk_env):
        """Tiny/low-entropy files with image extensions shouldn't be flagged."""
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/drawable/tiny.png",
            magika_type="unknown", entropy=3.0,
            android_type=None,
        ) is False

    # ── Layer 4: Magika-confirmed images → benign ───────────────────────

    def test_confirmed_png_high_entropy_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/drawable/photo.png",
            magika_type="png", entropy=7.95,
            android_type=None,
        ) is False

    def test_confirmed_webp_high_entropy_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/drawable/image.webp",
            magika_type="webp", entropy=7.99,
            android_type=None,
        ) is False

    def test_confirmed_jpeg_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/photos/bg.jpg",
            magika_type="jpeg", entropy=7.9,
            android_type=None,
        ) is False

    # ── Layer 5: Known suspicious file types ────────────────────────────

    def test_elf_in_assets_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/payload",
            magika_type="elf", entropy=5.0,
            android_type=None,
        ) is True

    def test_dex_in_assets_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/classes.dex",
            magika_type="dex", entropy=5.5,
            android_type=None,
        ) is True

    def test_shell_script_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/run.sh",
            magika_type="shell", entropy=4.0,
            android_type=None,
        ) is True

    def test_zip_in_assets_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/data.zip",
            magika_type="zip", entropy=7.5,
            android_type=None,
        ) is True

    def test_jar_in_assets_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/plugin.jar",
            magika_type="jar", entropy=6.0,
            android_type=None,
        ) is True

    def test_lzma_in_assets_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/blob.lzma",
            magika_type="lzma", entropy=7.9,
            android_type=None,
        ) is True

    # ── Layer 6: High-entropy unrecognized blobs ────────────────────────

    def test_unknown_type_high_entropy_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/xyzdata",
            magika_type="unknown", entropy=7.5,
            android_type=None,
        ) is True

    def test_empty_type_high_entropy_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/blob.dat",
            magika_type="empty", entropy=7.2,
            android_type=None,
        ) is True

    def test_unknown_type_low_entropy_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/config.dat",
            magika_type="unknown", entropy=4.5,
            android_type=None,
        ) is False

    # ── Layer 7: Extreme entropy on non-image recognized types ──────────

    def test_xml_extreme_entropy_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/raw/config.xml",
            magika_type="xml", entropy=7.9,
            android_type=None,
        ) is True

    def test_txt_extreme_entropy_is_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/readme.txt",
            magika_type="txt", entropy=7.86,
            android_type=None,
        ) is True

    def test_xml_normal_entropy_not_suspicious(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="res/raw/config.xml",
            magika_type="xml", entropy=4.0,
            android_type=None,
        ) is False

    # ── Benign types that should NOT be flagged ─────────────────────────

    def test_html_not_in_suspicious_types(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/index.html",
            magika_type="html", entropy=5.0,
            android_type=None,
        ) is False

    def test_javascript_not_in_suspicious_types(self, fake_apk_env):
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/app.js",
            magika_type="javascript", entropy=5.0,
            android_type=None,
        ) is False

    def test_sqlite_not_in_suspicious_types(self, fake_apk_env):
        """sqlite was intentionally excluded — too many legitimate uses."""
        ext = self._make_extractor(fake_apk_env)
        assert ext._is_suspicious_resource(
            path="assets/data.db",
            magika_type="sqlite", entropy=5.0,
            android_type=None,
        ) is False


# ============================================================================
# APKResourceExtractor — integration with extract()
# ============================================================================

class TestAPKResourceExtractorIntegration:
    """Integration tests: craft ZIP contents and verify end-to-end classification."""

    def test_extract_flags_elf_in_assets(self, tmp_path):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path = _create_fake_apk(str(tmp_path), {
            "assets/payload.bin": b"\x7fELF" + b"\x00" * 100,
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()

        # Mock Magika to return "elf" for the payload — real Magika needs
        # a valid ELF structure, but we control classification in unit tests.
        mock_magika = MagicMock()
        mock_magika.identify_bytes.return_value = MagicMock(
            output=MagicMock(label="elf")
        )

        ext = APKResourceExtractor(apk_path, logger, exporters=[MagicMock()], apk=mock_apk)
        with patch("magika.Magika", return_value=mock_magika):
            result = ext.extract()
        assert result is not None
        assert result["suspicious_file_count"] >= 1
        sus_paths = [s.path for s in result["suspicious_files"]]
        assert "assets/payload.bin" in sus_paths

    def test_extract_does_not_flag_normal_png(self, tmp_path):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path = _create_fake_apk(str(tmp_path), {
            "res/drawable/icon.png": b"\x89PNG\r\n\x1a\n" + b"\x00" * 50,
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()

        ext = APKResourceExtractor(apk_path, logger, exporters=[MagicMock()], apk=mock_apk)
        result = ext.extract()
        assert result is not None
        sus_paths = [s.path for s in result["suspicious_files"]]
        assert "res/drawable/icon.png" not in sus_paths

    def test_extract_stores_android_type_override(self, tmp_path):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        # AXML magic — Magika would likely misclassify, but stored_type
        # should be "android_binary_xml"
        axml_data = b'\x03\x00\x08\x00' + b'\x00' * 50
        apk_path = _create_fake_apk(str(tmp_path), {
            "res/layout/main.xml": axml_data,
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()

        ext = APKResourceExtractor(apk_path, logger, exporters=[MagicMock()], apk=mock_apk)
        result = ext.extract()
        assert result is not None
        axml_resources = [r for r in result["resources"]
                          if r.path == "res/layout/main.xml"]
        assert len(axml_resources) == 1
        assert axml_resources[0].filetype_magika == "android_binary_xml"
        assert axml_resources[0].is_suspicious is False

    def test_extract_skips_non_res_non_assets(self, tmp_path):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path = _create_fake_apk(str(tmp_path), {
            "lib/armeabi-v7a/libnative.so": b"\x7fELF" + b"\x00" * 50,
            "META-INF/MANIFEST.MF": b"Manifest-Version: 1.0\n",
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()

        ext = APKResourceExtractor(apk_path, logger, exporters=[MagicMock()], apk=mock_apk)
        result = ext.extract()
        if result:
            res_paths = [r.path for r in result["resources"]]
            assert "lib/armeabi-v7a/libnative.so" not in res_paths
            assert "META-INF/MANIFEST.MF" not in res_paths

    def test_prepare_export_includes_suspicious_flag(self, tmp_path):
        from redb.extractors.apk_extractors.apk_resources import APKResourceExtractor

        apk_path = _create_fake_apk(str(tmp_path), {
            "assets/evil.bin": b"\x7fELF" + b"\x00" * 100,
            "res/drawable/ok.png": b"\x89PNG\r\n\x1a\n" + b"\x00" * 50,
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()

        # Map filenames to Magika labels so the ELF payload gets flagged
        def _magika_side_effect(data):
            if data[:4] == b"\x7fELF":
                return MagicMock(output=MagicMock(label="elf"))
            return MagicMock(output=MagicMock(label="png"))

        mock_magika = MagicMock()
        mock_magika.identify_bytes.side_effect = _magika_side_effect

        ext = APKResourceExtractor(apk_path, logger, exporters=[MagicMock()], apk=mock_apk)
        with patch("magika.Magika", return_value=mock_magika):
            ext.extract()
        result = ext.prepare_export_data("ClickHouseExporter")
        assert result is not None
        data, col_names, col_types = result
        assert "is_suspicious" in col_names
        sus_idx = col_names.index("is_suspicious")
        # At least one row should have is_suspicious=1
        sus_values = [row[sus_idx] for row in data]
        assert 1 in sus_values


# ============================================================================
# APKNativeLibExtractor
# ============================================================================

class TestAPKNativeLibExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_native_libs import APKNativeLibExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKNativeLibExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_native_libs"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_native_libs import APKNativeLibExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKNativeLibExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_native_libs"

    def test_extract_finds_native_libs(self, tmp_path):
        from redb.extractors.apk_extractors.apk_native_libs import APKNativeLibExtractor
        from redb.models.dataclasses import APKNativeLib

        # Create APK with a native lib inside
        apk_path = _create_fake_apk(str(tmp_path), {
            "lib/armeabi-v7a/libnative.so": b"\x7fELF" + b"\x00" * 100,
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()
        exporters = [MagicMock()]

        ext = APKNativeLibExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result is not None
        assert isinstance(result, dict)
        assert "native_libs" in result
        assert len(result["native_libs"]) >= 1
        assert isinstance(result["native_libs"][0], APKNativeLib)
        assert result["native_libs"][0].abi == "armeabi-v7a"

    def test_extract_detects_packer_libs(self, tmp_path):
        from redb.extractors.apk_extractors.apk_native_libs import APKNativeLibExtractor

        # Create APK with a known packer lib
        apk_path = _create_fake_apk(str(tmp_path), {
            "lib/armeabi-v7a/libjiagu.so": b"\x7fELF" + b"\x00" * 50,
            "lib/armeabi-v7a/libnormal.so": b"\x7fELF" + b"\x00" * 50,
        })
        mock_apk = _make_mock_apk()
        logger = MagicMock()
        exporters = [MagicMock()]

        ext = APKNativeLibExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result is not None
        packer_libs = [lib for lib in result["native_libs"] if lib.is_known_packer]
        assert len(packer_libs) >= 1  # libjiagu.so is a known packer

    def test_extract_no_native_libs(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_native_libs import APKNativeLibExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        # The fake APK has no lib/*.so files
        mock_apk.get_files.return_value = ["classes.dex", "AndroidManifest.xml"]
        ext = APKNativeLibExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        assert result is None


# ============================================================================
# APKInconsistencyTestsExtractor
# ============================================================================

class TestAPKInconsistencyTestsExtractor:

    def test_tag(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_inconsistency_tests import APKInconsistencyTestsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKInconsistencyTestsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.tag() == "apk_inconsistency_tests"

    def test_clickhouse_table(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_inconsistency_tests import APKInconsistencyTestsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKInconsistencyTestsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext.get_clickhouse_table() == "redb_apk_inconsistency_tests"

    def test_extract_returns_dataclass(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_inconsistency_tests import APKInconsistencyTestsExtractor
        from redb.models.dataclasses import APKInconsistencyTests

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKInconsistencyTestsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()

        assert isinstance(result, APKInconsistencyTests)

    def test_extract_clean_apk_no_anomalies(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_inconsistency_tests import APKInconsistencyTestsExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKInconsistencyTestsExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        result = ext.extract()
        # A clean APK should have mostly False values
        assert result.test_zip_bomb is not None  # Should be set (True or False)


# ============================================================================
# APK workers.py integration — module registry
# ============================================================================

class TestAPKModuleRegistry:

    def test_all_apk_modules_in_registry(self):
        from redb.workers import get_module_by_name

        apk_modules = [
            "APKFeaturesExtractor",
            "APKManifestExtractor",
            "APKPermissionsExtractor",
            "APKSignatureExtractor",
            "APKDexExtractor",
            "APKResourceExtractor",
            "APKNativeLibExtractor",
            "APKInconsistencyTestsExtractor",
        ]
        for name in apk_modules:
            module_class = get_module_by_name(name)
            assert module_class is not None, f"{name} not found in module registry"

    def test_apk_module_classes_correct(self):
        from redb.workers import get_module_by_name
        from redb.extractors.apk_extractors import (
            APKFeaturesExtractor,
            APKManifestExtractor,
            APKPermissionsExtractor,
            APKSignatureExtractor,
            APKDexExtractor,
            APKResourceExtractor,
            APKNativeLibExtractor,
            APKInconsistencyTestsExtractor,
        )

        assert get_module_by_name("APKFeaturesExtractor") is APKFeaturesExtractor
        assert get_module_by_name("APKManifestExtractor") is APKManifestExtractor
        assert get_module_by_name("APKPermissionsExtractor") is APKPermissionsExtractor
        assert get_module_by_name("APKSignatureExtractor") is APKSignatureExtractor
        assert get_module_by_name("APKDexExtractor") is APKDexExtractor
        assert get_module_by_name("APKResourceExtractor") is APKResourceExtractor
        assert get_module_by_name("APKNativeLibExtractor") is APKNativeLibExtractor
        assert get_module_by_name("APKInconsistencyTestsExtractor") is APKInconsistencyTestsExtractor


# ============================================================================
# APK filetype guard — workers.py dispatch
# ============================================================================

class TestAPKWorkerDispatch:

    def test_apk_excluded_from_die_guard(self):
        """APK filetype should be in the exclusion set alongside macho for DIE/BasicProperties/Hash."""
        import redb.workers as workers_mod
        import inspect

        source = inspect.getsource(workers_mod.process_binary_file)
        # The guards should exclude "apk" from DIE, BasicProperties, HashExtractor.
        # The exact tuple may grow as new text-based formats are added (e.g.
        # "javascript"), so we check that "apk" is present in a filetype
        # exclusion guard rather than matching the full tuple literally.
        assert 'filetype not in ("macho", "apk"' in source

    def test_apk_dispatch_branch_exists(self):
        """The APK dispatch branch should exist and registry should provide all APK extractors."""
        import inspect
        import redb.workers as workers_mod
        from redb.extractor_registry import get_filetype_modules

        # Verify the APK branch exists in process_binary_file
        source = inspect.getsource(workers_mod.process_binary_file)
        assert 'elif filetype == "apk"' in source

        # Verify all APK extractors are registered and loadable
        apk_modules = get_filetype_modules("apk")
        apk_names = {m.__name__ for m in apk_modules}
        for expected in [
            "APKFeaturesExtractor", "APKManifestExtractor",
            "APKPermissionsExtractor", "APKSignatureExtractor",
            "APKDexExtractor", "APKResourceExtractor",
            "APKNativeLibExtractor", "APKInconsistencyTestsExtractor",
        ]:
            assert expected in apk_names, f"{expected} not in APK registry"

    def test_apk_dispatch_runs_basic_and_hash(self):
        """The APK dispatch branch should run BasicPropertiesExtractor and HashExtractor."""
        import redb.workers as workers_mod
        import inspect

        source = inspect.getsource(workers_mod.process_binary_file)
        # Find the apk block
        apk_idx = source.index('elif filetype == "apk"')
        apk_block = source[apk_idx:apk_idx + 4000]
        assert "BasicPropertiesExtractor" in apk_block
        assert "HashExtractor" in apk_block


# ============================================================================
# APKExtractor base class
# ============================================================================

class TestAPKExtractorBase:

    def test_binary_property(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

        # binary property should return the file content
        with open(apk_path, "rb") as f:
            expected = f.read()
        assert ext.binary == expected

    def test_is_valid_apk_with_apk(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        assert ext._is_valid_apk() is True

    def test_is_valid_apk_without_apk(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, _, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=None)
        ext.apk = None
        assert ext._is_valid_apk() is False

    def test_list_files(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        files = ext._list_files()
        assert len(files) == 5
        assert "classes.dex" in files

    def test_get_zip_file(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)
        zf = ext._get_zip_file()
        assert zf is not None
        zf.close()

    def test_sha256_computed_correctly(self, fake_apk_env):
        from redb.extractors.apk_extractors.apk_features import APKFeaturesExtractor

        apk_path, mock_apk, logger, exporters = fake_apk_env
        ext = APKFeaturesExtractor(apk_path, logger, exporters=exporters, apk=mock_apk)

        # Verify sha256 matches actual file content
        with open(apk_path, "rb") as f:
            expected_sha256 = hashlib.sha256(f.read()).hexdigest()
        assert ext.sha256 == expected_sha256