Wassim Hamidouche

240 papers A 6B 33C 31Misc 17Journal 109Unranked 44
YearRankTypeTitle / Venue / Authors
2026 J jnl
Neural Comput. Appl.
Ahmed Aldahdooh, Wassim Hamidouche, Olivier Déforges
2026 J jnl
CoRR
Hao Yu, Tianyi Xu, Michael A. Hedderich, Wassim Hamidouche, Syed Waqas Zamir, David Ifeoluwa Adelani
2026 J jnl
CoRR
Syed Waqas Zamir, Wassim Hamidouche, Boulbaba Ben Amor, Luana Marotti, Inbal Becker-Reshef, Juan M. Lavista Ferres
2026 J jnl
ACM Comput. Surv.
Christophe El Zeinaty, Wassim Hamidouche, Glenn Herrou, Daniel Ménard
2026 J jnl
Multim. Tools Appl.
Bachir Kaddar, Sid Ahmed Fezza, Elhocine Boutellaa, Wassim Hamidouche, Abdenour Hadid
2026 J jnl
ACM Comput. Surv.
Hanene F. Z. Brachemi Meftah, Wassim Hamidouche, Sid Ahmed Fezza, Olivier Déforges
2025 J jnl
Signal Process. Image Commun.
Ahmed Telili, Wassim Hamidouche, Ibrahim Farhat, Hadi Amirpour, Christian Timmerer, Ibrahim Khadraoui, Jiajie Lu, The Van Le, Jeonneung Baek, Jin Young Lee, Yiying Wei, Xiaopeng Sun, Yu Gao, Jiancheng Huang, Yujie Zhong
2025 J jnl
CoRR
Amit Misra, Syed Waqas Zamir, Wassim Hamidouche, Inbal Becker-Reshef, Juan M. Lavista Ferres
2025 J jnl
Expert Syst. J. Knowl. Eng.
Mamadou Keita, Wassim Hamidouche, Hessen Bougueffa Eutamene, Abdelmalik Taleb-Ahmed, David Camacho, Abdenour Hadid
2025 B conf
IJCNN
Christophe El Zeinaty, Wassim Hamidouche, Glenn Herrou, Daniel Ménard, Mérouane Debbah
2025 J jnl
CoRR
Christophe El Zeinaty, Wassim Hamidouche, Glenn Herrou, Daniel Ménard, Mérouane Debbah
2025 J jnl
ACM Trans. Multim. Comput. Commun. Appl.
Ahmed Telili, Wassim Hamidouche, Hadi Amirpour, Sid Ahmed Fezza, Christian Timmerer, Luce Morin
2025 B conf
ACIVS
Mamadou Keita, Wassim Hamidouche, Hessen Bougueffa Eutamene, Abdelmalik Taleb-Ahmed, Abdenour Hadid
2025 J jnl
CoRR
Mamadou Keita, Wassim Hamidouche, Hessen Bougueffa Eutamene, Abdelmalik Taleb-Ahmed, Abdenour Hadid
2025 J jnl
CoRR
Christophe El Zeinaty, Wassim Hamidouche, Glenn Herrou, Daniel Ménard
2025 Misc conf
ICASSP
Hanene F. Z. Brachemi Meftah, Wassim Hamidouche, Sid Ahmed Fezza, Olivier Déforges, Kassem Kallas
2025 J jnl
CoRR
Hanene Brachemi Meftah, Wassim Hamidouche, Sid Ahmed Fezza, Olivier Déforges, Kassem Kallas
2025 J jnl
CoRR
Hanene F. Z. Brachemi Meftah, Wassim Hamidouche, Sid Ahmed Fezza, Olivier Déforges
2025 J jnl
IEEE Commun. Mag.
Nassim Sehad, Lina Bariah, Wassim Hamidouche, Hamed Hellaoui, Riku Jäntti, Mérouane Debbah
2025 J jnl
Comput. Electr. Eng.
Hessen Bougueffa Eutamene, Wassim Hamidouche, Mamadou Keita, Abdelmalik Taleb-Ahmed, Abdenour Hadid
2025 J jnl
CoRR
Adnan Shahid, Adrian Kliks, Ahmed Al-Tahmeesschi, Ahmed Elbakary, Alexandros Nikou, Ali Maatouk, Ali Mokh, Amirreza Kazemi, Antonio De Domenico, Athanasios Karapantelakis, Bo Cheng, Bo Yang, Bohao Wang, Carlo Fischione, Chao Zhang, Chaouki Ben Issaid, Chau Yuen, Chenghui Peng, Chongwen Huang, Christina Chaccour, Christo Kurisummoottil Thomas, Dheeraj Sharma, Dimitris Kalogiros, Dusit Niyato, Eli De Poorter, Elissa Mhanna, Emilio Calvanese Strinati, Faouzi Bader, Fathi Abdeldayem, Fei Wang, Fenghao Zhu, Gianluca Fontanesi, Giovanni Geraci, Haibo Zhou, Hakimeh Purmehdi, Hamed Ahmadi, Hang Zou, Hongyang Du, Hoon Lee, Howard H. Yang, Iacopo Poli, Igor Carron, Ilias Chatzistefanidis, Inkyu Lee, Ioannis Pitsiorlas, Jaron Fontaine, Jiajun Wu, Jie Zeng, Jinan Li, Jinane Karam, Johny Gemayel, Juan Deng, Julien Frison, Kaibin Huang, Kehai Qiu, Keith Ball, Kezhi Wang, Kun Guo, Leandros Tassiulas, Lecorve Gwenole, Liexiang Yue, Lina Bariah, Louis Powell, Marcin Dryjanski, Maria Amparo Canaveras Galdon, Marios Kountouris, Maryam Hafeez, Maxime Elkael, Mehdi Bennis, Mehdi Ahmed Boudjelli, Meiling Dai, Mérouane Debbah, Michele Polese, Mohamad Assaad, Mohamed Benzaghta, Mohammad Al Refai, Moussab Djerrab, Mubeen Syed, Muhammad Amir, Na Yan, Najla Alkaabi, Nan Li, Nassim Sehad, Navid Nikaein, Omar Hashash, Pawel Sroka, Qianqian Yang, Qiyang Zhao, Rasoul Nikbakht Silab, Rex Ying, Roberto Morabito, Rongpeng Li, Ryad Madi, Salah Eddine El Ayoubi, Salvatore D'Oro, Samson Lasaulce, Serveh Shalmashi, Sige Liu, Sihem Cherrared, Swarna Bindu Chetty, Swastika Dutta, Syed A. R. Zaidi, Tianjiao Chen, Timothy Murphy, Tommaso Melodia, Tony Q. S. Quek, Vishnu Ram, Walid Saad, Wassim Hamidouche, Weilong Chen, Xiaoou Liu, Xiaoxue Yu, Xijun Wang, Xingyu Shang, Xinquan Wang, Xuelin Cao, Yang Su, Yanping Liang, Yansha Deng, Yifan Yang, Yingping Cui, Yu Sun, Yuxuan Chen, Yvan Pointurier, Zeinab Nehme, Zeinab Nezami, Zhaohui Yang, Zhaoyang Zhang, Zhe Liu, Zhenyu Yang, Zhu Han, Zhuang Zhou, Zihan Chen, Zirui Chen, Zitao Shuai
2025 J jnl
CoRR
Mamadou Keita, Wassim Hamidouche, Hessen Bougueffa Eutamene, Abdelmalik Taleb-Ahmed, Abdenour Hadid
2025 conf
DFF@MM
Mamadou Keita, Wassim Hamidouche, Hessen Bougueffa Eutamene, Abdelmalik Taleb-Ahmed, Abdenour Hadid
2025 J jnl
CoRR
Hanene F. Z. Brachemi Meftah, Wassim Hamidouche, Sid Ahmed Fezza, Olivier Déforges
2024 J jnl
ACM Trans. Multim. Comput. Commun. Appl.
Ahmed Telili, Sid Ahmed Fezza, Wassim Hamidouche, Hanene Brachemi Meftah
2024 J jnl
CoRR
Mamadou Keita, Wassim Hamidouche, Hessen Bougueffa Eutamene, Abdenour Hadid, Abdelmalik Taleb-Ahmed
2024 J jnl
ACM Trans. Multim. Comput. Commun. Appl.
Bachir Kaddar, Sid Ahmed Fezza, Zahid Akhtar, Wassim Hamidouche, Abdenour Hadid, Joan Serra-Sagristà
2024 Misc conf
ICASSP
Christophe El Zeinaty, Glenn Herrou, Wassim Hamidouche, Daniel Ménard
2024 A conf
MMSys
Hadi Amirpour, Mohammad Ghasempour, Lingfeng Qu, Wassim Hamidouche, Christian Timmerer
2024 J jnl
IEEE Consumer Electron. Mag.
Taieb Chachou, Wassim Hamidouche, Sid Ahmed Fezza, Ghalem Belalem
2024 conf
GMSys@MMSys
Ibrahim Farhat, Ibrahim Khadraoui, Wassim Hamidouche, Mohit K. Sharma
2024 conf
ISDFS
Khouloud Samrouth, Nicolas Beuve, Olivier Déforges, Nader Bakir, Wassim Hamidouche
2024 B conf
WCNC
Mohit K. Sharma, Ibrahim Farhat, Wassim Hamidouche
2024 conf
ICPR (21)
Mamadou Keita, Wassim Hamidouche, Hessen Bougueffa Eutamene, Abdelmalik Taleb-Ahmed, Abdenour Hadid
2024 J jnl
CoRR
Mamadou Keita, Wassim Hamidouche, Hessen Bougueffa Eutamene, Abdelmalik Taleb-Ahmed, Abdenour Hadid
2024 J jnl
CoRR
Nassim Sehad, Lina Bariah, Wassim Hamidouche, Hamed Hellaoui, Riku Jäntti, Mérouane Debbah
2024 J jnl
CoRR
Mamadou Keita, Wassim Hamidouche, Hassen Bougueffa, Abdenour Hadid, Abdelmalik Taleb-Ahmed
2024 J jnl
ACM Trans. Multim. Comput. Commun. Appl.
Nicolas Beuve, Wassim Hamidouche, Olivier Déforges
2024 J jnl
IEEE Commun. Mag.
Wassim Hamidouche, Lina Bariah, Mérouane Debbah
2024 C conf
VCIP
Hadi Amirpour, Mohammad Ghasempour, Farzad Tashtarian, Ahmed Telili, Samira Afzal, Wassim Hamidouche, Christian Timmerer
2024 J jnl
CoRR
Ahmed Ghorbel, Wassim Hamidouche, Luce Morin
2024 C conf
VCIP
Ahmed Ghorbel, Wassim Hamidouche, Luce Morin
2024 B conf
ICIP
Ahmed Telili, Ibrahim Farhat, Wassim Hamidouche, Hadi Amirpour
2024 J jnl
IEEE Open J. Commun. Soc.
Mohit K. Sharma, Ibrahim Farhat, Chen-Feng Liu, Nassim Sehad, Wassim Hamidouche, Mérouane Debbah
2024 B conf
ICIP
Marwa Tarchouli, Thomas Guionnet, Marc Rivière, Wassim Hamidouche, Meriem Outtas, Olivier Déforges
2024 J jnl
EURASIP J. Inf. Secur.
Kassem Kallas, Quentin Le Roux, Wassim Hamidouche, Teddy Furon
2024 conf
ICPR (Workshops and Challenges, 6)
Hessen Bougueffa Eutamene, Wassim Hamidouche, Mamadou Keita, Abdelmalik Taleb-Ahmed, Abdenour Hadid
2024 conf
EUSIPCO
Yiying Wei, Hadi Amirpour, Ahmed Telili, Wassim Hamidouche, Guo Lu, Christian Timmerer
2023 B conf
ICIP
Ahmed Ghorbel, Wassim Hamidouche, Luce Morin
2023 J jnl
CoRR
Ahmed Ghorbel, Wassim Hamidouche, Luce Morin
2023 conf
MHV
Marwa Tarchouli, Marc Rivière, Thomas Guionnet, Mickaël Raulet, Meriem Outtas, Olivier Déforges, Wassim Hamidouche
2023 J jnl
Multim. Tools Appl.
Naima Zouidi, Amina Kessentini, Wassim Hamidouche, Nouri Masmoudi, Daniel Ménard
2023 conf
EUVIP
Ahmed Ghorbel, Wassim Hamidouche, Luce Morin
2023 J jnl
CoRR
Ahmed Ghorbel, Wassim Hamidouche, Luce Morin
2023 J jnl
IEEE Trans. Image Process.
Zoubida Ameur, Wassim Hamidouche, Edouard François, Milos Radosavljevic, Daniel Ménard, Claire-Hélène Demarty
2023 B conf
ICIP
Ahmed Telili, Wassim Hamidouche, Sid Ahmed Fezza, Luce Morin
2023 C conf
MMSP
Taieb Chachou, Wassim Hamidouche, Sid Ahmed Fezza, Ghalem Belalem
2023 C conf
MMSP
Ibrahim Farhat, Pierre-Loup Cabarat, Daniel Ménard, Wassim Hamidouche, Olivier Déforges
2023 conf
EUVIP
Hanene Brachemi Meftah, Sid Ahmed Fezza, Wassim Hamidouche, Olivier Déforges
2023 J jnl
CoRR
Wassim Hamidouche, Lina Bariah, Mérouane Debbah
2023 J jnl
CoRR
Ahmed Ghorbel, Wassim Hamidouche, Luce Morin
2023 C conf
ISCAS
Ibrahim Farhat, Pierre-Loup Cabarat, Wassim Hamidouche, Patrice Angot, Philipe Gonon, Daniel Ménard
2023 J jnl
IEEE Trans. Circuits Syst. II Express Briefs
Nassim Sehad, Bilel Cherif, Ibrahim Khadraoui, Wassim Hamidouche, Faouzi Bader, Riku Jäntti, Mérouane Debbah
2023 J jnl
IEEE Trans. Circuits Syst. Video Technol.
Alexandre Tissier, Wassim Hamidouche, Souhaiel Belhadj Dit Mdalsi, Jarno Vanne, Franck Galpin, Daniel Ménard
2023 J jnl
CoRR
Xiaohong Liu, Xiongkuo Min, Wei Sun, Yulun Zhang, Kai Zhang, Radu Timofte, Guangtao Zhai, Yixuan Gao, Yuqin Cao, Tengchuan Kou, Yunlong Dong, Ziheng Jia, Yilin Li, Wei Wu, Shuming Hu, Sibin Deng, Pengxiang Xiao, Ying Chen, Kai Li, Kai Zhao, Kun Yuan, Ming Sun, Heng Cong, Hao Wang, Lingzhi Fu, Yusheng Zhang, Rongyu Zhang, Hang Shi, Qihang Xu, Longan Xiao, Zhiliang Ma, Mirko Agarla, Luigi Celona, Claudio Rota, Raimondo Schettini, Zhiwei Huang, Yanan Li, Xiaotao Wang, Lei Lei, Hongye Liu, Wei Hong, Ironhead Chuang, Allen Lin, Drake Guan, Iris Chen, Kae Lou, Willy Huang, Yachun Tasi, Yvonne Kao, Haotian Fan, Fangyuan Kong, Shiqi Zhou, Hao Liu, Yu Lai, Shanshan Chen, Wenqi Wang, Haoning Wu, Chaofeng Chen, Chunzheng Zhu, Zekun Guo, Shiling Zhao, Haibing Yin, Hongkui Wang, Hanene Brachemi Meftah, Sid Ahmed Fezza, Wassim Hamidouche, Olivier Déforges, Tengfei Shi, Azadeh Mansouri, Hossein Motamednia, Amir Hossein Bakhtiari, Ahmad Mahmoudi Aznaveh
2023 J jnl
J. Electronic Imaging
Bachir Kaddar, Sid Ahmed Fezza, Wassim Hamidouche, Zahid Akhtar, Abdenour Hadid
2023 A conf
MMSys
Marko Viitanen, Joose Sainio, Alexandre Mercat, Guillaume Gautier, Jarno Vanne, Ibrahim Farhat, Pierre-Loup Cabarat, Wassim Hamidouche, Daniel Ménard
2023 J jnl
J. Signal Process. Syst.
Naouel Haggui, Wassim Hamidouche, Fatma Belghith, Nouri Masmoudi, Jean-François Nezan
2023 J jnl
ACM Trans. Multim. Comput. Commun. Appl.
Yi Zhang, Fang-Yi Chao, Wassim Hamidouche, Olivier Déforges
2023 J jnl
J. Real Time Image Process.
Anup Saha, Wassim Hamidouche, Miguel Chavarrías, Fernando Pescador, Ibrahim Farhat
2023 J jnl
IEEE Trans. Image Process.
Yi Zhang, Jing Zhang, Wassim Hamidouche, Olivier Déforges
2023 J jnl
Appl. Intell.
Ahmed Aldahdooh, Wassim Hamidouche, Olivier Déforges
2023 C conf
MMSP
Cyril Bergeron, Catherine Lamy-Bergot, Wassim Hamidouche, William Puech
2023 conf
ICCV (Workshops)
Nicolas Beuve, Wassim Hamidouche, Olivier Déforges
2022 J jnl
IEEE Trans. Consumer Electron.
Ibrahim Farhat, Wassim Hamidouche, Adrien Grill, Daniel Ménard, Olivier Déforges
2022 J jnl
Artif. Intell. Rev.
Ahmed Aldahdooh, Wassim Hamidouche, Sid Ahmed Fezza, Olivier Déforges
2022 C conf
PCS
Ahmed Telili, Wassim Hamidouche, Sid Ahmed Fezza, Luce Morin
2022 J jnl
CoRR
Charles Bonnineau, Wassim Hamidouche, Jean-François Travers, Naty Sidaty, Jean-Yves Aubié, Olivier Déforges
2022 B conf
ICPR
Yi Zhang, Wassim Hamidouche, Olivier Déforges
2022 J jnl
Neural Comput. Appl.
Zoubida Ameur, Sid Ahmed Fezza, Wassim Hamidouche
2022 conf
MHV
Thibaud Biatek, Mickaël Raulet, Patrice Angot, Philippe Gonon, Cédric Thienot, Wassim Hamidouche, Pascal Perrot, Julien Lemotheux
2022 J jnl
Neural Comput. Appl.
Anouar Kherchouche, Sid Ahmed Fezza, Wassim Hamidouche
2022 C conf
PCS
Ashek Ahmmed, Wassim Hamidouche, Andrew J. Lambert, Mark Pickering, M. Manzur Murshed
2022 J jnl
IEEE Trans. Consumer Electron.
Wassim Hamidouche, Fernando Pescador, Thibaud Biatek, Edouard François
2022 C conf
PCS
Ibrahim Farhat, Wassim Hamidouche, Adrien Grill, Daniel Ménard, Olivier Déforges
2022 conf
EUVIP
Fatemeh Nasiri, Wassim Hamidouche, Luce Morin, Nicolas Dhollande, Jean-Yves Aubié
2022 C conf
MMSP
Sid Ahmed Fezza, Mohammed Yasser Ouis, Bachir Kaddar, Wassim Hamidouche, Abdenour Hadid
2022 J jnl
CoRR
Sid Ahmed Fezza, Mohammed Yasser Ouis, Bachir Kaddar, Wassim Hamidouche, Abdenour Hadid
2022 J jnl
CoRR
Ahmed Aldahdooh, Wassim Hamidouche, Olivier Déforges
2022 J jnl
IEEE Trans. Consumer Electron.
Wassim Hamidouche, Pierrick Philippe, Sid Ahmed Fezza, Mounir Haddou, Fernando Pescador, Daniel Ménard
2022 B conf
ICIP
Alexandre Tissier, Wassim Hamidouche, Jarno Vanne, Daniel Ménard
2022 J jnl
J. Signal Process. Syst.
Naouel Haggui, Fatma Belghith, Wassim Hamidouche, Nouri Masmoudi, Jean-François Nezan
2022 J jnl
IEEE Trans. Broadcast.
Charles Bonnineau, Wassim Hamidouche, Jérôme Fournier, Naty Sidaty, Jean-François Travers, Olivier Déforges
2022 J jnl
IEEE Access
Mousa Farajallah, Guillaume Gautier, Wassim Hamidouche, Olivier Déforges, Safwan El Assad
2022 conf
MHV
Thibaud Biatek, Eduard François, Cédric Thienot, Wassim Hamidouche
2022 conf
BrainLes@MICCAI
Ahmed Ghorbel, Ahmed Aldahdooh, Shadi Albarqouni, Wassim Hamidouche
2022 J jnl
CoRR
Ahmed Ghorbel, Ahmed Aldahdooh, Shadi Albarqouni, Wassim Hamidouche
2022 J jnl
IEEE Consumer Electron. Mag.
Wassim Hamidouche, Thibaud Biatek, Mohsen Abdoli, Edouard François, Fernando Pescador, Milos Radosavljevic, Daniel Ménard, Mickaël Raulet
2022 J jnl
IEEE Trans. Circuits Syst. Video Technol.
Yi Liu, Naty Sidaty, Wassim Hamidouche, Olivier Déforges, Cheolkon Jung
2022 C conf
MMSP
Mamadou Keita, Sid Ahmed Fezza, Wassim Hamidouche, Azeddine Beghdadi
2021 J jnl
IEEE Trans. Multim.
Fang-Yi Chao, Lu Zhang, Wassim Hamidouche, Olivier Déforges
2021 J jnl
CoRR
Ahmed Aldahdooh, Wassim Hamidouche, Sid Ahmed Fezza, Olivier Déforges
2021 C conf
VCIP
Charles Bonnineau, Wassim Hamidouche, Jean-François Travers, Naty Sidaty, Jean-Yves Aubié, Olivier Déforges
2021 J jnl
CoRR
Yi Zhang, Lu Zhang, Wassim Hamidouche, Olivier Déforges
2021 J jnl
CoRR
Théo Ladune, Pierrick Philippe, Wassim Hamidouche, Lu Zhang, Olivier Déforges
2021 J jnl
CoRR
Anouar Kherchouche, Sid Ahmed Fezza, Wassim Hamidouche
2021 conf
ADGD @ ACM Multimedia
Nicolas Beuve, Wassim Hamidouche, Olivier Déforges
2021 conf
CVPR Workshops
Dounia Hammou, Sid Ahmed Fezza, Wassim Hamidouche
2021 C conf
VCIP
Bachir Kaddar, Sid Ahmed Fezza, Wassim Hamidouche, Zahid Akhtar, Abdenour Hadid
2021 J jnl
IEEE Access
Iyad Hraini, Mousa Farajallah, Nabil Arman, Wassim Hamidouche
2021 A conf
BMVC
Yi Zhang, Geng Chen, Qian Chen, Yujia Sun, Yong Xia, Olivier Déforges, Wassim Hamidouche, Lu Zhang
2021 J jnl
CoRR
Yi Zhang, Geng Chen, Qian Chen, Yujia Sun, Olivier Déforges, Wassim Hamidouche, Lu Zhang
2021 J jnl
CoRR
Mouath Aouayeb, Wassim Hamidouche, Catherine Soladié, Kidiyo Kpalma, Renaud Séguier
2021 J jnl
IEEE Trans. Multim.
Nader Bakir, Wassim Hamidouche, Sid Ahmed Fezza, Khouloud Samrouth, Olivier Déforges
2021 J jnl
IEEE Trans. Consumer Electron.
Ibrahim Farhat, Wassim Hamidouche, Adrien Grill, Daniel Ménard, Olivier Déforges
2021 J jnl
Signal Process. Image Commun.
Mouath Aouayeb, Wassim Hamidouche, Catherine Soladié, Kidiyo Kpalma, Renaud Séguier
2021 C conf
PCS
Fatemeh Nasiri, Wassim Hamidouche, Luce Morin, Nicolas Dhollande, Gildas Cocherel
2021 conf
DASIP
Naouel Haggui, Fatma Belghith, Wassim Hamidouche, Nouri Masmoudi, Jean-François Nezan
2021 C conf
PCS
Charles Bonnineau, Wassim Hamidouche, Jean-François Travers, Naty Sidaty, Olivier Déforges
2021 J jnl
CoRR
Charles Bonnineau, Wassim Hamidouche, Jean-François Travers, Naty Sidaty, Olivier Déforges
2021 J jnl
CoRR
Jinjin Gu, Haoming Cai, Chao Dong, Jimmy S. Ren, Yu Qiao, Shuhang Gu, Radu Timofte, Manri Cheon, Sung-Jun Yoon, Byungyeon Kang, Junwoo Lee, Qing Zhang, Haiyang Guo, Yi Bin, Yuqing Hou, Hengliang Luo, Jingyu Guo, Zirui Wang, Hai Wang, Wenming Yang, Qingyan Bai, Shuwei Shi, Weihao Xia, Mingdeng Cao, Jiahao Wang, Yifan Chen, Yujiu Yang, Yang Li, Tao Zhang, Longtao Feng, Yiting Liao, Junlin Li, William Thong, José Costa Pereira, Ales Leonardis, Steven McDonagh, Kele Xu, Lehan Yang, Hengxing Cai, Pengfei Sun, Seyed Mehdi Ayyoubzadeh, Ali Royat, Sid Ahmed Fezza, Dounia Hammou, Wassim Hamidouche, Sewoong Ahn, Gwangjin Yoon, Koki Tsubota, Hiroaki Akutsu, Kiyoharu Aizawa
2021 J jnl
IEEE Trans. Circuits Syst. Video Technol.
Glenn Herrou, Charles Bonnineau, Wassim Hamidouche, Patrick Dumenil, Jérôme Fournier, Luce Morin
2021 J jnl
J. Signal Process. Syst.
Pierre-Loup Cabarat, Wassim Hamidouche, Daniel Ménard, Olivier Déforges
2021 J jnl
CoRR
Ahmed Aldahdooh, Wassim Hamidouche, Olivier Déforges
2021 J jnl
CoRR
Yi Zhang, Lu Zhang, Jing Zhang, Kang Wang, Wassim Hamidouche, Olivier Déforges
2021 J jnl
CoRR
Guillaume Gautier, Mousa Farajallah, Wassim Hamidouche, Olivier Déforges, Safwan El Assad
2021 J jnl
CoRR
Ahmed Aldahdooh, Wassim Hamidouche, Olivier Déforges
2021 conf
EUVIP
Sid Ahmed Fezza, Mamadou Keita, Wassim Hamidouche
2020 B conf
ICIP
Yi Zhang, Lu Zhang, Wassim Hamidouche, Olivier Déforges
2020 J jnl
CoRR
Yi Zhang, Lu Zhang, Wassim Hamidouche, Olivier Déforges
2020 conf
IPTA
Fatemeh Nasiri, Wassim Hamidouche, Luce Morin, Gildas Cocherel, Nicolas Dhollande
2020 conf
ICME Workshops
Fang-Yi Chao, Cagri Ozcinar, Chen Wang, Emin Zerman, Lu Zhang, Wassim Hamidouche, Olivier Déforges, Aljosa Smolic
2020 Misc conf
ICASSP
Théo Ladune, Pierrick Philippe, Wassim Hamidouche, Lu Zhang, Olivier Déforges
2020 J jnl
CoRR
Théo Ladune, Pierrick Philippe, Wassim Hamidouche, Lu Zhang, Olivier Déforges
2020 B conf
ICIP
Alexandre Tissier, Wassim Hamidouche, Jarno Vanne, Franck Galpin, Daniel Ménard
2020 B conf
IJCNN
Anouar Kherchouche, Sid Ahmed Fezza, Wassim Hamidouche, Olivier Déforges
2020 conf
ICME Workshops
Taieb Chachou, Sid Ahmed Fezza, Ghalem Belalem, Wassim Hamidouche
2020 C conf
ISCAS
Ibrahim Djemai, Sid Ahmed Fezza, Wassim Hamidouche, Olivier Déforges
2020 J jnl
IEEE Trans. Circuits Syst. Video Technol.
Ahmed Kammoun, Wassim Hamidouche, Pierrick Philippe, Olivier Déforges, Fatma Belghith, Nouri Masmoudi, Jean-François Nezan
2020 conf
MIPR
Yi Zhang, Lu Zhang, Wassim Hamidouche, Olivier Déforges
2020 A conf
ICME
Nader Bakir, Wassim Hamidouche, Sid Ahmed Fezza, Khouloud Samrouth, Olivier Déforges
2020 Misc conf
ICASSP
Ibrahim Farhat, Wassim Hamidouche, Adrien Grill, Daniel Ménard, Olivier Déforges
2020 J jnl
CoRR
Théo Ladune, Pierrick Philippe, Wassim Hamidouche, Lu Zhang, Olivier Déforges
2020 conf
MLSP
Théo Ladune, Pierrick Philippe, Wassim Hamidouche, Lu Zhang, Olivier Déforges
2020 C conf
MMSP
Anouar Kherchouche, Sid Ahmed Fezza, Wassim Hamidouche, Olivier Déforges
2020 C conf
MMSP
Théo Ladune, Pierrick Philippe, Wassim Hamidouche, Lu Zhang, Olivier Déforges
2020 J jnl
CoRR
Théo Ladune, Pierrick Philippe, Wassim Hamidouche, Lu Zhang, Olivier Déforges
2020 C conf
VCIP
Fatemeh Nasiri, Wassim Hamidouche, Luce Morin, Nicolas Dhollande, Gildas Cocherel
2020 J jnl
Cryptogr.
Mohammed Abu Taha, Wassim Hamidouche, Naty Sidaty, Marko Viitanen, Jarno Vanne, Safwan El Assad, Olivier Déforges
2020 B conf
ICIP
Thomas Amestoy, Wassim Hamidouche, Cyril Bergeron, Daniel Ménard
2020 J jnl
CoRR
Thomas Amestoy, Wassim Hamidouche, Cyril Bergeron, Daniel Ménard
2020 J jnl
IEEE Trans. Broadcast.
Thibaud Biatek, Wassim Hamidouche, Pierre-Loup Cabarat, Jean-François Travers, Olivier Déforges
2020 J jnl
Multim. Tools Appl.
Naty Sidaty, Julien Heulot, Wassim Hamidouche, Maxime Pelcat, Daniel Ménard
2020 C conf
VCIP
Fang-Yi Chao, Cagri Ozcinar, Lu Zhang, Wassim Hamidouche, Olivier Déforges, Aljosa Smolic
2020 J jnl
IEEE Trans. Image Process.
Thomas Amestoy, Alexandre Mercat, Wassim Hamidouche, Daniel Ménard, Cyril Bergeron
2020 Misc conf
ICASSP
Charles Bonnineau, Wassim Hamidouche, Jean-François Travers, Olivier Déforges
2019 J jnl
IEEE Access
Ronan Parois, Wassim Hamidouche, Pierre-Loup Cabarat, Mickaël Raulet, Naty Sidaty, Olivier Déforges
2019 conf
IC3D
Nour Hobloss, Andrei I. Purica, Attilio Fiandrotti, Marco Cagnazzo, Rémi Cozot, Wassim Hamidouche
2019 conf
MLSP
Mouath Aouayeb, Wassim Hamidouche, Kidiyo Kpalma, Amel Benazza-Benyahia
2019 J jnl
IEEE Trans. Circuits Syst. Video Technol.
Yi Liu, Naty Sidaty, Wassim Hamidouche, Olivier Déforges, Giuseppe Valenzise, Emin Zerman
2019 C conf
MMSP
Alexandre Tissier, Alexandre Mercat, Thomas Amestoy, Wassim Hamidouche, Jarno Vanne, Daniel Ménard
2019 C conf
PCS
Naty Sidaty, Wassim Hamidouche, Olivier Déforges, Pierrick Philippe, Jérôme Fournier
2019 J jnl
IEEE Access
Yassine Bakhti, Sid Ahmed Fezza, Wassim Hamidouche, Olivier Déforges
2019 B conf
DCC
Kevin Reuze, Wassim Hamidouche, Pierrick Philippe, Olivier Déforges
2019 conf
EUSIPCO
Ahmed Kammoun, Wassim Hamidouche, Pierrick Philippe, Fatma Belghith, Nouri Massmoudi, Jean-François Nezan
2019 C conf
PCS
Wassim Hamidouche, Pierrick Philippe, Camar-Eddine Mohamed, Ahmed Kammoun, Daniel Ménard, Olivier Déforges
2019 B conf
ICIP
Glenn Herrou, Wassim Hamidouche, Luce Morin
2019 J jnl
J. Real Time Image Process.
Alexandre Mercat, Florian Arrestier, Maxime Pelcat, Wassim Hamidouche, Daniel Ménard
2019 J jnl
IEEE Trans. Image Process.
Maxime Bichon, Julien Le Tanou, Michaël Ropert, Wassim Hamidouche, Luce Morin
2019 B conf
QoMEX
Sid Ahmed Fezza, Yassine Bakhti, Wassim Hamidouche, Olivier Déforges
2019 J jnl
CoRR
Sid Ahmed Fezza, Yassine Bakhti, Wassim Hamidouche, Olivier Déforges
2019 J jnl
J. Signal Process. Syst.
Alexandre Mercat, Florian Arrestier, Maxime Pelcat, Wassim Hamidouche, Daniel Ménard
2019 B conf
DCC
Nader Bakir, Wassim Hamidouche, Olivier Déforges, Khouloud Samrouth, Sid Ahmed Fezza, Mohamad Khalil
2019 Misc conf
ICASSP
Thomas Amestoy, Alexandre Mercat, Wassim Hamidouche, Cyril Bergeron, Daniel Ménard
2019 B conf
ICIP
Amira Filali, Vincent Ricordel, Nicolas Normand, Wassim Hamidouche
2019 conf
EUSIPCO
Nader Bakir, Sid Ahmed Fezza, Wassim Hamidouche, Khouloud Samrouth, Olivier Déforges
2019 B conf
QoMEX
Sid Ahmed Fezza, Wassim Hamidouche, Reda Abdellah Kamraoui, Olivier Déforges
2018 Misc conf
ATSIP
Ahmed Kammoun, S. Ben Jdidia, Fatma Belghith, Wassim Hamidouche, Jean-François Nezan, Nouri Masmoudi
2018 C conf
PCS
Thibaud Biatek, Jean-François Travers, Pierre-Loup Cabarat, Wassim Hamidouche
2018 conf
EUSIPCO
Mohammed AbuTaha, Naty Sidaty, Wassim Hamidouche, Olivier Déforges, Jarno Vanne, Marko Viitanen
2018 B conf
QoMEX
Meriem Outtas, Lu Zhang, Olivier Déforges, Wassim Hamidouche, Amina Serir
2018 J jnl
IEEE Trans. Consumer Electron.
Ahmed Kammoun, Wassim Hamidouche, Fatma Belghith, Jean-François Nezan, Nouri Masmoudi
2018 B conf
ICIP
Nader Bakir, Wassim Hamidouche, Olivier Déforges, Khouloud Samrouth, Mohamad Khalil
2018 C conf
ISCAS
Naty Ould Sidaty, Marko Viitanen, Wassim Hamidouche, Jarno Vanne, Olivier Déforges
2018 Misc conf
ICASSP
Maxime Bichon, Julien Le Tanou, Michaël Ropert, Wassim Hamidouche, Luce Morin, Lu Zhang
2018 B conf
DCC
Glenn Herrou, Wassim Hamidouche, Luce Morin
2018 C conf
PCS
Alexandre Mercat, Florian Arrestier, Maxime Pelcat, Wassim Hamidouche, Daniel Ménard
2018 conf
SiPS
Naty Ould Sidaty, Pierre-Loup Cabarat, Wassim Hamidouche, Daniel Ménard, Olivier Déforges
2018 J jnl
IEEE Trans. Comput. Aided Des. Integr. Circuits Syst.
Maxime Pelcat, Alexandre Mercat, Karol Desnos, Luca Maggiani, Yanzhou Liu, Julien Heulot, Jean-François Nezan, Wassim Hamidouche, Daniel Ménard, Shuvra S. Bhattacharyya
2018 conf
ICME Workshops
Fang-Yi Chao, Lu Zhang, Wassim Hamidouche, Olivier Déforges
2018 C conf
PCS
Maxime Bichon, Julien Le Tanou, Michaël Ropert, Wassim Hamidouche, Luce Morin, Lu Zhang
2018 C conf
PCS
Glenn Herrou, Wassim Hamidouche, Luce Morin
2017 J jnl
IEEE Trans. Consumer Electron.
Yi Liu, Wassim Hamidouche, Olivier Déforges, Fernando Pescador
2017 Misc conf
ICASSP
Naty Ould Sidaty, Wassim Hamidouche, Olivier Déforges
2017 B conf
ICIP
Yi Liu, Naty Ould Sidaty, Wassim Hamidouche, Olivier Déforges, Giuseppe Valenzise, Emin Zerman
2017 B conf
DCC
Kevin Reuzé, Pierrick Philippe, Wassim Hamidouche, Olivier Déforges
2017 B conf
ICIP
Naty Ould Sidaty, Wassim Hamidouche, Olivier Déforges, Pierrick Philippe
2017 Misc conf
ICASSP
Alexandre Mercat, Florian Arrestier, Wassim Hamidouche, Maxime Pelcat, Daniel Ménard
2017 J jnl
J. Real Time Image Process.
Erwan Nogues, Julien Heulot, Glenn Herrou, Ladislas Robin, Maxime Pelcat, Daniel Ménard, Erwan Raffin, Wassim Hamidouche
2017 conf
EUSIPCO
Ronan Parois, Wassim Hamidouche, Jérôme Viéron, Mickaël Raulet, Olivier Déforges
2017 B conf
QoMEX
Naty Ould Sidaty, Wassim Hamidouche, Olivier Déforges, Pierrick Philippe
2017 Misc conf
ICASSP
Alexandre Mercat, Florian Arrestier, Wassim Hamidouche, Maxime Pelcat, Daniel Ménard
2017 A conf
DATE
Alexandre Mercat, Justine Bonnot, Maxime Pelcat, Wassim Hamidouche, Daniel Ménard
2017 Misc conf
ICASSP
Maxime Bichon, Julien Le Tanou, Michaël Ropert, Wassim Hamidouche, Luce Morin, Lu Zhang
2017 B conf
ICIP
Meriem Outtas, Lu Zhang, Olivier Déforges, Amina Serir, Wassim Hamidouche
2017 conf
SiPS
Alexandre Mercat, Florian Arrestier, Maxime Pelcat, Wassim Hamidouche, Daniel Ménard
2017 Misc conf
ICASSP
Pierre-Loup Cabarat, Wassim Hamidouche, Olivier Déforges
2017 conf
DSP
Cyril Bergeron, Naty Ould Sidaty, Wassim Hamidouche, Benoit Boyadjis, Jean Le Feuvre, Youngkwon Lim
2017 J jnl
Signal Process. Image Commun.
Wassim Hamidouche, Mousa Farajallah, Naty Ould Sidaty, Safwan El Assad, Olivier Déforges
2017 conf
EUSIPCO
Naty Ould Sidaty, Julien Heulot, Wassim Hamidouche, Erwan Nogues, Maxime Pelcat, Daniel Ménard
2017 J jnl
J. Syst. Archit.
Alexandre Mercat, Justine Bonnot, Maxime Pelcat, Karol Desnos, Wassim Hamidouche, Daniel Ménard
2017 conf
SiPS
Anas Jallouli, Fatma Belghith, Mohamed Ali Ben Ayed, Wassim Hamidouche, Jean-François Nezan, Nouri Masmoudi
2016 J jnl
IEEE Trans. Circuits Syst. Video Technol.
Wassim Hamidouche, Mickaël Raulet, Olivier Déforges
2016 conf
ISIVC
Meriem Outtas, Lu Zhang, Olivier Déforges, Wassim Hamidouche, Amina Serir, Christine Cavaro-Ménard
2016 Misc conf
ICASSP
T. Biatek, Wassim Hamidouche, Jean-François Travers, Olivier Déforges
2016 conf
DASIP
Ronan Parois, Wassim Hamidouche, Elie Gabriel Mora, Mickaël Raulet, Olivier Déforges
2016 conf
DASIP
Ronan Parois, Wassim Hamidouche, Elie Gabriel Mora, Mickaël Raulet, Olivier Déforges
2016 conf
DASIP
Alexandre Mercat, Wassim Hamidouche, Maxime Pelcat, Daniel Ménard
2016 B conf
ICIP
Médéric Blestel, Michaël Ropert, Wassim Hamidouche
2016 C conf
PCS
Glenn Herrou, Wassim Hamidouche, Xavier Ducloux
2016 C conf
PCS
Kevin Reuze, Pierrick Philippe, Olivier Déforges, Wassim Hamidouche
2016 J jnl
J. Real Time Image Process.
Erwan Raffin, Erwan Nogues, Wassim Hamidouche, Seppo Tomperi, Maxime Pelcat, Daniel Ménard
2016 B conf
DCC
Thibaud Biatek, Wassim Hamidouche, Jean-François Travers, Olivier Déforges
2016 J jnl
IEEE Trans. Broadcast.
Thibaud Biatek, Wassim Hamidouche, Jean-François Travers, Olivier Déforges
2016 C conf
PCS
Thibaud Biatek, Wassim Hamidouche, Jean-François Travers, Olivier Déforges
2016 conf
EUSIPCO
Ronan Parois, Wassim Hamidouche, Elie Gabriel Mora, Mickaël Raulet, Olivier Déforges
2016 conf
DASIP
Erwan Raffin, Wassim Hamidouche, Erwan Nogues, Maxime Pelcat, Daniel Ménard
2015 conf
Conf. Computing Frontiers
Erwan Raffin, Wassim Hamidouche, Erwan Nogues, Maxime Pelcat, Daniel Ménard, Seppo Tomperi
2015 B conf
ICIP
Mousa Farajallah, Wassim Hamidouche, Olivier Déforges, Safwan El Assad
2015 Misc conf
ICASSP
Wassim Hamidouche, Mousa Farajallah, Mickaël Raulet, Olivier Déforges, Safwan El Assad
2015 conf
ICCE-Berlin
Thibaud Biatek, Wassim Hamidouche, Jean-François Travers, Olivier Déforges
2014 conf
ICME Workshops
Wassim Hamidouche, Gildas Cocherel, Jean Le Feuvre, Mickaël Raulet, Olivier Déforges
2014 Misc conf
ICASSP
Wassim Hamidouche, Mickaël Raulet, Olivier Déforges
2014 A conf
ICME
Wassim Hamidouche, Mickaël Raulet, Olivier Déforges
2014 B conf
ICIP
Wassim Hamidouche, Mickaël Raulet, Olivier Déforges
2014 B conf
ICIP
Benoît Martin, Wassim Hamidouche, Jean Le Feuvre, Mickaël Raulet
2013 J jnl
J. Vis. Commun. Image Represent.
Wassim Hamidouche, Christian Olivier, Yannis Pousset, Clency Perrine
2011 J jnl
J. Vis. Commun. Image Represent.
Wassim Hamidouche, Clency Perrine, Yannis Pousset, Christian Olivier
2010 Misc conf
ICASSP
Wassim Hamidouche, Clency Perrine, Yannis Pousset, Christian Olivier
2009 B conf
PIMRC
Wassim Hamidouche, Rodolphe Vauzelle, Christian Olivier, Yannis Pousset, Clency Perrine
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