James C. Gee

291 papers A* 13A 8B 19C 1Misc 1Journal 105Unranked 123
YearRankTypeTitle / Venue / Authors
2026 J jnl
CoRR
Rohit Jena, Pratik Chaudhari, James C. Gee
2026 ed.
MICCAI (1)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (2)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (3)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (4)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (9)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (5)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (6)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (7)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (8)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (10)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (11)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (12)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (13)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (14)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (15)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2026 ed.
MICCAI (16)
James C. Gee, Daniel C. Alexander, Jaesung Hong, Juan Eugenio Iglesias, Carole H. Sudre, Archana Venkataraman, Polina Golland, Jong Hyo Kim, Jinah Park
2025 J jnl
J. Imaging Inform. Medicine
Neil Chatterjee, Jeffrey T. Duda, James C. Gee, Ameena Elahi, Kristen Martin, Van Doan, Hannah Liu, Matthew T. MacLean, Daniel J. Rader, Arijitt Borthakur, Charles E. Kahn Jr., Hersh Sagreiya, Walter R. Witschey
2025 J jnl
CoRR
Rohit Jena, Vedant Zope, Pratik Chaudhari, James C. Gee
2025 conf
ICCVW
Hongzhuo Chen, Rahul Shukla, Ruiming Wu, Shu Yang, Duy Duong-Tran, Duy Minh Ho Nguyen, Mathias Niepert, Cameron Beeche, James C. Gee, Jeffrey T. Duda, Rakesh Sharma, Christos Davatzikos, Walter R. Witschey, Bojian Hou, Li Shen
2025 J jnl
J. Imaging Inform. Medicine
Garv Mehdiratta, Jeffrey T. Duda, Ameena Elahi, Arijitt Borthakur, Neil Chatterjee, James C. Gee, Hersh Sagreiya, Walter R. Witschey, Charles E. Kahn
2025 J jnl
Medical Image Anal.
Rohit Jena, Pratik Chaudhari, James C. Gee
2025 A* conf
ICML
Michael S. Yao, James C. Gee, Osbert Bastani
2025 J jnl
CoRR
Michael S. Yao, James C. Gee, Osbert Bastani
2025 J jnl
CoRR
Rohit Jena, Pratik Chaudhari, James C. Gee
2025 J jnl
CoRR
Gabriel A. B. do Nascimento, Vincent Dong, Guilherme J. Cavalcante, Alex Nguyen, Thaís Gaudencio do Rêgo, Yuri Malheiros, Telmo de Menezes e Silva Filho, Carla R. Zeballos Torrez, James C. Gee, Annemarie McCarthy, Andrew D. A. Maidment, Bruno Barufaldi
2025 J jnl
CoRR
Guilherme J. Cavalcante, José Gabriel A. Moreira, Gabriel A. B. do Nascimento, Vincent Dong, Alex Nguyen, Thaís Gaudencio do Rêgo, Yuri Malheiros, Telmo de Menezes e Silva Filho, Carla R. Zeballos Torrez, James C. Gee, Annemarie McCarthy, Andrew D. A. Maidment, Bruno Barufaldi
2024 J jnl
CoRR
Yifan Wu, Yang Liu, Yue Yang, Michael S. Yao, Wenli Yang, Xuehui Shi, Lihong Yang, Dongjun Li, Yueming Liu, James C. Gee, Xuan Yang, Wenbin Wei, Shi Gu
2024 A* conf
NeurIPS
Yue Yang, Mona Gandhi, Yufei Wang, Yifan Wu, Michael S. Yao, Chris Callison-Burch, James C. Gee, Mark Yatskar
2024 J jnl
CoRR
Yue Yang, Mona Gandhi, Yufei Wang, Yifan Wu, Michael S. Yao, Chris Callison-Burch, James C. Gee, Mark Yatskar
2024 J jnl
CoRR
Rohit Jena, Pratik Chaudhari, James C. Gee
2024 A* conf
NeurIPS
Rohit Jena, Deeksha Sethi, Pratik Chaudhari, James C. Gee
2024 J jnl
CoRR
Rohit Jena, Deeksha Sethi, Pratik Chaudhari, James C. Gee
2024 J jnl
CoRR
Michael S. Yao, Allison Chae, Charles E. Kahn Jr., Walter R. Witschey, James C. Gee, Hersh Sagreiya, Osbert Bastani
2024 J jnl
CoRR
Rohit Jena, Pratik Chaudhari, James C. Gee
2024 J jnl
CoRR
Michael S. Yao, Yimeng Zeng, Hamsa Bastani, Jacob R. Gardner, James C. Gee, Osbert Bastani
2024 A* conf
NeurIPS
Michael S. Yao, Yimeng Zeng, Hamsa Bastani, Jacob R. Gardner, James C. Gee, Osbert Bastani
2024 J jnl
CoRR
Yifan Wu, Mengjin Dong, Rohit Jena, Chen Qin, James C. Gee
2024 J jnl
IEEE Trans. Medical Imaging
Zeyuan Chen, Yuanjie Zheng, James C. Gee
2023 A* conf
CVPR
Rohit Jena, Lukas Zhornyak, Nehal Doiphode, Pratik Chaudhari, Vivek Buch, James C. Gee, Jianbo Shi
2023 J jnl
CoRR
Huzheng Yang, James C. Gee, Jianbo Shi
2023 conf
Digital and Computational Pathology
Hyung S. Roh, David J. Irwin, Mónica Muñoz López, Maria Mercedes Iñiguez de Onzoño Martin, Ranjit Ittyerah, Sydney Lim, Madigan L. Bedard, John L. Robinson, Theresa Schuck, Emilio Artacho-Pérula, María del Mar Arroyo Jiménez, Maria del Pilar Marcos Rabal, Francisco Javier Molina Romero, Sandra Cebada Sánchez, José Carlos Delgado González, Carlos de la Rosa-Prieto, Marta Córcoles Parada, Edward B. Lee, Daniel T. Ohm, Laura E. M. Wisse, David A. Wolk, James C. Gee, Ricardo Insausti, Paul A. Yushkevich, Min Chen
2023 J jnl
CoRR
Huzheng Yang, James C. Gee, Jianbo Shi
2023 J jnl
CoRR
Rohit Jena, Pratik Chaudhari, James C. Gee, Ganesh Subramanian Iyer, Siddharth Choudhary, Brandon M. Smith
2023 J jnl
CoRR
Huzheng Yang, Jianbo Shi, James C. Gee
2023 J jnl
CoRR
Rohit Jena, Ganesh Subramanian Iyer, Siddharth Choudhary, Brandon M. Smith, Pratik Chaudhari, James C. Gee
2023 conf
PRIME@MICCAI
Michael S. Yao, Allison Chae, Matthew T. MacLean, Anurag Verma, Jeffrey T. Duda, James C. Gee, Drew A. Torigian, Daniel J. Rader, Charles E. Kahn, Walter R. Witschey, Hersh Sagreiya
2023 J jnl
CoRR
Yifan Wu, Pengchuan Zhang, Wenhan Xiong, Barlas Oguz, James C. Gee, Yixin Nie
2023 J jnl
CoRR
Yifan Wu, Rohit Jena, Mehmet A. Gülsün, Vivek K. Singh, Puneet Sharma, James C. Gee
2022 J jnl
CoRR
Rohit Jena, Lukas Zhornyak, Nehal Doiphode, Vivek Buch, James C. Gee, Jianbo Shi
2022 J jnl
CoRR
Michael S. Yao, Allison Chae, Matthew T. MacLean, Anurag Verma, Jeffrey T. Duda, James C. Gee, Drew A. Torigian, Daniel J. Rader, Charles E. Kahn, Walter R. Witschey, Hersh Sagreiya
2022 A* conf
CVPR
Yifan Wu, Tom Z. Jiahao, Jiancong Wang, Paul A. Yushkevich, M. Ani Hsieh, James C. Gee
2021 J jnl
J. Digit. Imaging
Jeffrey D. Rudie, Jeffrey T. Duda, Michael Tran Duong, Po-Hao Chen, Long Xie, Robert Kurtz, Jeffrey B. Ware, Joshua Choi, Raghav R. Mattay, Emmanuel J. Botzolakis, James C. Gee, R. Nick Bryan, Tessa Sundaram Cook, Suyash Mohan, Ilya M. Nasrallah, Andreas M. Rauschecker
2021 J jnl
CoRR
Yifan Wu, Tom Z. Jiahao, Jiancong Wang, Paul A. Yushkevich, James C. Gee, M. Ani Hsieh
2020 conf
ISBI
Alison M. Pouch, Paul A. Yushkevich, Abdullah H. Aly, Alexander H. F. Woltersom, Edidiong Okon, Ahmed H. Aly, Natalie Yushkevich, Shobhana Parameshwaran, Jiancong Wang, Baris U. Oguz, James C. Gee, Ipek Oguz, Nadav Schwartz
2020 A conf
WACV
Jiancong Wang, Yuhua Chen, Yifan Wu, Jianbo Shi, James C. Gee
2020 conf
MICCAI (6)
Yifan Wu, Jiancong Wang, William Lindsay, Tarmily Wen, Jianbo Shi, James C. Gee
2020 J jnl
CoRR
Lingzhi Zhang, Jiancong Wang, Yinshuang Xu, Jie Min, Tarmily Wen, James C. Gee, Jianbo Shi
2020 A* conf
CVPR
Lingzhi Zhang, Jiancong Wang, Yinshuang Xu, Jie Min, Tarmily Wen, James C. Gee, Jianbo Shi
2020 conf
ISBI
Heng jin, Jessica I. W. Morgan, James C. Gee, Min Chen
2019 J jnl
CoRR
Jiancong Wang, Long Xie, Paul A. Yushkevich, James C. Gee
2019 conf
ICIG (1)
Zelu Deng, Jianbin Gao, Tao Huang, James C. Gee
2019 J jnl
CoRR
Jiancong Wang, Yuhua Chen, Yifan Wu, Jianbo Shi, James C. Gee
2019 conf
ICIG (1)
Jianbin Gao, Huan Tang, James C. Gee
2019 ed.
IPMI
Albert C. S. Chung, James C. Gee, Paul A. Yushkevich, Siqi Bao
2019 conf
MICCAI (3)
Huzheng Yang, Xiaoxiao Li, Yifan Wu, Siyi Li, Su Lu, James S. Duncan, James C. Gee, Shi Gu
2019 J jnl
ACM Trans. Knowl. Discov. Data
Yumeng Guo, Fulai Chung, Guozheng Li, Jiancong Wang, James C. Gee
2019 conf
OMIA@MICCAI
Min Chen, James C. Gee, Jessica I. W. Morgan, Geoffrey Karl Aguirre
2018 conf
COMPAY/OMIA@MICCAI
Min Chen, James C. Gee, Jerry L. Prince, Geoffrey Karl Aguirre
2018 C conf
HealthCom
Sandro Amofa, Emmanuel Boateng Sifah, Kwame Opuni-Boachie Obour Agyekum, Abla Smahi, Qi Xia, James C. Gee, Jianbin Gao
2018 J jnl
J. Supercomput.
Emmanuel Boateng Sifah, Qi Xia, Kwame Opuni-Boachie Obour Agyekum, Sandro Amofa, Jianbin Gao, Ruidong Chen, Hu Xia, James C. Gee, Xiaojiang Du, Mohsen Guizani
2018 conf
DATRA/PIPPI@MICCAI
Baris U. Oguz, Jiancong Wang, Natalie Yushkevich, Alison M. Pouch, James C. Gee, Paul A. Yushkevich, Nadav Schwartz, Ipek Oguz
2018 conf
ICCAI
Emelia Opoku Aboagye, James C. Gee, Jianbin Gao, Rajesh Kumar, Riaz Ullah Khan
2018 conf
RAMBO+BIA+TIA@MICCAI
William Lindsay, Jiancong Wang, Nicholas Sachs, Eduardo Barbosa, James C. Gee
2018 conf
iThings/GreenCom/CPSCom/SmartData
Kwame Opuni-Boachie Obour Agyekum, Qi Xia, Emmanuel Boateng Sifah, Sandro Amofa, Kingsley Nketia Acheampong, Jianbin Gao, Ruidong Chen, Hu Xia, James C. Gee, Xiaojiang Du, Mohsen Guizani
2017 conf
FIFI/OMIA@MICCAI
Min Chen, Jiancong Wang, Ipek Oguz, Brian L. Vanderbeek, James C. Gee
2017 J jnl
Comput. Biol. Medicine
Eduardo Barbosa Jr., Scott Simpson, James C. Lee, Nicholas J. Tustison, James C. Gee, Haochang Shou
2017 J jnl
Comput. Math. Methods Medicine
Yunlong He, Yuanjie Zheng, Yanna Zhao, Yanju Ren, Jian Lian, James C. Gee
2016 conf
Biomedical Applications in Molecular, Structural, and Functional Imaging
Yufan He, Yankui Sun, Min Chen, Yuanjie Zheng, Hui Liu, Cecilia Leon, William Beltran, James C. Gee
2016 J jnl
IEEE Trans. Biomed. Eng.
Fabio Babiloni, James C. Gee
2015 J jnl
NeuroImage
Benjamin M. Kandel, Danny J. J. Wang, John A. Detre, James C. Gee, Brian B. Avants
2015 J jnl
Comput. Medical Imaging Graph.
Yuanjie Zheng, Benzheng Wei, Hui Liu, Rui Xiao, James C. Gee
2015 J jnl
Neuroinformatics
Nicholas J. Tustison, K. L. Shrinidhi, Max Wintermark, Christopher R. Durst, Benjamin M. Kandel, James C. Gee, Murray C. Grossman, Brian B. Avants
2015 J jnl
J. Univers. Comput. Sci.
Qi Xia, Jianbing Ni, Ansuura John Bosco Aristotle Kanpogninge, James C. Gee
2014 conf
Digital Mammography / IWDM
Predrag R. Bakic, David D. Pokrajac, Mathew Thomas, Angeliki Skoura, Tatyana Nuzhnaya, Vasileios Megalooikonomou, Brad M. Keller, Yuanjie Zheng, Despina Kontos, James C. Gee, Gilda Cardenosa, Andrew D. A. Maidment
2014 conf
ISBI
Jue Wu, Manzar Ashtari, Laura M. Betancourt, Nancy L. Brodsky, Joan M. Giannetta, James C. Gee, Hallam Hurt, Brian B. Avants
2014 J jnl
NeuroImage
Daniel H. Adler, John Pluta, Salmon Kadivar, Caryne Craige, James C. Gee, Brian B. Avants, Paul A. Yushkevich
2014 J jnl
Medical Image Anal.
Yuanjie Zheng, Ebenezer Daniel, Allan A. Hunter III, Rui Xiao, Jianbin Gao, Hongsheng Li, Maureen G. Maguire, David H. Brainard, James C. Gee
2014 J jnl
NeuroImage
Nicholas J. Tustison, Philip A. Cook, Arno Klein, Gang Song, Sandhitsu R. Das, Jeffrey T. Duda, Benjamin M. Kandel, Niels van Strien, James R. Stone, James C. Gee, Brian B. Avants
2014 J jnl
NeuroImage
Philip A. Cook, Corey T. McMillan, Brian B. Avants, Jonathan E. Peelle, James C. Gee, Murray Grossman
2014 J jnl
Frontiers Neuroinformatics
Jeffrey T. Duda, Philip A. Cook, James C. Gee
2014 conf
MICCAI (3)
Benjamin M. Kandel, Danny J. J. Wang, James C. Gee, Brian B. Avants
2014 J jnl
NeuroImage
Paramveer S. Dhillon, David A. Wolk, Sandhitsu R. Das, Lyle H. Ungar, James C. Gee, Brian B. Avants
2014 J jnl
Frontiers Neuroinformatics
Brian B. Avants, Nicholas J. Tustison, James C. Gee, Gang Song, Baohua Wu, Michael Stauffer
2013 conf
MICCAI (1)
Yuanjie Zheng, Rui Xiao, Yan Wang, James C. Gee
2013 conf
Computer-Aided Diagnosis
Yuanjie Zheng, Yan Wang, Brad M. Keller, Emily F. Conant, James C. Gee, Despina Kontos
2013 conf
ISBI
Yuanjie Zheng, Brian Vanderbeek, Ebenezer Daniel, Dwight Stambolian, Maureen G. Maguire, David H. Brainard, James C. Gee
2013 conf
PRNI
Paramveer S. Dhillon, James C. Gee, Lyle H. Ungar, Brian B. Avants
2013 conf
MICCAI (3)
Jeffrey T. Duda, John A. Detre, Junghoon Kim, James C. Gee, Brian B. Avants
2013 ed.
IPMI
James C. Gee, Sarang C. Joshi, Kilian M. Pohl, William M. Wells III, Lilla Zöllei
2013 conf
MICCAI (2)
Yuanjie Zheng, Dwight Stambolian, Joan O'Brien, James C. Gee
2013 conf
IPMI
Benjamin M. Kandel, David A. Wolk, James C. Gee, Brian B. Avants
2013 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Yuanjie Zheng, Stephen Lin, Sing Bing Kang, Rui Xiao, James C. Gee, Chandra Kambhamettu
2013 conf
Computer-Aided Diagnosis
Yan Wang, Brad M. Keller, Yuanjie Zheng, Raymond Joseph Acciavatti, James C. Gee, Andrew D. A. Maidment, Despina Kontos
2013 conf
IPMI
Gang Song, Yang Liu, Baohua Wu, Brian B. Avants, James C. Gee
2012 conf
Digital Mammography / IWDM
Yan Wang, Brad M. Keller, Yuanjie Zheng, Raymond Joseph Acciavatti, James C. Gee, Andrew D. A. Maidment, Despina Kontos
2012 conf
WBIR
Brian B. Avants, Nicholas J. Tustison, Gang Song, Baohua Wu, Michael Stauffer, Matthew McCormick, Hans J. Johnson, James C. Gee
2012 J jnl
NeuroImage
Nagesh Adluru, Hui Zhang, Andrew S. Fox, Steven E. Shelton, Chad M. Ennis, Anne M. Bartosic, Jonathan A. Oler, Do P. M. Tromp, Elizabeth Zakszewski, James C. Gee, Ned H. Kalin, Andrew L. Alexander
2012 conf
ISBI
Seung-Goo Kim, Brian B. Avants, Hyekyoung Lee, James C. Gee, Moo K. Chung, Richard J. Davidson, Jamie L. Hanson, Seth D. Pollak
2012 conf
MICCAI (3)
Brian B. Avants, Paramveer S. Dhillon, Benjamin M. Kandel, Philip A. Cook, Corey McMillan, Murray Grossman, James C. Gee
2012 conf
ISBI
Paramveer S. Dhillon, Brian B. Avants, Lyle H. Ungar, James C. Gee
2012 conf
ISBI
Daniel H. Adler, Alex Yang Liu, John Pluta, Salmon Kadivar, Sylvia Orozco, Hongzhi Wang, James C. Gee, Brian B. Avants, Paul A. Yushkevich
2012 conf
ISBI
Yuanjie Zheng, Brian Vanderbeek, Rui Xiao, Ebenezer Daniel, Dwight Stambolian, Maureen G. Maguire, Joan O'Brien, James C. Gee
2011 J jnl
IEEE Trans. Medical Imaging
Caroline C. Brun, Natasha Leporé, Xavier Pennec, Yi-Yu Chou, Agatha D. Lee, Greig I. de Zubicaray, Katie McMahon, Margaret J. Wright, James C. Gee, Paul M. Thompson
2011 J jnl
NeuroImage
Brian B. Avants, Nicholas J. Tustison, Gang Song, Philip A. Cook, Arno Klein, James C. Gee
2011 conf
MICCAI (3)
Brad M. Keller, Diane Nathan, Yan Wang, Yuanjie Zheng, James C. Gee, Emily F. Conant, Despina Kontos
2011 J jnl
Neuroinformatics
Brian B. Avants, Nicholas J. Tustison, Jue Wu, Philip A. Cook, James C. Gee
2011 conf
AE-CAI
Jue Wu, Kathryn A. Davis, Allan Azarion, Yuanjie Zheng, Hongzhi Wang, Brian Litt, James C. Gee
2011 J jnl
IEEE Trans. Medical Imaging
Keelin Murphy, Bram van Ginneken, Joseph M. Reinhardt, Sven Kabus, Kai Ding, Xiang Deng, Kunlin Cao, Kaifang Du, Gary E. Christensen, Vincent Garcia, Tom Vercauteren, Nicholas Ayache, Olivier Commowick, Grégoire Malandain, Ben Glocker, Nikos Paragios, Nassir Navab, Vladlena Gorbunova, Jon Sporring, Marleen de Bruijne, Xiao Han, Mattias P. Heinrich, Julia A. Schnabel, Mark Jenkinson, Cristian Lorenz, Marc Modat, Jamie McClelland, Sébastien Ourselin, Sascha E. A. Muenzing, Max A. Viergever, Dante De Nigris, D. Louis Collins, Tal Arbel, Marta Peroni, Rui Li, Gregory C. Sharp, Alexander Schmidt-Richberg, Jan Ehrhardt, René Werner, Dirk Smeets, Dirk Loeckx, Gang Song, Nicholas J. Tustison, Brian B. Avants, James C. Gee, Marius Staring, Stefan Klein, Berend C. Stoel, Martin Urschler, Manuel Werlberger, Jef Vandemeulebroucke, Simon Rit, David Sarrut, Josien P. W. Pluim
2011 conf
IPMI
Yuanjie Zheng, Allan A. Hunter III, Jue Wu, Hongzhi Wang, Jianbin Gao, Maureen G. Maguire, James C. Gee
2011 conf
ISBI
Yuanjie Zheng, Hongzhi Wang, Jue Wu, Jianbin Gao, James C. Gee
2011 J jnl
IEEE Trans. Medical Imaging
Nicholas J. Tustison, Suyash P. Awate, Gang Song, Tessa Sundaram Cook, James C. Gee
2011 conf
ISBI
Seung-Goo Kim, Moo K. Chung, Jamie L. Hanson, Brian B. Avants, James C. Gee, Richard J. Davidson, Seth D. Pollak
2011 J jnl
IEEE Trans. Image Process.
Nicholas J. Tustison, Brian B. Avants, Marcelo Siqueira, James C. Gee
2010 conf
WBIR
Hui Zhang, Paul A. Yushkevich, Daniel Rueckert, James C. Gee
2010 J jnl
Medical Image Anal.
Hui Zhang, Suyash P. Awate, Sandhitsu R. Das, John H. Woo, Elias R. Melhem, James C. Gee, Paul A. Yushkevich
2010 conf
ISBI
Yuanjie Zheng, Andrew D. A. Maidment, James C. Gee
2010 J jnl
NeuroImage
Suyash P. Awate, Paul A. Yushkevich, Zhuang Song, Daniel J. Licht, James C. Gee
2010 J jnl
J. Zhejiang Univ. Sci. C
Sheng-Zheng Wang, Jie Yang, James C. Gee
2010 J jnl
NeuroImage
Brian B. Avants, Philip A. Cook, Lyle H. Ungar, James C. Gee, Murray Grossman
2010 J jnl
NeuroImage
Hengyi Rao, Laura M. Betancourt, Joan M. Giannetta, Nancy L. Brodsky, Marc Korczykowski, Brian B. Avants, James C. Gee, Jiongjiong Wang, Hallam Hurt, John A. Detre, Martha J. Farah
2010 A* conf
CVPR
Yuanjie Zheng, James C. Gee
2010 J jnl
NeuroImage
Arno Klein, Satrajit S. Ghosh, Brian B. Avants, B. T. Thomas Yeo, Bruce Fischl, Babak A. Ardekani, James C. Gee, J. John Mann, Ramin V. Parsey
2010 conf
MICAI (1)
Ramón Aranda, Mariano Rivera, Alonso Ramirez-Manzanares, Manzar Ashtari, James C. Gee
2010 J jnl
IEEE Trans. Medical Imaging
Nicholas J. Tustison, Brian B. Avants, Philip A. Cook, Yuanjie Zheng, Alexander Egan, Paul A. Yushkevich, James C. Gee
2010 conf
MICCAI (2)
Jeffrey T. Duda, Corey McMillan, Murray Grossman, James C. Gee
2010 conf
Computer-Aided Diagnosis
Qiang Wang, Amalia Charisi, Longin Jan Latecki, James C. Gee, Vasilis Megalooikonomou
2010 conf
MICCAI (1)
Brian B. Avants, Philip A. Cook, Corey McMillan, Murray Grossman, Nicholas J. Tustison, Yuanjie Zheng, James C. Gee
2010 J jnl
NeuroImage
Brian B. Avants, Paul A. Yushkevich, John Pluta, David Minkoff, Marc Korczykowski, John A. Detre, James C. Gee
2009 conf
ISBI
Sheng-Zheng Wang, James C. Gee, Jie Yang
2009 conf
IPMI
Nicholas J. Tustison, Suyash P. Awate, Gang Song, Tessa Sundaram Cook, James C. Gee
2009 conf
MICCAI (1)
Hui Zhang, Suyash P. Awate, Sandhitsu R. Das, John H. Woo, Elias R. Melhem, James C. Gee, Paul A. Yushkevich
2009 J jnl
NeuroImage
Paul A. Yushkevich, Brian B. Avants, John Pluta, Sandhitsu R. Das, David Minkoff, Dawn Mechanic-Hamilton, Simon Glynn, Stephen Pickup, Weixia Liu, James C. Gee, Murray Grossman, John A. Detre
2009 conf
MICCAI (1)
Yuanjie Zheng, Murray Grossman, Suyash P. Awate, James C. Gee
2009 conf
ISBI
Gang Song, Eduardo Barbosa Jr., Nicholas J. Tustison, James C. Gee, Warren B. Gefter, Maryl Kreider, Drew A. Torigian
2009 conf
MICAI
Alonso Ramirez-Manzanares, Mariano Rivera, James C. Gee
2009 J jnl
IEEE Trans. Image Process.
Nicholas J. Tustison, Brian B. Avants, James C. Gee
2009 J jnl
NeuroImage
Arno Klein, Jesper L. R. Andersson, Babak A. Ardekani, John Ashburner, Brian B. Avants, Ming-Chang Chiang, Gary E. Christensen, D. Louis Collins, James C. Gee, Pierre Hellier, Joo Hyun Song, Mark Jenkinson, Claude Lepage, Daniel Rueckert, Paul M. Thompson, Tom Vercauteren, Roger P. Woods, J. John Mann, Ramin V. Parsey
2009 conf
MICCAI (1)
Suyash P. Awate, Paul A. Yushkevich, Daniel J. Licht, James C. Gee
2009 J jnl
Artif. Intell. Medicine
Despina Kontos, Vasileios Megalooikonomou, James C. Gee
2009 conf
IPMI
Suyash P. Awate, Paul A. Yushkevich, Zhuang Song, Daniel J. Licht, James C. Gee
2009 J jnl
NeuroImage
Sandhitsu R. Das, Brian B. Avants, Murray Grossman, James C. Gee
2009 conf
MICCAI (1)
Hui Zhang, Paul A. Yushkevich, Daniel Rueckert, James C. Gee
2008 conf
MICCAI (1)
Suyash P. Awate, Lawrence Win, Paul A. Yushkevich, Robert T. Schultz, James C. Gee
2008 conf
MICCAI (1)
Alonso Ramirez-Manzanares, Philip A. Cook, James C. Gee
2008 conf
ISBI
Philip A. Cook, Hui Zhang, Suyash P. Awate, James C. Gee
2008 conf
ISBI
Hui Sun, Brian B. Avants, Alejandro F. Frangi, Sebastián Ordas, James C. Gee, Paul A. Yushkevich
2008 conf
ISBI
Paul A. Yushkevich, Brian B. Avants, John Pluta, David Minkoff, Stephen Pickup, Weixia Liu, John A. Detre, Murray Grossman, James C. Gee
2008 conf
MICCAI (2)
Hui Sun, Brian B. Avants, Alejandro F. Frangi, Federico Sukno, James C. Gee, Paul A. Yushkevich
2008 J jnl
IEEE Signal Process. Mag.
James C. Gee
2008 J jnl
NeuroImage
Susana Novais-Santos, James C. Gee, Maliha Shah, Vanessa Troiani, Melissa Work, Murray Grossman
2008 conf
CVPR Workshops
Jeffrey T. Duda, Brian B. Avants, Junghoon Kim, Hui Zhang, Sunil Patel, John Whyte, James C. Gee
2008 conf
ISBI
Suyash P. Awate, Hui Zhang, Tony J. Simon, James C. Gee
2008 conf
ISBI
Zhuang Song, Suyash P. Awate, James C. Gee
2008 conf
ISBI
Nicholas J. Tustison, Suyash P. Awate, Talissa A. Altes, James C. Gee, Jing Cai, G. Wilson Miller, Eduard E. de Lange, John P. Mugler III
2008 conf
CVPR Workshops
Alonso Ramirez-Manzanares, Hui Zhang, Mariano Rivera, James C. Gee
2008 conf
MICCAI (1)
Paul A. Yushkevich, Brian B. Avants, John Pluta, David Minkoff, John A. Detre, Murray Grossman, James C. Gee
2008 conf
ISBI
Sandhitsu R. Das, Dawn Mechanic-Hamilton, Marc Korczykowski, Brian B. Avants, John A. Detre, James C. Gee, Paul A. Yushkevich
2008 J jnl
NeuroImage
Junghoon Kim, Brian B. Avants, Sunil Patel, John Whyte, H. Branch Coslett, John Pluta, John A. Detre, James C. Gee
2008 J jnl
NeuroImage
Paul A. Yushkevich, Hui Zhang, Tony J. Simon, James C. Gee
2008 conf
ISBI
Hui Zhang, Paul A. Yushkevich, Tony J. Simon, James C. Gee
2008 J jnl
Medical Image Anal.
Brian B. Avants, Charles L. Epstein, Murray Grossman, James C. Gee
2008 J jnl
J. Math. Imaging Vis.
Marcelo Siqueira, Longin Jan Latecki, Nicholas J. Tustison, Jean H. Gallier, James C. Gee
2007 conf
IPMI
Suyash P. Awate, James C. Gee
2007 J jnl
IEEE Trans. Medical Imaging
Suyash P. Awate, Hui Zhang, James C. Gee
2007 conf
MICCAI (1)
Zhuang Song, Suyash P. Awate, Daniel J. Licht, James C. Gee
2007 conf
MICCAI (2)
Hui Sun, Paul A. Yushkevich, Hui Zhang, Philip A. Cook, Jeffrey T. Duda, Tony J. Simon, James C. Gee
2007 conf
MICCAI (1)
Suyash P. Awate, Hui Zhang, James C. Gee
2007 J jnl
IEEE Trans. Medical Imaging
James C. Gee, Paul M. Thompson
2007 J jnl
IEEE Trans. Medical Imaging
Hui Zhang, Brian B. Avants, Paul A. Yushkevich, John H. Woo, Sumei Wang, L. F. McCluskey, L. B. Elman, Elias R. Melhem, James C. Gee
2007 J jnl
NeuroImage
Paul A. Yushkevich, John A. Detre, Dawn Mechanic-Hamilton, María A. Fernández-Seara, Kathy Z. Tang, Angela Hoang, Marc Korczykowski, Hui Zhang, James C. Gee
2007 conf
MICCAI (1)
Tessa Sundaram Cook, Nicholas J. Tustison, Jürgen Biederer, Ralf Tetzlaff, James C. Gee
2007 A* conf
ICCV
Nicholas J. Tustison, Brian B. Avants, James C. Gee
2007 A* conf
ICCV
Sandhitsu R. Das, Brian B. Avants, Murray Grossman, James C. Gee
2007 A* conf
ICCV
Gang Song, Brian B. Avants, James C. Gee
2007 conf
MICCAI (1)
Brian B. Avants, Jeffrey T. Duda, Hui Zhang, James C. Gee
2007 J jnl
NeuroImage
Yong Fan, Hengyi Rao, Hallam Hurt, Joan M. Giannetta, Marc Korczykowski, David Shera, Brian B. Avants, James C. Gee, Jiongjiong Wang, Dinggang Shen
2007 J jnl
IEEE ACM Trans. Comput. Biol. Bioinform.
Lydia Ng, Sayan D. Pathak, Chihchau Kuan, Christopher Lau, Hong-wei Dong, Andrew Sodt, Chinh Dang, Brian B. Avants, Paul A. Yushkevich, James C. Gee, David R. Haynor, Ed S. Lein, Allan Jones, Michael Hawrylycz
2007 conf
ISBI
Paul A. Yushkevich, John A. Detre, James C. Gee
2007 conf
ISBI
Nicholas J. Tustison, Talissa A. Altes, James C. Gee, Jing Cai, Eduard E. de Lange, John P. Mugler III
2007 J jnl
NeuroImage
Susana Novais-Santos, James C. Gee, Maliha Shah, Vanessa Troiani, Melissa Work, Murray Grossman
2007 J jnl
IEEE Trans. Medical Imaging
Hui Sun, Paul A. Yushkevich, Hui Zhang, Philip A. Cook, Jeffrey T. Duda, Tony J. Simon, James C. Gee
2007 conf
MICCAI (2)
Brian B. Avants, Chivon Anderson, Murray Grossman, James C. Gee
2007 A* conf
ICCV
Paul A. Yushkevich, Hui Zhang, Tony J. Simon, James C. Gee
2007 conf
ISBI
Brian B. Avants, Charles L. Epstein, James C. Gee
2007 conf
MICCAI (2)
Hui Zhang, Paul A. Yushkevich, Daniel Rueckert, James C. Gee
2006 conf
WBIR
Paul A. Yushkevich, Brian B. Avants, Lydia Ng, Michael Hawrylycz, Pablo D. Burstein, Hui Zhang, James C. Gee
2006 conf
WBIR
Nicholas J. Tustison, Brian B. Avants, Tessa A. Sundaram, Jeffrey T. Duda, James C. Gee
2006 conf
ISBI
Zhuang Song, Nicholas J. Tustison, Brian B. Avants, James C. Gee
2006 conf
CVPR Workshops
Brian B. Avants, Hallam Hurt, Joan M. Giannetta, Charles L. Epstein, Jiongjiong Wang, James C. Gee
2006 ch.
Visualization and Processing of Tensor Fields
James C. Gee, Hui Zhang, Abraham Dubb, Brian B. Avants, Paul A. Yushkevich, Jeffrey T. Duda
2006 J jnl
IEEE Trans. Medical Imaging
Paul A. Yushkevich, Hui Zhang, James C. Gee
2006 J jnl
Medical Image Anal.
Hui Zhang, Paul A. Yushkevich, Daniel C. Alexander, James C. Gee
2006 ch.
Visualization and Processing of Tensor Fields
James C. Gee, Daniel C. Alexander
2006 J jnl
Medical Image Anal.
J. B. Antoine Maintz, James C. Gee
2006 conf
CVPR Workshops
Hui Sun, Paul A. Yushkevich, Hui Zhang, James C. Gee, Tony J. Simon
2006 conf
MIAR
Nicholas J. Tustison, James C. Gee
2006 conf
MIAR
Brian B. Avants, Charles L. Epstein, James C. Gee
2006 conf
MICCAI (2)
Paul A. Yushkevich, John A. Detre, Kathy Z. Tang, Angela Hoang, Dawn Mechanic-Hamilton, María A. Fernández-Seara, Marc Korczykowski, Hui Zhang, James C. Gee
2006 conf
MICCAI (2)
Zhuang Song, Nicholas J. Tustison, Brian B. Avants, James C. Gee
2006 J jnl
Medical Image Anal.
Brian B. Avants, P. Thomas Schoenemann, James C. Gee
2006 J jnl
Medical Image Anal.
Sumiaki Matsumoto, Harold L. Kundel, James C. Gee, Warren B. Gefter, Hiroto Hatabu
2006 conf
WBIR
Brian B. Avants, Murray Grossman, James C. Gee
2006 J jnl
NeuroImage
Paul A. Yushkevich, Joseph Piven, Heather Cody Hazlett, Rachel Gimpel Smith, Sean Ho, James C. Gee, Guido Gerig
2005 A conf
MICCAI
Philip A. Cook, Hui Zhang, Brian B. Avants, Paul A. Yushkevich, Daniel C. Alexander, James C. Gee, Olga Ciccarelli, Alan J. Thompson
2005 J jnl
Int. J. Comput. Geom. Appl.
Suneeta Ramaswami, Marcelo Siqueira, Tessa A. Sundaram, Jean H. Gallier, James C. Gee
2005 A conf
MICCAI
Hui Zhang, Paul A. Yushkevich, James C. Gee
2005 conf
VLSM
Brian B. Avants, Charles L. Epstein, James C. Gee
2005 conf
IPMI
Paul A. Yushkevich, Hui Zhang, James C. Gee
2005 B conf
CBMS
Despina Kontos, Vasileios Megalooikonomou, James C. Gee
2005 conf
MICCAI (2)
Paul A. Yushkevich, Hui Zhang, James C. Gee
2005 conf
MICCAI (2)
Tessa A. Sundaram, Brian B. Avants, James C. Gee
2005 J jnl
Medical Image Anal.
Tessa A. Sundaram, James C. Gee
2005 J jnl
NeuroImage
Tony J. Simon, Lijun Ding, Joel P. Bish, Donna M. McDonald-McGinn, Elaine H. Zackai, James C. Gee
2004 conf
MICCAI (2)
Tessa A. Sundaram, Brian B. Avants, James C. Gee
2004 conf
MICCAI (2)
Abraham Dubb, Paul A. Yushkevich, Zhiyong Xie, Ruben C. Gur, Raquel E. Gur, James C. Gee
2004 conf
CVPR (1)
Hui Zhang, Paul A. Yushkevich, James C. Gee
2004 conf
ISBI
Brian B. Avants, James C. Gee
2004 J jnl
Artif. Intell. Medicine
Alexei Manso Corrêa Machado, James C. Gee, Mario Fernando Montenegro Campos
2004 conf
ECCV Workshops CVAMIA and MMBIA
Brian B. Avants, James C. Gee
2004 conf
ISBI
Hui Zhang, Paul A. Yushkevich, James C. Gee
2004 A conf
BMVC
Mariano Rivera, James C. Gee
2004 J jnl
IEEE Signal Process. Mag.
Alexei Manso Corrêa Machado, James C. Gee, Mario Fernando Montenegro Campos
2003 conf
IMR
Suneeta Ramaswami, Marcelo Siqueira, Tessa A. Sundaram, Jean H. Gallier, James C. Gee
2003 B conf
Image Processing
Jeffrey T. Duda, Mariano Rivera, Daniel C. Alexander, James C. Gee
2003 J jnl
J. Electronic Imaging
Alexei Manso Corrêa Machado, Mario Fernando Montenegro Campos, James C. Gee
2003 ed.
WBIR
James C. Gee, J. B. Antoine Maintz, Michael W. Vannier
2003 conf
MICCAI (2)
Abraham Dubb, Zhiyong Xie, Ruben C. Gur, Raquel E. Gur, James C. Gee
2003 J jnl
NeuroImage
Abraham Dubb, Ruben C. Gur, Brian B. Avants, James C. Gee
2003 conf
Scale-Space
Brian B. Avants, James C. Gee
2003 conf
WBIR
Brian B. Avants, James C. Gee
2003 J jnl
J. Electronic Imaging
James C. Gee, Mostafa Analoui
2003 J jnl
Comput. Vis. Image Underst.
Peter Rogelj, Stanislav Kovacic, James C. Gee
2003 B conf
Image Processing
Punam K. Saha, Hui Zhang, Jayaram K. Udupa, James C. Gee
2003 conf
IPMI
Brian B. Avants, James C. Gee
2003 B conf
Image Processing
Zhiyong Xie, Lydia Ng, James C. Gee
2002 J jnl
NeuroImage
Murray Grossman, Ayanna Cooke, Christian Devita, David C. Alsop, John A. Detre, Willis Chen, James C. Gee
2002 conf
MICCAI (1)
James C. Gee, Tessa A. Sundaram, Ichiro Hasegawa, Hidemasa Uematsu, Hiroto Hatabu
2002 conf
MICCAI (1)
Aljaz Noe, James C. Gee
2002 conf
ECCV (3)
Mariano Rivera, James C. Gee
2002 B conf
Image Processing
Jeffrey T. Duda, Hee Kwon Song, Ronald Wolf, Alex C. Wright, James C. Gee, Felix W. Wehrli
2002 conf
ISBI
James C. Gee, Daniel C. Alexander, Mariano Rivera, Jeffrey T. Duda
2002 B conf
Image Processing
Mariano Rivera, James C. Gee
2002 conf
ISBI
Luis Ibáñez, Lydia Ng, James C. Gee, Stephen R. Aylward
2002 conf
ISBI
Brian B. Avants, James C. Gee
2002 J jnl
NeuroImage
David J. Pettey, James C. Gee
2002 Misc conf
AMIA
Abraham Dubb, Brian B. Avants, Ruben C. Gur, James C. Gee
2002 conf
MICCAI (2)
Abraham Dubb, Brian B. Avants, Ruben C. Gur, James C. Gee
2002 B conf
Image Processing
Brian B. Avants, James C. Gee
2002 conf
SIBGRAPI
Alexei Manso Corrêa Machado, James C. Gee, Mario Fernando Montenegro Campos
2002 J jnl
NeuroImage
Murray Grossman, Phyllis Koenig, Christian Devita, Guila Glosser, David C. Alsop, John A. Detre, James C. Gee
2002 conf
MICCAI (1)
Michael P. Lin, Christian Devita, James C. Gee, Murray Grossman
2002 B conf
Image Processing
Peter Rogelj, Stanislav Kovacic, James C. Gee
2001 B conf
Image Processing
David J. Pettey, James C. Gee
2001 A conf
MICCAI
Brian B. Avants, Marcelo Siqueira, James C. Gee
2001 B conf
Image Processing
Alexei Manso Corrêa Machado, James C. Gee, Mario F. M. Campos
2001 conf
IPMI
Aljaz Noe, James C. Gee
2001 B conf
Image Processing
Aljaz Noe, Stanislav Kovacic, James C. Gee
2001 J jnl
IEEE Trans. Medical Imaging
Daniel C. Alexander, Carlo Pierpaoli, Peter J. Basser, James C. Gee
2001 conf
IPMI
David J. Pettey, James C. Gee
2001 B conf
Image Processing
Vikram Chalana, Lydia Ng, Larry R. Rystrom, James C. Gee, David R. Haynor
2000 J jnl
Comput. Vis. Image Underst.
Daniel C. Alexander, James C. Gee
2000 B conf
Image Processing
Alexei Manso Corrêa Machado, James C. Gee, Mario F. M. Campos
2000 B conf
Image Processing
James C. Gee
2000 conf
SIBGRAPI
Alexei Manso Corrêa Machado, Mario Fernando Montenegro Campos, James C. Gee
2000 B conf
Image Processing
Daniel C. Alexander, Carlo Pierpaoli, Peter J. Basser, James C. Gee
1999 A* conf
CVPR
Daniel C. Alexander, James C. Gee, Ruzena Bajcsy
1999 A conf
MICCAI
Alexei Manso Corrêa Machado, James C. Gee, Mario Fernando Montenegro Campos
1999 B conf
Image Processing
Alexei Manso Corrêa Machado, Mario F. M. Campos, James C. Gee
1999 conf
IPMI
Alexei Manso Corrêa Machado, Mario Fernando Montenegro Campos, James C. Gee
1999 B conf
Image Processing
James C. Gee, Brian A. Fabella, Siddharth E. Fernandes, Bruce I. Turetsky, Ruben C. Gur, Raquel E. Gur
1999 J jnl
Pattern Recognit.
James C. Gee
1999 A conf
BMVC
Daniel C. Alexander, James C. Gee, Ruzena Bajcsy
1999 A conf
MICCAI
Daniel C. Alexander, James C. Gee, Ruzena Bajcsy
1998 B conf
Image Processing
Alexei Manso Corrêa Machado, James C. Gee
1998 conf
SIBGRAPI
Alexei M. C. Machado, James C. Gee, Mario F. M. Campos
1997 conf
CVRMed
James C. Gee, David R. Haynor, Lionel Le Briquer, Ruzena Bajcsy
1997 conf
IPMI
Lionel Le Briquer, James C. Gee
1996 B conf
Image Processing
James C. Gee, David R. Haynor
1994 conf
VBC
James C. Gee, Christian Barillot, Lionel Le Briquer, David R. Haynor, Ruzena Bajcsy
1993 J jnl
Interact. Comput.
James C. Gee, Woodrow Barfield, David R. Haynor, Yongmin Kim
tests/integration/test_js_extractors.py
← Index tests/integration/test_js_extractors.py python
"""
Integration tests for JavaScript-specific extractors.
"""
import sys
import types
from unittest.mock import MagicMock

# Mock the oscrypto/signify/certvalidator chain before any redb imports.
# oscrypto fails on environments with OpenSSL 3.x due to version detection.
# JS extractors don't use PE signatures so this is safe.
_MOCK_MODULES = [
    # oscrypto/signify chain (PE signature analysis)
    "oscrypto", "oscrypto.errors", "oscrypto._openssl",
    "oscrypto._openssl._libcrypto", "oscrypto._openssl._libcrypto_ctypes",
    "oscrypto._openssl.util", "oscrypto.util", "oscrypto.kdf",
    "oscrypto._asymmetric", "oscrypto.asymmetric",
    "certvalidator", "certvalidator.validate",
    "signify", "signify.fingerprinter", "signify.authenticode",
    "signify.authenticode.signed_pe", "signify.authenticode.authroot",
    "signify.pkcs7", "signify.pkcs7.signeddata", "signify.pkcs7.signerinfo",
    "signify.x509", "signify.x509.certificates", "signify.x509.context",
    # hashing libs used by HashExtractor
    "ppdeep", "tlsh",
    # binary format libs (not needed for JS)
    "dotnetfile", "lief",
    "flare_floss", "yara_x",
    "machofile",
]
for _mod in _MOCK_MODULES:
    if _mod not in sys.modules:
        try:
            __import__(_mod)
        except (ImportError, Exception):
            sys.modules[_mod] = MagicMock()

import pytest
from pathlib import Path
from unittest.mock import patch

pytestmark = [pytest.mark.integration, pytest.mark.js]

TEST_FILES_DIR = Path(__file__).parent.parent.parent / "test_files"

JS_MALICIOUS_PATH = str(TEST_FILES_DIR / "test_malicious.js")


@pytest.fixture
def js_malicious_path():
    path = TEST_FILES_DIR / "test_malicious.js"
    if not path.exists():
        pytest.skip(f"Test file not found: {path}")
    return str(path)


# ============================================================================
# JSFeaturesExtractor Tests
# ============================================================================

class TestJSFeaturesExtractor:

    def test_extract_valid_js(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor
        from redb.models.dataclasses import JSFeatures

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            assert result is not None
            assert isinstance(result, JSFeatures)

    def test_entropy_is_reasonable(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            assert result.text_entropy > 0.0
            assert result.text_entropy < 8.0

    def test_detects_obfuscation(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            # The fixture is dense `\xHH` hex escapes (>5% of source) plus
            # text entropy >5.0 — two strong signals, which clears the
            # strong-signal gate the heuristic now requires.
            assert result.is_likely_obfuscated is True
            assert result.obfuscation_score >= 40
            assert len(result.obfuscation_techniques) > 0
            # `obfuscator_name` is None when js-x-ray isn't installed in the
            # test environment, and a recognised family name when it is.
            assert result.obfuscator_name is None or isinstance(
                result.obfuscator_name, str
            )

    def test_detects_eval_usage(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            assert result.eval_count >= 1
            assert result.fromcharcode_count >= 1
            assert result.atob_count >= 1

    def test_detects_wscript_environment(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            assert result.detected_environment == "wscript"
            assert result.script_type == "wscript"

    def test_line_metrics(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            assert result.line_count > 0
            assert result.char_count > 0
            assert result.max_line_length > 0
            assert result.avg_line_length > 0

    def test_export_data_format(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(js_malicious_path, mock_logger)
            extractor.extract()
            export = extractor.prepare_export_data('ClickHouseExporter')

            assert export is not None
            data, col_names, col_types = export
            assert len(col_names) == len(col_types)
            assert len(data) == 1
            assert len(data[0]) == len(col_names)

    def test_clickhouse_table_name(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(js_malicious_path, mock_logger)
            assert extractor.get_clickhouse_table() == "redb_js_features"

    def test_empty_file_returns_none(self, tmp_path, mock_logger):
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        empty_js = tmp_path / "empty.js"
        empty_js.write_text("")

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(str(empty_js), mock_logger)
            result = extractor.extract()
            assert result is None

    def test_pattern_counts_are_case_insensitive(self, tmp_path, mock_logger):
        """eval_count / atob_count come from js_patterns.PATTERNS, which are
        compiled with re.IGNORECASE. Capitalised forms must still be counted."""
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        mixed_case_js = tmp_path / "mixed.js"
        mixed_case_js.write_text(
            'EVAL("1+1");\n'
            'eval("2+2");\n'
            'ATOB("YWJj");\n'
        )

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(str(mixed_case_js), mock_logger)
            result = extractor.extract()
            assert result is not None
            # Both `EVAL(` and `eval(` should be counted.
            assert result.eval_count == 2
            assert result.atob_count == 1

    # ----- script_type / detected_environment classification ----------------
    # First-match-wins; ordering is what makes the HTA-vs-WScript split work,
    # so we pin one positive case per value and one orthogonality case.

    @pytest.mark.parametrize("source,expected_type", [
        ("#@~^abcdef==^#~@", "jse"),
        ("<?xml version='1.0'?>\n<job><script language='JScript'>x=1;</script></job>", "wsf"),
        ("<html><head><hta:application id='x'/></head><script>x=1</script></html>", "hta"),
        ("<html><body><script>alert(1)</script></body></html>", "embedded_html"),
        ("var s = new ActiveXObject('WScript.Shell');", "wscript"),
        ("import {foo} from 'bar';\nexport const x = 1;", "esm"),
        ("import 'side-effect.js';\nconst x = 1;", "esm"),
        ("const fs = require('fs');\nmodule.exports = {};", "node_module"),
        ("function add(a, b) { return a + b; }", "standalone"),
    ])
    def test_script_type_classification(self, tmp_path, mock_logger, source, expected_type):
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        js_file = tmp_path / "sample.js"
        js_file.write_text(source)
        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(str(js_file), mock_logger, source=source)
            result = extractor.extract()
            assert result is not None
            assert result.script_type == expected_type

    @pytest.mark.parametrize("source,expected_env", [
        ("var s = new ActiveXObject('WScript.Shell');", "wscript"),
        ("chrome.runtime.onMessage.addListener(()=>{}); chrome.tabs.query({});", "browser_extension"),
        ("self.addEventListener('fetch', e => e.respondWith(caches.match(e.request)));", "service_worker"),
        ("const text = await Deno.readTextFile('a.txt');", "deno"),
        ("const fs = require('fs'); process.env.X;", "node"),
        ("document.getElementById('x'); window.location;", "browser"),
        ("function add(a, b) { return a + b; }", "unknown"),
    ])
    def test_environment_classification(self, tmp_path, mock_logger, source, expected_env):
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        js_file = tmp_path / "sample.js"
        js_file.write_text(source)
        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(str(js_file), mock_logger, source=source)
            result = extractor.extract()
            assert result is not None
            assert result.detected_environment == expected_env

    def test_hta_with_wscript_keeps_format_and_runtime_orthogonal(self, tmp_path, mock_logger):
        """HTA droppers commonly call WScript APIs. script_type captures the
        file format (`hta`) and detected_environment captures the runtime
        surface (`wscript`); both axes must survive a single sample."""
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        source = (
            "<html><head><hta:application id='x'/></head>"
            "<script>new ActiveXObject('WScript.Shell').Run('cmd.exe');</script></html>"
        )
        js_file = tmp_path / "sample.hta"
        js_file.write_text(source)
        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(str(js_file), mock_logger, source=source)
            result = extractor.extract()
            assert result.script_type == "hta"
            assert result.detected_environment == "wscript"

    def test_bare_exports_property_is_not_node_module(self, tmp_path, mock_logger):
        """Regression: `module.exports` and `require(` mark a CommonJS module,
        but a bare `exports.` substring (e.g. `obj.exports.foo`) should not."""
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        source = "var x = obj.exports.foo;\nfunction f() { return 1; }\n"
        js_file = tmp_path / "sample.js"
        js_file.write_text(source)
        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSFeaturesExtractor(str(js_file), mock_logger, source=source)
            result = extractor.extract()
            assert result.script_type == "standalone"


# ============================================================================
# JSSuspiciousAPIsExtractor Tests
# ============================================================================

class TestJSSuspiciousAPIsExtractor:

    def test_extract_finds_apis(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_suspicious_apis import JSSuspiciousAPIsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSSuspiciousAPIsExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            assert result is not None
            assert len(result) > 0

    def test_detects_eval(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_suspicious_apis import JSSuspiciousAPIsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSSuspiciousAPIsExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            api_names = [f['api_name'] for f in result]
            assert "eval" in api_names

    def test_detects_wscript_apis(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_suspicious_apis import JSSuspiciousAPIsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSSuspiciousAPIsExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            api_names = [f['api_name'] for f in result]
            assert "WScript.CreateObject" in api_names
            assert "WScript.Shell" in api_names

    def test_detects_network_apis(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_suspicious_apis import JSSuspiciousAPIsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSSuspiciousAPIsExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            api_names = [f['api_name'] for f in result]
            assert "XMLHttpRequest" in api_names
            assert "fetch" in api_names

    def test_detects_registry_access(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_suspicious_apis import JSSuspiciousAPIsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSSuspiciousAPIsExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            api_names = [f['api_name'] for f in result]
            assert "RegWrite" in api_names

    def test_categories_are_valid(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_suspicious_apis import JSSuspiciousAPIsExtractor, SUSPICIOUS_APIS

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSSuspiciousAPIsExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            valid_categories = set(SUSPICIOUS_APIS.keys())
            for finding in result:
                assert finding['api_category'] in valid_categories
                assert finding['call_count'] > 0
                assert len(finding['line_numbers']) > 0

    def test_export_data_format(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_suspicious_apis import JSSuspiciousAPIsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSSuspiciousAPIsExtractor(js_malicious_path, mock_logger)
            extractor.extract()
            export = extractor.prepare_export_data('ClickHouseExporter')

            assert export is not None
            data, col_names, col_types = export
            assert len(col_names) == len(col_types)
            assert len(data) > 0
            assert len(data[0]) == len(col_names)

    def test_clickhouse_table_name(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_suspicious_apis import JSSuspiciousAPIsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSSuspiciousAPIsExtractor(js_malicious_path, mock_logger)
            assert extractor.get_clickhouse_table() == "redb_js_suspicious_apis"

    def test_benign_js_has_no_findings(self, tmp_path, mock_logger):
        from redb.extractors.js_extractors.js_suspicious_apis import JSSuspiciousAPIsExtractor

        benign_js = tmp_path / "benign.js"
        benign_js.write_text("function add(a, b) { return a + b; }\nconsole.log(add(1, 2));")

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSSuspiciousAPIsExtractor(str(benign_js), mock_logger)
            result = extractor.extract()
            assert result is None

    def test_pattern_match_is_case_insensitive(self, tmp_path, mock_logger):
        """Patterns in js_patterns.PATTERNS are compiled with re.IGNORECASE.
        A capitalised `EVAL(` is not actually valid JS at runtime but obfuscated
        droppers occasionally use stringly-built names; verifying the match
        survives capitalisation guards against an accidental flag regression.
        """
        from redb.extractors.js_extractors.js_suspicious_apis import JSSuspiciousAPIsExtractor

        upper_js = tmp_path / "upper.js"
        upper_js.write_text('var x = EVAL("1+1");\nFETCH("http://evil.test");')

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSSuspiciousAPIsExtractor(str(upper_js), mock_logger)
            result = extractor.extract()
            assert result is not None
            api_names = {f['api_name'] for f in result}
            assert "eval" in api_names
            assert "fetch" in api_names


# ============================================================================
# JSStringsExtractor Tests
# ============================================================================

class TestJSStringsExtractor:

    def test_extract_finds_strings(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_strings import JSStringsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSStringsExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            assert result is not None
            assert len(result) > 0

    def test_decodes_hex_strings(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_strings import JSStringsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSStringsExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            hex_decoded = [s for s in result if s['string_encoding'] == 'hex']
            assert len(hex_decoded) > 0

    def test_decodes_charcode(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_strings import JSStringsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSStringsExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            charcode_decoded = [s for s in result if s['string_encoding'] == 'charcode']
            assert len(charcode_decoded) > 0
            assert any('powershell' in s['string'] for s in charcode_decoded)

    def test_decodes_base64(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_strings import JSStringsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSStringsExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            b64_decoded = [s for s in result if s['string_encoding'] == 'base64']
            assert len(b64_decoded) > 0

    def test_reconstructs_concatenated_strings(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_strings import JSStringsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSStringsExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            concat = [s for s in result if s['string_encoding'] == 'concat']
            assert len(concat) > 0

    def test_strings_have_standard_fields(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_strings import JSStringsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSStringsExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            for s in result:
                assert s['string_entropy'] >= 0
                assert s['string_length'] > 0
                assert s['string_raw_length'] > 0
                assert s['string_offset'] > 0
                assert len(s['string']) > 0
                assert len(s['string_raw']) > 0

    def test_export_data_format(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_strings import JSStringsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSStringsExtractor(js_malicious_path, mock_logger)
            extractor.extract()
            export = extractor.prepare_export_data('ClickHouseExporter')

            assert export is not None
            data, col_names, col_types = export
            assert len(col_names) == len(col_types)
            assert len(data) > 0
            assert len(data[0]) == len(col_names)

    def test_writes_to_shared_strings_table(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_strings import JSStringsExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSStringsExtractor(str(js_malicious_path), mock_logger)
            assert extractor.get_clickhouse_table() == "code_binja_strings_raw"

    def test_line_numbers_correct_in_multiline_source(self, tmp_path, mock_logger):
        """Pin the bisect-based _find_line_number against a multi-line source.

        Builds a JS file where each encoded literal sits on a known line, then
        verifies the `string_offset` (1-indexed line number) the extractor
        emits matches the planted line. Regression guard for the swap from the
        historical O(N) `source[:start].count("\\n")` to O(log L) bisect.
        """
        from redb.extractors.js_extractors.js_strings import JSStringsExtractor

        # 800 filler lines, then a known hex-escape literal on line 801
        # (1-indexed), then 100 more filler, then a charcode call on line 902.
        prefix = "\n".join([f"function f{i}() {{ return {i}; }}" for i in range(800)])
        line_801 = 'var h = "\\x68\\x74\\x74\\x70\\x73\\x3a\\x2f\\x2f"'
        middle = "\n".join([f"var v{i} = {i};" for i in range(100)])
        line_902 = 'var c = String.fromCharCode(112, 111, 119, 101, 114)'
        source = "\n".join([prefix, line_801, middle, line_902]) + "\n"

        js_path = tmp_path / "multiline.js"
        js_path.write_text(source)

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSStringsExtractor(str(js_path), mock_logger)
            result = extractor.extract()

            assert result is not None
            by_encoding = {}
            for f in result:
                by_encoding.setdefault(f['string_encoding'], []).append(f)

            assert any(f['string_offset'] == 801 for f in by_encoding.get('hex', [])), (
                f"hex literal should be reported on line 801; got "
                f"{[f['string_offset'] for f in by_encoding.get('hex', [])]}"
            )
            assert any(f['string_offset'] == 902 for f in by_encoding.get('charcode', [])), (
                f"charcode call should be reported on line 902; got "
                f"{[f['string_offset'] for f in by_encoding.get('charcode', [])]}"
            )

    def test_publishes_findings_to_context(self, js_malicious_path, mock_logger):
        """JSStringsExtractor must mirror its findings onto JSContext.decoded_strings
        so post-loop consumers (the IOC plumbing in workers.py) can read the
        decoded strings without holding a reference to this extractor."""
        from redb.extractors.js_extractors.js_strings import JSStringsExtractor
        from redb.extractors.js_extractors.js_context import JSContext

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            ctx = JSContext.from_path(js_malicious_path, log=mock_logger)
            assert ctx.decoded_strings is None  # not populated until extract runs

            extractor = JSStringsExtractor(js_malicious_path, mock_logger, context=ctx)
            findings = extractor.extract()

            assert ctx.decoded_strings is findings, (
                "ctx.decoded_strings should reference the same list returned "
                "by extract(), not a copy"
            )


# ============================================================================
# JSDeobfuscationExtractor Tests
# ============================================================================

class TestJSDeobfuscationExtractor:

    def test_extract_with_jsbeautifier_fallback(self, js_malicious_path, mock_logger):
        """Should fall back to jsbeautifier when webcrack is not installed."""
        from redb.extractors.js_extractors.js_deobfuscation import JSDeobfuscationExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSDeobfuscationExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            if result is not None:
                assert result['deobfuscator_used'] is not None
                assert result['deobfuscation_successful'] is True
                assert result['original_size'] > 0
                assert result['deobfuscated_size'] > 0
                assert len(result['deobfuscated_sha256']) == 64

    def test_export_data_format(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_deobfuscation import JSDeobfuscationExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSDeobfuscationExtractor(js_malicious_path, mock_logger)
            result = extractor.extract()

            if result is not None:
                export = extractor.prepare_export_data('ClickHouseExporter')
                assert export is not None
                data, col_names, col_types = export
                assert len(col_names) == len(col_types)
                assert len(data[0]) == len(col_names)

    def test_clickhouse_table_name(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_deobfuscation import JSDeobfuscationExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSDeobfuscationExtractor(js_malicious_path, mock_logger)
            assert extractor.get_clickhouse_table() == "redb_js_deobfuscation"

    def test_runs_deobfuscator_without_separate_probe(self, js_malicious_path, mock_logger):
        """The deobfuscator binary is invoked directly — no separate
        `--help` probe per sample. When the binary is missing, the
        FileNotFoundError is swallowed silently (no error log) and the
        extractor falls through to jsbeautifier.
        """
        from redb.extractors.js_extractors import js_deobfuscator as deob_helper
        from redb.extractors.js_extractors.js_deobfuscation import (
            JSDeobfuscationExtractor,
        )

        # _check_tool_available must be gone entirely.
        assert not hasattr(JSDeobfuscationExtractor, "_check_tool_available")

        popen_calls = []

        def fake_popen(*args, **kwargs):
            popen_calls.append(args[0] if args else kwargs.get("args"))
            raise FileNotFoundError(2, "No such file or directory", "fake-deobfuscator")

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'), \
                patch.object(deob_helper.subprocess, 'Popen', fake_popen):
            extractor = JSDeobfuscationExtractor(js_malicious_path, mock_logger)
            extractor.extract()

        # Exactly one Popen attempt — the actual run. No extra probe call.
        assert len(popen_calls) == 1, (
            f"expected one Popen call (the deobfuscator run), got "
            f"{len(popen_calls)}: {popen_calls}"
        )
        # FileNotFoundError must not have been logged at error level.
        for call in mock_logger.error.call_args_list:
            joined = " ".join(str(a) for a in call.args)
            assert "not found" not in joined.lower(), (
                f"missing-binary case must not log at error level; got {call}"
            )


# ============================================================================
# JSContentExtractor Tests
# ============================================================================

class TestJSContentExtractor:
    """JSContentExtractor persists raw + normalised text into code_text_content."""

    def test_extract_populates_both_columns(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_content import JSContentExtractor
        from redb.extractors.js_extractors.js_context import JSContext

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            ctx = JSContext.from_path(js_malicious_path, log=mock_logger)
            extractor = JSContentExtractor(js_malicious_path, mock_logger, context=ctx)
            result = extractor.extract()

            assert result is not None
            assert result['content_type'] == 'javascript'
            assert result['text_raw']
            # jsbeautifier should normalise even non-obfuscated input, so
            # against the bundled test sample we expect a normalised text.
            assert result['text_normalized'] is not None
            assert result['normalizer_used'] is not None

    def test_normalized_is_null_when_deobfuscator_returns_nothing(
        self, js_malicious_path, mock_logger
    ):
        """When neither webcrack nor jsbeautifier produces output the
        normalized columns must be NULL — distinguishes failure from a
        legitimate empty result."""
        from redb.extractors.js_extractors.js_content import JSContentExtractor
        from redb.extractors.js_extractors.js_context import JSContext
        from redb.extractors.js_extractors import js_deobfuscator as deob_helper

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'), \
                patch.object(deob_helper, 'deobfuscate', lambda src, log: (None, None)):
            ctx = JSContext.from_path(js_malicious_path, log=mock_logger)
            extractor = JSContentExtractor(js_malicious_path, mock_logger, context=ctx)
            result = extractor.extract()

            assert result is not None
            assert result['text_raw']
            assert result['text_normalized'] is None
            assert result['normalizer_used'] is None

    def test_does_not_run_its_own_deobfuscation(self, js_malicious_path, mock_logger):
        """JSContentExtractor reads from JSContext.deobfuscated, which is a
        cached_property. Two extractors sharing one context must trigger
        deobfuscation at most once across both extracts."""
        from redb.extractors.js_extractors.js_content import JSContentExtractor
        from redb.extractors.js_extractors.js_deobfuscation import (
            JSDeobfuscationExtractor,
        )
        from redb.extractors.js_extractors.js_context import JSContext
        from redb.extractors.js_extractors import js_deobfuscator as deob_helper

        calls = {"n": 0}

        def counting_deobfuscate(source, log):
            calls["n"] += 1
            return None, None

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'), \
                patch.object(deob_helper, 'deobfuscate', counting_deobfuscate):
            ctx = JSContext.from_path(js_malicious_path, log=mock_logger)
            JSDeobfuscationExtractor(js_malicious_path, mock_logger, context=ctx).extract()
            JSContentExtractor(js_malicious_path, mock_logger, context=ctx).extract()

            assert calls["n"] == 1, (
                f"deobfuscate() must be called once across both extractors "
                f"sharing a context, got {calls['n']}"
            )

    def test_export_data_format(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_content import JSContentExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSContentExtractor(js_malicious_path, mock_logger)
            extractor.extract()
            data, col_names, col_types = extractor.prepare_export_data(
                'ClickHouseExporter'
            )
            assert col_names == [
                'sha256', 'content_type', 'text_raw', 'text_normalized',
                'normalizer_used', 'analysis_date',
            ]
            assert len(col_names) == len(col_types) == len(data[0])
            assert data[0][1] == 'javascript'

    def test_clickhouse_table_name(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_content import JSContentExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            extractor = JSContentExtractor(js_malicious_path, mock_logger)
            assert extractor.get_clickhouse_table() == 'code_text_content'


# ============================================================================
# Source Sharing Tests
# ============================================================================

class TestSourceSharing:
    """Test that source text can be shared across extractors."""

    def test_shared_source_produces_same_results(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            with open(js_malicious_path, 'r') as f:
                source = f.read()

            ext_shared = JSFeaturesExtractor(js_malicious_path, mock_logger, source=source)
            ext_fresh = JSFeaturesExtractor(js_malicious_path, mock_logger)

            r_shared = ext_shared.extract()
            r_fresh = ext_fresh.extract()

            assert r_shared.text_entropy == r_fresh.text_entropy
            assert r_shared.eval_count == r_fresh.eval_count
            assert r_shared.obfuscation_score == r_fresh.obfuscation_score


# ============================================================================
# JSContext Sharing Tests
# ============================================================================

class TestJSContextSharing:
    """Verify the shared JSContext threads through every extractor cleanly:
    same outputs as independent runs, expensive work computed exactly once."""

    def test_shared_context_produces_same_results(self, js_malicious_path, mock_logger):
        from redb.extractors.js_extractors.js_context import JSContext
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor
        from redb.extractors.js_extractors.js_suspicious_apis import (
            JSSuspiciousAPIsExtractor,
        )

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            ctx = JSContext.from_path(js_malicious_path, log=mock_logger)

            shared_features = JSFeaturesExtractor(
                js_malicious_path, mock_logger, context=ctx
            ).extract()
            shared_apis = JSSuspiciousAPIsExtractor(
                js_malicious_path, mock_logger, context=ctx
            ).extract()

            fresh_features = JSFeaturesExtractor(
                js_malicious_path, mock_logger
            ).extract()
            fresh_apis = JSSuspiciousAPIsExtractor(
                js_malicious_path, mock_logger
            ).extract()

            # Features dataclass must compare equal across paths.
            assert shared_features == fresh_features
            # Suspicious-API findings: same set of api_name/category pairs.
            shared_pairs = {(f['api_name'], f['api_category']) for f in shared_apis}
            fresh_pairs = {(f['api_name'], f['api_category']) for f in fresh_apis}
            assert shared_pairs == fresh_pairs

    def test_context_scan_is_computed_once(self, js_malicious_path, mock_logger):
        """Two extractors sharing one JSContext must each trigger their backing
        scan at most once.

        The context exposes two cached scan results: `scan` (raw source) and
        `scan_deobfuscated` (deobfuscated text, populated only by the dual-pass
        consumers in JSSuspiciousAPIsExtractor / JSDeobfuscationExtractor).
        Both are cached_property — repeating the access from a second extractor
        must not re-run scan_source. Whether the deobfuscated scan happens at
        all depends on the deobfuscator producing a different text for this
        sample; we accept either outcome and only fail if the per-cache
        single-run invariant is broken.
        """
        from redb.extractors.js_extractors import js_context as js_ctx_module
        from redb.extractors.js_extractors.js_context import JSContext
        from redb.extractors.js_extractors.js_features import JSFeaturesExtractor
        from redb.extractors.js_extractors.js_suspicious_apis import (
            JSSuspiciousAPIsExtractor,
        )

        real_scan = js_ctx_module.scan_source
        call_count = {"n": 0}

        def counting_scan(*args, **kwargs):
            call_count["n"] += 1
            return real_scan(*args, **kwargs)

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'), \
                patch.object(js_ctx_module, 'scan_source', counting_scan):
            ctx = JSContext.from_path(js_malicious_path, log=mock_logger)

            JSFeaturesExtractor(
                js_malicious_path, mock_logger, context=ctx
            ).extract()
            JSSuspiciousAPIsExtractor(
                js_malicious_path, mock_logger, context=ctx
            ).extract()
            after_first_pair = call_count["n"]

            # The raw scan and (optionally) the deobfuscated scan are the only
            # two scan_source calls allowed for this sample.
            assert 1 <= after_first_pair <= 2, (
                f"scan_source must run at most twice (raw + deobf) when context "
                f"is shared, got {after_first_pair}"
            )

            # Re-running the same extractors must not re-trigger any scan: both
            # cached_property results are pinned by the first access.
            JSFeaturesExtractor(
                js_malicious_path, mock_logger, context=ctx
            ).extract()
            JSSuspiciousAPIsExtractor(
                js_malicious_path, mock_logger, context=ctx
            ).extract()

            assert call_count["n"] == after_first_pair, (
                f"cached scans re-ran across extractor instances: "
                f"{after_first_pair} -> {call_count['n']}"
            )

    def test_context_deobfuscation_runs_once(self, js_malicious_path, mock_logger):
        """Multiple consumers reading JSContext.deobfuscated must trigger the
        external deobfuscator at most once. cached_property guarantees this;
        the test pins the contract so a future regression fails loudly."""
        from redb.extractors.js_extractors import js_context as js_ctx_module
        from redb.extractors.js_extractors.js_context import JSContext

        deob_calls = {"n": 0}
        real_helper = None

        def counting_deobfuscate(source, log):
            deob_calls["n"] += 1
            return None, None  # cheap stub: no need to actually run webcrack

        # Patch the import that JSContext.deobfuscated does lazily inside the
        # cached_property. We monkeypatch the helper module directly.
        from redb.extractors.js_extractors import js_deobfuscator as deob_helper

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'), \
                patch.object(deob_helper, 'deobfuscate', counting_deobfuscate):
            ctx = JSContext.from_path(js_malicious_path, log=mock_logger)
            _ = ctx.deobfuscated
            _ = ctx.deobfuscated  # second access must hit the cache
            _ = ctx.deobfuscated  # third access too

            assert deob_calls["n"] == 1, (
                f"deobfuscate() must be called once per context, got "
                f"{deob_calls['n']}"
            )

    def test_context_ast_parsed_once(self, js_malicious_path, mock_logger):
        """Repeated access to JSContext.ast must invoke pyjsparser.parse at
        most once. cached_property guarantees this; the test pins the
        contract so a future regression (e.g. dropping cached_property)
        fails loudly."""
        from redb.extractors.js_extractors.js_context import JSContext

        try:
            import pyjsparser  # noqa: F401
        except ImportError:
            pytest.skip("pyjsparser not installed")

        parse_calls = {"n": 0}
        real_parse = pyjsparser.parse

        def counting_parse(source):
            parse_calls["n"] += 1
            return real_parse(source)

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'), \
                patch.object(pyjsparser, 'parse', counting_parse):
            ctx = JSContext.from_path(js_malicious_path, log=mock_logger)
            _ = ctx.ast
            _ = ctx.ast  # second access: must hit the cache
            _ = ctx.ast  # third access: still cached

            # parse may legitimately be 0 if the source can't be parsed and
            # the exception was swallowed; otherwise it must be exactly 1.
            assert parse_calls["n"] <= 1, (
                f"pyjsparser.parse called {parse_calls['n']} times for one "
                "context — cached_property must hold the parsed AST"
            )


# ============================================================================
# JS IOC Surface Tests (end-to-end)
# ============================================================================

class TestJSIOCSurfaces:
    """workers.py-style assembly of analysis_results: text_raw, text_normalized,
    and decoded strings. End-to-end through IOCExtractorFromResults."""

    def _build_analysis_results(self, ctx, raw_source, sha256):
        """Mirror the dict workers.py builds in the JS IOC block. Kept in a
        helper so the test exercises the same shape the production code uses
        without dragging the full workers.py dispatch into the test harness."""
        import hashlib

        text_raw_entries = [{"content": raw_source, "content_hash": sha256}]
        text_normalized_entries = []
        deobf_text, _ = ctx.deobfuscated
        if deobf_text and deobf_text != raw_source:
            text_normalized_entries.append({
                "content": deobf_text,
                "content_hash": hashlib.sha256(
                    deobf_text.encode("utf-8")
                ).hexdigest(),
            })
        decoded_strings = ctx.decoded_strings or []
        strings_entries = [
            {
                "string": s.get("string", ""),
                "string_offset": s.get("string_offset", 0),
            }
            for s in decoded_strings
        ]
        return {
            "strings": strings_entries,
            "text_raw": text_raw_entries,
            "text_normalized": text_normalized_entries,
        }

    def test_decoded_string_url_lands_via_strings_surface(self, tmp_path, mock_logger):
        """A URL hidden inside a String.fromCharCode(...) call is decoded by
        JSStringsExtractor into a plaintext string; running IOC extraction
        over the decoded `strings` surface picks it up with source_type=string.
        Without the broadened plumbing the URL would be invisible to IOC
        extraction (regex doesn't see it through the charcode encoding in
        the raw source)."""
        from redb.extractors.ioc_extractor.ioc_extractor import IOCExtractorFromResults
        from redb.extractors.ioc_extractor.standalone_ioc_extractor import (
            IOCType, SourceType,
        )
        from redb.extractors.js_extractors.js_context import JSContext
        from redb.extractors.js_extractors.js_strings import JSStringsExtractor

        # String.fromCharCode of "http://hidden.example.com" (25 chars, codes
        # built inline below).
        url = "http://hidden.example.com"
        codes = ", ".join(str(ord(c)) for c in url)
        source = (
            'var benign = "harmless string here";\n'
            f'var u = String.fromCharCode({codes});\n'
        )
        sample_path = tmp_path / "charcode.js"
        sample_path.write_text(source)

        sha256 = "b" * 64

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            ctx = JSContext.from_path(str(sample_path), log=mock_logger)
            JSStringsExtractor(str(sample_path), mock_logger, context=ctx).extract()

            analysis_results = self._build_analysis_results(ctx, ctx.source, sha256)
            iocs = IOCExtractorFromResults(
                analysis_results=analysis_results,
                sha256=sha256,
                log=mock_logger,
            ).extract()

            url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
            assert any(
                "hidden.example.com" in i.ioc_value
                and i.source_type == SourceType.STRING
                for i in url_iocs
            ), (
                f"charcode-decoded URL must be picked up via the strings "
                f"surface; got {[(i.ioc_value, i.source_type) for i in url_iocs]}"
            )

    def test_raw_url_lands_via_text_raw(self, tmp_path, mock_logger):
        """A plain URL in the raw source lands with source_type=text_raw, the
        new universal surface (was source_type=decompiled_function before #C)."""
        from redb.extractors.ioc_extractor.ioc_extractor import IOCExtractorFromResults
        from redb.extractors.ioc_extractor.standalone_ioc_extractor import (
            IOCType, SourceType,
        )
        from redb.extractors.js_extractors.js_context import JSContext

        source = 'fetch("https://plain.example.com/c2");\n'
        sample_path = tmp_path / "plain.js"
        sample_path.write_text(source)
        sha256 = "c" * 64

        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'):
            ctx = JSContext.from_path(str(sample_path), log=mock_logger)
            analysis_results = self._build_analysis_results(ctx, ctx.source, sha256)
            iocs = IOCExtractorFromResults(
                analysis_results=analysis_results,
                sha256=sha256,
                log=mock_logger,
            ).extract()

            url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
            assert any(
                "plain.example.com" in i.ioc_value
                and i.source_type == SourceType.TEXT_RAW
                for i in url_iocs
            )

    def test_no_text_normalized_entry_when_deobf_equals_raw(self, tmp_path, mock_logger):
        """If the deobfuscator returns text byte-identical to the raw source
        (or returns nothing), the analysis_results dict must not add a
        text_normalized entry — avoids double-scraping the same content."""
        from redb.extractors.js_extractors.js_context import JSContext
        from redb.extractors.js_extractors import js_deobfuscator as deob_helper

        source = 'fetch("https://noop.example.com/");\n'
        sample_path = tmp_path / "noop.js"
        sample_path.write_text(source)
        sha256 = "d" * 64

        # Force the deobfuscator to return identical text — exercises the
        # equality check in workers.py / _build_analysis_results.
        with patch('redb.settings.ELASTIC_BINARIES_COLLECTION', 'test'), \
                patch.object(deob_helper, 'deobfuscate',
                             lambda src, log: (src, "stub")):
            ctx = JSContext.from_path(str(sample_path), log=mock_logger)
            analysis_results = self._build_analysis_results(ctx, ctx.source, sha256)

            assert analysis_results["text_normalized"] == [], (
                "text_normalized must be empty when deobfuscation produced "
                "byte-identical output"
            )