Ramakrishnan Durairajan

72 papers A* 5A 7B 6C 2Misc 4Journal 17Unranked 31
YearRankTypeTitle / Venue / Authors
2026 conf
KDD (1)
Riya Ponraj, Ramakrishnan Durairajan, Yu Wang
2026 conf
NINeS
Chris Misa, Walter Willinger, Ramakrishnan Durairajan, Reza Rejaie
2025 conf
QuNet@SIGCOMM
Kevin Bohan, Bella Bose, Steven Corbato, Thinh Nguyen, Inder Monga, Brian Smith, Wenji Wu, Ramakrishnan Durairajan
2025 J jnl
IEEE Trans. Dependable Secur. Comput.
Matthew Nance Hall, Zaoxing Liu, Vyas Sekar, Ramakrishnan Durairajan
2025 conf
HotStorage
Long Tran, River Bartz, Ramakrishnan Durairajan, Ulrich Kremer, Sudarsun Kannan
2025 B conf
MASCOTS
Joseph Colton, Ramakrishnan Durairajan
2025 conf
CoNEXT (Short Papers)
Chris Misa, Walter Willinger, Ramakrishnan Durairajan, Reza Rejaie
2025 conf
MILCOM
Long Tran, River Bartz, Rajesh Sankaran, Ulrich Kremer, Ramakrishnan Durairajan, Sudarsun Kannan
2025 Misc conf
HotNets
Chris Misa, Ramakrishnan Durairajan
2025 J jnl
CoRR
Chris Misa, Ramakrishnan Durairajan
2025 conf
LEO-NET
Chris Misa, Ramakrishnan Durairajan
2025 J jnl
CoRR
Chris Misa, Ramakrishnan Durairajan, Arpit Gupta, Reza Rejaie, Walter Willinger
2025 A* conf
SIGCOMM
Caleb Wang, Ying Zhang, Qianli Dong, Esteban Carisimo, Ramakrishnan Durairajan, Fabián E. Bustamante
2025 A* conf
WWW
Miguel A. Bermejo-Agueda, Patricia Callejo, Rubén Cuevas, Ángel Cuevas, Ramakrishnan Durairajan, Reza Rejaie, Álvaro Mayol
2025 conf
NAIC
Chris Misa, Matthew Nance Hall, Reza Rejaie, Walter Willinger, Ramakrishnan Durairajan
2024 conf
PACMI@SOSP
Abduarraheem Elfandi, Hannah Sagalyn, Ramakrishnan Durairajan, Walter Willinger
2024 conf
PAM (2)
Aleksandr Stevens, Blaise Iradukunda, Brad Bailey, Ramakrishnan Durairajan
2024 J jnl
IEEE/ACM Trans. Netw.
Chris Misa, Ramakrishnan Durairajan, Reza Rejaie, Walter Willinger
2024 J jnl
IEEE Trans. Netw. Serv. Manag.
Matthew Nance Hall, Paul Barford, Klaus-Tycho Foerster, Ramakrishnan Durairajan
2024 A* conf
SP
Chris Misa, Ramakrishnan Durairajan, Arpit Gupta, Reza Rejaie, Walter Willinger
2024 conf
CloudNet
Joseph Colton, Ramakrishnan Durairajan, Reza Rejaie, Walter Willinger
2023 B conf
CLOUD
Bahador Yeganeh, Ramakrishnan Durairajan, Reza Rejaie, Walter Willinger
2023 conf
eBPF@SIGCOMM
Joel Sommers, Nolan Rudolph, Ramakrishnan Durairajan
2023 J jnl
SIGMETRICS Perform. Evaluation Rev.
Arpit Gupta, Ramakrishnan Durairajan, Walter Willinger
2023 conf
LEO-NET
Joseph Mclaughlin, Jee Choi, Ramakrishnan Durairajan
2022 J jnl
IEEE J. Sel. Areas Commun.
Jared Knofczynski, Ramakrishnan Durairajan, Walter Willinger
2022 Misc conf
NSDI
Chris Misa, Walt O'Connor, Ramakrishnan Durairajan, Reza Rejaie, Walter Willinger
2021 J jnl
Opt. Switch. Netw.
Matthew Nance Hall, Klaus-Tycho Foerster, Stefan Schmid, Ramakrishnan Durairajan
2021 conf
OptSys@SIGCOMM
Matthew Nance Hall, Paul Barford, Klaus-Tycho Foerster, Manya Ghobadi, William Jensen, Ramakrishnan Durairajan
2021 conf
TMA
Joel Sommers, Ramakrishnan Durairajan
2021 B conf
PAM
Juno Mayer, Valerie Sahakian, Emilie Hooft, Douglas Toomey, Ramakrishnan Durairajan
2021 J jnl
IEEE Netw.
Chris Misa, Ramakrishnan Durairajan, Reza Rejaie, Walter Willinger
2020 B conf
PAM
Bahador Yeganeh, Ramakrishnan Durairajan, Reza Rejaie, Walter Willinger
2020 conf
NetAI@SIGCOMM
Yukhe Lavinia, Ramakrishnan Durairajan, Reza Rejaie, Walter Willinger
2020 conf
TMA
Paul Barford, Ramakrishnan Durairajan, Matthew Nance Hall, Sathiya Kumaran Mani
2020 J jnl
CoRR
Matthew Nance Hall, Ramakrishnan Durairajan, Vyas Sekar
2020 conf
SPIN@SIGCOMM
Matthew Nance Hall, Guyue Liu, Ramakrishnan Durairajan, Vyas Sekar
2020 J jnl
Comput. Commun. Rev.
Chris Misa, Dennis Guse, Oliver Hohlfeld, Ramakrishnan Durairajan, Anna Sperotto, Alberto Dainotti, Reza Rejaie
2020 conf
HotStorage
Baber Khalid, Nolan Rudolph, Ramakrishnan Durairajan, Sudarsun Kannan
2020 conf
TMA
Soheil Jamshidi, Zayd Hammoudeh, Ramakrishnan Durairajan, Daniel Lowd, Reza Rejaie, Walter Willinger
2019 conf
LocalRec@SIGSPATIAL
Yugali Gullapalli, Jeremy Koritzinsky, Meenakshi Syamkumar, Paul Barford, Ramakrishnan Durairajan, Joel Sommers
2019 conf
ANRW
Chris Misa, Sudarsun Kannan, Ramakrishnan Durairajan
2019 C conf
ICMLA
Anirudh Muthukumar, Ramakrishnan Durairajan
2019 A conf
Internet Measurement Conference
Bahador Yeganeh, Ramakrishnan Durairajan, Reza Rejaie, Walter Willinger
2019 J jnl
Future Gener. Comput. Syst.
Roshan Lal Neupane, Travis Neely, Prasad Calyam, Nishant Chettri, Mark Vassell, Ramakrishnan Durairajan
2019 conf
ANRW
Sathiya Kumaran Mani, Paul Barford, Ramakrishnan Durairajan, Joel Sommers
2018 J jnl
CoRR
Sathiya Kumaran Mani, Ramakrishnan Durairajan, Paul Barford, Joel Sommers
2018 conf
IOT
Sathiya Kumaran Mani, Ramakrishnan Durairajan, Paul Barford, Joel Sommers
2018 A* conf
KDD
Giovanni Comarela, Ramakrishnan Durairajan, Paul Barford, Dino P. Christenson, Mark Crovella
2018 conf
MECOMM@SIGCOMM
Meenakshi Syamkumar, Paul Barford, Ramakrishnan Durairajan
2018 Misc conf
ICDCN
Roshan Lal Neupane, Travis Neely, Nishant Chettri, Mark Vassell, Yuanxun Zhang, Prasad Calyam, Ramakrishnan Durairajan
2018 J jnl
CoRR
Ramakrishnan Durairajan, Paul Barford, Joel Sommers, Walter Willinger
2018 A conf
Internet Measurement Conference
Robert Beverly, Ramakrishnan Durairajan, David Plonka, Justin P. Rohrer
2018 J jnl
CoRR
Robert Beverly, Ramakrishnan Durairajan, David Plonka, Justin P. Rohrer
2018 conf
ANRW
Ramakrishnan Durairajan, Carol Barford, Paul Barford
2018 conf
ITC (1)
Ramakrishnan Durairajan, Sathiya Kumaran Mani, Paul Barford, Robert D. Nowak, Joel Sommers
2018 J jnl
CoRR
Ramakrishnan Durairajan, Sathiya Kumaran Mani, Paul Barford, Robert D. Nowak, Joel Sommers
2018 B conf
Networking
Meenakshi Syamkumar, Sathiya Kumaran Mani, Ramakrishnan Durairajan, Paul Barford, Joel Sommers
2017 J jnl
Comput. Commun. Rev.
Ramakrishnan Durairajan, Paul Barford
2017 A conf
Internet Measurement Conference
Joel Sommers, Ramakrishnan Durairajan, Paul Barford
2016 conf
GAIA@SIGCOMM
Ramakrishnan Durairajan, Paul Barford
2016 C conf
VizSEC
Meenakshi Syamkumar, Ramakrishnan Durairajan, Paul Barford
2016 A conf
Internet Measurement Conference
Sathiya Kumaran Mani, Ramakrishnan Durairajan, Paul Barford, Joel Sommers
2015 A* conf
SIGCOMM
Ramakrishnan Durairajan, Paul Barford, Joel Sommers, Walter Willinger
2015 Misc conf
HotNets
Ramakrishnan Durairajan, Sathiya Kumaran Mani, Joel Sommers, Paul Barford
2015 conf
SPECTS@SummerSim
Mukta Gupta, Ramakrishnan Durairajan, Meenakshi Syamkumar, Paul Barford, Joel Sommers
2014 A conf
CoNEXT
Ramakrishnan Durairajan, Joel Sommers, Paul Barford
2014 A conf
Internet Measurement Conference
Ramakrishnan Durairajan, Joel Sommers, Paul Barford
2014 conf
HotSDN
Ramakrishnan Durairajan, Joel Sommers, Paul Barford
2013 B conf
IWQoS
Wenfei Wu, Yizheng Chen, Ramakrishnan Durairajan, Dongchan Kim, Ashok Anand, Aditya Akella
2013 conf
HotPlanet@SIGCOMM
Ramakrishnan Durairajan, Subhadip Ghosh, Xin Tang, Paul Barford, Brian Eriksson
2013 A conf
CoNEXT
Brian Eriksson, Ramakrishnan Durairajan, Paul Barford
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