Xiaoke Zhu

82 papers A* 7A 3B 2C 2Journal 53Unranked 15
YearRankTypeTitle / Venue / Authors
2026 J jnl
Sci. Comput. Program.
Xiaoke Zhu, Yufeng Shi, Xiaopan Chen, Caihong Yuan, Fumin Qi, Xiao-Yuan Jing
2026 J jnl
Vis. Comput.
Xiaoke Zhu, Boyuan Li, Xiaopan Chen, Fumin Qi, Caihong Yuan, Xiao-Yuan Jing
2026 J jnl
Inf. Softw. Technol.
Xiaoke Zhu, Tao Zhang, Xiaopan Chen, Zhiqiang Li, Xiao-Yuan Jing
2025 J jnl
Pattern Recognit.
Xiaoke Zhu, Lingyun Dong, Xiaopan Chen, Xinyu Zhang, Fumin Qi, Xiao-Yuan Jing
2025 J jnl
Digit. Signal Process.
Caihong Yuan, Bo Jiang, Xiaopan Chen, Xiaoke Zhu, Wenjuan Liang
2025 J jnl
Expert Syst. Appl.
Qiwen Lu, Shengbo Chen, Xiaoke Zhu
2025 conf
PRCV (9)
Daojun Han, Bendong Qiao, Xiaoke Zhu, Juntao Zhang, Zhigang Han, Linkun Fan, Mengxin Jin
2025 J jnl
Int. J. Pattern Recognit. Artif. Intell.
Xiaopan Chen, Zhiwei Dong, Xiaoke Zhu, Fan Zhang, Caihong Yuan
2025 J jnl
Complex Intell. Syst.
Caihong Yuan, Zhijie Guan, Yuanchen Xu, Xiaopan Chen, Xiaoke Zhu, Wenjuan Liang
2025 J jnl
Proc. ACM Manag. Data
Wenchao Bai, Wenfei Fan, Shuhao Liu, Kehan Pang, Xiaoke Zhu, Jiahui Jin
2024 J jnl
Proc. VLDB Endow.
Yang Liu, Wenfei Fan, Shuhao Liu, Xiaoke Zhu, Jianxin Li
2024 J jnl
J. Imaging
Qiwen Lu, Shengbo Chen, Xiaoke Zhu
2024 B conf
IEEE Big Data
Xiaoke Zhu, Qi Zhang, Wei Zhou, Ling Liu
2024 J jnl
CoRR
Xiaoke Zhu, Min Xie, Ting Deng, Qi Zhang
2024 J jnl
Proc. VLDB Endow.
Xiaoke Zhu, Min Xie, Ting Deng, Qi Zhang
2024 J jnl
Neurocomputing
Wei Liu, Xiaoyuan Jing, Xiaodong Jia, Xiaoke Zhu, Yaru Hao
2024 C conf
ISPA
Caihong Yuan, Yuanchen Xu, Zhijie Guan, Xiaopan Chen, Xiaoke Zhu, Wenjuan Liang
2024 A conf
ICME
Xiaoke Zhu, Danyang Li, Xiaopan Chen, Fumin Qi, Fan Zhang, Xiao-Yuan Jing
2024 J jnl
CoRR
Wenjia Jiang, Xiaoke Zhu, Jiakang Gao, Di Liao
2023 conf
iThings/GreenCom/CPSCom/SmartData/Cybermatics
Chenshuang Su, Mingdong Zou, Yujie Zhou, Xiaoke Zhu, Wenjuan Liang, Caihong Yuan
2023 J jnl
BMC Bioinform.
Linjun Chen, Xiao-Yuan Jing, Yaru Hao, Wei Liu, Xiaoke Zhu, Wei Han
2023 J jnl
IEEE ACM Trans. Comput. Biol. Bioinform.
Fan Zhang, Yawei Zhang, Xiaoke Zhu, Xiaopan Chen, Fuhao Lu, Xinhong Zhang
2023 J jnl
ACM Trans. Multim. Comput. Commun. Appl.
Xiaoke Zhu, Changlong Li, Xiaopan Chen, Xinyu Zhang, Xiao-Yuan Jing
2023 J jnl
Multim. Tools Appl.
Xiaoke Zhu, Minghao Zheng, Xiaopan Chen, Xinyu Zhang, Caihong Yuan, Fan Zhang
2023 conf
ICONIP (6)
Jing Liu, Fei Wu, Hao Jin, Xiaoke Zhu, Xiao-Yuan Jing
2023 J jnl
Proc. VLDB Endow.
Xiaoke Zhu, Yang Liu, Shuhao Liu, Wenfei Fan
2023 conf
ICCSE (1)
Yujie Zhou, Caihong Yuan, Chenshuang Su, Mingdong Zou, Xiaoke Zhu, Wenjuan Liang
2022 J jnl
Neural Comput. Appl.
Xiaodong Jia, Xiao-Yuan Jing, Xiaoke Zhu, Ziyun Cai, Chang-Hui Hu
2022 A* conf
ICDE
Ting Deng, Wenfei Fan, Ping Lu, Xiaomeng Luo, Xiaoke Zhu, Wanhe An
2022 J jnl
Signal Process. Image Commun.
Xiaopan Chen, Changlong Li, Xiaoke Zhu, Liang Zheng, Ya Chen, Shanshan Zheng, Caihong Yuan
2022 J jnl
Neural Comput. Appl.
Xinyu Zhang, Weidong Li, Xiaoke Zhu, Xiao-Yuan Jing
2022 J jnl
Comput. Methods Programs Biomed.
Fan Zhang, Yingqi Zhang, Xiaoke Zhu, Xiaopan Chen, Haishun Du, Xinhong Zhang
2021 conf
CSAI
Xiaoke Zhu, Jitong Zhang, Xiaopan Chen, Danyang Li, Yufei Wang, Minghao Zheng
2021 J jnl
Multim. Tools Appl.
Yide Di, Xiaoke Zhu, Xin Jin, Qiwei Dou, Wei Zhou, Qing Duan
2021 B conf
CLOUD
Xiaoke Zhu, Qi Zhang, Taining Cheng, Ling Liu, Wei Zhou, Jing He
2021 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Xiao-Yuan Jing, Xinyu Zhang, Xiaoke Zhu, Fei Wu, Xinge You, Yang Gao, Shiguang Shan, Jing-Yu Yang
2021 conf
ICVIP
Xiaoke Zhu, Changlong Li, Xiaopan Chen, Minghao Zheng, Yunwei Li, Jihui Hu
2021 J jnl
IEEE Access
Fei Ma, Xiao-Yuan Jing, Li Cheng, Songsong Wu, Hao Zhang, Yong-Fang Yao, Xiaoke Zhu
2021 J jnl
IEEE Trans. Circuits Syst. Video Technol.
Xiaopan Chen, Xiaoke Zhu, Shanshan Zheng, Taihao Zheng, Fan Zhang
2021 J jnl
IEEE Trans. Pattern Anal. Mach. Intell.
Xiaodong Jia, Xiao-Yuan Jing, Xiaoke Zhu, Songcan Chen, Bo Du, Ziyun Cai, Zhenyu He, Dong Yue
2021 J jnl
IEEE Trans. Serv. Comput.
Xiaoke Zhu, Xiao-Yuan Jing, Di Wu, Zhenyu He, Jicheng Cao, Dong Yue, Lina Wang
2020 conf
ICACI
Shujuan Wang, Xiaoke Zhu
2020 conf
ICIAI
Jiawei Wang, Guorong Cui, Xiaoke Zhu, Huijian Liu, Junsong Liu, Xuebin Jia
2020 J jnl
IEEE Access
Xiaoke Zhu, Pengfei Ye, Xiao-Yuan Jing, Xinyu Zhang, Xiang Cui, Xiaopan Chen, Fan Zhang
2020 C conf
PDCAT
Fumin Qi, Xiao-Yuan Jing, Xiaoke Zhu, Xiaodong Jia, Li Cheng, Yichuan Dong, Ziseng Fang, Fei Ma, Shengzhong Feng
2020 J jnl
Multim. Tools Appl.
Li Cheng, Xiao-Yuan Jing, Xiaoke Zhu, Chang-Hui Hu, Guangwei Gao, Songsong Wu
2020 J jnl
Neural Comput. Appl.
Li Cheng, Xiao-Yuan Jing, Xiaoke Zhu, Fei Ma, Chang-Hui Hu, Ziyun Cai, Fumin Qi
2020 J jnl
Neural Comput. Appl.
Xinyu Zhang, Xiao-Yuan Jing, Xiaoke Zhu, Fei Ma
2019 J jnl
CoRR
Xiaoke Zhu, Qi Zhang, Ling Liu, Taining Cheng, Shaowen Yao, Wei Zhou, Jing He
2019 J jnl
Pattern Recognit.
Xiaoke Zhu, Xiao-Yuan Jing, Fan Zhang, Xinyu Zhang, Xinge You, Xiang Cui
2019 J jnl
Autom. Softw. Eng.
Zhiqiang Li, Xiao-Yuan Jing, Xiaoke Zhu, Hongyu Zhang, Baowen Xu, Shi Ying
2019 J jnl
IEEE Access
Fei Ma, Xiao-Yuan Jing, Yong-Fang Yao, Xiaoke Zhu, Zhiping Peng
2019 J jnl
Multim. Tools Appl.
Fei Ma, Xiaoke Zhu, Xinyu Zhang, Liang Yang, Mei Zuo, Xiao-Yuan Jing
2019 J jnl
Neurocomputing
Fei Ma, Xiaoke Zhu, Cailing Wang, Huajun Liu, Xiao-Yuan Jing
2019 J jnl
Neurocomputing
Fei Ma, Xiaoke Zhu, Qinglong Liu, Chengfang Song, Xiao-Yuan Jing, Dengpan Ye
2019 J jnl
CoRR
Xiaoke Zhu, Tai-ning Chen, Jing He, Wei Zhou
2019 J jnl
IEEE Trans. Software Eng.
Zhiqiang Li, Xiao-Yuan Jing, Xiaoke Zhu, Hongyu Zhang, Baowen Xu, Shi Ying
2019 J jnl
Neural Comput. Appl.
Xiaoke Zhu, Xiao-Yuan Jing, Fei Ma, Li Cheng, Yilin Ren
2018 conf
ICONIP (1)
Li Cheng, Xiao-Yuan Jing, Xiaoke Zhu, Fumin Qi, Fei Ma, Xiaodong Jia, Liang Yang, Chunhe Wang
2018 conf
ICSE (Companion Volume)
Di Wu, Xiao-Yuan Jing, Haowen Chen, Xiaoke Zhu, Hongyu Zhang, Mei Zuo, Lu Zi, Chen Zhu
2018 J jnl
Autom. Softw. Eng.
Zhiqiang Li, Xiao-Yuan Jing, Fei Wu, Xiaoke Zhu, Baowen Xu, Shi Ying
2018 J jnl
Softw. Test. Verification Reliab.
Zhiqiang Li, Xiao-Yuan Jing, Xiaoke Zhu
2018 J jnl
IEEE Trans. Inf. Forensics Secur.
Xiaoke Zhu, Xiao-Yuan Jing, Xinge You, Wangmeng Zuo, Shiguang Shan, Wei-Shi Zheng
2018 J jnl
IET Softw.
Zhiqiang Li, Xiao-Yuan Jing, Xiaoke Zhu
2018 J jnl
IEEE Trans. Circuits Syst. Video Technol.
Xiaoke Zhu, Xiao-Yuan Jing, Liang Yang, Xinge You, Dan Chen, Guangwei Gao, Yunhong Wang
2018 J jnl
IEEE Trans. Image Process.
Xiaoke Zhu, Xiao-Yuan Jing, Xinge You, Xinyu Zhang, Taiping Zhang
2017 conf
ICONIP (3)
Sen Li, Xiao-Yuan Jing, Xiaoke Zhu, Xinyu Zhang, Fei Ma
2017 A* conf
IJCAI
Fei Wu, Xiao-Yuan Jing, Wangmeng Zuo, Ruiping Wang, Xiaoke Zhu
2017 conf
SPAC
Fei Ma, Xiaoke Zhu, Xiao-Yuan Jing
2017 A conf
ICSME
Zhiqiang Li, Xiao-Yuan Jing, Xiaoke Zhu, Hongyu Zhang
2017 A* conf
AAAI
Xiaoke Zhu, Xiao-Yuan Jing, Fei Wu, Yunhong Wang, Wangmeng Zuo, Wei-Shi Zheng
2017 A* conf
AAAI
Xiaoke Zhu, Xiao-Yuan Jing, Fei Wu, Di Wu, Li Cheng, Sen Li, Ruimin Hu
2017 conf
SPAC
Fei Ma, Qinglong Liu, Xiaoke Zhu, Xiao-Yuan Jing
2017 J jnl
Inf. Softw. Technol.
Fumin Qi, Xiao-Yuan Jing, Xiaoke Zhu, Xiaoyuan Xie, Baowen Xu, Shi Ying
2017 J jnl
IEEE Trans. Image Process.
Xiao-Yuan Jing, Xiaoke Zhu, Fei Wu, Ruimin Hu, Xinge You, Yunhong Wang, Hui Feng, Jing-Yu Yang
2016 A conf
ICME
Xiaoke Zhu, Xiao-Yuan Jing, Fei Wu, Wei-Shi Zheng, Ruimin Hu, Chunxia Xiao, Chao Liang
2016 J jnl
Pattern Recognit.
Xiao-Yuan Jing, Fei Wu, Xiaoke Zhu, Xiwei Dong, Fei Ma, Zhiqiang Li
2016 A* conf
ASE
Fumin Qi, Xiao-Yuan Jing, Xiaoke Zhu, Fei Wu, Li Cheng
2016 A* conf
IJCAI
Xiaoke Zhu, Xiao-Yuan Jing, Fei Wu, Hui Feng
2015 A* conf
CVPR
Xiao-Yuan Jing, Xiaoke Zhu, Fei Wu, Xinge You, Qinglong Liu, Dong Yue, Ruimin Hu, Baowen Xu
2004 conf
ISCAS (4)
Keiji Kida, Xiaoke Zhu, Changwen Zhuang, Yasuhiro Takashima, Shigetoshi Nakatake
2004 conf
ASP-DAC
Takashi Nojima, Xiaoke Zhu, Yasuhiro Takashima, Shigetoshi Nakatake, Yoji Kajitani
tests/unit/test_apk_strings_ioc.py
← Index tests/unit/test_apk_strings_ioc.py python
"""Tests for APK string extraction and IOC extraction integration.

Covers:
- APKCodeAnalyzer._extract_strings() — string extraction from DEX objects
- APKCodeAnalyzer._string_entropy() — Shannon entropy computation
- DecompileAPK strings export table schema
- IOCExtractorFromResults with APK-format analysis_results
- Worker IOC wiring for APK
"""
import os
import tempfile
from dataclasses import asdict
from unittest.mock import MagicMock, patch

import pytest

from redb.extractors.ioc_extractor.ioc_extractor import IOCExtractorFromResults
from redb.extractors.ioc_extractor.standalone_ioc_extractor import (
    IOCType,
    SourceType,
)

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


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _make_analyzer(min_instructions=1):
    """Create an APKCodeAnalyzer with mocked dependencies."""
    with patch(
        "redb.extractors.decompiler.apk.analyzer.JADXDecompiler"
    ), patch(
        "redb.extractors.decompiler.apk.analyzer.ApktoolDisassembler"
    ):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer
        analyzer = APKCodeAnalyzer("/fake/path.apk")
        analyzer.min_instructions = min_instructions
        return analyzer


def _make_mock_dex(strings):
    """Create a mock DEX object that returns the given strings."""
    dex = MagicMock()
    dex.get_strings.return_value = strings
    return dex


def _create_fake_apk_file():
    """Create a temporary file to act as a fake APK."""
    fd, path = tempfile.mkstemp(suffix=".apk")
    os.write(fd, b"PK\x03\x04fake apk content for hashing")
    os.close(fd)
    return path


# ---------------------------------------------------------------------------
# Tests: _string_entropy
# ---------------------------------------------------------------------------

class TestStringEntropy:
    """Tests for Shannon entropy computation."""

    def test_empty_string(self):
        analyzer = _make_analyzer()
        assert analyzer._string_entropy("") == 0.0

    def test_single_char(self):
        analyzer = _make_analyzer()
        assert analyzer._string_entropy("a") == 0.0

    def test_repeated_char(self):
        analyzer = _make_analyzer()
        assert analyzer._string_entropy("aaaa") == 0.0

    def test_two_equal_chars(self):
        analyzer = _make_analyzer()
        # "ab" -> entropy = 1.0
        assert abs(analyzer._string_entropy("ab") - 1.0) < 0.01

    def test_higher_entropy(self):
        analyzer = _make_analyzer()
        # More variety = higher entropy
        low = analyzer._string_entropy("aabb")
        high = analyzer._string_entropy("abcd")
        assert high > low

    def test_url_string_entropy(self):
        analyzer = _make_analyzer()
        entropy = analyzer._string_entropy("https://evil.com/payload")
        assert entropy > 2.0  # URLs have moderate entropy


# ---------------------------------------------------------------------------
# Tests: _extract_strings
# ---------------------------------------------------------------------------

class TestExtractStrings:
    """Tests for DEX string extraction."""

    def test_basic_extraction(self):
        analyzer = _make_analyzer()
        dex = _make_mock_dex(["hello", "world"])
        result = analyzer._extract_strings([dex])

        assert len(result) == 2
        assert result[0]["string"] == "hello"
        assert result[1]["string"] == "world"

    def test_string_fields_present(self):
        analyzer = _make_analyzer()
        dex = _make_mock_dex(["test_string"])
        result = analyzer._extract_strings([dex])

        entry = result[0]
        assert "string" in entry
        assert "string_encoding" in entry
        assert "string_offset" in entry
        assert "string_length" in entry
        assert "string_entropy" in entry
        assert entry["string_encoding"] == "UTF8"
        assert entry["string_length"] == len("test_string")

    def test_deduplication_across_dex(self):
        """Same string in multiple DEX files is only extracted once."""
        analyzer = _make_analyzer()
        dex1 = _make_mock_dex(["shared", "unique1"])
        dex2 = _make_mock_dex(["shared", "unique2"])
        result = analyzer._extract_strings([dex1, dex2])

        string_values = [s["string"] for s in result]
        assert string_values.count("shared") == 1
        assert "unique1" in string_values
        assert "unique2" in string_values
        assert len(result) == 3

    def test_empty_strings_filtered(self):
        analyzer = _make_analyzer()
        dex = _make_mock_dex(["", "valid", None, "also_valid"])
        result = analyzer._extract_strings([dex])

        string_values = [s["string"] for s in result]
        assert "" not in string_values
        assert None not in string_values
        assert "valid" in string_values
        assert "also_valid" in string_values

    def test_no_dex_files(self):
        analyzer = _make_analyzer()
        result = analyzer._extract_strings([])
        assert result == []

    def test_dex_get_strings_returns_none(self):
        analyzer = _make_analyzer()
        dex = MagicMock()
        dex.get_strings.return_value = None
        result = analyzer._extract_strings([dex])
        assert result == []

    def test_dex_get_strings_exception(self):
        analyzer = _make_analyzer()
        dex = MagicMock()
        dex.get_strings.side_effect = RuntimeError("corrupt DEX")
        result = analyzer._extract_strings([dex])
        assert result == []

    def test_entropy_computed(self):
        analyzer = _make_analyzer()
        dex = _make_mock_dex(["abcdefgh"])
        result = analyzer._extract_strings([dex])
        assert result[0]["string_entropy"] > 0.0

    def test_incremental_offsets(self):
        """String offsets are incremented sequentially."""
        analyzer = _make_analyzer()
        dex = _make_mock_dex(["a", "b", "c"])
        result = analyzer._extract_strings([dex])

        offsets = [s["string_offset"] for s in result]
        assert offsets == [0, 1, 2]

    def test_url_strings_preserved(self):
        """URL strings are extracted without modification."""
        analyzer = _make_analyzer()
        dex = _make_mock_dex([
            "https://evil.com/payload",
            "http://c2.malware.org/gate",
        ])
        result = analyzer._extract_strings([dex])
        strings = [s["string"] for s in result]
        assert "https://evil.com/payload" in strings
        assert "http://c2.malware.org/gate" in strings


# ---------------------------------------------------------------------------
# Tests: strings in extract() integration
# ---------------------------------------------------------------------------

class TestExtractStringsIntegration:
    """Tests that strings are included in extract() results."""

    @patch("redb.extractors.decompiler.apk.analyzer.APKCodeAnalyzer._run_androguard")
    @patch("redb.extractors.decompiler.apk.analyzer.ApktoolDisassembler")
    @patch("redb.extractors.decompiler.apk.analyzer.JADXDecompiler")
    def test_extract_includes_strings(self, mock_jadx, mock_apktool, mock_androguard):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer

        mock_dex = _make_mock_dex(["hello", "world"])
        mock_analysis = MagicMock()
        mock_analysis.get_methods.return_value = []
        mock_androguard.return_value = (MagicMock(), [mock_dex], mock_analysis)

        analyzer = APKCodeAnalyzer("/fake/path.apk", log=MagicMock())
        results = analyzer.extract()

        assert "strings" in results
        assert len(results["strings"]) == 2
        assert results["strings"][0]["string"] == "hello"
        analyzer.cleanup()

    @patch("redb.extractors.decompiler.apk.analyzer.APKCodeAnalyzer._run_androguard")
    @patch("redb.extractors.decompiler.apk.analyzer.ApktoolDisassembler")
    @patch("redb.extractors.decompiler.apk.analyzer.JADXDecompiler")
    def test_extract_strings_error_is_non_fatal(self, mock_jadx, mock_apktool, mock_androguard):
        from redb.extractors.decompiler.apk.analyzer import APKCodeAnalyzer

        # Return a bad dex list that will cause _extract_strings to fail
        mock_analysis = MagicMock()
        mock_analysis.get_methods.return_value = []
        # dexs is not iterable — triggers the except block
        mock_androguard.return_value = (MagicMock(), 42, mock_analysis)

        analyzer = APKCodeAnalyzer("/fake/path.apk", log=MagicMock())
        results = analyzer.extract()

        # Should not crash, strings should be empty, error recorded
        assert results["strings"] == []
        assert any(
            e.get("error_location") == "strings"
            for e in results["analysis_errors"]
        )
        analyzer.cleanup()


# ---------------------------------------------------------------------------
# Tests: DecompileAPK strings export table
# ---------------------------------------------------------------------------

class TestDecompileAPKStringsExport:
    """Tests for strings table in DecompileAPK.prepare_export_data()."""

    def _make_extractor_with_strings(self, strings_data):
        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_refs": [],
            "smali_content": [],
            "smali_refs": [],
            "similarity_metrics": [],
            "strings": strings_data,
            "analysis_errors": [],
        }
        return ext, apk_path

    def test_strings_table_present(self):
        ext, path = self._make_extractor_with_strings([
            {"string": "hello", "string_encoding": "UTF8",
             "string_offset": 0, "string_length": 5, "string_entropy": 2.32}
        ])
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            assert "strings_raw" in export
        finally:
            os.unlink(path)

    def test_strings_table_schema(self):
        ext, path = self._make_extractor_with_strings([
            {"string": "hello", "string_encoding": "UTF8",
             "string_offset": 0, "string_length": 5, "string_entropy": 2.32}
        ])
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            sr = export["strings_raw"]
            assert sr["table"] == "code_binja_strings_raw"
            assert len(sr["column_names"]) == 8
            assert len(sr["column_type_names"]) == 8
            assert "sha256" in sr["column_names"]
            assert "string" in sr["column_names"]
            assert "string_raw" in sr["column_names"]
            assert "string_encoding" in sr["column_names"]
            assert "string_offset" in sr["column_names"]
            assert "string_length" in sr["column_names"]
            assert "string_raw_length" in sr["column_names"]
            assert "string_entropy" in sr["column_names"]
            assert len(sr["data"]) == 1
            assert len(sr["data"][0]) == 8
        finally:
            os.unlink(path)

    def test_strings_sha256_propagated(self):
        ext, path = self._make_extractor_with_strings([
            {"string": "test", "string_encoding": "UTF8",
             "string_offset": 0, "string_length": 4, "string_entropy": 2.0}
        ])
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            row = export["strings_raw"]["data"][0]
            assert row[0] == "a" * 64  # sha256 is first column
        finally:
            os.unlink(path)

    def test_empty_strings_not_exported(self):
        ext, path = self._make_extractor_with_strings([])
        try:
            export = ext.prepare_export_data("ClickHouseExporter")
            assert "strings_raw" not in export
        finally:
            os.unlink(path)


# ---------------------------------------------------------------------------
# Tests: IOCExtractorFromResults with APK format
# ---------------------------------------------------------------------------

class TestIOCExtractorAPKFormat:
    """Tests that IOCExtractorFromResults handles APK analysis_results."""

    def setup_method(self):
        self.log = MagicMock()

    def test_extract_iocs_from_apk_strings(self):
        """IOCs extracted from APK strings (same format as Binja)."""
        results = {
            "strings": [
                {"string": "https://evil.com/payload", "string_offset": 0},
                {"string": "Contact [email protected]", "string_offset": 1},
            ],
            "decompiled": [],
            "decompiled_content": [],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()

        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert len(url_iocs) >= 1
        assert any("evil.com" in i.ioc_value for i in url_iocs)

    def test_extract_iocs_from_apk_decompiled_content(self):
        """IOCs extracted from APK's decompiled_content key."""
        results = {
            "strings": [],
            "decompiled": [],  # Binja format — empty
            "decompiled_content": [  # APK format
                {
                    "decompiled_method": "connect('https://c2.malware.org/gate');",
                    "decompiled_method_hash": "hash123",
                    "method_type": "USER",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()

        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert len(url_iocs) >= 1

    def test_apk_library_methods_skipped(self):
        """LIBRARY methods in decompiled_content are skipped."""
        results = {
            "strings": [],
            "decompiled": [],
            "decompiled_content": [
                {
                    "decompiled_method": "https://should-skip.com",
                    "decompiled_method_hash": "lib_hash",
                    "method_type": "LIBRARY",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        assert len(iocs) == 0

    def test_combined_binja_and_apk_sources(self):
        """Both Binja and APK format decompiled entries are processed."""
        results = {
            "strings": [],
            "decompiled": [
                {
                    "decompiled_function": "call('https://binja.example.com');",
                    "decompiled_function_hash": "binja_hash",
                    "function_type": "USER",
                }
            ],
            "decompiled_content": [
                {
                    "decompiled_method": "send('https://apk.example.com');",
                    "decompiled_method_hash": "apk_hash",
                    "method_type": "USER",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()

        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        urls = [i.ioc_value for i in url_iocs]
        assert any("binja.example.com" in u for u in urls)
        assert any("apk.example.com" in u for u in urls)

    def test_apk_results_only(self):
        """Works when only APK keys are present (no 'decompiled' key)."""
        results = {
            "strings": [
                {"string": "https://evil.com", "string_offset": 0},
            ],
            "decompiled_content": [
                {
                    "decompiled_method": "x = 1",
                    "decompiled_method_hash": "h",
                    "method_type": "USER",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        # Should not crash, and should find URL from strings
        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert len(url_iocs) >= 1

    def test_source_type_is_string_for_strings(self):
        results = {
            "strings": [
                {"string": "https://evil.com/test", "string_offset": 42},
            ],
            "decompiled_content": [],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert url_iocs[0].source_type == SourceType.STRING
        assert url_iocs[0].source_identifier == "42"

    def test_source_type_is_decompiled_for_methods(self):
        results = {
            "strings": [],
            "decompiled_content": [
                {
                    "decompiled_method": "https://evil.com/method",
                    "decompiled_method_hash": "myhash",
                    "method_type": "USER",
                }
            ],
        }
        extractor = IOCExtractorFromResults(results, sha256="a" * 64, log=self.log)
        iocs = extractor.extract()
        url_iocs = [i for i in iocs if i.ioc_type == IOCType.URL]
        assert url_iocs[0].source_type == SourceType.DECOMPILED_FUNCTION
        assert url_iocs[0].source_identifier == "myhash"


# ---------------------------------------------------------------------------
# Tests: Worker IOC wiring for APK
# ---------------------------------------------------------------------------

class TestWorkerAPKIOCWiring:
    """Tests that workers.py wires IOC extraction for APK."""

    def test_worker_source_contains_ioc_for_apk(self):
        """Verify workers.py source has IOC extraction in APK branch."""
        import inspect
        from redb import workers
        source = inspect.getsource(workers)
        # APK branch should import IOCExtractorFromResults
        assert "IOCExtractorFromResults" in source
        # Should be in the APK section (near DecompileAPK)
        apk_section = source[source.index("DecompileAPK("):]
        ioc_pos = apk_section.find("IOCExtractorFromResults")
        assert ioc_pos != -1, "IOCExtractorFromResults not found after DecompileAPK"