Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion objection/console/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def version() -> None:
@click.option('--script-source', '-l', default=None, help=(
'A script file to use with the the "path" config type. '
'Remember that use the name of this file in your "path". It will be next to the config.'), show_default=False)
@click.option('--bundle-id', '-b', default=None, help='The bundleid to set when codesigning the IPA')
@click.option('--bundle-id', '-B', default=None, help='The bundleid to set when codesigning the IPA')
def patchipa(source: str, gadget_version: str, codesign_signature: str, provision_file: str, binary_name: str,
skip_cleanup: bool, pause: bool, unzip_unicode: bool, gadget_config: str, script_source: str,
bundle_id: str) -> None:
Expand Down
13 changes: 13 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ Homepage = "https://github.com/sensepost/objection"
Repository = "https://github.com/sensepost/objection"
"Bug Tracker" = "https://github.com/sensepost/objection/issues"

[project.optional-dependencies]
test = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
]

[project.scripts]
objection = "objection.console.cli:cli"

Expand All @@ -55,3 +61,10 @@ objection = [
"utils/assets/*.xml",
"agent.js",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"
addopts = "-v --strict-markers"
Comment on lines +65 to +70
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions adding uv run pytest to a uv config, but this change set only adds pytest dependencies and pytest ini options; there is no tool.uv/uv config entry or script/Makefile update wiring uv run pytest. If a uv configuration change is intended, it looks missing from this PR (or the description should be updated).

Copilot uses AI. Check for mistakes.
16 changes: 8 additions & 8 deletions tests/commands/android/test_keystore.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from unittest import mock

from objection.commands.android.keystore import entries, clear
from ...helpers import capture
from ...helpers import capture, normalize_table_whitespace


class TestKeystore(unittest.TestCase):
Expand All @@ -13,11 +13,11 @@ def test_entries_handles_empty_data(self, mock_api):
with capture(entries, []) as o:
output = o

expected_output = """Alias Key Certificate
------- ----- -------------
expected_output = """Alias Key Certificate
----- --- -----------
"""

self.assertEqual(output, expected_output)
self.assertEqual(normalize_table_whitespace(output), normalize_table_whitespace(expected_output))

@mock.patch('objection.state.connection.state_connection.get_api')
def test_entries_handles(self, mock_api):
Expand All @@ -30,12 +30,12 @@ def test_entries_handles(self, mock_api):
with capture(entries, []) as o:
output = o

expected_output = """Alias Key Certificate
------- ----- -------------
test True True
expected_output = """Alias Key Certificate
----- ---- -----------
test True True
"""

self.assertEqual(output, expected_output)
self.assertEqual(normalize_table_whitespace(output), normalize_table_whitespace(expected_output))

@mock.patch('objection.state.connection.state_connection.get_api')
@mock.patch('objection.commands.android.keystore.click.confirm')
Expand Down
24 changes: 12 additions & 12 deletions tests/commands/ios/test_bundles.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from objection.commands.ios.bundles import show_frameworks, _should_include_apple_bundles, _should_print_full_path, \
_is_apple_bundle, show_bundles
from ...helpers import capture
from ...helpers import capture, normalize_table_whitespace


class TestBundles(unittest.TestCase):
Expand Down Expand Up @@ -76,7 +76,7 @@ def test_show_frameworks_prints_without_apple_bundles(self, mock_api):
MapKit za.apple.MapKit 1 /MapKit
"""

self.assertEqual(output, expected)
self.assertEqual(normalize_table_whitespace(output), normalize_table_whitespace(expected))

@mock.patch('objection.state.connection.state_connection.get_api')
def test_show_frameworks_prints_with_apple_bundles(self, mock_api):
Expand All @@ -93,7 +93,7 @@ def test_show_frameworks_prints_with_apple_bundles(self, mock_api):
MapKit za.apple.MapKit 1 /MapKit
"""

self.assertEqual(output, expected)
self.assertEqual(normalize_table_whitespace(output), normalize_table_whitespace(expected))

@mock.patch('objection.state.connection.state_connection.get_api')
def test_show_frameworks_prints_with_apple_bundles_and_full_paths(self, mock_api):
Expand All @@ -110,7 +110,7 @@ def test_show_frameworks_prints_with_apple_bundles_and_full_paths(self, mock_api
MapKit za.apple.MapKit 1 /MapKit
"""

self.assertEqual(output, expected)
self.assertEqual(normalize_table_whitespace(output), normalize_table_whitespace(expected))

@mock.patch('objection.state.connection.state_connection.get_api')
def test_show_bundles_prints_bundles(self, mock_api):
Expand All @@ -119,15 +119,15 @@ def test_show_bundles_prints_bundles(self, mock_api):
with capture(show_bundles, []) as o:
output = o

expected = """Executable Bundle Version Path
------------------------ ---------------------------------- --------- -------------------------------------------
AppleIDSSOAuthentication com.apple.AppleIDSSOAuthentication 1 /AppleIDSSOAuthentication
LinguisticData com.apple.LinguisticData 1 ...nguisticDataLinguisticDataLinguisticData
hockeyapp net.hockeyapp.sdk.ios 1 /hockeyapp
MapKit za.apple.MapKit 1 /MapKit
expected = """Executable Bundle Version Path
------------------------ ---------------------------------- ------- ------------------------------------------------------------------------
AppleIDSSOAuthentication com.apple.AppleIDSSOAuthentication 1 /AppleIDSSOAuthentication
LinguisticData com.apple.LinguisticData 1 /LinguisticData/LinguisticDataLinguisticDataLinguisticDataLinguisticData
hockeyapp net.hockeyapp.sdk.ios 1 /hockeyapp
MapKit za.apple.MapKit 1 /MapKit
"""

self.assertEqual(output, expected)
self.assertEqual(normalize_table_whitespace(output), normalize_table_whitespace(expected))

@mock.patch('objection.state.connection.state_connection.get_api')
def test_show_bundles_prints_bundles(self, mock_api):
Expand All @@ -144,4 +144,4 @@ def test_show_bundles_prints_bundles(self, mock_api):
MapKit za.apple.MapKit 1 /MapKit
"""

self.assertEqual(output, expected)
self.assertEqual(normalize_table_whitespace(output), normalize_table_whitespace(expected))
10 changes: 5 additions & 5 deletions tests/commands/ios/test_cookies.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from unittest import mock

from objection.commands.ios.cookies import get
from ...helpers import capture
from ...helpers import capture, normalize_table_whitespace


class TestCookies(unittest.TestCase):
Expand Down Expand Up @@ -30,9 +30,9 @@ def test_get(self, mock_api):
with capture(get, []) as o:
output = o

expected_output = """Name Value Expires Domain Path Secure HTTPOnly
------ ------- ------------------------- -------- ------ -------- ----------
foo bar 01-01-1970 00:00:00 +0000 foo.com / false true
expected_output = """Name Value Expires Domain Path Secure HTTPOnly
---- ----- ------------------------- ------- ---- ------ --------
foo bar 01-01-1970 00:00:00 +0000 foo.com / false true
"""

self.assertEqual(output, expected_output)
self.assertEqual(normalize_table_whitespace(output), normalize_table_whitespace(expected_output))
18 changes: 9 additions & 9 deletions tests/commands/ios/test_keychain.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from objection.commands.ios.keychain import _should_output_json, dump, dump_raw, clear, add, \
_data_flag_has_identifier, _get_flag_value, _should_do_smart_decode
from ...helpers import capture
from ...helpers import capture, normalize_table_whitespace


class TestKeychain(unittest.TestCase):
Expand Down Expand Up @@ -68,10 +68,10 @@ def test_dump_to_screen_handles_empty_data(self, mock_api):
expected_output = """Note: You may be asked to authenticate using the devices passcode or TouchID
Save the output by adding `--json keychain.json` to this command
Dumping the iOS keychain...
Created Accessible ACL Type Account Service Data
--------- ------------ ----- ------ --------- --------- ------
Created Accessible ACL Type Account Service Data
------- ---------- --- ---- ------- ------- ----
"""
self.assertEqual(output, expected_output)
self.assertEqual(normalize_table_whitespace(output), normalize_table_whitespace(expected_output))

@mock.patch('objection.state.connection.state_connection.get_api')
def test_dump_to_screen(self, mock_api):
Expand All @@ -87,11 +87,11 @@ def test_dump_to_screen(self, mock_api):
expected_output = """Note: You may be asked to authenticate using the devices passcode or TouchID
Save the output by adding `--json keychain.json` to this command
Dumping the iOS keychain...
Created Accessible ACL Type Account Service Data
--------- ------------ ----- ------ --------- --------- ------
now None None foo foo bar
Created Accessible ACL Type Account Service Data
------- ---------- ---- ---- ------- ------- ----
now None None foo foo bar
"""
self.assertEqual(output, expected_output)
self.assertEqual(normalize_table_whitespace(output), normalize_table_whitespace(expected_output))

@mock.patch('objection.state.connection.state_connection.get_api')
def test_dump_raw(self, mock_api):
Expand Down Expand Up @@ -141,7 +141,7 @@ def test_adds_item_validates_data_key_to_need_identifier(self):
with capture(add, ['--data', 'test_data']) as o:
output = o

self.assertEqual(output, 'When specifying the --data flag, either --account or --server should also be added\n')
self.assertEqual(output, 'When specifying the --data flag, either --account or --service should also be added\n')

@mock.patch('objection.state.connection.state_connection.get_api')
def test_adds_item_successfully(self, mock_api):
Expand Down
10 changes: 5 additions & 5 deletions tests/commands/ios/test_nsurlcredentialstorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from unittest import mock

from objection.commands.ios.nsurlcredentialstorage import dump
from ...helpers import capture
from ...helpers import capture, normalize_table_whitespace


class TestNsusercredentialstorage(unittest.TestCase):
Expand All @@ -20,9 +20,9 @@ def test_dump(self, mock_api):
with capture(dump, []) as o:
output = o

expected_output = """Protocol Host Port Authentication Method User Password
---------- ------- ------ ----------------------- ------ ----------
https foo.bar 80 Default foo bar
expected_output = """Protocol Host Port Authentication Method User Password
-------- ------- ---- --------------------- ---- --------
https foo.bar 80 Default foo bar
"""

self.assertEqual(output, expected_output)
self.assertEqual(normalize_table_whitespace(output), normalize_table_whitespace(expected_output))
57 changes: 33 additions & 24 deletions tests/commands/test_filemanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
_pwd_android, ls, _ls_ios, _ls_android, download, _download_ios, _download_android, upload, rm, _rm_android
from objection.state.device import device_state, Ios, Android
from objection.state.filemanager import file_manager_state
from ..helpers import capture
from ..helpers import capture, normalize_table_whitespace


class TestFileManager(unittest.TestCase):
Expand Down Expand Up @@ -278,13 +278,16 @@ def test_lists_readable_ios_directory_using_helper_method(self, mock_api):
output = o

expected_outut = """NSFileType Perms NSFileProtection Read Write Owner Group Size Creation Name
------------ ------- ------------------ ------ ------- ------- ------- --------- ---------- ------
A B C True False D (E) F (G) 115.4 GiB H test
------------ ------- ------------------ ------ ------- ------- ------- --------- ---------- ------
A B C True False D (E) F (G) 115.4 GiB H test

Readable: True Writable: False
"""
Readable: True Writable: False
"""

self.assertEqual(output, expected_outut)
self.assertEqual(
normalize_table_whitespace(output),
normalize_table_whitespace(expected_outut),
)

@mock.patch('objection.state.connection.state_connection.get_api')
def test_lists_readable_ios_directory_using_helper_method_no_attributes(self, mock_api):
Expand All @@ -306,13 +309,16 @@ def test_lists_readable_ios_directory_using_helper_method_no_attributes(self, mo
output = o

expected_outut = """NSFileType Perms NSFileProtection Read Write Owner Group Size Creation Name
------------ ------- ------------------ ------ ------- --------- --------- ------ ---------- ------
n/a n/a n/a True True n/a (n/a) n/a (n/a) n/a n/a test
------------ ------- ------------------ ------ ------- --------- --------- ------ ---------- ------
n/a n/a n/a True True n/a (n/a) n/a (n/a) n/a n/a test

Readable: True Writable: True
"""
Readable: True Writable: True
"""

self.assertEqual(output, expected_outut)
self.assertEqual(
normalize_table_whitespace(output),
normalize_table_whitespace(expected_outut),
)

@mock.patch('objection.state.connection.state_connection.get_api')
def test_lists_unreadable_ios_directory_using_helper_method(self, mock_api):
Expand Down Expand Up @@ -354,13 +360,16 @@ def test_lists_readable_android_directory_using_helper_method(self, mock_api):
output = o

expected_outut = """Type Last Modified Read Write Hidden Size Name
------ ----------------------- ------ ------- -------- ------- ------
File 2017-10-05 07:36:41 GMT True True False 249.0 B test
------ ----------------------- ------ ------- -------- ------- ------
File 2017-10-05 07:36:41 GMT True True False 249.0 B test

Readable: True Writable: True
"""
Readable: True Writable: True
"""

self.assertEqual(output, expected_outut)
self.assertEqual(
normalize_table_whitespace(output),
normalize_table_whitespace(expected_outut),
)

@mock.patch('objection.state.connection.state_connection.get_api')
def test_lists_unreadable_android_directory_using_helper_method(self, mock_api):
Expand Down Expand Up @@ -407,7 +416,7 @@ def test_downloads_file_with_ios_helper(self, mock_open, mock_api):

file_manager_state.cwd = '/foo'

with capture(_download_ios, '/foo', '/bar') as o:
with capture(_download_ios, '/foo', '/bar', False) as o:
output = o

expected_output = """Downloading /foo to /bar
Expand All @@ -423,7 +432,7 @@ def test_downloads_file_with_ios_helper(self, mock_open, mock_api):
def test_downloads_file_but_fails_on_unreadable_with_ios_helper(self, mock_api):
mock_api.return_value.ios_file_readable.return_value = False

with capture(_download_ios, '/foo', '/bar') as o:
with capture(_download_ios, '/foo', '/bar', False) as o:
output = o

self.assertEqual(output, 'Downloading /foo to /bar\nUnable to download file. File is not readable.\n')
Expand All @@ -433,10 +442,10 @@ def test_downloads_file_but_fails_on_file_type_with_ios_helper(self, mock_api):
mock_api.return_value.ios_file_readable.return_value = True
mock_api.return_value.ios_file_path_is_file.return_value = False

with capture(_download_ios, '/foo', '/bar') as o:
with capture(_download_ios, '/foo', '/bar', False) as o:
output = o

self.assertEqual(output, 'Downloading /foo to /bar\nUnable to download file. Target path is not a file.\n')
self.assertEqual(output, 'Downloading /foo to /bar\nTo download folders, specify --folder.\n')

@mock.patch('objection.state.connection.state_connection.get_api')
@mock.patch('objection.commands.filemanager.open', create=True)
Expand All @@ -447,7 +456,7 @@ def test_downloads_file_with_android_helper(self, mock_open, mock_api):

file_manager_state.cwd = '/foo'

with capture(_download_android, '/foo', '/bar') as o:
with capture(_download_android, '/foo', '/bar', False) as o:
output = o

expected = """Downloading /foo to /bar
Expand All @@ -466,7 +475,7 @@ def test_downloads_file_but_fails_on_unreadable_with_android_helper(self, mock_o

file_manager_state.cwd = '/foo'

with capture(_download_android, '/foo', '/bar') as o:
with capture(_download_android, '/foo', '/bar', False) as o:
output = o

self.assertFalse(mock_open.called)
Expand All @@ -480,11 +489,11 @@ def test_downloads_file_but_fails_on_file_type_with_android_helper(self, mock_op

file_manager_state.cwd = '/foo'

with capture(_download_android, '/foo', '/bar') as o:
with capture(_download_android, '/foo', '/bar', False) as o:
output = o

self.assertFalse(mock_open.called)
self.assertEqual(output, 'Downloading /foo to /bar\nUnable to download file. Target path is not a file.\n')
self.assertEqual(output, 'Downloading /foo to /bar\nTo download folders, specify --folder.\n')

def test_file_upload_method_proxy_validates_arguments(self):
with capture(upload, []) as o:
Expand Down
Loading
Loading