mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-01 10:22:08 +00:00
169 lines
7.5 KiB
Python
169 lines
7.5 KiB
Python
import contextlib
|
|
import io
|
|
from pathlib import Path
|
|
import sys
|
|
from types import SimpleNamespace
|
|
import unittest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "src"))
|
|
sys.path.insert(0, str(ROOT / "remote"))
|
|
from proxmenux_oci import registry_accounts as accounts
|
|
|
|
|
|
class Cancelled(RuntimeError):
|
|
pass
|
|
|
|
|
|
class UI:
|
|
def __init__(self, choices, answers=(), password='MENU_TEST_TOKEN'):
|
|
self.choices = iter(choices)
|
|
self.answers = iter(answers)
|
|
self.password_value = password
|
|
self.seen = []
|
|
|
|
def choose(self, text, options, default=None, **kwargs):
|
|
self.seen.append((text, options, kwargs))
|
|
return next(self.choices)
|
|
|
|
def ask(self, text, default=None):
|
|
self.seen.append(text)
|
|
return next(self.answers)
|
|
|
|
def password(self, text):
|
|
self.seen.append(text)
|
|
if isinstance(self.password_value, Exception):
|
|
raise self.password_value
|
|
return self.password_value
|
|
|
|
def message(self, text, title=None):
|
|
self.seen.append((title, text))
|
|
|
|
def info(self, text, title=None):
|
|
self.seen.append((title, text))
|
|
|
|
def confirm(self, text, default=False, **kwargs):
|
|
self.seen.append(text)
|
|
return False
|
|
|
|
|
|
class RegistryMenuTests(unittest.TestCase):
|
|
def run_menu(self, ui, auth, extra_modules=None):
|
|
modules = {'proxmenux_oci.ui': SimpleNamespace(UserCancelled=Cancelled),
|
|
'proxmenux_oci.host': SimpleNamespace(storages=lambda content: [{'storage': 'local', 'avail': 10**9}])}
|
|
modules.update(extra_modules or {})
|
|
with patch.object(accounts, 'require_root'), patch.object(accounts.importlib, 'import_module', side_effect=lambda name: modules[name]):
|
|
accounts.registry_accounts_menu(ui, engine='/fake/engine', auth=auth)
|
|
|
|
def account(self):
|
|
return {'registry': 'ghcr.io', 'account': 'example', 'method': 'Username / token', 'status': 'Saved'}
|
|
|
|
def auth(self, rows=None):
|
|
return SimpleNamespace(accounts=MagicMock(return_value=rows or []), login=MagicMock(), logout=MagicMock())
|
|
|
|
def test_native_menu_can_add_an_account_without_exposing_the_token(self):
|
|
auth = self.auth()
|
|
ui = UI(['add', None], ['ghcr.io', 'example'])
|
|
self.run_menu(ui, auth)
|
|
auth.login.assert_called_once_with('ghcr.io', 'example', 'MENU_TEST_TOKEN')
|
|
self.assertNotIn('MENU_TEST_TOKEN', repr(ui.seen))
|
|
|
|
def test_cancelled_password_does_not_change_credentials(self):
|
|
auth = self.auth()
|
|
ui = UI(['add', None], ['ghcr.io', 'example'], password=Cancelled())
|
|
self.run_menu(ui, auth)
|
|
auth.login.assert_not_called()
|
|
|
|
def test_invalid_authority_cannot_reach_login(self):
|
|
auth = self.auth()
|
|
ui = UI(['add', None], ['https://ghcr.io'])
|
|
self.run_menu(ui, auth)
|
|
auth.login.assert_not_called()
|
|
self.assertIn('registry hostname', repr(ui.seen))
|
|
|
|
def test_account_can_download_through_the_shared_native_cache_helper(self):
|
|
auth = self.auth([self.account()])
|
|
ui = UI(['account-0', 'download', 'local', None], ['ghcr.io/example/private:latest'])
|
|
result = {'archive': '/cache/proxmenux-update-amd64-fixture.tar', 'size': 1024}
|
|
with patch.object(accounts, 'download_registry_image', return_value=result) as download:
|
|
self.run_menu(ui, auth)
|
|
download.assert_called_once_with('ghcr.io/example/private:latest', 'local')
|
|
self.assertIn('Image downloaded and verified', repr(ui.seen))
|
|
|
|
def test_selected_account_does_not_pull_from_another_registry(self):
|
|
auth = self.auth([self.account()])
|
|
ui = UI(['account-0', 'download', None], ['quay.io/example/private:latest'])
|
|
with patch.object(accounts, 'download_registry_image') as download:
|
|
self.run_menu(ui, auth)
|
|
download.assert_not_called()
|
|
|
|
def test_logout_requires_confirmation_in_the_menu(self):
|
|
auth = self.auth([self.account()])
|
|
ui = UI(['account-0', 'logout', None])
|
|
self.run_menu(ui, auth)
|
|
auth.logout.assert_not_called()
|
|
|
|
def test_menu_offers_install_download_and_credential_management(self):
|
|
auth = self.auth([self.account()])
|
|
ui = UI(['account-0', None, None])
|
|
self.run_menu(ui, auth)
|
|
actions = ui.seen[1][1]
|
|
self.assertEqual([tag for tag, label in actions], ['inspect', 'download', 'install', 'login', 'logout'])
|
|
|
|
def test_menu_preserves_the_configured_language_when_an_operation_fails(self):
|
|
from proxmenux_oci import i18n
|
|
cache = {'Saved': 'Guardado'}
|
|
with patch.object(i18n, '_language', 'es'), patch.object(i18n, '_cache', cache), \
|
|
patch.object(accounts, 'registry_accounts_menu', side_effect=RuntimeError('test')):
|
|
with self.assertRaises(RuntimeError):
|
|
accounts.open_registry_accounts(UI([]))
|
|
self.assertEqual(i18n._language, 'es')
|
|
self.assertIs(i18n._cache, cache)
|
|
|
|
def test_native_main_menu_dispatches_to_registry_accounts(self):
|
|
from proxmenux_oci import cli
|
|
catalog = SimpleNamespace(index_path=SimpleNamespace(exists=lambda: True),
|
|
load_index=lambda: {'applications': []})
|
|
ui = UI(['registries', None])
|
|
with patch.object(cli, 'interactive_ui', return_value=ui), \
|
|
patch.object(accounts, 'open_registry_accounts') as open_accounts:
|
|
self.assertEqual(cli.interactive(catalog), 0)
|
|
open_accounts.assert_called_once_with(ui)
|
|
|
|
def test_terminal_fallback_can_sign_in_without_exposing_the_password(self):
|
|
from proxmenux_oci.ui import TerminalUI
|
|
auth = self.auth()
|
|
output = io.StringIO()
|
|
with patch.object(accounts, 'require_root'), \
|
|
patch('builtins.input', side_effect=['1', 'registry.test', 'example', '0']), \
|
|
patch('getpass.getpass', return_value='MENU_TEST_TOKEN'), \
|
|
contextlib.redirect_stdout(output):
|
|
accounts.registry_accounts_menu(TerminalUI(), auth=auth)
|
|
auth.login.assert_called_once_with('registry.test', 'example', 'MENU_TEST_TOKEN')
|
|
self.assertIn('Signed in to registry.test', output.getvalue())
|
|
self.assertNotIn('MENU_TEST_TOKEN', output.getvalue())
|
|
|
|
def test_startup_configuration_errors_return_a_message_to_the_main_menu(self):
|
|
from proxmenux_oci.registry_auth import RegistryError
|
|
ui = UI([])
|
|
with patch.object(accounts, 'registry_accounts_menu', side_effect=RegistryError('Invalid auth path')):
|
|
accounts.open_registry_accounts(ui)
|
|
self.assertIn('Invalid auth path', repr(ui.seen))
|
|
|
|
def test_private_image_install_uses_the_existing_native_lxc_wizard(self):
|
|
auth = self.auth([self.account()])
|
|
ui = UI(['account-0', 'install', 'default', None], ['ghcr.io/example/private:latest'])
|
|
template = {'id': 'private-app', 'compatibility': {'automatic_install_candidate': True}}
|
|
install = MagicMock()
|
|
modules = {'proxmenux_oci.installer': SimpleNamespace(DEFAULT_MODE='default', ADVANCED_MODE='advanced'),
|
|
'proxmenux_oci.cli': SimpleNamespace(install_template=install)}
|
|
with patch.object(accounts, 'image_template', return_value=template), \
|
|
patch.object(accounts, 'registry_environment', contextlib.nullcontext):
|
|
self.run_menu(ui, auth, modules)
|
|
install.assert_called_once_with(ui, template, 'private-app', 'default')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|