Ioannis Panageas

105 papers A* 32A 11B 1C 1Journal 55Unranked 4
YearRankTypeTitle / Venue / Authors
2026 J jnl
CoRR
Ioannis Anagnostides, Ioannis Panageas, Nikolas Patris, Tuomas Sandholm
2026 J jnl
CoRR
David P. Woodruff, Vincent Cohen-Addad, Lalit Jain, Jieming Mao, Song Zuo, MohammadHossein Bateni, Simina Brânzei, Michael P. Brenner, Lin Chen, Ying Feng, Lance Fortnow, Gang Fu, Ziyi Guan, Zahra Hadizadeh, Mohammad T. Hajiaghayi, Mahdi JafariRaviz, Adel Javanmard, Karthik C. S., Ken-ichi Kawarabayashi, Ravi Kumar, Silvio Lattanzi, Euiwoong Lee, Yi Li, Ioannis Panageas, Dimitris Paparas, Benjamin Przybocki, Bernardo Subercaseaux, Ola Svensson, Shayan Taherijam, Xuan Wu, Eylon Yogev, Morteza Zadimoghaddam, Samson Zhou, Yossi Matias, James Manyika, Vahab Mirrokni
2026 J jnl
Games Econ. Behav.
Ioannis Anagnostides, Fivos Kalogiannis, Ioannis Panageas, Emmanouil-Vasileios Vlatakis-Gkaragkounis, Stephen M. McAleer
2026 J jnl
CoRR
Anas Barakat, Ioannis Panageas, Antonios Varvitsiotis
2026 J jnl
CoRR
Andreas Kontogiannis, Vasilis Pollatos, Panayotis Mertikopoulos, Ioannis Panageas
2026 J jnl
CoRR
Arnab Maiti, Claire Jie Zhang, Kevin Jamieson, Jamie H. Morgenstern, Ioannis Panageas, Lillian J. Ratliff
2026 J jnl
CoRR
Ioannis Anagnostides, Rohan Chauhan, Ioannis Panageas, Tuomas Sandholm, Jingming Yan
2026 J jnl
CoRR
Simina Brânzei, Ioannis Panageas, Dimitris Paparas
2025 J jnl
CoRR
Ioannis Anagnostides, Emanuel Tewolde, Brian Hu Zhang, Ioannis Panageas, Vincent Conitzer, Tuomas Sandholm
2025 J jnl
CoRR
Andreas Kontogiannis, Vasilis Pollatos, Gabriele Farina, Panayotis Mertikopoulos, Ioannis Panageas
2025 C conf
IWOCA
Michael T. Goodrich, Songyu Liu, Ioannis Panageas
2025 J jnl
CoRR
Michael T. Goodrich, Songyu Liu, Ioannis Panageas
2025 J jnl
CoRR
Rohan Chauhan, Ioannis Panageas
2025 J jnl
CoRR
Jingming Yan, Yiyuan Luo, Vaggos Chatziafratis, Ioannis Panageas, Parnian Shahkar, Stelios Stavroulakis
2025 J jnl
CoRR
Ioannis Anagnostides, Ioannis Panageas, Tuomas Sandholm, Jingming Yan
2024 A* conf
ICLR
Iosif Sakos, Stefanos Leonardos, Stelios Andrew Stavroulakis, Will Overman, Ioannis Panageas, Georgios Piliouras
2024 A* conf
AAAI
Nikolas Patris, Stelios Stavroulakis, Fivos Kalogiannis, Rose Zhang, Ioannis Panageas
2024 J jnl
CoRR
Nikolas Patris, Stelios Stavroulakis, Fivos Kalogiannis, Rose Zhang, Ioannis Panageas
2024 conf
WINE
Volkan Cevher, Wei Chen, Leello Dadi, Jing Dong, Ioannis Panageas, Stratis Skoulakis, Luca Viano, Baoxiang Wang, Siwei Wang, Jingyu Wu
2024 A conf
UAI
Yi Feng, Ping Li, Ioannis Panageas, Xiao Wang
2024 J jnl
CoRR
Yi Feng, Ping Li, Ioannis Panageas, Xiao Wang
2024 A* conf
NeurIPS
Fivos Kalogiannis, Jingming Yan, Ioannis Panageas
2024 J jnl
CoRR
Fivos Kalogiannis, Jingming Yan, Ioannis Panageas
2024 A* conf
ICLR
Nikolas Patris, Ioannis Panageas
2024 A* conf
AAAI
Ioannis Anagnostides, Ioannis Panageas, Gabriele Farina, Tuomas Sandholm
2024 J jnl
CoRR
Leello Tadesse Dadi, Ioannis Panageas, Stratis Skoulakis, Luca Viano, Volkan Cevher
2024 A* conf
ICML
Andreas Kontogiannis, Vasilis Pollatos, Sotiris Kanellopoulos, Panayotis Mertikopoulos, Aris Pagourtzis, Ioannis Panageas
2024 conf
WINE
Ioannis Panageas, Thorben Tröbst, Vijay V. Vazirani
2023 A* conf
EC
Ioannis Anagnostides, Fivos Kalogiannis, Ioannis Panageas, Emmanouil-Vasileios Vlatakis-Gkaragkounis, Stephen McAleer
2023 J jnl
CoRR
Ioannis Anagnostides, Fivos Kalogiannis, Ioannis Panageas, Emmanouil-Vasileios Vlatakis-Gkaragkounis, Stephen McAleer
2023 A* conf
ICLR
Fivos Kalogiannis, Ioannis Anagnostides, Ioannis Panageas, Emmanouil V. Vlatakis-Gkaragkounis, Vaggos Chatziafratis, Stelios Andrew Stavroulakis
2023 A* conf
NeurIPS
Ioannis Panageas, Nikolas Patris, Stratis Skoulakis, Volkan Cevher
2023 J jnl
CoRR
Ioannis Panageas, Nikolas Patris, Stratis Skoulakis, Volkan Cevher
2023 A* conf
AAAI
Sai Ganesh Nagarajan, Gerasimos Palaiopanos, Ioannis Panageas, Tushar Vaidya, Samson Yu
2023 A* conf
NeurIPS
Ioannis Anagnostides, Ioannis Panageas, Gabriele Farina, Tuomas Sandholm
2023 J jnl
CoRR
Ioannis Anagnostides, Ioannis Panageas, Gabriele Farina, Tuomas Sandholm
2023 A* conf
NeurIPS
Yi Feng, Hu Fu, Qun Hu, Ping Li, Ioannis Panageas, Bo Peng, Xiao Wang
2023 J jnl
CoRR
Yi Feng, Hu Fu, Qun Hu, Ping Li, Ioannis Panageas, Bo Peng, Xiao Wang
2023 J jnl
CoRR
Ioannis Anagnostides, Ioannis Panageas, Gabriele Farina, Tuomas Sandholm
2023 J jnl
CoRR
Ioannis Panageas, Stratis Skoulakis, Luca Viano, Xiao Wang, Volkan Cevher
2023 A* conf
ICML
Ioannis Panageas, Stratis Skoulakis, Luca Viano, Xiao Wang, Volkan Cevher
2023 A* conf
ICLR
Fivos Kalogiannis, Ioannis Panageas, Emmanouil V. Vlatakis-Gkaragkounis
2023 A* conf
NeurIPS
Fivos Kalogiannis, Ioannis Panageas
2023 J jnl
CoRR
Fivos Kalogiannis, Ioannis Panageas
2022 A* conf
IJCAI
Yi Feng, Ioannis Panageas, Xiao Wang
2022 J jnl
CoRR
Yi Feng, Ioannis Panageas, Xiao Wang
2022 J jnl
CoRR
Fivos Kalogiannis, Ioannis Anagnostides, Ioannis Panageas, Emmanouil-Vasileios Vlatakis-Gkaragkounis, Vaggos Chatziafratis, Stelios Stavroulakis
2022 conf
SOSA
Ioannis Anagnostides, Ioannis Panageas
2022 A* conf
ICLR
Stefanos Leonardos, Will Overman, Ioannis Panageas, Georgios Piliouras
2022 A conf
AISTATS
Roy Fox, Stephen M. McAleer, Will Overman, Ioannis Panageas
2022 A* conf
ICML
Ioannis Anagnostides, Ioannis Panageas, Gabriele Farina, Tuomas Sandholm
2022 J jnl
CoRR
Ioannis Anagnostides, Ioannis Panageas, Gabriele Farina, Tuomas Sandholm
2022 A* conf
NeurIPS
Vaggos Chatziafratis, Ioannis Panageas, Clayton Sanford, Stelios Stavroulakis
2022 J jnl
CoRR
Vaggos Chatziafratis, Ioannis Panageas, Clayton Sanford, Stelios Andrew Stavroulakis
2022 A* conf
NeurIPS
Ioannis Anagnostides, Gabriele Farina, Ioannis Panageas, Tuomas Sandholm
2022 J jnl
CoRR
Ioannis Anagnostides, Gabriele Farina, Ioannis Panageas, Tuomas Sandholm
2021 J jnl
CoRR
Ioannis Panageas, Thorben Tröbst, Vijay V. Vazirani
2021 A conf
AISTATS
Arnab Bhattacharyya, Rathin Desai, Sai Ganesh Nagarajan, Ioannis Panageas
2021 J jnl
CoRR
Stefanos Leonardos, Will Overman, Ioannis Panageas, Georgios Piliouras
2021 J jnl
CoRR
Roy Fox, Stephen McAleer, Will Overman, Ioannis Panageas
2021 A conf
AISTATS
Qi Lei, Sai Ganesh Nagarajan, Ioannis Panageas, Xiao Wang
2021 J jnl
CoRR
Fivos Kalogiannis, Ioannis Panageas, Emmanouil V. Vlatakis-Gkaragkounis
2020 J jnl
CoRR
Vaggos Chatziafratis, Sai Ganesh Nagarajan, Ioannis Panageas
2020 A* conf
ICML
Vaggos Chatziafratis, Sai Ganesh Nagarajan, Ioannis Panageas
2020 J jnl
CoRR
Ioannis Panageas, Stratis Skoulakis, Antonios Varvitsiotis, Xiao Wang
2020 A* conf
ICLR
Vaggos Chatziafratis, Sai Ganesh Nagarajan, Ioannis Panageas, Xiao Wang
2020 J jnl
CoRR
Arnab Bhattacharyya, Rathin Desai, Sai Ganesh Nagarajan, Ioannis Panageas
2020 A* conf
NeurIPS
Xiao Wang, Qi Lei, Ioannis Panageas
2020 J jnl
CoRR
Xiao Wang, Qi Lei, Ioannis Panageas
2020 J jnl
CoRR
Qi Lei, Sai Ganesh Nagarajan, Ioannis Panageas, Xiao Wang
2020 A conf
AISTATS
Constantinos Daskalakis, Nishanth Dikkala, Ioannis Panageas
2020 J jnl
CoRR
Constantinos Daskalakis, Nishanth Dikkala, Ioannis Panageas
2020 B conf
ALT
Sai Ganesh Nagarajan, Ioannis Panageas
2019 J jnl
CoRR
Vaggos Chatziafratis, Sai Ganesh Nagarajan, Ioannis Panageas, Xiao Wang
2019 A* conf
NeurIPS
Ioannis Panageas, Georgios Piliouras, Xiao Wang
2019 J jnl
Math. Program.
Jason D. Lee, Ioannis Panageas, Georgios Piliouras, Max Simchowitz, Michael I. Jordan, Benjamin Recht
2019 A conf
ITCS
Constantinos Daskalakis, Ioannis Panageas
2019 A* conf
ICML
Ioannis Panageas, Georgios Piliouras, Xiao Wang
2019 J jnl
CoRR
Sai Ganesh Nagarajan, Ioannis Panageas
2019 J jnl
CoRR
Constantinos Daskalakis, Nishanth Dikkala, Ioannis Panageas
2019 A* conf
STOC
Constantinos Daskalakis, Nishanth Dikkala, Ioannis Panageas
2018 A* conf
EC
Tung Mai, Milena Mihail, Ioannis Panageas, Will Ratcliff, Vijay V. Vazirani, Peter Yunker
2018 J jnl
CoRR
Constantinos Daskalakis, Ioannis Panageas
2018 A* conf
NeurIPS
Constantinos Daskalakis, Ioannis Panageas
2018 J jnl
CoRR
Constantinos Daskalakis, Ioannis Panageas
2017 J jnl
CoRR
Jason D. Lee, Ioannis Panageas, Georgios Piliouras, Max Simchowitz, Michael I. Jordan, Benjamin Recht
2017 A conf
ITCS
Ioannis Panageas, Georgios Piliouras
2017 conf
NIPS
Gerasimos Palaiopanos, Ioannis Panageas, Georgios Piliouras
2017 J jnl
CoRR
Gerasimos Palaiopanos, Ioannis Panageas, Georgios Piliouras
2017 A conf
ITCS
Ruta Mehta, Ioannis Panageas, Georgios Piliouras, Prasad Tetali, Vijay V. Vazirani
2017 A* conf
ICALP
Tung Mai, Ioannis Panageas, Vijay V. Vazirani
2017 J jnl
CoRR
Tung Mai, Ioannis Panageas, Will Ratcliff, Vijay V. Vazirani, Peter Yunker
2016 A* conf
EC
Ioannis Panageas, Georgios Piliouras
2016 A* conf
SODA
Ioannis Panageas, Piyush Srivastava, Nisheeth K. Vishnoi
2016
Ioannis Panageas
2016 J jnl
CoRR
Ioannis Panageas, Georgios Piliouras
2016 A* conf
ICALP
Ioannis Panageas, Nisheeth K. Vishnoi
2016 J jnl
CoRR
Tung Mai, Ioannis Panageas, Vijay V. Vazirani
2016 A conf
ESA
Ruta Mehta, Ioannis Panageas, Georgios Piliouras, Sadra Yazdanbod
2015 A conf
ITCS
Ruta Mehta, Ioannis Panageas, Georgios Piliouras
2015 J jnl
CoRR
Ruta Mehta, Ioannis Panageas, Georgios Piliouras, Prasad Tetali, Vijay V. Vazirani
2014 J jnl
CoRR
Ioannis Panageas, Georgios Piliouras
2014 J jnl
CoRR
Ruta Mehta, Ioannis Panageas, Georgios Piliouras
2014 J jnl
CoRR
Ruta Mehta, Ioannis Panageas, Georgios Piliouras, Sadra Yazdanbod
2013 A conf
IROS
Yong-Dian Jian, Doru Balcan, Ioannis Panageas, Prasad Tetali, Frank Dellaert
tests/unit/test_apk_dataclasses.py
← Index tests/unit/test_apk_dataclasses.py python
"""
Unit tests for APK dataclasses in redb/models/dataclasses.py.
"""
import pytest
from dataclasses import asdict, fields

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


# ============================================================================
# APKFeatures
# ============================================================================

class TestAPKFeaturesDataclass:
    """Tests for APKFeatures dataclass."""

    def test_named_fields(self):
        from redb.models.dataclasses import APKFeatures

        f = APKFeatures(
            package_name="com.example.app",
            app_name="Example App",
            version_code=1,
            version_name="1.0.0",
        )
        assert f.package_name == "com.example.app"
        assert f.app_name == "Example App"
        assert f.version_code == 1
        assert f.version_name == "1.0.0"

    def test_all_fields_default_none_or_safe(self):
        from redb.models.dataclasses import APKFeatures

        f = APKFeatures()
        # Nullable fields default to None
        assert f.package_name is None
        assert f.app_name is None
        assert f.version_code is None
        assert f.version_name is None
        assert f.min_sdk_version is None
        assert f.target_sdk_version is None
        assert f.compile_sdk_version is None
        assert f.main_activity is None
        # Non-nullable fields have safe defaults
        assert f.is_debuggable is False
        assert f.allow_backup is True
        assert f.uses_cleartext_traffic is False
        assert f.supported_abis == []
        assert f.dex_count == 0
        assert f.total_dex_size == 0
        assert f.total_file_count == 0
        assert f.has_native_code is False
        assert f.has_assets is False
        assert f.uses_libraries == []
        assert f.earliest_content_modification is None
        assert f.latest_content_modification is None
        assert f.contains_embedded_apk is False

    def test_all_fields_populated(self):
        from redb.models.dataclasses import APKFeatures

        f = APKFeatures(
            package_name="com.malware.test",
            app_name="Malware",
            version_code=42,
            version_name="6.6.6",
            min_sdk_version=21,
            target_sdk_version=33,
            compile_sdk_version=33,
            main_activity="com.malware.test.MainActivity",
            is_debuggable=True,
            allow_backup=False,
            uses_cleartext_traffic=True,
            supported_abis=["armeabi-v7a", "arm64-v8a", "x86"],
            dex_count=3,
            total_dex_size=5000000,
            total_file_count=200,
            has_native_code=True,
            has_assets=True,
            uses_libraries=["com.google.android.maps"],
            earliest_content_modification="2024-01-01T00:00:00Z",
            latest_content_modification="2024-06-15T12:00:00Z",
            contains_embedded_apk=True,
        )
        assert f.is_debuggable is True
        assert f.dex_count == 3
        assert len(f.supported_abis) == 3
        assert f.contains_embedded_apk is True

    def test_asdict_roundtrip(self):
        from redb.models.dataclasses import APKFeatures

        f = APKFeatures(
            package_name="com.test",
            app_name="Test",
            version_code=1,
            version_name="1.0",
        )
        d = asdict(f)
        assert d["package_name"] == "com.test"
        assert d["is_debuggable"] is False
        assert d["supported_abis"] == []

    def test_partial_extraction(self):
        """Verify that APKFeatures can be created with only some fields populated,
        simulating a partial extraction where some fields failed."""
        from redb.models.dataclasses import APKFeatures

        f = APKFeatures(
            package_name="com.example.app",
            # app_name failed to extract — left as None
            version_code=10,
            # version_name failed to extract — left as None
            is_debuggable=True,
            dex_count=2,
        )
        assert f.package_name == "com.example.app"
        assert f.app_name is None
        assert f.version_code == 10
        assert f.version_name is None
        assert f.is_debuggable is True
        assert f.dex_count == 2


# ============================================================================
# APKManifestComponent
# ============================================================================

class TestAPKManifestComponentDataclass:

    def test_creation(self):
        from redb.models.dataclasses import APKManifestComponent

        c = APKManifestComponent(
            component_type="activity",
            class_name="com.example.MainActivity",
            is_exported=True,
            intent_actions=["android.intent.action.MAIN"],
            intent_categories=["android.intent.category.LAUNCHER"],
        )
        assert c.component_type == "activity"
        assert c.class_name == "com.example.MainActivity"
        assert c.is_exported is True
        assert len(c.intent_actions) == 1
        assert len(c.intent_categories) == 1

    def test_defaults(self):
        from redb.models.dataclasses import APKManifestComponent

        c = APKManifestComponent(
            component_type="service",
            class_name="com.example.MyService",
            is_exported=False,
        )
        assert c.intent_actions == []
        assert c.intent_categories == []


# ============================================================================
# APKManifest
# ============================================================================

class TestAPKManifestDataclass:

    def test_required_fields(self):
        from redb.models.dataclasses import APKManifest

        m = APKManifest(
            activity_count=5,
            service_count=2,
            receiver_count=3,
            provider_count=1,
        )
        assert m.activity_count == 5
        assert m.service_count == 2
        assert m.receiver_count == 3
        assert m.provider_count == 1

    def test_optional_list_defaults(self):
        from redb.models.dataclasses import APKManifest

        m = APKManifest(
            activity_count=0,
            service_count=0,
            receiver_count=0,
            provider_count=0,
        )
        assert m.activities == []
        assert m.services == []
        assert m.receivers == []
        assert m.providers == []
        assert m.exported_components == []
        assert m.intent_filters_by_action == []
        assert m.intent_filters_by_category == []
        assert m.uses_features == []
        assert m.meta_data is None
        assert m.manifest_xml is None

    def test_fully_populated(self):
        from redb.models.dataclasses import APKManifest

        m = APKManifest(
            activity_count=2,
            service_count=1,
            receiver_count=1,
            provider_count=0,
            activities=["MainActivity", "SettingsActivity"],
            services=["SyncService"],
            receivers=["BootReceiver"],
            providers=[],
            exported_components=["MainActivity", "BootReceiver"],
            intent_filters_by_action=["android.intent.action.MAIN"],
            uses_features=["android.hardware.camera"],
            manifest_xml="<manifest>...</manifest>",
        )
        assert len(m.activities) == 2
        assert len(m.exported_components) == 2
        assert m.manifest_xml is not None


# ============================================================================
# APKPermission / APKPermissions
# ============================================================================

class TestAPKPermissionDataclass:

    def test_creation(self):
        from redb.models.dataclasses import APKPermission

        p = APKPermission(
            permission_name="android.permission.CAMERA",
            protection_level="dangerous",
        )
        assert p.permission_name == "android.permission.CAMERA"
        assert p.protection_level == "dangerous"
        assert p.is_custom is False

    def test_custom_permission(self):
        from redb.models.dataclasses import APKPermission

        p = APKPermission(
            permission_name="com.example.CUSTOM_PERM",
            protection_level="signature",
            is_custom=True,
        )
        assert p.is_custom is True


class TestAPKPermissionsDataclass:

    def test_required_fields(self):
        from redb.models.dataclasses import APKPermissions

        p = APKPermissions(
            total_permission_count=10,
            dangerous_permission_count=3,
        )
        assert p.total_permission_count == 10
        assert p.dangerous_permission_count == 3

    def test_defaults(self):
        from redb.models.dataclasses import APKPermissions

        p = APKPermissions(
            total_permission_count=0,
            dangerous_permission_count=0,
        )
        assert p.permissions == []
        assert p.dangerous_permissions == []
        assert p.custom_permissions == []
        assert p.permission_details == []
        assert p.permhash is None

    def test_with_permission_details(self):
        from redb.models.dataclasses import APKPermission, APKPermissions

        details = [
            APKPermission("android.permission.INTERNET", "normal"),
            APKPermission("android.permission.CAMERA", "dangerous"),
        ]
        p = APKPermissions(
            total_permission_count=2,
            dangerous_permission_count=1,
            permissions=["android.permission.INTERNET", "android.permission.CAMERA"],
            dangerous_permissions=["android.permission.CAMERA"],
            permission_details=details,
            permhash="abc123def456",
        )
        assert len(p.permission_details) == 2
        assert p.permhash == "abc123def456"


# ============================================================================
# APKCertificate / APKCodeSigningInfo
# ============================================================================

class TestAPKCertificateDataclass:

    def test_required_fields(self):
        from redb.models.dataclasses import APKCertificate

        c = APKCertificate(
            subject="CN=Android Debug",
            issuer="CN=Android Debug",
            serial_number="1234567890",
            valid_from="2024-01-01",
            valid_to="2054-01-01",
            thumbprint_sha1="aabbccdd" * 5,
            thumbprint_sha256="aabbccdd" * 8,
        )
        assert c.subject == "CN=Android Debug"
        assert c.issuer == "CN=Android Debug"
        assert c.is_self_signed is False

    def test_optional_fields(self):
        from redb.models.dataclasses import APKCertificate

        c = APKCertificate(
            subject="CN=Test",
            issuer="CN=CA",
            serial_number="1",
            valid_from="2024-01-01",
            valid_to="2025-01-01",
            thumbprint_sha1="aa" * 20,
            thumbprint_sha256="bb" * 32,
            algorithm="SHA256withRSA",
            key_size=2048,
            is_self_signed=True,
        )
        assert c.algorithm == "SHA256withRSA"
        assert c.key_size == 2048
        assert c.is_self_signed is True


class TestAPKCodeSigningInfoDataclass:

    def test_required_fields(self):
        from redb.models.dataclasses import APKCodeSigningInfo

        info = APKCodeSigningInfo(
            _id="abcdef1234567890" * 4,
            is_signed=True,
        )
        assert info.is_signed is True
        assert info.signature_scheme_versions == []
        assert info.number_of_certificates == 0
        assert info.x509_certificates is None
        assert info.apk_signer_subject is None
        assert info.apk_signer_issuer is None

    def test_fully_populated(self):
        from redb.models.dataclasses import APKCertificate, APKCodeSigningInfo

        cert = APKCertificate(
            subject="CN=Test",
            issuer="CN=Test",
            serial_number="1",
            valid_from="2024-01-01",
            valid_to="2025-01-01",
            thumbprint_sha1="aa" * 20,
            thumbprint_sha256="bb" * 32,
        )
        info = APKCodeSigningInfo(
            _id="cc" * 32,
            is_signed=True,
            signature_scheme_versions=[1, 2, 3],
            number_of_certificates=1,
            x509_certificates=[cert],
            apk_signer_subject="CN=Test",
            apk_signer_issuer="CN=Test",
        )
        assert len(info.signature_scheme_versions) == 3
        assert info.number_of_certificates == 1
        assert len(info.x509_certificates) == 1


# ============================================================================
# APKDexFile
# ============================================================================

class TestAPKDexFileDataclass:

    def test_required_fields(self):
        from redb.models.dataclasses import APKDexFile

        d = APKDexFile(
            filename="classes.dex",
            sha256="aa" * 32,
            class_count=100,
            method_count=500,
            string_count=1000,
        )
        assert d.filename == "classes.dex"
        assert d.class_count == 100
        assert d.method_count == 500
        assert d.string_count == 1000

    def test_optional_fields_defaults(self):
        from redb.models.dataclasses import APKDexFile

        d = APKDexFile(
            filename="classes2.dex",
            sha256="bb" * 32,
            class_count=0,
            method_count=0,
            string_count=0,
        )
        assert d.tlsh is None
        assert d.top_packages is None
        assert d.api_usage is None
        assert d.obfuscation_indicators is None

    def test_tlsh_populated(self):
        from redb.models.dataclasses import APKDexFile

        tlsh_value = "T1" + "A" * 68
        d = APKDexFile(
            filename="classes.dex",
            sha256="aa" * 32,
            class_count=100,
            method_count=500,
            string_count=1000,
            tlsh=tlsh_value,
        )
        assert d.tlsh == tlsh_value

    def test_with_api_usage(self):
        from redb.models.dataclasses import APKDexFile

        d = APKDexFile(
            filename="classes.dex",
            sha256="cc" * 32,
            class_count=50,
            method_count=200,
            string_count=300,
            top_packages=[{"name": "com.example", "class_count": 30}],
            api_usage={
                "crypto": ["Ljavax/crypto/Cipher;->getInstance"],
                "reflection": ["Ljava/lang/Class;->forName"],
            },
            obfuscation_indicators={"short_class_names_ratio": 0.8, "single_char_methods_ratio": 0.5},
        )
        assert len(d.api_usage) == 2
        assert d.obfuscation_indicators["short_class_names_ratio"] == 0.8


# ============================================================================
# APKResource
# ============================================================================

class TestAPKResourceDataclass:

    def test_creation(self):
        from redb.models.dataclasses import APKResource

        r = APKResource(
            path="res/drawable/icon.png",
            size=1024,
            sha256="dd" * 32,
            filetype_magika="png",
        )
        assert r.path == "res/drawable/icon.png"
        assert r.size == 1024
        assert r.is_suspicious is False

    def test_suspicious_resource(self):
        from redb.models.dataclasses import APKResource

        r = APKResource(
            path="assets/payload.dex",
            size=50000,
            sha256="ee" * 32,
            filetype_magika="dex",
            is_suspicious=True,
        )
        assert r.is_suspicious is True


# ============================================================================
# APKNativeLib
# ============================================================================

class TestAPKNativeLibDataclass:

    def test_creation(self):
        from redb.models.dataclasses import APKNativeLib

        lib = APKNativeLib(
            abi="arm64-v8a",
            filename="libnative.so",
            size=200000,
            sha256="ff" * 32,
        )
        assert lib.abi == "arm64-v8a"
        assert lib.filename == "libnative.so"
        assert lib.is_known_packer is False

    def test_known_packer(self):
        from redb.models.dataclasses import APKNativeLib

        lib = APKNativeLib(
            abi="armeabi-v7a",
            filename="libjiagu.so",
            size=100000,
            sha256="aa" * 32,
            is_known_packer=True,
        )
        assert lib.is_known_packer is True


# ============================================================================
# APKInconsistencyTests
# ============================================================================

class TestAPKInconsistencyTestsDataclass:

    def test_all_defaults_none(self):
        from redb.models.dataclasses import APKInconsistencyTests

        t = APKInconsistencyTests()
        assert t.test_zip_bomb is None
        assert t.test_zip_duplicate_entries is None
        assert t.test_zip_path_traversal is None
        assert t.test_zip_suspicious_timestamps is None
        assert t.test_hidden_dex_files is None
        assert t.test_manifest_component_mismatch is None
        assert t.test_debuggable_release is None
        assert t.test_emulator_detection_strings is None
        assert t.test_debugger_detection is None
        assert t.test_root_detection is None

    def test_all_fields_populated(self):
        from redb.models.dataclasses import APKInconsistencyTests

        t = APKInconsistencyTests(
            test_zip_bomb=True,
            test_zip_duplicate_entries=False,
            test_zip_path_traversal=True,
            test_zip_suspicious_timestamps=False,
            test_hidden_dex_files=True,
            test_manifest_component_mismatch=False,
            test_debuggable_release=True,
            test_emulator_detection_strings=True,
            test_debugger_detection=False,
            test_root_detection=True,
        )
        assert t.test_zip_bomb is True
        assert t.test_debuggable_release is True
        # Count true values
        d = asdict(t)
        true_count = sum(1 for v in d.values() if v is True)
        assert true_count == 6

    def test_field_count(self):
        from redb.models.dataclasses import APKInconsistencyTests

        # Exactly 10 inconsistency tests per PDD specification
        assert len(fields(APKInconsistencyTests)) == 10


# ============================================================================
# Hashes dataclass — APK permhash field
# ============================================================================

class TestHashesAPKField:

    def test_permhash_default_none(self):
        from redb.models.dataclasses import Hashes

        h = Hashes(md5="a", sha1="b", sha256="c", ssdeep_hash="d", tlsh_hash="e")
        assert h.permhash is None

    def test_permhash_populated(self):
        from redb.models.dataclasses import Hashes

        h = Hashes(
            md5="a", sha1="b", sha256="c", ssdeep_hash="d", tlsh_hash="e",
            permhash="abc123" * 10 + "abcd",
        )
        assert h.permhash is not None
        assert len(h.permhash) == 64


# ============================================================================
# Tag enum — APK entries
# ============================================================================

class TestAPKTagEnum:

    def test_all_apk_tags_exist(self):
        from redb.extractors.enum import Tag

        expected_tags = [
            "APK_FEATURES", "APK_MANIFEST", "APK_PERMISSIONS", "APK_SIGNATURE",
            "APK_DEX", "APK_RESOURCES", "APK_NATIVE_LIBS", "APK_INCONSISTENCY_TESTS",
        ]
        for name in expected_tags:
            assert hasattr(Tag, name), f"Tag.{name} missing"

    def test_apk_tag_values(self):
        from redb.extractors.enum import Tag

        assert Tag.APK_FEATURES.value == "apk_features"
        assert Tag.APK_MANIFEST.value == "apk_manifest"
        assert Tag.APK_PERMISSIONS.value == "apk_permissions"
        assert Tag.APK_SIGNATURE.value == "apk_signature"
        assert Tag.APK_DEX.value == "apk_dex"
        assert Tag.APK_RESOURCES.value == "apk_resources"
        assert Tag.APK_NATIVE_LIBS.value == "apk_native_libs"
        assert Tag.APK_INCONSISTENCY_TESTS.value == "apk_inconsistency_tests"