Wei Cao

231 papers A* 5A 2B 7C 13Misc 5Journal 132Unranked 67
YearRankTypeTitle / Venue / Authors
2026 J jnl
Expert Syst. Appl.
Xianyong Ma, Yufan Wang, Guowei Li, Zejiao Dong, Qilin Huang, Gershome G. Abaho, Wei Cao
2026 J jnl
CoRR
Yulun Wu, Ruyi Zha, Wei Cao, Yingying Li, Yuanhao Cai, Yaoyao Liu
2026 J jnl
npj Digit. Medicine
Yuxuan Zeng, Gonghao Ling, Haojie Zhang, Wei Cao, Xuan Zheng, Xiaoxian Deng, Lan Lan, Rongqing Sun, Xintian Liu, Lin Tian, Haibo Xu, Ziyu Wang, Gangcheng Zhang
2026 J jnl
CoRR
Wei Cao, Hao Zhang, Fengrui Tian, Yulun Wu, Yingying Li, Shenlong Wang, Ning Yu, Yaoyao Liu
2026 J jnl
Neurocomputing
Wei Cao, Shanshan Wang
2026 J jnl
Finite Fields Their Appl.
Wei Cao
2026 J jnl
Discret. Appl. Math.
Wei Cao
2025 conf
ICAIE
Ke Tan, Wei Cao, Zihui Sun
2025 C conf
ISCAS
Maryam Rafati, Seyed Ruhallah Qasemi, Wei Cao, Alireza Saberkari, Atila Alvandpour
2025 J jnl
IEEE Trans. Instrum. Meas.
Wei Cao, Zong Meng, Jimeng Li, Jie Wu, Fengjie Fan
2025 J jnl
Expert Syst. Appl.
Kai Chen, Zong Meng, Dengyun Sun, Wei Cao, Yonglei Ren, Weiliang Sun, Jie Wu
2025 J jnl
Expert Syst. Appl.
Zong Meng, Shufan Ma, Wei Cao, Jimeng Li, Lixiao Cao, Fengjie Fan, Xingzhao Wang
2025 J jnl
Neurocomputing
Shiyuan Wang, Fumin Wang, Mingqiang Guo, Ying Huang, Wei Cao
2025 J jnl
CoRR
Yuxuan Zeng, Wenhao Xie, Wei Cao, Tan Peng, Yue Hou, Ziyu Wang, Jing Shi
2025 conf
RCAR
Jiawei Liu, Tianzhe Bao, Benjian Zou, Wei Cao, Xiaodong Na, Lin Wu, Ping Zhou
2025 J jnl
IEEE Trans. Ind. Electron.
Zhixiang Li, Yunqing Pei, Lu Chen, Laili Wang, Wei Cao, Zesong Leng
2025 conf
VTC2025-Spring
Yanshi Sun, Wei Cao, Ning Wang, Momiao Zhou, Zhiguo Ding
2025 A* conf
IJCAI
Jun Wang, Wenjie Du, Yiyuan Yang, Linglong Qian, Wei Cao, Keli Zhang, Wenjia Wang, Yuxuan Liang, Qingsong Wen
2025 J jnl
Signal Image Video Process.
Jiaxin Wu, Shoulie Xie, Wei Cao, Shiqian Wu
2025 J jnl
IEEE Trans. Wirel. Commun.
Yanshi Sun, Wei Cao, Ning Wang, Momiao Zhou, Zhiguo Ding
2025 A* conf
AAAI
Changyuan Tian, Zhicong Lu, Zequn Zhang, Heming Yang, Wei Cao, Zhi Guo, Xian Sun, Li Jin
2025 J jnl
IEEE Trans. Intell. Transp. Syst.
Wei Cao, Shiqian Wu, Dianwei Wang, Sos S. Agaian, Zhan Song
2025 J jnl
J. Supercomput.
Fangjun Liu, Huifu Zhang, Yonghua Hu, Anxing Xie, Wei Cao, Shangfeng Mo
2025 J jnl
CoRR
Wenliang Li, Rui Yan, Xu Zhang, Li Chen, Hongji Zhu, Jing Zhao, Junjun Li, Mengru Li, Wei Cao, Zihang Jiang, Wei Wei, Kun Zhang, Shaohua Kevin Zhou
2025 J jnl
CoRR
Wei Cao, Shanshan Wang
2025 J jnl
Expert Syst. Appl.
Shiyuan Wang, Mingqiang Guo, Dengke Wang, Wei Cao, Ying Huang, Jiaming Zhang
2025 J jnl
IEEE Access
Wei Cao, Fan Chen
2025 conf
ESWC (1)
Hongkuan Zhou, Stefan Schimid, Yicong Li, Lavdim Halilaj, Xiangtong Yao, Wei Cao
2025 J jnl
CoRR
Hongkuan Zhou, Stefan Schmid, Yicong Li, Lavdim Halilaj, Xiangtong Yao, Wei Cao
2025 J jnl
Future Gener. Comput. Syst.
Wei Cao, Wenting Shen, Jing Qin, Hao Lin
2025 J jnl
CoRR
Wei Cao, Marcel Hallgarten, Tianyu Li, Daniel Dauner, Xunjiang Gu, Caojun Wang, Yakov Miron, Marco Aiello, Hongyang Li, Igor Gilitschenski, Boris Ivanovic, Marco Pavone, Andreas Geiger, Kashyap Chitta
2025 conf
RCAR
Wei Cao, Tianzhe Bao, Xiaodong Na, Lin Wu, Ping Zhou
2025 J jnl
Comput. Phys. Commun.
Leran Lu, Wei Cao, Romain Botella
2025 J jnl
CoRR
Bang Hu, Changze Lv, Mingjie Li, Yunpeng Liu, Xiaoqing Zheng, Fengzhe Zhang, Wei Cao, Fan Zhang
2024 J jnl
CoRR
Yiqiu Wang, Meixia Tao, Shu Sun, Wei Cao
2024 conf
ICIC (5)
Kanghua Hui, Youzhi Wang, Haohan Liu, Sihua Gao, Wei Cao
2024 J jnl
IET Comput. Vis.
Wei Cao, Xiaoyong Wang, Xianxiang Liu, Yishuai Xu
2024 J jnl
Expert Syst. Appl.
Yang Guan, Zong Meng, Jimeng Li, Wei Cao, Dengyun Sun, Jingbo Liu, Fengjie Fan
2024 J jnl
Eng. Appl. Artif. Intell.
Zicheng Zhang, Anguo Li, Li Wang, Wei Cao, Jianlin Yang
2024 B conf
GLOBECOM
Yiqiu Wang, Meixia Tao, Shu Sun, Wei Cao
2024 J jnl
Neural Process. Lett.
Bin Zhao, Bin Li, Jiqun Zhang, Wei Cao, Yilong Gao
2024 J jnl
CoRR
Jun Wang, Wenjie Du, Wei Cao, Keli Zhang, Wenjia Wang, Yuxuan Liang, Qingsong Wen
2024 J jnl
Sensors
Jiankang Ren, Jun Zhang, Xu Li, Wei Cao, Shengyu Li, Wenxin Chu, Chengzhang Song
2024 conf
NorCAS
Wei Cao, Alireza Saberkari, Atila Alvandpour
2024 J jnl
CoRR
Yanshi Sun, Wei Cao, Ning Wang, Momiao Zhou, Zhiguo Ding
2024 J jnl
IEEE Trans. Commun.
Yanshi Sun, Wei Cao, Momiao Zhou, Zhiguo Ding
2024 Misc conf
IDC
Yijun Zhao, Yiming Cheng, Shiying Ding, Yan Fang, Wei Cao, Ke Liu, Jiacheng Cao
2024 A* conf
CVPR
Wei Cao, Chang Luo, Biao Zhang, Matthias Nießner, Jiapeng Tang
2024 J jnl
CoRR
Wei Cao, Chang Luo, Biao Zhang, Matthias Nießner, Jiapeng Tang
2024 conf
ICIC (6)
Jianming Zhang, Mingshuang Wu, Wei Cao, Zi Xing
2024 conf
ISCID
Wei Cao, Yongcheng Xie
2024 J jnl
Energy Inform.
Wei Cao, Xiuhua Wei
2024 J jnl
IEEE J. Biomed. Health Informatics
Wuxiang Shi, Yurong Li, Naiqing Cai, Rukai Chen, Wei Cao, Jixiang Li
2024 conf
PEAI
Quan Zhou, Yongchang Lao, Yongliang Yin, Wei Cao
2024 conf
ICIC (8)
Zhigao Zeng, Jin Wang, Wei Cao, Jianxin Wang, Jianming Zhang
2024 J jnl
IEEE J. Sel. Top. Appl. Earth Obs. Remote. Sens.
Wei Cao, Guangluan Xu, Yingchao Feng, Hongqi Wang, Siyu Hu, Min Li
2024 J jnl
IEEE Trans. Artif. Intell.
Luefeng Chen, Wei Cao, Biao Zheng, Min Wu, Witold Pedrycz, Kaoru Hirota
2023 conf
CCBR
Jingjing Wang, Yuping Ye, Wei Cao, Juan Zhao, Zhan Song
2023 J jnl
Adv. Eng. Softw.
Ping Lu, Shiyuan Guo, Yang Shu, Bin Liu, Peifeng Li, Wei Cao, Kaiyong Jiang
2023 J jnl
Expert Syst. Appl.
Zong Meng, Huihui He, Wei Cao, Jimeng Li, Lixiao Cao, Jingjing Fan, Meng Zhu, Fengjie Fan
2023 J jnl
Remote. Sens.
Hao Wang, Wei Shao, Yunfeng Hu, Wei Cao, Yunzhi Zhang
2023 J jnl
CoRR
Wei Cao, Liguo Zhou, Yuhong Huang, Alois C. Knoll
2023 J jnl
Symmetry
Fuchong Li, Wei Liu, Li Tian, Wei Cao, Xu Li, Junhong Guo, Jinfeng Cui, Baoping Yang
2023 J jnl
Comput. Networks
Guoan Zhang, Wei Cao, Xiaohui Gu
2023 conf
DRC
Ankit Kumar, Arnab Pal, Kamyar Parto, Wei Cao, Kaustav Banerjee
2023 J jnl
IEEE J. Sel. Top. Appl. Earth Obs. Remote. Sens.
Runyan Du, Wei Cao, Wenkai Zhang, Zhi Guo, Xian Sun, Shuoke Li, Jihao Li
2023 J jnl
CoRR
Yanshi Sun, Wei Cao, Momiao Zhou, Zhiguo Ding
2023 J jnl
Remote. Sens.
Yao Pan, Yunhe Yin, Wei Cao
2023 J jnl
Appl. Soft Comput.
Dengyun Sun, Zong Meng, Yang Guan, Jingbo Liu, Wei Cao, Fengjie Fan
2023 A* conf
USENIX Security Symposium
Chenyang Lyu, Jiacheng Xu, Shouling Ji, Xuhong Zhang, Qinying Wang, Binbin Zhao, Gaoning Pan, Wei Cao, Peng Cheng, Raheem Beyah
2023 J jnl
CoRR
Chenyang Lyu, Jiacheng Xu, Shouling Ji, Xuhong Zhang, Qinying Wang, Binbin Zhao, Gaoning Pan, Wei Cao, Raheem Beyah
2023 conf
GLOBECOM (Workshops)
Yanshi Sun, Wei Cao, Momiao Zhou, Zhiguo Ding
2023 J jnl
CoRR
Yanshi Sun, Wei Cao, Momiao Zhou, Zhiguo Ding
2023 A* conf
ICML
Tianping Zhang, Zheyu Aqa Zhang, Zhiyuan Fan, Haoyan Luo, Fengyuan Liu, Qian Liu, Wei Cao, Li Jian
2023 J jnl
Pattern Recognit. Lett.
Ruiping Wang, Wei Cao, Shihong Wu, Meng Jia, Xiaoping Wang
2023 J jnl
Remote. Sens.
Shuo Wang, Yu Sheng, Youhua Ran, Bingquan Wang, Wei Cao, Erxing Peng, Chenyang Peng
2023 J jnl
Comput. Secur.
Wei Cao, Wenting Shen, Zhixiang Zhang, Jing Qin
2023 conf
QRS Companion
Xiaoming Xie, Jinfa Xu, Suiyuan Shen, Wei Cao, Guigang Zhang
2023 J jnl
IEEE Access
Fang Liu, Wei Cao, Yuping Ye, Feifei Gu, Shiyang Long, Zhan Song
2023 J jnl
J. Chem. Inf. Model.
Romain Botella, Andrey A. Kistanov, Wei Cao
2023 B conf
FG
Wei Cao, Tianyi Wang, Anming Dong, Minglei Shu
2023 conf
NorCAS
Wei Cao, Alireza Saberkari, Atila Alvandpour
2023 J jnl
CoRR
Hongkuan Zhou, Aifen Sui, Wei Cao, Letian Shi
2023 J jnl
Briefings Bioinform.
Jie Hao, Jiawei Zou, Jiaqiang Zhang, Ke Chen, Duojiao Wu, Wei Cao, Guoguo Shang, Jean Yee Hwa Yang, KongFatt Wong-Lin, Hourong Sun, Zhen Zhang, Xiangdong Wang, Wantao Chen, Xin Zou
2022 J jnl
IEEE Access
Wei Cao, Xifu Wang
2022 conf
ACCV (3)
Wei Cao, Yuping Ye, Chu Shi, Zhan Song
2022 J jnl
IEEE Trans. Affect. Comput.
Wei Cao, Kun Zhang, Shulan Ruan, Hanqing Tao, Sirui Zhao, Hao Wang, Qi Liu, Enhong Chen
2022 J jnl
CoRR
Xiaosong Gu, Wei Cao, Yicong Zhu, Xuan Song, Yu Huang, Xiaoxing Ma
2022 B conf
SRDS
Xiaosong Gu, Wei Cao, Yicong Zhu, Xuan Song, Yu Huang, Xiaoxing Ma
2022 J jnl
Digit. Investig.
Tianyi Wang, Ming Liu, Wei Cao, Kam-Pui Chow
2022 J jnl
J. Comput. Des. Eng.
Huaqin Cheng, Bin Liu, Meiying Liu, Wei Cao
2022 J jnl
CoRR
Andrey A. Kistanov, Stepan A. Shcherbinin, Romain Botella, Artur Davletshin, Wei Cao
2022 J jnl
Eur. J. Oper. Res.
Yanfei Kang, Wei Cao, Fotios Petropoulos, Feng Li
2022 C conf
IGARSS
Ziying Xia, Guolong Gan, Siyu Liu, Wei Cao, Jian Cheng
2022 J jnl
ISPRS Int. J. Geo Inf.
Zhuoran Jiang, Xinxin Zhou, Wei Cao, Zaihong Sun, Changbin Wu
2022 J jnl
Adv. Intell. Syst.
Frédéric Héraly, Miao Zhang, Agnes Åhl, Wei Cao, Lennart Bergström, Jiayin Yuan
2022 J jnl
Vis. Comput.
Jianwen Xu, Wei Cao, Bin Liu, Kaiyong Jiang
2022 J jnl
IEEE Access
Wei Cao, Xifu Wang
2022 B conf
IWCMC
Chenchen Zhang, Nan Zhang, Wei Cao, Hua Rui
2022 J jnl
Adv. Eng. Informatics
Zong Meng, Wei Cao, Dengyun Sun, Qian Li, Wuxu Ma, Fengjie Fan
2022 J jnl
Remote. Sens.
Wei Cao, Jiayi Wu, Yufeng Shi, Dong Chen
2022 J jnl
IEEE Geosci. Remote. Sens. Lett.
Wei Cao, Shiqian Wu, Zhaoyi Liu, Sos S. Agaian
2022 conf
ICBDS
Wei Cao, Yang Hu, Shuang Yang, Xue-Yang Zhu, Jia Yu
2022 conf
AIAM
Shengtao Tang, Guojian Yu, Junchao Zhu, Lubin Feng, Dongxin Li, Wei Cao
2022 J jnl
Int. J. Data Sci. Anal.
Wei Cao, Kun Zhang, Han Wu, Tong Xu, Enhong Chen, Guangyi Lv, Ming He
2022 conf
AsiaSim (1)
Wei Cao, Xiaoyi Liu, Pinggang Yu, Xiao Xu, Jie Zhang
2021 J jnl
IEEE Computer Graphics and Applications
Wei Cao, Shiqian Wu, Dianwei Wang, Jiaxin Wu
2021 conf
HCI (19)
Hong Liu, Zhong Siyang, Wan Yixin, Junya Yu, Wei Cao
2021 J jnl
IEEE Trans. Syst. Man Cybern. Syst.
Jiewu Leng, Shide Ye, Man Zhou, J. Leon Zhao, Qiang Liu, Wei Guo, Wei Cao, Leijie Fu
2021 J jnl
Int. J. Cogn. Informatics Nat. Intell.
Hui Wang, Tie Cai, Wei Cao
2021 conf
WCSP
Zhonglin Ding, Wei Cao, Zheng Zhang, Zhikun Wang, Yi Zhang, Shuang Yang
2021 J jnl
IEEE Signal Process. Lett.
Wei Cao, Shiqian Wu, Jiaxin Wu, Zhaoyi Liu, Yuwen Li
2021 conf
CICAI
Wei Cao, Kun Zhang, Hanqing Tao, Weidong He, Qi Liu, Enhong Chen, Jianhui Ma
2021 J jnl
IEEE Access
Wei Cao, Jianying Yan, Zili Jin, Zhao Han, Han Zhang, Jinxiu Qu, Man Zhang
2021 J jnl
IEEE Trans. Intell. Transp. Syst.
Hua Han, MengChu Zhou, Xiwu Shang, Wei Cao, Abdullah Abusorrah
2021 conf
ICFET
Feiyu Wang, Wei Cao, Rong Liao, Baoqing Song, En Zhang, Binghua Su
2021 conf
VTC Spring
Chenchen Zhang, Wei Cao, Nan Zhang, Kaibo Tian, Ruyue Li
2020 J jnl
CoRR
Harri Saarnisaari, Sudhir Dixit, Mohamed-Slim Alouini, Abdelaali Chaoub, Marco Giordani, Adrian Kliks, Marja Matinmikko-Blue, Nan Zhang, Anuj Agrawal, Mats Andersson, Vimal Bhatia, Wei Cao, Yunfei Chen, Wei Feng, Marjo Heikkilä, Josep Miquel Jornet, Luciano Leonel Mendes, Heikki Karvonen, Brejesh Lall, Matti Latva-aho, Xiangling Li, Kalle Lähetkangas, Moshe T. Masonta, Alok Pandey, Pekka Pirinen, Khaled M. Rabie, Tlou M. Ramoroka, Hanna Saarela, Amit Singhal, Kaibo Tian, Justin Wang, Chenchen Zhang, Yang Zhen, Haibo Zhou
2020 J jnl
Traitement du Signal
Xiaohong Xiao, Jiangang Xie, Jiping Niu, Wei Cao
2020 conf
ICCPR
Zhanpeng Gan, Huarong Xu, Yuanrong He, Wei Cao
2020 conf
Kaleidoscope
Chenchen Zhang, Nan Zhang, Wei Cao, Kaibo Tian, Zhen Yang
2020 J jnl
Complex.
Yang-Wu Shen, Ding Wang, Hao Chen, Jian Zuo, Min Xu, Wei Cao
2020 J jnl
IEEE Access
Guofeng Tong, Yong Li, Dong Chen, Qi Sun, Wei Cao, Guiqiu Xiang
2020 J jnl
Integr.
Shang Ma, Wei Cao, Shengqiang Jiang, Jianhao Hu, Xin Lei, Xiongzhong Xiong
2020 conf
ENC
Hieu Trung Tran, Wei Cao, Marcus Reutemann, Liwen Dai, Ralph Ostermeier, Georg Kormann
2020 J jnl
Sensors
Ruiping Wang, Liangcai Zeng, Shiqian Wu, Wei Cao, Kelvin K. L. Wong
2020 J jnl
Symmetry
Yifan He, Wei Cao, Xiaofeng Du, Changlin Chen
2020 J jnl
Comput. Biol. Medicine
Lingtao Yu, Pengcheng Wang, Yusheng Yan, Yongqiang Xia, Wei Cao
2020 J jnl
Trans. Inst. Meas. Control
Shengquan Li, Juan Li, Yongwei Tang, Yanqiu Shi, Wei Cao
2020 conf
WCSP
Ziyang Li, Li Tian, Yachao Yin, Wei Cao
2020 A conf
FAST
Wei Cao, Yang Liu, Zhushi Cheng, Ning Zheng, Wei Li, Wenjie Wu, Linqiang Ouyang, Peng Wang, Yijing Wang, Ray Kuan, Zhenjun Liu, Feng Zhu, Tong Zhang
2020 J jnl
Comput. Methods Programs Biomed.
Jialin Tang, Qinglang Su, Binghua Su, Simon Fong, Wei Cao, Xueyuan Gong
2019 J jnl
Sensors
Bin Du, Zhaoyang Tong, Xihui Mu, Jianjie Xu, Shuai Liu, Zhiwei Liu, Wei Cao, Zhimei Qi
2019 C conf
IGARSS
Wei Cao, Lin Huang, Yunfeng Hu, Duanyang Xu, Hongyan Ren, Junxing Yang
2019 J jnl
Remote. Sens.
Yuzhi Tang, Quanqin Shao, Jiyuan Liu, Haiyang Zhang, Fan Yang, Wei Cao, Dan Wu, Guoli Gong
2019 J jnl
IEEE Commun. Mag.
Bin Cao, Long Zhang, Yun Li, Daquan Feng, Wei Cao
2019 J jnl
IEEE Access
Su Gao, Wei Cao, Luhai Fan, Jihong Liu
2019 J jnl
Bioinform.
Jie Hao, Wei Cao, Jian Huang, Xin Zou, Zeguang Han
2019 conf
5G World Forum
Chenchen Zhang, Wei Cao, Zhen Yang, Kaibo Tian, Nan Zhang
2019 B conf
ICPADS
Shasha Tian, Xiuguo Zhang, Yingjun Zhang, Zhiying Cao, Wei Cao
2019 C conf
IGARSS
Wei Cao, Lin Huang, Yunfeng Hu, Duanyang Xu, Hongyan Ren, Junxing Yang, Lulu Liu
2019 J jnl
Clust. Comput.
Jiaqi Li, Wei Cao, Xiaolin Tian
2019 conf
ICCE-TW
Yi-Ying Lu, Wei Cao, Jun-Wen Xue, Binghua Su
2019 J jnl
IEEE Access
Man Zhang, Shuming Shi, Wendong Cheng, Yunbo Shen, Wei Cao
2018 conf
DSP
Guiwu Yang, Jianhao Hu, Guoqiang Yao, Jienan Chen, Wei Cao
2018 J jnl
Complex.
Yuangen Yao, Wei Cao, Qiming Pei, Chengzhang Ma, Ming Yi
2018 J jnl
CoRR
Wei Cao, Wenxu Song, Xinge Li, Sixiao Zheng, Ge Zhang, Yanting Wu, Sailing He, Huilin Zhu, Jiajia Chen
2018 conf
CSAE
Chang Yi, Xiuguo Zhang, Wei Cao
2018 Misc conf
SETTA
Junyan Qian, Cong Chen, Wei Cao, Zhongyi Zhai, Lingzhong Zhao
2018 J jnl
IEEE Signal Process. Lett.
Yi Yang, Wei Cao, Shiqian Wu, Zhengguo Li
2018 C conf
IGARSS
Junbang Wang, Guangxin Lu, Wei Cao, Shaoqiang Wang, Quanqin Shao, Guicai Li, ArshadAli Shedayi, Jiangwen Fan
2018 conf
ICNSC
Kai Ding, He Shi, Jizhuang Hui, Yongjian Liu, Bin Zhu, Fuqiang Zhang, Wei Cao
2018 C conf
IGARSS
Wei Cao, Lulu Liu, Dan Wu, Zhai Jun, Yunfeng Hu, Duanyang Xu
2017 Misc conf
Visualization and Data Analysis
Lieyu Shi, Lei Zhang, Wei Cao, Guoning Chen
2017 J jnl
Comput. Electr. Eng.
Songyang Wu, Yong Zhang, Wei Cao
2017 J jnl
Wirel. Pers. Commun.
Wei Cao, Jing Lei, Weidong Hu, Wei Li
2017 J jnl
Memetic Comput.
Wei Cao, Hua Han, Xian-Kun Sun, Zhijun Fang
2017 C conf
IGARSS
Yuzhi Tang, Quanqin Shao, Jiyuan Liu, Jiangwen Fan, Wei Cao, Lin Huang, Fan Yang, Xingjian Guo
2016 conf
BESC
Qian Li, Zhenglu Yang, Wei Cao, Kentaro Shimizu
2016 J jnl
Concurr. Eng. Res. Appl.
Peilu Sun, Chaoyang Zhang, Pingyu Jiang, Wei Cao
2016 J jnl
RFC
Mach (Guoyi) Chen, Wei Cao, Attila Takács, Ping Pan
2016 J jnl
Intell. Data Anal.
Anping Song, Xuehai Ding, Jianjiao Chen, Mingbo Li, Wei Cao, Ke Pu
2016 J jnl
IEICE Electron. Express
Junyan Qian, Wei Cao, Jia Hu, Jingwei Zhang, Zhoubo Xu, Zhide Zhou
2016 J jnl
IET Commun.
Baoguo Li, Jing Lei, Wei Cao, Lei Wen, Yuheng Mou
2015 J jnl
NeuroImage
Wei Li, Nian Wang, Fang Yu, Hui Han, Wei Cao, Rebecca Romero, Bundhit Tantiwongkosi, Timothy Q. Duong, Chunlei Liu
2015 conf
BigMM
Jin Yang, Jianmin Pang, Jintao Yu, Wei Cao
2015 J jnl
Neurocomputing
Yun Song, Wei Cao, Yanfei Shen, Gaobo Yang
2015 J jnl
Neurocomputing
Yanfei Shen, Jintao Li, Zhenmin Zhu, Wei Cao, Yun Song
2015 conf
FSKD
Runling Peng, Wei Cao, Chenghai Xu, Jianhui Tian
2015 J jnl
BMC Medical Informatics Decis. Mak.
Yiming Chen, Wei Cao, Xianchao Gao, Huishan Ong, Tong Ji
2015 J jnl
Remote. Sens.
Tian He, Quanqin Shao, Wei Cao, Lin Huang, Lulu Liu
2015 J jnl
计算机科学
Wei Cao, Qiuyue Wang, Xiongpai Qin, Shan Wang
2014 conf
SOSE
Li Zhao, Ge Fu, Qian Liu, Xinran Liu, Wei Cao
2014 A conf
IPDPS
Chuanfu Xu, Lilun Zhang, Xiaogang Deng, Jianbin Fang, Guangxue Wang, Wei Cao, Yonggang Che, Yongxian Wang, Wei Liu
2014 J jnl
Clust. Comput.
Wei Cao, Chuanfu Xu, Zhenghua Wang, Lu Yao, Hua-Yong Liu
2014 J jnl
J. Comput. Phys.
Chuanfu Xu, Xiaogang Deng, Lilun Zhang, Jianbin Fang, Guangxue Wang, Yi Jiang, Wei Cao, Yonggang Che, Yongxian Wang, Zhenghua Wang, Wei Liu, Xinghua Cheng
2014 conf
CWSN
Wei Cao, Tao Yang, Shuai Zhang, Ke Zhao, Bin Cheng
2014 C conf
CSCWD
Chao Liu, Pingyu Jiang, Wei Cao
2014 J jnl
NeuroImage
Wei Cao, Wei Li, Hui Han, Shonagh K. O'Leary-Moore, Kathleen K. Sulik, G. Allan Johnson, Chunlei Liu
2014 conf
TREC
Qiuyue Wang, Shaochen Shi, Wei Cao
2014 conf
FSKD
Zhen Meng, Jing Shao, Wei Cao, Jianhui Li, Yuanchun Zhou, Xuezhi Wang
2014 J jnl
RFC
Mach (Guoyi) Chen, Wei Cao, So Ning, Frederic Jounay, Simon Delord
2014 J jnl
Neurocomputing
Yun Song, Wei Cao, Zunliang He
2013 conf
ESSDERC
Wei Cao, Jiahao Kang, Wei Liu, Yasin Khatami, Deblina Sarkar, Kaustav Banerjee
2013 conf
EMC/HumanCom
Yong Liu, Fan Jia, Wei Cao
2013 conf
EMC/HumanCom
Qi Wang, Wei Cao, Yun Liu
2013 B conf
EDBT
Wei Cao, Dennis E. Shasha
2013 J jnl
Bell Labs Tech. J.
Bin Guo, Wei Cao, An Tao, Dragan Samardzija
2013 J jnl
J. Netw. Comput. Appl.
Leijie Fu, Pingyu Jiang, Wei Cao
2013 C conf
ISC
Chuanfu Xu, Xiaogang Deng, Lilun Zhang, Yi Jiang, Wei Cao, Jianbin Fang, Yonggang Che, Yongxian Wang, Wei Liu
2013 conf
ICIC (1)
Wei Cao, Yun Song, Zunliang He, Zhimin Zhou
2013 B conf
EDBT
Wei Cao, Dennis E. Shasha
2012 conf
DASFAA Workshops
Yulei Fan, Wei Cao, Xiaofeng Meng
2012 conf
CCIS
Weijie Li, Haiqiang Chen, Wei Cao, Xin Zhou
2012 conf
CHINACOM
Bin Guo, Wei Cao, An Tao, Dragan Samardzija
2012 conf
FSKD
Zhen Meng, Jianhui Li, Yunchun Zhou, Wei Cao, Xiao Xiao, Jing Zhao, Hui Dong, Shouzhou Zhang
2012 conf
WAIM
Zeping Lu, Xiaoying Qi, Wei Cao, Xiaofeng Meng
2012 Misc conf
ICNC
Jiayan Wen, Wei Cao
2011 conf
BMEI
Lu Yao, Zhenghua Wang, Zongzhe Li, Wei Cao, Yongxian Wang
2011 conf
PAKDD (2)
Chao Luo, Yanchang Zhao, Dan Luo, Chengqi Zhang, Wei Cao
2011 conf
ICIC (1)
Wei Cao, Hui Zhou, Zhimin Zhou, Zuowei Fu
2011 conf
BMEI
Wei Cao, Zhenghua Wang, Zongzhe Li, Lu Yao, Yongxian Wang
2011 C conf
IGARSS
Hao Liu, Fuzhou Duan, Wenji Zhao, Liliang Pan, Wei Cao, Huili Gong
2011 conf
ICICA (2)
Haichao Luo, Kai Zhou, Teng Chen, Wei Cao
2011 conf
ICICA (LNCS)
Wei Cao, Sheng-jiang Zhang, Zhen-lin Lu, Zi-la Jia
2011 J jnl
SIGMOD Rec.
Philippe Bonnet, Stefan Manegold, Matias Bjørling, Wei Cao, Javier González, Joel A. Granados, Nancy Hall, Stratos Idreos, Milena Ivanova, Ryan Johnson, David Koop, Tim Kraska, René Müller, Dan Olteanu, Paolo Papotti, Christine F. Reilly, Dimitris Tsirogiannis, Cong Yu, Juliana Freire, Dennis E. Shasha
2011 J jnl
SIGMOD Rec.
Xiaofeng Meng, Peiquan Jin, Wei Cao, Lihua Yue
2011 conf
EMEIT
Licheng Qu, Dayue Sun, Wei Cao, Peng Li
2011 conf
BMEI
Zhen Meng, Jianhui Li, Yunchun Zhou, Qi Liu, Yong Liu, Wei Cao
2010 conf
Geoinformatics
Wei Cao, Fuzhou Duan, Huili Gong, Wenji Zhao
2010 C conf
IGARSS
Wei Cao, Fuzhou Duan, Huili Gong, Wenji Zhao
2010 J jnl
Adv. Inf. Sci. Serv. Sci.
Shaoliang Meng, Wei Cao, Yixin Yan, Shengming Li
2010 conf
ITNG
Shi-wei Gao, Wei Cao
2010 J jnl
RFC
Hui Liu, Wei Cao, Hitoshi Asaeda
2010 J jnl
J. Next Gener. Inf. Technol.
Wei Cao, Yixin Yan, Shengming Li
2009 J jnl
Int. J. Distributed Sens. Networks
Jiwen Liu, Wendong Wang, Xiangyang Gong, Hui Liu, Wei Cao, Xuesong Gao
2009 conf
APWeb/WAIM
Xiongpai Qin, Wei Cao, Shan Wang
2009 conf
FSKD (6)
Wei Cao, Lanling Zeng
2009 conf
SpringSim
Xiongpai Qin, Wei Cao, Shan Wang
2008 C conf
ISPA
Ruixue Li, Zhiyi Fang, Wei Cao, Yumei Yin, Peng Xu
2008 C conf
PDCAT
Xiongpai Qin, Yanqin Xiao, Wei Cao, Shan Wang
2008 conf
WAIM
Wei Cao, Xiongpai Qin, Shan Wang
2007 conf
ASAP
Ning Qu, Yansong Zheng, Wei Cao, Xu Cheng
2005 J jnl
Microelectron. Reliab.
Jian Chen, Jianbin Xu, Kun Xue, Jin An, Ning Ke, Wei Cao, Haibo Xia, Jing Shi, Decheng Tian
2005 conf
TREC
Wei Cao, Aijun An, Xiangji Huang
2003 J jnl
Comput. Syst. Sci. Eng.
Haifeng Zhou, Wei Cao, Zhenghui Lin
2003 J jnl
Comput. Artif. Intell.
Haifeng Zhou, Zhenghui Lin, Wei Cao
2003 J jnl
Microelectron. Reliab.
Haifeng Zhou, Zhenghui Lin, Wei Cao
2002 conf
DELTA
Haifeng Zhou, Zhenghui Lin, Wei Cao
2001 J jnl
IEEE Trans. Instrum. Meas.
Gang Zhang, Shaohui Li, Zhipeng Zhang, Wei Cao
1989 Misc conf
ICASSP
Gagan Mirchandani, Wei Cao, Barry Bosworth
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