Xiangrong Tong

107 papers A* 1A 1B 2C 1Misc 1Journal 75Unranked 26
YearRankTypeTitle / Venue / Authors
2026 J jnl
Inf. Fusion
Yuhan Xiu, Xiangrong Tong
2025 J jnl
Entropy
Qianxiao Yue, Xiangrong Tong
2025 J jnl
IEEE Trans. Comput. Soc. Syst.
Yingxin Li, Yingjie Wang, Peng Wang, Weilong Wang, Xiangrong Tong
2025 J jnl
Swarm Evol. Comput.
Peng Wang, Yue Chen, Changsheng Zhang, Xiangrong Tong, Yingjie Wang
2025 J jnl
Expert Syst. Appl.
Hongwei Jiang, Wenming Ma, Jian Dai, Jianguo Ding, Xiangrong Tong, Yingjie Wang, Xiaolin Du, Dalong Jiang, YiXuan Luo, Jinghui Zhang
2025 J jnl
IEEE Trans. Mob. Comput.
Yahong Li, Yingjie Wang, Gang Li, Xiangrong Tong, Zhipeng Cai
2025 J jnl
Knowl. Based Syst.
Pengkun Li, Xiangrong Tong, Yingjie Wang, Qiang Zhang
2025 J jnl
Appl. Intell.
Yinggang Li, Xiangrong Tong, Zhongming Lv
2025 J jnl
Expert Syst. Appl.
YiXuan Luo, Wenming Ma, Shudong Liu, Xiangrong Tong, Xiaolin Du, Zhaowei Liu, Mingqi Li, Hongwei Jiang
2025 J jnl
IEEE Trans. Cloud Comput.
Yingjie Wang, Lingkang Meng, Peiyong Duan, Xiangrong Tong, Zice Sun, Zhaowei Liu, Zhipeng Cai
2024 J jnl
IEEE Internet Things J.
Chenghao Zhang, Yingjie Wang, Weilong Wang, Haijing Zhang, Zhaowei Liu, Xiangrong Tong, Zhipeng Cai
2024 J jnl
Digit. Commun. Networks
Xiaodong Zhuang, Xiangrong Tong
2024 J jnl
Appl. Soft Comput.
Kaiwen Zhao, Xiangrong Tong, Peng Wang, Yingjie Wang, Yue Chen
2024 A conf
ICWS
Kaige Jiang, Yang Gao, Peng Wang, Zhaolong Gao, Xiangrong Tong, Yingjie Wang, Zhipeng Cai, Yingxin Li, Shilong Jin
2024 B conf
CEC
Haiwei Wang, Yongchao Chen, Chenchen Zhang, Yaqing Hou, Xiangrong Tong, Qiang Zhang
2024 J jnl
IEEE Trans. Mob. Comput.
Zhongwei Zhan, Yingjie Wang, Peiyong Duan, Akshita Maradapu Vera Venkata Sai, Zhaowei Liu, Chaocan Xiang, Xiangrong Tong, Weilong Wang, Zhipeng Cai
2024 J jnl
IEEE Trans. Mob. Comput.
Weijian Su, Pengfei Wang, Zhiyi Wang, Muhammad Ameen, Tiwei Tao, Xiangrong Tong, Qiang Zhang
2024 J jnl
IEEE Trans. Comput. Soc. Syst.
Ying Zhao, Yingjie Wang, Peiyong Duan, Haijing Zhang, Zhaowei Liu, Xiangrong Tong, Zhipeng Cai
2024 J jnl
Knowl. Based Syst.
Zihan Zhao, Xiangrong Tong, Yingjie Wang, Qiang Zhang
2024 conf
CAI
Jie Kang, Yaqing Hou, Yifeng Zeng, Yongchao Chen, Xiangrong Tong, Xin Xu, Qiang Zhang
2024 B conf
CEC
Wenqiang Ma, Yaqing Hou, Hua Yu, Xiangrong Tong, Zexuan Zhu, Qiang Zhang
2023 conf
DMBD (1)
Haonan Zhai, Xiangrong Tong
2023 J jnl
IEEE Trans. Mob. Comput.
Weilong Wang, Yingjie Wang, Peiyong Duan, Tianen Liu, Xiangrong Tong, Zhipeng Cai
2023 J jnl
IEEE Access
Kaiwen Zhao, Peng Wang, Xiangrong Tong
2023 J jnl
IEEE Trans. Ind. Informatics
Zhuoran Lu, Yingjie Wang, Xiangrong Tong, Chunxiao Mu, Yu Chen, Yingshu Li
2023 J jnl
CoRR
Zhongwei Zhan, Yingjie Wang, Peiyong Duan, Akshita Maradapu Vera Venkata Sai, Zhaowei Liu, Chaocan Xiang, Xiangrong Tong, Weilong Wang, Zhipeng Cai
2023 conf
KSEM (3)
Yinggang Li, Xiangrong Tong
2023 J jnl
Comput. Networks
Xinhao Li, Yingjie Wang, Bingyi Xie, Lingkang Meng, Zhaowei Liu, Xiangrong Tong, Ao Zhou, Zhipeng Cai
2023 J jnl
J. Cloud Comput.
Lingkang Meng, Yingjie Wang, Haipeng Wang, Xiangrong Tong, Zice Sun, Zhipeng Cai
2023 J jnl
IEEE Trans. Serv. Comput.
Qi Zhang, Yingjie Wang, Guisheng Yin, Xiangrong Tong, Akshita Maradapu Vera Venkata Sai, Zhipeng Cai
2022 conf
ICSAI
Ruili Xiao, Xiangrong Tong, Yinggang Li
2022 J jnl
CAAI Trans. Intell. Technol.
Jiaying Che, Xiangrong Tong, Lei Yu
2022 J jnl
Int. J. Bio Inspired Comput.
Shuo-shuo Zhang, Xiangrong Tong, Shuigen Wang
2022 J jnl
IEEE Internet Things J.
Chuanxiu Chi, Yingjie Wang, Xiangrong Tong, Madhuri Siddula, Zhipeng Cai
2022 J jnl
Digit. Commun. Networks
Qi Zhang, Yingjie Wang, Zhipeng Cai, Xiangrong Tong
2022 J jnl
Comput. Networks
Weilong Wang, Yingjie Wang, Yan Huang, Chunxiao Mu, Zice Sun, Xiangrong Tong, Zhipeng Cai
2022 conf
CCIS
Yinggang Li, Xiangrong Tong
2022 J jnl
Appl. Soft Comput.
Mingze Wang, Yingjie Wang, Akshita Maradapu Vera Venkata Sai, Zhaowei Liu, Yang Gao, Xiangrong Tong, Zhipeng Cai
2022 J jnl
IEEE Trans. Comput. Soc. Syst.
Fuxing Li, Yingjie Wang, Yang Gao, Xiangrong Tong, Nan Jiang, Zhipeng Cai
2022 J jnl
IEEE Access
Yu Li, Xiangrong Tong
2021 J jnl
IEEE Access
Chaoqing Ma, Qiang Zheng, Juan Wang, Qing Cai, Honglun Li, Ruoyu Du, Xiangrong Tong
2021 J jnl
Int. J. Intell. Syst.
Zice Sun, Yingjie Wang, Zhipeng Cai, Tianen Liu, Xiangrong Tong, Nan Jiang
2021 conf
ICSAI
Jiaying Che, Xiangrong Tong
2021 conf
PRCV (3)
Yiyu Zhang, Qiang Zheng, Kun Zhao, Honglun Li, Chaoqing Ma, Shuanhu Wu, Xiangrong Tong
2021 conf
PRCV (3)
Hu Yu, Qiang Zheng, Kun Zhao, Honglun Li, Chaoqing Ma, Shuanhu Wu, Xiangrong Tong
2021 conf
ICIG (2)
Bin Liu, Qiang Zheng, Kun Zhao, Honglun Li, Chaoqing Ma, Shuanhu Wu, Xiangrong Tong
2021 J jnl
IEEE Access
Guangyuan Li, Jun Lv, Xiangrong Tong, Chengyan Wang, Guang Yang
2021 J jnl
CoRR
Guangyuan Li, Jun Lv, Xiangrong Tong, Chengyan Wang, Guang Yang
2021 J jnl
Wirel. Commun. Mob. Comput.
Xianglong Kong, Xiangrong Tong, Yingjie Wang
2021 conf
ICIG (2)
Bo Li, Qiang Zheng, Kun Zhao, Honglun Li, Chaoqing Ma, Shuanhu Wu, Xiangrong Tong
2021 J jnl
Wirel. Commun. Mob. Comput.
Chuanxiu Chi, Yingjie Wang, Yingshu Li, Xiangrong Tong
2021 J jnl
ACM Trans. Intell. Syst. Technol.
Tianlun Dai, Bohan Li, Ziqiang Yu, Xiangrong Tong, Meng Chen, Gang Chen
2021 conf
ICACI
Zice Sun, Yingjie Wang, Xiangrong Tong, Qingxian Pan, Wenyi Liu, Jiqiu Zhang
2021 J jnl
CoRR
Jun Lv, Guangyuan Li, Xiangrong Tong, Weibo Chen, Jiahao Huang, Chengyan Wang, Guang Yang
2021 J jnl
Comput. Biol. Medicine
Jun Lv, Guangyuan Li, Xiangrong Tong, Weibo Chen, Jiahao Huang, Chengyan Wang, Guang Yang
2021 J jnl
IEEE Trans. Reliab.
Xiangfu Zhao, Xiangrong Tong, Dantong Ouyang, Liming Zhang, Yanzhi Hou
2021 J jnl
IEEE Access
Yan Wang, Xiangrong Tong
2020 J jnl
IEEE Access
Peng Wang, Xiangrong Tong
2020 J jnl
Secur. Commun. Networks
Tianen Liu, Yingjie Wang, Zhipeng Cai, Xiangrong Tong, Qingxian Pan, Jindong Zhao
2020 conf
CISP-BMEI
Xiaodong Zhuang, Xiangrong Tong
2020 J jnl
Comput. Networks
Yingjie Wang, Yang Gao, Yingshu Li, Xiangrong Tong
2020 conf
CISP-BMEI
Sen Huang, Xiangfu Zhao, Xiangrong Tong
2020 conf
DASFAA (2)
Yanwei Yu, Peng Lv, Xiangrong Tong, Junyu Dong
2020 conf
CISP-BMEI
Ru Kong, Xiangrong Tong
2020 J jnl
IEEE Access
Ru Kong, Xiangrong Tong
2020 J jnl
Knowl. Based Syst.
Kai Chen, Yanwei Yu, Peng Song, Xianfeng Tang, Lei Cao, Xiangrong Tong
2020 conf
CISP-BMEI
Jiaying Che, Xiangrong Tong, Ru Kong
2020 J jnl
Knowl. Based Syst.
Peng Lv, Yanwei Yu, Yangyang Fan, Xianfeng Tang, Xiangrong Tong
2020 J jnl
Complex.
Xiangfu Zhao, Gianfranco Lamperti, Dantong Ouyang, Xiangrong Tong
2020 conf
CISP-BMEI
Xianglong Kong, Xiangrong Tong
2020 J jnl
IEEE Internet Things J.
Tianen Liu, Yingjie Wang, Yingshu Li, Xiangrong Tong, Lianyong Qi, Nan Jiang
2020 conf
CISP-BMEI
Yan Wang, Xiangrong Tong
2020 J jnl
Knowl. Based Syst.
Fang Liu, Yanwei Yu, Peng Song, Yangyang Fan, Xiangrong Tong
2020 J jnl
Inf. Sci.
Weiqing Yan, Guanghui Yue, Jindong Xu, Yanwei Yu, Kai Wang, Chang Tang, Xiangrong Tong
2020 J jnl
IEEE Trans. Comput. Soc. Syst.
Yingjie Wang, Zhipeng Cai, Zhi-Hui Zhan, Bingxu Zhao, Xiangrong Tong, Lianyong Qi
2020 J jnl
计算机科学
Wenjing Yang, Nan Zhang, Xiangrong Tong, Zhenbin Du
2019 J jnl
IEEE Trans. Comput. Soc. Syst.
Yingjie Wang, Zhipeng Cai, Zhi-Hui Zhan, Yue-Jiao Gong, Xiangrong Tong
2019 A* conf
AAAI
Andrew Cohen, Xingye Qiao, Lei Yu, Elliot Way, Xiangrong Tong
2019 J jnl
CoRR
Andrew Cohen, Xingye Qiao, Lei Yu, Elliot Way, Xiangrong Tong
2019 J jnl
CoRR
Andrew Cohen, Lei Yu, Xingye Qiao, Xiangrong Tong
2019 J jnl
IEEE Access
Fei Wang, Zhijun Li, Xiangrong Tong, Lei Chen
2019 J jnl
IEEE Access
Fei Wang, Zhijun Li, Xiangrong Tong
2019 J jnl
EURASIP J. Wirel. Commun. Netw.
Faxin Qi, Xiangrong Tong, Lei Yu, Yingjie Wang
2019 J jnl
Sensors
Bingxu Zhao, Yingjie Wang, Yingshu Li, Yang Gao, Xiangrong Tong
2019 J jnl
计算机科学
Tianyou Yu, Nan Zhang, Xiaodong Yue, Xiangrong Tong, Heqing Kong
2018 J jnl
Int. J. Sens. Networks
Yingjie Wang, Xiangrong Tong, Kai Wang, Baode Fan, Zaobo He, Guisheng Yin
2018 J jnl
Sensors
Ying Hu, Yingjie Wang, Yingshu Li, Xiangrong Tong
2018 conf
ELM
Weiqing Yan, Shuigen Wang, Guanghui Yue, Jindong Xu, Xiangrong Tong, Laihua Wang
2018 J jnl
IET Networks
Kai Wang, Yude Zhao, Shugang Liu, Xiangrong Tong
2018 J jnl
IEEE Access
Zhongyang Chi, Yingjie Wang, Yan Huang, Xiangrong Tong
2018 J jnl
IEEE Access
Yingjie Wang, Yingshu Li, Zhongyang Chi, Xiangrong Tong
2018 J jnl
Comput. Networks
Yingjie Wang, Zhipeng Cai, Xiangrong Tong, Yang Gao, Guisheng Yin
2017 conf
IIKI
Yingjie Wang, Zhipeng Cai, Zhongyang Chi, Xiangrong Tong, Lijie Li
2017 J jnl
KSII Trans. Internet Inf. Syst.
Kai Wang, Wei Bao, Yingjie Wang, Xiangrong Tong
2017 J jnl
计算机科学
Yanyan Guo, Nan Zhang, Xiangrong Tong
2017 J jnl
计算机科学
Nan Zhang, Xin Xu, Xiangrong Tong, Xueyi Gao, Lili Jiang
2016 conf
BDCloud-SocialCom-SustainCom
Yingjie Wang, Xiangrong Tong, Zaobo He, Yang Gao, Kai Wang
2016 J jnl
Comput. Networks
Yingjie Wang, Zhipeng Cai, Guisheng Yin, Yang Gao, Xiangrong Tong, Guanying Wu
2016 conf
ICNC-FSKD
Xiangrong Tong, Wei Zhang, Yingjie Wang
2015 conf
WASA
Yingjie Wang, Yingshu Li, Yang Gao, Xiangrong Tong
2015 C conf
ICIS
Shiping Zhou, Wei Zhang, Xiangrong Tong
2012 J jnl
Comput. Sci. Inf. Syst.
Xiangrong Tong, Wei Zhang, Houkuan Huang
2012 conf
ADMI
Xiangrong Tong, Wei Zhang, Yu Long, Houkuan Huang
2010 Misc conf
ICNC
Xiangrong Tong, Liang Guo
2009 J jnl
Expert Syst. Appl.
Xiangrong Tong, Houkuan Huang, Wei Zhang
2009 conf
ICNC (5)
Xiangrong Tong, Wei Zhang
2008 conf
FSKD (2)
Xiangrong Tong, Wei Zhang
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"