Vijay V. Vazirani

257 papers A* 55A 13B 14C 1Misc 4Journal 130Unranked 36
YearRankTypeTitle / Venue / Authors
2026 J jnl
Games Econ. Behav.
Ilan Adler, Martin Bullinger, Vijay V. Vazirani
2026 J jnl
CoRR
Rohith Reddy Gangam, Tung Mai, Nitya Raju, Vijay V. Vazirani
2026 J jnl
Algorithmica
Vijay V. Vazirani
2025 J jnl
CoRR
Vijay V. Vazirani
2025 J jnl
SIAM J. Comput.
Vijay V. Vazirani, Mihalis Yannakakis
2025 conf
FSTTCS
Rohith Reddy Gangam, Shayan Taherijam, Vijay V. Vazirani
2025 J jnl
CoRR
Rohith Reddy Gangam, Shayan Taherijam, Vijay V. Vazirani
2025 A conf
AAMAS
Jugal Garg, Thorben Tröbst, Vijay V. Vazirani
2025 J jnl
CoRR
Rohith Reddy Gangam, Shayan Taherijam, Vijay V. Vazirani
2024 J jnl
CoRR
Ilan Adler, Martin Bullinger, Vijay V. Vazirani
2024 J jnl
Math. Oper. Res.
Vijay V. Vazirani
2024 A* conf
EC
Thorben Tröbst, Vijay V. Vazirani
2024 J jnl
CoRR
Thorben Tröbst, Vijay V. Vazirani
2024 J jnl
CoRR
Jugal Garg, Thorben Tröbst, Vijay V. Vazirani
2024 J jnl
Auton. Agents Multi Agent Syst.
Jugal Garg, Thorben Tröbst, Vijay V. Vazirani
2024 J jnl
CoRR
Vijay V. Vazirani
2024 J jnl
CoRR
Rohith Reddy Gangam, Naveen Garg, Parnian Shahkar, Vijay V. Vazirani
2024 B conf
SAGT
Vijay V. Vazirani
2024 conf
WINE
Ioannis Panageas, Thorben Tröbst, Vijay V. Vazirani
2023 A conf
AAMAS
Jugal Garg, Thorben Tröbst, Vijay V. Vazirani
2023 J jnl
CoRR
Rohith Reddy Gangam, Tung Mai, Nitya Raju, Vijay V. Vazirani
2023 J jnl
Inf. Process. Lett.
Thorben Tröbst, Vijay V. Vazirani
2023 J jnl
CoRR
Vijay V. Vazirani
2023 J jnl
CoRR
Vijay V. Vazirani
2023 conf
FSTTCS
Vijay V. Vazirani
2023 J jnl
CoRR
Thorben Tröbst, Vijay V. Vazirani
2022 conf
FSTTCS
Rohith Reddy Gangam, Tung Mai, Nitya Raju, Vijay V. Vazirani
2022 J jnl
CoRR
Vijay V. Vazirani
2022 J jnl
CoRR
Vijay V. Vazirani
2022 A conf
ITCS
Mojtaba Hosseini, Vijay V. Vazirani
2022 conf
FSTTCS
Vijay V. Vazirani
2022 A conf
AAMAS
Jugal Garg, Thorben Tröbst, Vijay V. Vazirani
2022 B conf
MFCS
Vijay V. Vazirani
2022 J jnl
Games Econ. Behav.
Vijay V. Vazirani
2021 J jnl
CoRR
Ioannis Panageas, Thorben Tröbst, Vijay V. Vazirani
2021 A conf
ITCS
Vijay V. Vazirani, Mihalis Yannakakis
2021 J jnl
SIAM J. Comput.
David Eppstein, Vijay V. Vazirani
2021 J jnl
CoRR
Mojtaba Hosseini, Vijay V. Vazirani
2021 J jnl
CoRR
Vijay V. Vazirani
2021 J jnl
CoRR
Vijay V. Vazirani
2020 J jnl
CoRR
Vijay V. Vazirani
2020 J jnl
CoRR
Thorben Tröbst, Vijay V. Vazirani
2020 J jnl
CoRR
Jugal Garg, Thorben Tröbst, Vijay V. Vazirani
2020 J jnl
CoRR
Vijay V. Vazirani
2020 J jnl
Theor. Comput. Sci.
Ruta Mehta, Vijay V. Vazirani
2020 J jnl
CoRR
Vijay V. Vazirani, Mihalis Yannakakis
2020 A conf
ITCS
Nima Anari, Vijay V. Vazirani
2020 J jnl
J. ACM
Nima Anari, Vijay V. Vazirani
2020 conf
FSTTCS
Karthik Gajulapalli, James A. Liu, Tung Mai, Vijay V. Vazirani
2019 J jnl
CoRR
Nima Anari, Vijay V. Vazirani
2019 J jnl
CoRR
Karthik Gajulapalli, James A. Liu, Vijay V. Vazirani
2019 B conf
SPAA
David Eppstein, Vijay V. Vazirani
2019 J jnl
CoRR
Tung Mai, Vijay V. Vazirani
2018 J jnl
CoRR
Tung Mai, Vijay V. Vazirani
2018 J jnl
CoRR
Tung Mai, Vijay V. Vazirani
2018 A* conf
SODA
Nikhil R. Devanur, Jugal Garg, Ruta Mehta, Vijay V. Vazirani, Sadra Yazdanbod
2018 A* conf
EC
Tung Mai, Milena Mihail, Ioannis Panageas, Will Ratcliff, Vijay V. Vazirani, Peter Yunker
2018 A conf
ESA
Tung Mai, Vijay V. Vazirani
2018 J jnl
CoRR
Tung Mai, Vijay V. Vazirani
2018 J jnl
CoRR
David Eppstein, Vijay V. Vazirani
2018 A* conf
SODA
Nima Anari, Tung Mai, Shayan Oveis Gharan, Vijay V. Vazirani
2018 A* conf
FOCS
Nima Anari, Vijay V. Vazirani
2018 J jnl
Math. Oper. Res.
Jugal Garg, Ruta Mehta, Vijay V. Vazirani
2018 J jnl
ACM Trans. Economics and Comput.
Jugal Garg, Ruta Mehta, Vijay V. Vazirani, Sadra Yazdanbod
2017 conf
WINE
Kamal Jain, Tung Mai, Vijay V. Vazirani
2017 Misc conf
COCOON
Ruta Mehta, Vijay V. Vazirani
2017 J jnl
CoRR
Tung Mai, Richard Peng, Anup B. Rao, Vijay V. Vazirani
2017 A* conf
EC
Richard Cole, Nikhil R. Devanur, Vasilis Gkatzelis, Kamal Jain, Tung Mai, Vijay V. Vazirani, Sadra Yazdanbod
2017 A conf
ITCS
Ruta Mehta, Ioannis Panageas, Georgios Piliouras, Prasad Tetali, Vijay V. Vazirani
2017 A* conf
ICALP
Tung Mai, Ioannis Panageas, Vijay V. Vazirani
2017 J jnl
CoRR
Nima Anari, Vijay V. Vazirani
2017 conf
BIBM
Rahul Nihalani, Sriram P. Chockalingam, Shaowei Zhu, Vijay V. Vazirani, Srinivas Aluru
2017 J jnl
CoRR
Tung Mai, Ioannis Panageas, Will Ratcliff, Vijay V. Vazirani, Peter Yunker
2017 A* conf
STOC
Jugal Garg, Ruta Mehta, Vijay V. Vazirani, Sadra Yazdanbod
2016 J jnl
CoRR
Kamal Jain, Tung Mai, Vijay V. Vazirani
2016 J jnl
CoRR
Richard Cole, Nikhil R. Devanur, Vasilis Gkatzelis, Kamal Jain, Tung Mai, Vijay V. Vazirani, Sadra Yazdanbod
2016 J jnl
Theory Comput.
Jugal Garg, Ruta Mehta, Vijay V. Vazirani
2016 J jnl
CoRR
Nima Anari, Tung Mai, Shayan Oveis Gharan, Vijay V. Vazirani
2016 J jnl
CoRR
Nikhil R. Devanur, Kamal Jain, Tung Mai, Vijay V. Vazirani, Sadra Yazdanbod
2016 J jnl
CoRR
Tung Mai, Ioannis Panageas, Vijay V. Vazirani
2015 J jnl
SIAM J. Comput.
Jugal Garg, Ruta Mehta, Milind A. Sohoni, Vijay V. Vazirani
2015 J jnl
CoRR
Nikhil R. Devanur, Jugal Garg, Ruta Mehta, Vijay V. Vazirani, Sadra Yazdanbod
2015 conf
FSTTCS
Leonard J. Schulman, Vijay V. Vazirani
2015 conf
ICALP (1)
Jugal Garg, Ruta Mehta, Vijay V. Vazirani, Sadra Yazdanbod
2015 B conf
SAGT
Ruta Mehta, Vijay V. Vazirani, Sadra Yazdanbod
2015 J jnl
CoRR
Ruta Mehta, Ioannis Panageas, Georgios Piliouras, Prasad Tetali, Vijay V. Vazirani
2014 A* conf
STOC
Jugal Garg, Ruta Mehta, Vijay V. Vazirani
2014 J jnl
Dagstuhl Reports
Nimrod Megiddo, Kurt Mehlhorn, Rahul Savani, Vijay V. Vazirani
2014 conf
WINE
Maria-Florina Balcan, Amit Daniely, Ruta Mehta, Ruth Urner, Vijay V. Vazirani
2014 J jnl
CoRR
Maria-Florina Balcan, Amit Daniely, Ruta Mehta, Ruth Urner, Vijay V. Vazirani
2014 J jnl
CoRR
Jugal Garg, Ruta Mehta, Vijay V. Vazirani, Sadra Yazdanbod
2014 A* conf
SODA
Jugal Garg, Vijay V. Vazirani
2014 J jnl
CoRR
Ruta Mehta, Vijay V. Vazirani, Sadra Yazdanbod
2014 J jnl
SIAM J. Discret. Math.
Deeparnab Chakrabarty, Gagan Goel, Vijay V. Vazirani, Lei Wang, Changyuan Yu
2013 J jnl
CoRR
Ruta Mehta, Vijay V. Vazirani
2013 J jnl
CoRR
Jugal Garg, Vijay V. Vazirani
2013 J jnl
SIAM J. Discret. Math.
Vijay V. Vazirani
2013 J jnl
CoRR
Anupam Gupta, Viswanath Nagarajan, Vijay V. Vazirani
2013 B conf
IPCO
Anupam Gupta, Viswanath Nagarajan, Vijay V. Vazirani
2012 A* conf
STOC
Jugal Garg, Ruta Mehta, Milind A. Sohoni, Vijay V. Vazirani
2012 J jnl
CoRR
Leonard J. Schulman, Vijay V. Vazirani
2012 J jnl
CoRR
Vijay V. Vazirani
2012 Misc conf
CSR
Vijay V. Vazirani
2012 J jnl
SIAM J. Discret. Math.
Vijay V. Vazirani
2012 A* conf
SODA
Vijay V. Vazirani
2012 J jnl
J. ACM
Vijay V. Vazirani
2011 J jnl
CoRR
Vijay V. Vazirani
2011 J jnl
Math. Oper. Res.
Gagan Goel, Vijay V. Vazirani
2011 A* conf
SIGCOMM
Vytautas Valancius, Cristian Lumezanu, Nick Feamster, Ramesh Johari, Vijay V. Vazirani
2011 J jnl
J. ACM
Vijay V. Vazirani, Mihalis Yannakakis
2011 J jnl
CoRR
Vytautas Valancius, Cristian Lumezanu, Nick Feamster, Ramesh Johari, Vijay V. Vazirani
2011 J jnl
Math. Program.
Deeparnab Chakrabarty, Nikhil R. Devanur, Vijay V. Vazirani
2010 conf
Equilibrium Computation
Edith Elkind, Nimrod Megiddo, Peter Bro Miltersen, Bernhard von Stengel, Vijay V. Vazirani
2010 B conf
SAGT
Vijay V. Vazirani
2010 B conf
SAGT
Gagan Goel, Vijay V. Vazirani
2010 J jnl
SIAM J. Discret. Math.
Deeparnab Chakrabarty, Aranyak Mehta, Vijay V. Vazirani
2010 J jnl
Games Econ. Behav.
Kamal Jain, Vijay V. Vazirani
2010 ed.
Equilibrium Computation
Edith Elkind, Nimrod Megiddo, Peter Bro Miltersen, Vijay V. Vazirani, Bernhard von Stengel
2010 J jnl
CoRR
Kamal Jain, Vijay V. Vazirani
2010 A conf
ICS
Vijay V. Vazirani, Mihalis Yannakakis
2010 J jnl
CoRR
Vijay V. Vazirani
2010 conf
WINE
Vijay V. Vazirani
2010 J jnl
CoRR
Vijay V. Vazirani
2010 J jnl
SIAM J. Discret. Math.
Deeparnab Chakrabarty, Nikhil R. Devanur, Vijay V. Vazirani
2010 J jnl
Math. Oper. Res.
Vijay V. Vazirani
2009 J jnl
CoRR
Vijay V. Vazirani
2009 conf
WINE
Vijay V. Vazirani, Lei Wang
2008 J jnl
SIAM J. Comput.
Ivona Bezáková, Daniel Stefankovic, Vijay V. Vazirani, Eric Vigoda
2008 J jnl
Electron. Colloquium Comput. Complex.
Vijay V. Vazirani, Lei Wang
2008 conf
WINE
Deeparnab Chakrabarty, Gagan Goel, Vijay V. Vazirani, Lei Wang, Changyuan Yu
2008 J jnl
SIAM J. Comput.
Kamal Jain, Vijay V. Vazirani
2008 A conf
Internet Measurement Conference
Anirudh Ramachandran, Srinivasan Seetharaman, Nick Feamster, Vijay V. Vazirani
2008 A conf
CoNEXT
Vytautas Valancius, Nick Feamster, Ramesh Johari, Vijay V. Vazirani
2008 J jnl
J. ACM
Nikhil R. Devanur, Christos H. Papadimitriou, Amin Saberi, Vijay V. Vazirani
2008 C conf
AAIM
Vijay V. Vazirani
2008 conf
SWAT
Vijay V. Vazirani
2008 B conf
IPCO
Deeparnab Chakrabarty, Nikhil R. Devanur, Vijay V. Vazirani
2008 J jnl
Algorithmica
Nayantara Bhatnagar, Dana Randall, Vijay V. Vazirani, Eric Vigoda
2008 conf
FSTTCS
Noam Berger, Nevin Kapur, Leonard J. Schulman, Vijay V. Vazirani
2008 J jnl
Electron. Colloquium Comput. Complex.
Noam Berger, Nevin Kapur, Leonard J. Schulman, Vijay V. Vazirani
2007 J jnl
Theor. Comput. Sci.
Dinesh Garg, Kamal Jain, Kunal Talwar, Vijay V. Vazirani
2007 J jnl
J. ACM
Aranyak Mehta, Amin Saberi, Umesh V. Vazirani, Vijay V. Vazirani
2007 book
Noam Nisan, Tim Roughgarden, Éva Tardos, Vijay V. Vazirani
2007 J jnl
Theor. Comput. Sci.
Sanjiv Kapoor, Aranyak Mehta, Vijay V. Vazirani
2007 conf
WINE
Nimrod Megiddo, Vijay V. Vazirani
2007 A* conf
STOC
Kamal Jain, Vijay V. Vazirani
2007 conf
WINE
Vijay V. Vazirani
2006 A* conf
SODA
Ivona Bezáková, Daniel Stefankovic, Vijay V. Vazirani, Eric Vigoda
2006 conf
ICALP (1)
Deeparnab Chakrabarty, Aranyak Mehta, Vijay V. Vazirani
2006 J jnl
Electron. Colloquium Comput. Complex.
Deeparnab Chakrabarty, Nikhil R. Devanur, Vijay V. Vazirani
2006 Misc conf
International Conference on Computational Science (2)
Mohammad Taghi Hajiaghayi, Kamal Jain, Lap Chi Lau, Ion I. Mandoiu, Alexander Russell, Vijay V. Vazirani
2006 conf
WINE
Deeparnab Chakrabarty, Nikhil R. Devanur, Vijay V. Vazirani
2006 B conf
ITW
Kamal Jain, Vijay V. Vazirani, Gideon Yuval
2006 J jnl
IEEE Trans. Inf. Theory
Kamal Jain, Vijay V. Vazirani, Gideon Yuval
2006 J jnl
J. Algorithms
Aranyak Mehta, Scott Shenker, Vijay V. Vazirani
2006 B conf
LATIN
Nayantara Bhatnagar, Dana Randall, Vijay V. Vazirani, Eric Vigoda
2005 J jnl
Comb.
Leonard J. Schulman, Vijay V. Vazirani
2005 conf
WINE
Dinesh Garg, Kamal Jain, Kunal Talwar, Vijay V. Vazirani
2005 conf
WINE
Kamal Jain, Aranyak Mehta, Kunal Talwar, Vijay V. Vazirani
2005 A* conf
FOCS
Aranyak Mehta, Amin Saberi, Umesh V. Vazirani, Vijay V. Vazirani
2005 conf
WINE
Sanjiv Kapoor, Aranyak Mehta, Vijay V. Vazirani
2005 A* conf
SODA
Kamal Jain, Vijay V. Vazirani, Yinyu Ye
2005 B conf
ISIT
Kamal Jain, Vijay V. Vazirani, Raymond W. Yeung, Gideon Yuval
2005 conf
WINE
Nikhil R. Devanur, Naveen Garg, Rohit Khandekar, Vinayaka Pandit, Amin Saberi, Vijay V. Vazirani
2005 J jnl
Decis. Support Syst.
Nikhil R. Devanur, Milena Mihail, Vijay V. Vazirani
2004 J jnl
Algorithmica
Kamal Jain, Vijay V. Vazirani
2004 conf
APPROX-RANDOM
Rahul Garg, Sanjiv Kapoor, Vijay V. Vazirani
2004 J jnl
J. Algorithms
Naveen Garg, Vijay V. Vazirani, Mihalis Yannakakis
2004 A* conf
EC
Aranyak Mehta, Vijay V. Vazirani
2003 A* conf
STOC
Micah Adler, Eran Halperin, Richard M. Karp, Vijay V. Vazirani
2003 conf
FSTTCS
Nikhil R. Devanur, Vijay V. Vazirani
2003 A* conf
EC
Nikhil R. Devanur, Vijay V. Vazirani
2003 J jnl
J. ACM
Kamal Jain, Mohammad Mahdian, Evangelos Markakis, Amin Saberi, Vijay V. Vazirani
2003 A* conf
EC
Aranyak Mehta, Scott Shenker, Vijay V. Vazirani
2003 A* conf
EC
Nikhil R. Devanur, Milena Mihail, Vijay V. Vazirani
2002 J jnl
J. Algorithms
Kamal Jain, Ion I. Mandoiu, Vijay V. Vazirani, David P. Williamson
2002 ed.
APPROX
Klaus Jansen, Stefano Leonardi, Vijay V. Vazirani
2002 A* conf
STOC
Kamal Jain, Vijay V. Vazirani
2002 J jnl
CoRR
Kamal Jain, Mohammad Mahdian, Evangelos Markakis, Amin Saberi, Vijay V. Vazirani
2002 A* conf
FOCS
Nikhil R. Devanur, Christos H. Papadimitriou, Amin Saberi, Vijay V. Vazirani
2001 conf
Information Hiding
Ramarathnam Venkatesan, Vijay V. Vazirani, Saurabh Sinha
2001 conf
RANDOM-APPROX
Mohammad Mahdian, Evangelos Markakis, Amin Saberi, Vijay V. Vazirani
2001 A* conf
STOC
Kamal Jain, Vijay V. Vazirani
2001 J jnl
J. ACM
Kamal Jain, Vijay V. Vazirani
2001 book
Vijay V. Vazirani
2000 J jnl
IEEE Trans. Comput. Aided Des. Integr. Circuits Syst.
Ion I. Mandoiu, Vijay V. Vazirani, Joseph L. Ganley
2000 conf
APPROX
Kamal Jain, Vijay V. Vazirani
2000 A conf
Theoretical Aspects of Computer Science
Vijay V. Vazirani
2000 J jnl
Theor. Comput. Sci.
Vijay V. Vazirani
1999 A* conf
SODA
Kamal Jain, Ion I. Mandoiu, Vijay V. Vazirani, David P. Williamson
1999 A conf
ICCAD
Ion I. Mandoiu, Vijay V. Vazirani, Joseph L. Ganley
1999 J jnl
SIAM J. Comput.
Naveen Garg, Huzur Saran, Vijay V. Vazirani
1999 A* conf
STOC
Leonard J. Schulman, Vijay V. Vazirani
1999 A* conf
SODA
Sridhar Rajagopalan, Vijay V. Vazirani
1999 A* conf
FOCS
Kamal Jain, Vijay V. Vazirani
1998 J jnl
SIAM J. Comput.
Sridhar Rajagopalan, Vijay V. Vazirani
1998 J jnl
IEEE Trans. Inf. Theory
Kamal Jain, Ion I. Mandoiu, Vijay V. Vazirani
1998 conf
APPROX
Vijay V. Vazirani
1997 J jnl
Algorithmica
Naveen Garg, Vijay V. Vazirani, Mihalis Yannakakis
1996 A* conf
FOCS
Vijay V. Vazirani, Huzur Saran, B. Sundar Rajan
1996 J jnl
IEEE Trans. Inf. Theory
Vijay V. Vazirani, Huzur Saran, B. Sundar Rajan
1996 J jnl
SIAM J. Comput.
Naveen Garg, Vijay V. Vazirani, Mihalis Yannakakis
1995 J jnl
Comb.
David P. Williamson, Michel X. Goemans, Milena Mihail, Vijay V. Vazirani
1995 J jnl
Math. Program.
Naveen Garg, Vijay V. Vazirani
1995 J jnl
SIAM J. Comput.
Huzur Saran, Vijay V. Vazirani
1995 Misc conf
COCOON
Vijay V. Vazirani
1994 conf
FSTTCS
Vivek Arora, Santosh S. Vempala, Huzur Saran, Vijay V. Vazirani
1994 J jnl
Comb.
Vijay V. Vazirani
1994 A* conf
FOCS
Naveen Garg, Huzur Saran, Vijay V. Vazirani
1994 A* conf
ICALP
Naveen Garg, Vijay V. Vazirani, Mihalis Yannakakis
1994 J jnl
Theor. Comput. Sci.
Samir Khuller, Stephen G. Mitchell, Vijay V. Vazirani
1994 J jnl
SIAM J. Comput.
H. Narayanan, Huzur Saran, Vijay V. Vazirani
1993 B conf
IPCO
Naveen Garg, Vijay V. Vazirani
1993 A* conf
STOC
David P. Williamson, Michel X. Goemans, Milena Mihail, Vijay V. Vazirani
1993 A* conf
STOC
Naveen Garg, Vijay V. Vazirani, Mihalis Yannakakis
1993 J jnl
J. Algorithms
David Pearson, Vijay V. Vazirani
1993 J jnl
Inf. Process. Lett.
Éva Tardos, Vijay V. Vazirani
1993 A* conf
ICALP
Naveen Garg, Vijay V. Vazirani, Mihalis Yannakakis
1993 A* conf
FOCS
Sridhar Rajagopalan, Vijay V. Vazirani
1992 J jnl
SIAM J. Comput.
Samir Khuller, Stephen G. Mitchell, Vijay V. Vazirani
1992 A* conf
SODA
H. Narayanan, Huzur Saran, Vijay V. Vazirani
1992 A* conf
ICALP
Vijay V. Vazirani, Mihalis Yannakakis
1991 A* conf
FOCS
Huzur Saran, Vijay V. Vazirani
1991 A* conf
ICALP
Samir Khuller, Stephen G. Mitchell, Vijay V. Vazirani
1991 J jnl
Theor. Comput. Sci.
Samir Khuller, Vijay V. Vazirani
1991 B conf
WADS
Dalit Naor, Vijay V. Vazirani
1990 conf
FSTTCS
David Pearson, Vijay V. Vazirani
1990 B conf
IPCO
Vijay V. Vazirani
1990 A* conf
STOC
Richard M. Karp, Umesh V. Vazirani, Vijay V. Vazirani
1989 J jnl
J. Algorithms
Michael O. Rabin, Vijay V. Vazirani
1989 J jnl
Inf. Comput.
Vijay V. Vazirani
1989 J jnl
Discret. Appl. Math.
Vijay V. Vazirani, Mihalis Yannakakis
1989 A* conf
FOCS
Samir Khuller, Stephen G. Mitchell, Vijay V. Vazirani
1989 J jnl
SIAM J. Comput.
Umesh V. Vazirani, Vijay V. Vazirani
1988 conf
SWAT
Vijay V. Vazirani
1988 A* conf
ICALP
Vijay V. Vazirani, Mihalis Yannakakis
1987 J jnl
Algorithmica
Richard M. Karp, Frank Thomson Leighton, Ronald L. Rivest, Clark D. Thompson, Umesh V. Vazirani, Vijay V. Vazirani
1987 A* conf
STOC
Ketan Mulmuley, Umesh V. Vazirani, Vijay V. Vazirani
1987 J jnl
Comb.
Ketan Mulmuley, Umesh V. Vazirani, Vijay V. Vazirani
1986 J jnl
Theor. Comput. Sci.
Leslie G. Valiant, Vijay V. Vazirani
1986 J jnl
Theor. Comput. Sci.
Mark Jerrum, Leslie G. Valiant, Vijay V. Vazirani
1986 conf
FSTTCS
Umesh V. Vazirani, Vijay V. Vazirani
1985 conf
FSTTCS
Dexter Kozen, Umesh V. Vazirani, Vijay V. Vazirani
1985 A* conf
STOC
Leslie G. Valiant, Vijay V. Vazirani
1985 A* conf
FOCS
Umesh V. Vazirani, Vijay V. Vazirani
1985 A* conf
STOC
Umesh V. Vazirani, Vijay V. Vazirani
1984 A* conf
FOCS
Umesh V. Vazirani, Vijay V. Vazirani
1984 A* conf
CRYPTO
Umesh V. Vazirani, Vijay V. Vazirani
1983 J jnl
Theor. Comput. Sci.
Umesh V. Vazirani, Vijay V. Vazirani
1983 A* conf
FOCS
Richard M. Karp, Frank Thomson Leighton, Ronald L. Rivest, Clark D. Thompson, Umesh V. Vazirani, Vijay V. Vazirani
1983 A* conf
CRYPTO
Umesh V. Vazirani, Vijay V. Vazirani
1983 A* conf
CRYPTO
Manuel Blum, Umesh V. Vazirani, Vijay V. Vazirani
1983 A* conf
FOCS
Umesh V. Vazirani, Vijay V. Vazirani
1982 A* conf
FOCS
Umesh V. Vazirani, Vijay V. Vazirani
1982 J jnl
Inf. Process. Lett.
Larry J. Stockmeyer, Vijay V. Vazirani
1982 J jnl
Oper. Res. Lett.
Eugene L. Lawler, Michael George Luby, Vijay V. Vazirani
1980 A* conf
FOCS
Silvio Micali, Vijay V. Vazirani
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