Ido Nevat

95 papers A* 1A 1B 7Misc 5Journal 52Unranked 28
YearRankTypeTitle / Venue / Authors
2024 J jnl
J. Frankl. Inst.
Shunan Sheng, Qikun Xiang, Ido Nevat, Ariel Neufeld
2023 J jnl
Entropy
Roméo Tayewo, François Septier, Ido Nevat, Gareth W. Peters
2022 J jnl
CoRR
Shunan Sheng, Qikun Xiang, Ido Nevat, Ariel Neufeld
2021 J jnl
IEEE Internet Things J.
Biswadeep Chakraborty, Dinil Mon Divakaran, Ido Nevat, Gareth W. Peters, Mohan Gurusamy
2021 J jnl
EURASIP J. Wirel. Commun. Netw.
Laurent Clavier, Gareth W. Peters, François Septier, Ido Nevat
2021 J jnl
Entropy
Gareth W. Peters, Ido Nevat, Sai Ganesh Nagarajan, Tomoko Matsui
2020 J jnl
IEEE Trans. Signal Process.
Qikun Xiang, Ido Nevat, Gareth W. Peters
2020 J jnl
CoRR
Biswadeep Chakraborty, Dinil Mon Divakaran, Ido Nevat, Gareth W. Peters, Mohan Gurusamy
2020 J jnl
IEEE Access
Angesom Ataklity Tesfay, Eric Pierre Simon, Ido Nevat, Laurent Clavier
2020 conf
DCCN
Angesom Ataklity Tesfay, Eric Pierre Simon, Ido Nevat, Laurent Clavier
2020 J jnl
Comput. Commun.
Jing Yang Koh, Gareth W. Peters, Ido Nevat, Derek Leong
2019 Misc conf
COMSNETS
Bharat Atul Desai, Dinil Mon Divakaran, Ido Nevat, Gareth W. Peters, Mohan Gurusamy
2019 conf
NANOCOM
Malcolm Egan, Valeria Loscrì, Ido Nevat, Trung Q. Duong, Marco Di Renzo
2019 conf
IEEE SENSORS
Pengfei Zhang, Gareth W. Peters, Ido Nevat, Keng Boon Teo, Yixin Wang
2019 J jnl
IEEE Internet Things J.
Yan-Bin Chen, Ido Nevat, Pengfei Zhang, Sai Ganesh Nagarajan, Hung-Yu Wei
2018 J jnl
IEEE/ACM Trans. Netw.
Ido Nevat, Dinil Mon Divakaran, Sai Ganesh Nagarajan, Pengfei Zhang, Le Su, Li Ling Ko, Vrizlynn L. L. Thing
2018 conf
VTC Spring
Ido Nevat, Sai Ganesh Nagarajan, Pengfei Zhang
2018 J jnl
IEEE Trans. Signal Inf. Process. over Networks
Markus Fröhle, Themistoklis Charalambous, Ido Nevat, Henk Wymeersch
2018 conf
EUSIPCO
Ido Nevat, François Septier, Karin Avnit, Gareth W. Peters, Laurent Clavier
2018 J jnl
IEEE Commun. Lett.
Shihao Yan, Gareth W. Peters, Ido Nevat, Robert A. Malaney
2018 J jnl
IEEE Trans. Signal Process.
Pengfei Zhang, Ido Nevat, Gareth W. Peters, François Septier, Michael A. Osborne
2017 A* conf
IJCAI
Qikun Xiang, Jie Zhang, Ido Nevat, Pengfei Zhang
2017 A conf
AAMAS
Qikun Xiang, Jie Zhang, Ido Nevat, Pengfei Zhang
2017 J jnl
Trans. Emerg. Telecommun. Technol.
Malcolm Egan, Gareth W. Peters, Ido Nevat, Mahyar Shirvanimoghaddam, Iain B. Collings
2017 J jnl
Digit. Investig.
Dinil Mon Divakaran, Fok Kar Wai, Ido Nevat, Vrizlynn L. L. Thing
2017 J jnl
IEEE Internet Things J.
Sai Ganesh Nagarajan, Pengfei Zhang, Ido Nevat
2017 B conf
WCNC
Jingting Liu, Ido Nevat, Pengfei Zhang, Gareth W. Peters
2017 J jnl
IEEE Trans. Inf. Forensics Secur.
Jing Yang Koh, Derek Leong, Gareth W. Peters, Ido Nevat, Wai-Choong Wong
2017 J jnl
IEEE Trans. Signal Process.
Ido Nevat, Pengfei Zhang, Gad Frenkel, Gareth W. Peters
2017 conf
ICC
Jing Yang Koh, Gareth W. Peters, Derek Leong, Ido Nevat, Wai-Choong Wong
2017 J jnl
IEEE Internet Things J.
Pengfei Zhang, Sai Ganesh Nagarajan, Ido Nevat
2016 J jnl
IEEE Trans. Signal Process.
Thi Le Thu Nguyen, François Septier, Harizo Rajaona, Gareth W. Peters, Ido Nevat, Yves Delignon
2016 conf
TRUST@AAMAS
Qikun Xiang, Ido Nevat, Pengfei Zhang, Jie Zhang
2016 J jnl
IEEE Internet Things J.
Jing Yang Koh, Ido Nevat, Derek Leong, Wai-Choong Wong
2016 J jnl
CoRR
Jing Yang Koh, Ido Nevat, Derek Leong, Wai-Choong Wong
2016 J jnl
IEEE Trans. Wirel. Commun.
Shihao Yan, Ido Nevat, Gareth W. Peters, Robert A. Malaney
2016 J jnl
IEEE Trans. Veh. Technol.
Shihao Yan, Robert A. Malaney, Ido Nevat, Gareth W. Peters
2016 J jnl
IEEE Trans. Signal Inf. Process. over Networks
Ido Nevat, Gareth W. Peters, Karin Avnit, François Septier, Laurent Clavier
2016 B conf
NOMS
Huiguang Liang, Ido Nevat, Hyong S. Kim, Hwee-Pink Tan, Wai-Leong Yeow
2015 J jnl
IEEE J. Sel. Areas Commun.
Wai Hong Ronald Chan, Pengfei Zhang, Ido Nevat, Sai Ganesh Nagarajan, Alvin C. Valera, Hwee-Xian Tan, Natarajan Gautam
2015 conf
ICC
Wai Hong Ronald Chan, Pengfei Zhang, Wenyu Zhang, Ido Nevat, Alvin C. Valera, Hwee-Xian Tan, Natarajan Gautam
2015 J jnl
IEEE Trans. Signal Process.
Ido Nevat, Gareth W. Peters, François Septier, Tomoko Matsui
2015 J jnl
IEEE Trans. Signal Process.
Pengfei Zhang, Ido Nevat, Gareth W. Peters, Gaoxi Xiao, Hwee Pink Tan
2015 conf
VTC Spring
Shihao Yan, Robert A. Malaney, Ido Nevat, Gareth W. Peters
2015 J jnl
CoRR
Thi Le Thu Nguyen, François Septier, Harizo Rajaona, Gareth W. Peters, Ido Nevat, Yves Delignon
2015 conf
ICC
Malcolm Egan, Gareth W. Peters, Ido Nevat, Iain B. Collings
2015 conf
ICC
Xin Yan, Laurent Clavier, Gareth W. Peters, Nourddine Azzaoui, François Septier, Ido Nevat
2015 conf
ICC
Ido Nevat, Gareth W. Peters, François Septier, Tomoko Matsui
2014 conf
GlobalSIP
Gareth W. Peters, Tor André Myrvoll, Tomoko Matsui, Ido Nevat, François Septier
2014 J jnl
IEEE Trans. Signal Process.
Ido Nevat, Gareth W. Peters, Iain B. Collings
2014 conf
MILCOM
Pengfei Zhang, Gareth W. Peters, Ido Nevat, Gaoxi Xiao, Hwee-Pink Tan
2014 conf
ISSNIP
Pengfei Zhang, Jing Yang Koh, Shaowei Lin, Ido Nevat
2014 Misc conf
ICASSP
Gareth W. Peters, Ido Nevat, Laurent Clavier, François Septier
2014 J jnl
IEEE Trans. Wirel. Commun.
Ido Nevat, Gareth W. Peters, Arnaud Doucet, Jinhong Yuan
2014 J jnl
CoRR
Shihao Yan, Robert A. Malaney, Ido Nevat, Gareth W. Peters
2014 J jnl
CoRR
Shihao Yan, Ido Nevat, Gareth W. Peters, Robert A. Malaney
2014 J jnl
CoRR
Shihao Yan, Robert A. Malaney, Ido Nevat, Gareth W. Peters
2014 conf
ISSNIP
Gareth W. Peters, Ido Nevat, Shaowei Lin, Tomoko Matsui
2014 conf
ISSNIP
Ido Nevat, Ory Eger, Gareth W. Peters, François Septier
2014 J jnl
IEEE Trans. Veh. Technol.
Shihao Yan, Robert A. Malaney, Ido Nevat, Gareth W. Peters
2014 conf
ICC
Shihao Yan, Robert A. Malaney, Ido Nevat, Gareth W. Peters
2014 J jnl
CoRR
Malcolm A. Egan, Gareth W. Peters, Ido Nevat, Iain B. Collings
2014 conf
AusCTW
Shihao Yan, Robert A. Malaney, Ido Nevat, Gareth W. Peters
2013 Misc conf
ICASSP
François Septier, Gareth W. Peters, Ido Nevat
2013 conf
ICC
Ido Nevat, Gareth W. Peters, Iain B. Collings
2013 B conf
PIMRC
Ido Nevat, Gareth W. Peters, Iain B. Collings
2013 conf
ICC
Ni Ding, Ido Nevat, Gareth W. Peters, Jinhong Yuan
2013 J jnl
IEEE Trans. Signal Process.
Ido Nevat, Gareth W. Peters, Iain B. Collings
2012 J jnl
CoRR
Shihao Yan, Robert A. Malaney, Ido Nevat, Gareth W. Peters
2012 B conf
GLOBECOM
Shihao Yan, Robert A. Malaney, Ido Nevat, Gareth W. Peters
2012 B conf
WCNC
Ido Nevat, Gareth W. Peters, Jinhong Yuan
2012 conf
AusCTW
Ido Nevat, Gareth W. Peters, Iain B. Collings
2012 J jnl
CoRR
Shihao Yan, Robert A. Malaney, Ido Nevat, Gareth W. Peters
2012 conf
ISWCS
Wei Gu, Gareth W. Peters, Laurent Clavier, François Septier, Ido Nevat
2012 J jnl
IEEE Trans. Veh. Technol.
Gareth W. Peters, Ido Nevat, Jinhong Yuan, Iain B. Collings
2012 B conf
WCNC
Ido Nevat, Gareth W. Peters, Jinhong Yuan, Iain B. Collings
2011 conf
VTC Fall
Ido Nevat, Gareth W. Peters, Jinhong Yuan
2011 J jnl
CoRR
Ido Nevat, Gareth W. Peters, Iain B. Collings, Jinhong Yuan
2011 J jnl
CoRR
Gareth W. Peters, Ido Nevat, Jinhong Yuan, Iain B. Collings
2010 J jnl
IEEE Trans. Signal Process.
Gareth W. Peters, Ido Nevat, Scott A. Sisson, Yanan Fan, Jinhong Yuan
2010 J jnl
CoRR
Ido Nevat, Gareth W. Peters, Jinhong Yuan
2010 J jnl
CoRR
Ido Nevat, Gareth W. Peters, Arnaud Doucet, Jinhong Yuan
2010 J jnl
IEEE Trans. Commun.
Ido Nevat, Gareth W. Peters, Jinhong Yuan
2010 J jnl
CoRR
Chong Han, Ido Nevat, Gareth W. Peters, Jinhong Yuan
2010 J jnl
IEEE Trans. Veh. Technol.
Ido Nevat, Tao Yang, Karin Avnit, Jinhong Yuan
2009 conf
VTC Spring
Ido Nevat, Gareth W. Peters, Jinhong Yuan
2009 J jnl
IEEE Trans. Signal Process.
Gareth W. Peters, Ido Nevat, Jinhong Yuan
2009 J jnl
IEEE Trans. Veh. Technol.
Ido Nevat, Jinhong Yuan
2009
Ido Nevat
2008 B conf
PIMRC
Ido Nevat, Gareth W. Peters, Jinhong Yuan
2008 Misc conf
ICASSP
Ido Nevat, Gareth W. Peters, Jinhong Yuan
2008 conf
VTC Spring
Ido Nevat, Gareth W. Peters, Jinhong Yuan
2007 conf
ICASSP (3)
Ido Nevat, Jinhong Yuan
2007 conf
ISWCS
Ido Nevat, Jinhong Yuan
2007 Misc conf
CISS
Ido Nevat, Ami Wiesel, Jinhong Yuan, Yonina C. Eldar
tests/unit/test_apk_decompile_extractor.py
← Index tests/unit/test_apk_decompile_extractor.py python
"""
Unit tests for DecompileAPK extractor, export data, and ClickHouse schemas.

Tests the main DecompileAPK extractor class following the DecompileBinja test pattern.
All analysis is mocked — no JADX/apktool/Java installation required.
"""
import hashlib
import os
import tempfile
import zipfile

import pytest
from unittest.mock import MagicMock, patch, PropertyMock
from dataclasses import asdict

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


def _create_fake_apk_file():
    """Create a minimal APK file for testing and return its path."""
    tmpdir = tempfile.mkdtemp()
    apk_path = os.path.join(tmpdir, "test.apk")
    with zipfile.ZipFile(apk_path, "w") as zf:
        zf.writestr("AndroidManifest.xml", b"<manifest/>")
        zf.writestr("classes.dex", b"dex\n035\x00" + b"\x00" * 100)
    return apk_path


# ============================================================================
# DecompileAPK Extractor Tests
# ============================================================================

class TestDecompileAPK:
    """Tests for the DecompileAPK extractor class."""

    def test_tag(self):
        from redb.extractors.decompiler.DecompileAPK import DecompileAPK
        apk_path = _create_fake_apk_file()
        try:
            ext = DecompileAPK(apk_path, MagicMock())
            assert ext.tag() == "apk_decompiled"
        finally:
            os.unlink(apk_path)

    def test_get_clickhouse_table(self):
        from redb.extractors.decompiler.DecompileAPK import DecompileAPK
        apk_path = _create_fake_apk_file()
        try:
            ext = DecompileAPK(apk_path, MagicMock())
            assert ext.get_clickhouse_table() is None
        finally:
            os.unlink(apk_path)

    def test_timeout_default(self):
        from redb.extractors.decompiler.DecompileAPK import DecompileAPK
        apk_path = _create_fake_apk_file()
        try:
            ext = DecompileAPK(apk_path, MagicMock())
            assert ext.APK_DECOMPILE_TIMEOUT == 600
        finally:
            os.unlink(apk_path)

    def test_timeout_from_env(self):
        from redb.extractors.decompiler.DecompileAPK import DecompileAPK
        apk_path = _create_fake_apk_file()
        try:
            with patch.dict(os.environ, {"APK_DECOMPILE_TIMEOUT": "900"}):
                ext = DecompileAPK(apk_path, MagicMock())
                assert ext.APK_DECOMPILE_TIMEOUT == 900
        finally:
            os.unlink(apk_path)

    def test_timeout_invalid_env(self):
        from redb.extractors.decompiler.DecompileAPK import DecompileAPK
        apk_path = _create_fake_apk_file()
        try:
            with patch.dict(os.environ, {"APK_DECOMPILE_TIMEOUT": "not_a_number"}):
                ext = DecompileAPK(apk_path, MagicMock())
                assert ext.APK_DECOMPILE_TIMEOUT == 600
        finally:
            os.unlink(apk_path)

    def test_calculate_md5(self):
        from redb.extractors.decompiler.DecompileAPK import DecompileAPK
        apk_path = _create_fake_apk_file()
        try:
            ext = DecompileAPK(apk_path, MagicMock())
            result = ext.calculate_md5("test_string")
            assert len(result) == 32
            assert result == hashlib.md5(b"test_string").hexdigest()
        finally:
            os.unlink(apk_path)

    def test_context_manager(self):
        from redb.extractors.decompiler.DecompileAPK import DecompileAPK
        apk_path = _create_fake_apk_file()
        try:
            with DecompileAPK(apk_path, MagicMock()) as ext:
                assert ext is not None
        finally:
            os.unlink(apk_path)

    def test_cleanup_run(self):
        from redb.extractors.decompiler.DecompileAPK import DecompileAPK
        apk_path = _create_fake_apk_file()
        try:
            ext = DecompileAPK(apk_path, MagicMock())
            ext.analyzer = MagicMock()
            ext.cleanup_run()
            assert ext.analyzer is None
        finally:
            os.unlink(apk_path)


# ============================================================================
# Export Data Tests
# ============================================================================

class TestPrepareExportData:
    """Tests for DecompileAPK.prepare_export_data() ClickHouse schemas."""

    def _make_extractor_with_results(self):
        from redb.extractors.decompiler.DecompileAPK import DecompileAPK
        apk_path = _create_fake_apk_file()
        ext = DecompileAPK(apk_path, MagicMock())
        ext.analysis_results = {
            "sha256": "a" * 64,
            "sha1": "b" * 40,
            "md5": "c" * 32,
            "decompiled_content": [
                {
                    "decompiled_method_hash": "d" * 64,
                    "decompiled_method": "public void foo() {}",
                    "decompiled_method_type": "USER",
                    "decompiled_has_string_encryption": False,
                    "decompiled_has_reflection_calls": False,
                    "decompiled_excessive_goto_count": False,
                }
            ],
            "decompiled_refs": [
                {
                    "decompiled_method_hash": "d" * 64,
                    "smali_method_hash": "e" * 64,
                    "decompiled_class_name": "com.example.Foo",
                    "decompiled_method_name": "foo",
                    "decompiled_method_signature": "()V",
                    "decompiled_method_prototype": "void foo()",
                    "functions_caller": ["bar"],
                    "functions_call": ["baz"],
                }
            ],
            "smali_content": [
                {
                    "smali_method_hash": "e" * 64,
                    "smali_method": "invoke-virtual {p0}, Lfoo;->bar()V\nreturn-void",
                    "smali_method_type": "USER",
                    "smali_instructions_count": 10,
                    "smali_register_count": 3,
                    "smali_has_string_encryption": False,
                    "smali_has_reflection_calls": False,
                    "smali_excessive_goto_count": False,
                    "smali_flattened_score": 0.0,
                    "smali_mba_score": 0.0,
                }
            ],
            "smali_refs": [
                {
                    "smali_method_hash": "e" * 64,
                    "decompiled_method_hash": "d" * 64,
                    "smali_class_name": "com.example.Foo",
                    "smali_method_name": "foo",
                    "smali_method_signature": "()V",
                    "ssdeep_smali": None,
                    "tlsh_smali": None,
                }
            ],
            "similarity_metrics": [
                {
                    "smali_method_hash": "e" * 64,
                    "cyclomatic_complexity": 2,
                    "ssdeep_smali": None,
                    "tlsh_smali": None,
                    "ssdeep_smali_normalized": None,
                    "tlsh_smali_normalized": None,
                    "minhash": [],
                }
            ],
            "cfg": [
                {
                    "smali_method_hash": "e" * 64,
                    "cfg_topology_hash": b'\x01' * 16,
                    "block_count": 3,
                    "edge_count": 3,
                    "cfg_instructions_count": 10,
                    "call_count": 1,
                    "cyclomatic_complexity": 2,
                    "loop_count": 0,
                    "max_depth": 2,
                    "max_fan_out": 2,
                    "md_index_topdown": 12345,
                    "md_index_bottomup": 67890,
                    "prime_product_smali": 999,
                    "cfg_feature_tlsh": None,
                    "wl_minhash": [0] * 128,
                    "bb_features": [[5, 1, 0, 1, 1, 0, 0, 2]] * 3,
                    "cfg_adjacency": [0x00000001, 0x00000002],
                }
            ],
            "strings": [
                {
                    "string": "https://evil.example.com/payload",
                    "string_encoding": "UTF8",
                    "string_offset": 0,
                    "string_length": 31,
                    "string_entropy": 3.95,
                }
            ],
            "analysis_errors": [
                {
                    "class_name": "com.example.Foo",
                    "method_name": "bar",
                    "error_location": "jadx",
                    "error_message": "timeout",
                    "error_type": "TimeoutError",
                }
            ],
        }
        return ext, apk_path

    def test_returns_none_when_no_results(self):
        from redb.extractors.decompiler.DecompileAPK import DecompileAPK
        apk_path = _create_fake_apk_file()
        try:
            ext = DecompileAPK(apk_path, MagicMock())
            assert ext.prepare_export_data("ClickHouseExporter") is None
        finally:
            os.unlink(apk_path)

    def test_multi_table_flag(self):
        ext, apk_path = self._make_extractor_with_results()
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            assert export["multi_table"] is True
        finally:
            os.unlink(apk_path)

    def test_all_table_keys_present(self):
        ext, apk_path = self._make_extractor_with_results()
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            assert "decompiled_content" in export
            assert "decompiled_refs" in export
            assert "smali_content" in export
            assert "smali_refs" in export
            assert "method_similarity_metrics" in export
            assert "strings_raw" in export
            assert "analysis_errors" in export
        finally:
            os.unlink(apk_path)

    def test_decompiled_content_schema(self):
        ext, apk_path = self._make_extractor_with_results()
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            dc = export["decompiled_content"]
            assert dc["table"] == "code_apk_decompiled_methods_content"
            assert len(dc["column_names"]) == 7
            assert len(dc["column_type_names"]) == 7
            assert "decompiled_method_hash" in dc["column_names"]
            assert "decompiled_method" in dc["column_names"]
            assert "analysis_date" in dc["column_names"]
            assert len(dc["data"]) == 1
            assert len(dc["data"][0]) == 7
        finally:
            os.unlink(apk_path)

    def test_decompiled_refs_schema(self):
        ext, apk_path = self._make_extractor_with_results()
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            dr = export["decompiled_refs"]
            assert dr["table"] == "code_apk_decompiled_methods_references"
            assert len(dr["column_names"]) == 10
            assert len(dr["column_type_names"]) == 10
            assert "sha256" in dr["column_names"]
            assert "sha1" not in dr["column_names"]
            assert "md5" not in dr["column_names"]
            assert "functions_caller" in dr["column_names"]
            assert "functions_call" in dr["column_names"]
        finally:
            os.unlink(apk_path)

    def test_smali_content_schema(self):
        ext, apk_path = self._make_extractor_with_results()
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            sc = export["smali_content"]
            assert sc["table"] == "code_apk_smali_methods_content"
            assert len(sc["column_names"]) == 11
            assert len(sc["column_type_names"]) == 11
            assert "smali_instructions_count" in sc["column_names"]
            assert "smali_register_count" in sc["column_names"]
            assert "smali_flattened_score" in sc["column_names"]
            assert "smali_mba_score" in sc["column_names"]
        finally:
            os.unlink(apk_path)

    def test_smali_refs_schema(self):
        ext, apk_path = self._make_extractor_with_results()
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            sr = export["smali_refs"]
            assert sr["table"] == "code_apk_smali_methods_references"
            assert len(sr["column_names"]) == 7
            assert len(sr["column_type_names"]) == 7
            assert "sha1" not in sr["column_names"]
            assert "md5" not in sr["column_names"]
            assert "ssdeep_smali" not in sr["column_names"]
            assert "tlsh_smali" not in sr["column_names"]
        finally:
            os.unlink(apk_path)

    def test_similarity_metrics_schema(self):
        ext, apk_path = self._make_extractor_with_results()
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            sm = export["method_similarity_metrics"]
            assert sm["table"] == "code_apk_method_similarity_metrics"
            assert len(sm["column_names"]) == 7
            assert "minhash" in sm["column_names"]
            assert "ssdeep_smali_normalized" in sm["column_names"]
            assert "tlsh_smali_normalized" in sm["column_names"]
            # cyclomatic_complexity moved to cfg table
            assert "cyclomatic_complexity" not in sm["column_names"]
            assert "block_count" not in sm["column_names"]
            assert "loop_count" not in sm["column_names"]
        finally:
            os.unlink(apk_path)

    def test_analysis_errors_schema(self):
        ext, apk_path = self._make_extractor_with_results()
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            ae = export["analysis_errors"]
            assert ae["table"] == "code_apk_analysis_errors"
            assert len(ae["column_names"]) == 9
            assert "error_hash" in ae["column_names"]
            assert "status" in ae["column_names"]
        finally:
            os.unlink(apk_path)

    def test_sha256_propagated_to_refs(self):
        ext, apk_path = self._make_extractor_with_results()
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            # Check that sha256 from analysis_results appears in ref rows
            dr_row = export["decompiled_refs"]["data"][0]
            assert dr_row[0] == "a" * 64  # sha256
            # sha1 and md5 no longer included in refs
            assert "sha1" not in export["decompiled_refs"]["column_names"]
            assert "md5" not in export["decompiled_refs"]["column_names"]
        finally:
            os.unlink(apk_path)

    def test_empty_tables_not_included(self):
        from redb.extractors.decompiler.DecompileAPK import DecompileAPK
        apk_path = _create_fake_apk_file()
        try:
            ext = DecompileAPK(apk_path, MagicMock())
            ext.analysis_results = {
                "sha256": "a" * 64,
                "sha1": "b" * 40,
                "md5": "c" * 32,
                "decompiled_content": [],
                "decompiled_refs": [],
                "smali_content": [],
                "smali_refs": [],
                "similarity_metrics": [],
                "cfg": [],
                "strings": [],
                "analysis_errors": [],
            }
            export = ext.prepare_export_data("ClickHouseExporter")
            assert export == {"multi_table": True}
        finally:
            os.unlink(apk_path)

    def test_non_clickhouse_exporter_returns_none(self):
        ext, apk_path = self._make_extractor_with_results()
        try:
            result = ext.prepare_export_data("ElasticsearchExporter")
            assert result is None
        finally:
            os.unlink(apk_path)


# ============================================================================
# Dataclass Tests
# ============================================================================

class TestAPKCodeAnalysisDataclasses:
    """Tests for the new APK code analysis dataclasses."""

    def test_decompiled_method_content(self):
        from redb.models.dataclasses import APKDecompiledMethodContent
        dc = APKDecompiledMethodContent(
            decompiled_method_hash="a" * 64,
            decompiled_method="public void foo() {}",
            decompiled_method_type="USER",
        )
        assert dc.decompiled_has_string_encryption is False
        assert dc.decompiled_has_reflection_calls is False
        assert dc.decompiled_excessive_goto_count is False

    def test_decompiled_method_content_populated(self):
        from redb.models.dataclasses import APKDecompiledMethodContent
        dc = APKDecompiledMethodContent(
            decompiled_method_hash="a" * 64,
            decompiled_method="public void foo() {}",
            decompiled_method_type="USER",
            decompiled_has_string_encryption=True,
            decompiled_has_reflection_calls=True,
            decompiled_excessive_goto_count=True,
        )
        assert dc.decompiled_has_string_encryption is True
        d = asdict(dc)
        assert d["decompiled_method_hash"] == "a" * 64

    def test_decompiled_method_reference(self):
        from redb.models.dataclasses import APKDecompiledMethodReference
        dr = APKDecompiledMethodReference(
            sha256="a" * 64,
            sha1="b" * 40,
            md5="c" * 32,
            decompiled_method_hash="d" * 64,
        )
        assert dr.functions_caller == []
        assert dr.functions_call == []
        assert dr.smali_method_hash is None

    def test_smali_method_content(self):
        from redb.models.dataclasses import APKSmaliMethodContent
        sc = APKSmaliMethodContent(
            smali_method_hash="e" * 64,
            smali_method="invoke-virtual {p0}, Lfoo;->bar()V",
            smali_method_type="USER",
            smali_instructions_count=5,
            smali_register_count=3,
        )
        assert sc.smali_instructions_count == 5
        assert sc.smali_register_count == 3

    def test_smali_method_reference(self):
        from redb.models.dataclasses import APKSmaliMethodReference
        sr = APKSmaliMethodReference(
            sha256="a" * 64,
            sha1="b" * 40,
            md5="c" * 32,
            smali_method_hash="e" * 64,
        )
        assert sr.decompiled_method_hash is None
        assert sr.ssdeep_smali is None
        assert sr.tlsh_smali is None

    def test_method_similarity_metrics(self):
        from redb.models.dataclasses import APKMethodSimilarityMetrics
        ms = APKMethodSimilarityMetrics(smali_method_hash="e" * 64)
        assert ms.cyclomatic_complexity is None
        assert ms.ssdeep_smali_normalized is None
        assert ms.tlsh_smali_normalized is None
        assert ms.minhash == []

    def test_code_analysis_error(self):
        from redb.models.dataclasses import APKCodeAnalysisError
        err = APKCodeAnalysisError(
            sha256="a" * 64,
            error_location="jadx",
            error_message="timeout",
        )
        assert err.class_name is None
        assert err.method_name is None
        assert err.error_type is None


# ============================================================================
# Tag Enum Tests
# ============================================================================

class TestAPKDecompiledTag:
    """Tests for the APK_DECOMPILED tag enum."""

    def test_tag_exists(self):
        from redb.extractors.enum import Tag
        assert hasattr(Tag, "APK_DECOMPILED")

    def test_tag_value(self):
        from redb.extractors.enum import Tag
        assert Tag.APK_DECOMPILED.value == "apk_decompiled"


# ============================================================================
# Worker Integration Tests
# ============================================================================

class TestAPKWorkerDecompileIntegration:
    """Tests that DecompileAPK is registered in workers.py."""

    def test_module_registered(self):
        """Verify DecompileAPK is discoverable via the extractor registry."""
        from redb.extractor_registry import get_extractor_class
        cls = get_extractor_class("DecompileAPK")
        assert cls is not None, "DecompileAPK not found in extractor registry"
        assert cls.__name__ == "DecompileAPK"

    def test_worker_apk_dispatch_includes_decompile(self):
        import inspect
        import redb.workers as workers
        source = inspect.getsource(workers.process_binary_file)
        assert "DecompileAPK" in source