from base64 import b64encode from django.core.files import File from django.urls import reverse from rest_framework.test import APIClient from winget.models import Package, Version, Installer from winget.tests.util import TestCaseThatUploadsFiles, \ MultiUploadInMemoryFile, create_tenant, get_file_contents import json class APITest(TestCaseThatUploadsFiles): def setUp(self): super().setUp() self.client = APIClient() self.credentials = ('user', 'pw') self.tenant = create_tenant(*self.credentials) self.other_credentials = ('user2', 'pw2') create_tenant(*self.other_credentials) def test_create_package(self): response = self._check_unauthorized_and_request( 'post', 'package-list', _PACKAGE_PAYLOAD ) self.assertEqual(201, response.status_code) package = Package.objects.get(id=response.json()['id']) self._assert_dict_obj_equal(_PACKAGE_PAYLOAD, package) self.assertEqual(self.tenant, package.tenant) return package def test_create_version(self, use_correct_credentials=True): package = self.test_create_package() payload = dict(_VERSION_PAYLOAD) payload['package'] = package.id response = self._check_unauthorized_and_request( 'post', 'version-list', payload, use_correct_credentials ) expected_status = 201 if use_correct_credentials else 400 self.assertEqual(expected_status, response.status_code) if use_correct_credentials: version = Version.objects.get(id=response.json()['id']) self._assert_dict_obj_equal(payload, version) return version def test_create_installer(self, use_correct_credentials=True, **kwargs): version = self.test_create_version() payload = dict(_INSTALLER_PAYLOAD) payload.update(kwargs) payload['version'] = version.id response = self._check_unauthorized_and_request( 'post', 'installer-list', payload, use_correct_credentials ) expected_status = 201 if use_correct_credentials else 400 self.assertEqual(expected_status, response.status_code) if use_correct_credentials: installer = Installer.objects.get(id=response.json()['id']) self._assert_dict_obj_equal(payload, installer) self.assertTrue(installer.sha256.startswith('6b86b273')) return installer def test_create_nested_installer(self): version = self.test_create_version() payload = dict(_INSTALLER_PAYLOAD) payload['version'] = version.id def send_payload(modifications=None): modified_payload = dict(payload) modified_payload.update(modifications or {}) return self._check_unauthorized_and_request( 'post', 'installer-list', modified_payload ) def check_errors(payload_modifications, expected_errors): response = send_payload(payload_modifications) self.assertEqual(400, response.status_code) self.assertEqual(expected_errors, response.json()) check_errors({'nested_installer': 'test.exe'}, { 'nested_installer': [ 'Nested installer can only be set when type is "zip".' ] }) check_errors({'nested_installer_type': 'inno'}, { 'nested_installer_type': [ 'Nested installer type can only be set when type is "zip".' ] }) payload['type'] = 'zip' check_errors({}, { 'nested_installer': [ 'Nested installer is required when type is "zip".' ], 'nested_installer_type': [ 'Nested installer type is required when type is "zip".' ] }) check_errors({'nested_installer': 'nested.exe'}, { 'nested_installer_type': [ 'Nested installer type is required when type is "zip".' ] }) check_errors({'nested_installer_type': 'exe'}, { 'nested_installer': [ 'Nested installer is required when type is "zip".' ] }) payload['nested_installer'] = 'nested.msi' check_errors({'nested_installer_type': 'WRONG'}, { 'nested_installer_type': ['"WRONG" is not a valid choice.'] }) payload['nested_installer_type'] = 'msi' response = send_payload() self.assertEqual(201, response.status_code) installer = Installer.objects.get(id=response.json()['id']) self._assert_dict_obj_equal(payload, installer) self.assertTrue(installer.sha256.startswith('6b86b273')) def test_edit_package(self, use_correct_credentials=True): package = self.test_create_package() payload = dict(_PACKAGE_PAYLOAD) payload['name'] = 'new name' response = self._check_unauthorized_and_request( 'put', 'package-detail', payload, use_correct_credentials, {'pk': package.id} ) expected_status = 200 if use_correct_credentials else 404 self.assertEqual( expected_status, response.status_code, response.content ) if use_correct_credentials: package.refresh_from_db() self._assert_dict_obj_equal(payload, package) def test_cannot_edit_other_tenants_packages(self): self.test_edit_package(use_correct_credentials=False) def test_edit_version(self, use_correct_credentials=True): version = self.test_create_version() payload = { 'version': '0.0.2', 'package': version.package.id } response = self._check_unauthorized_and_request( 'put', 'version-detail', payload, use_correct_credentials, {'pk': version.id} ) expected_status = 200 if use_correct_credentials else 404 self.assertEqual( expected_status, response.status_code, response.content ) if use_correct_credentials: version.refresh_from_db() self._assert_dict_obj_equal(payload, version) def test_cannot_edit_other_tenants_versions(self): self.test_edit_version(use_correct_credentials=False) def test_edit_installer(self, use_correct_credentials=True): installer = self.test_create_installer() payload = dict(_INSTALLER_PAYLOAD) payload['version'] = installer.version.id payload['file'] = MultiUploadInMemoryFile('file2.exe', b'2') response = self._check_unauthorized_and_request( 'put', 'installer-detail', payload, use_correct_credentials, {'pk': installer.id} ) expected_status = 200 if use_correct_credentials else 404 self.assertEqual( expected_status, response.status_code, response.content ) if use_correct_credentials: installer.refresh_from_db() self._assert_dict_obj_equal(payload, installer) self.assertTrue( installer.sha256.startswith('d4735e3a'), installer.sha256 ) def test_installer_switch_silent(self): self.test_create_installer(**{'silent_switch': '/ABC'}) def test_installer_switch_silent_progress(self): self.test_create_installer(**{'silent_progress_switch': '/ABC'}) def test_installer_switch_interactive(self): self.test_create_installer(**{'interactive_switch': '/ABC'}) def test_installer_switch_install_location(self): self.test_create_installer(**{'install_location_switch': '/ABC'}) def test_installer_switch_log(self): self.test_create_installer(**{'log_switch': '/ABC'}) def test_installer_switch_upgrade(self): self.test_create_installer(**{'upgrade_switch': '/ABC'}) def test_installer_switch_custom(self): self.test_create_installer(**{'custom_switch': '/ABCq'}) def test_cannot_edit_other_tenants_installers(self): self.test_edit_installer(use_correct_credentials=False) def test_cannot_create_version_for_other_tenants_package(self): self.test_create_version(use_correct_credentials=False) def test_cannot_create_installer_for_other_tenants_version(self): self.test_create_installer(use_correct_credentials=False) def _check_unauthorized_and_request( self, method, view_name, data, use_correct_credentials=True, view_kwargs=None ): self._check_unauthorized(method, view_name, data, view_kwargs) credentials = self.credentials if use_correct_credentials \ else self.other_credentials return self._request( method, view_name, data, credentials, view_kwargs ) def _check_unauthorized(self, method, view_name, data, view_kwargs=None): for credentials in ( None, ('wrong', 'wrong'), (self.credentials[0], 'wrong'), ('wrong', self.credentials[1]) ): response = \ self._request(method, view_name, data, credentials, view_kwargs) self.assertEqual(403, response.status_code) def _request( self, method, view_name, data, credentials=None, view_kwargs=None ): args, kwargs = self._get_request_args_kwargs( view_name, data, credentials, view_kwargs ) return getattr(self.client, method)(*args, **kwargs) def _get_request_args_kwargs( self, view_name, data, credentials=None, view_kwargs=None ): kwargs = {} if credentials: kwargs['HTTP_AUTHORIZATION'] = \ 'Basic ' + b64encode(':'.join(credentials).encode()).decode() if any(isinstance(v, File) for v in data.values()): kwargs['format'] = 'multipart' else: kwargs['content_type'] = 'application/json' data = json.dumps(data) url = reverse(f'winget:{view_name}', kwargs=view_kwargs) return (url, data), kwargs def _assert_dict_obj_equal(self, d, o): for key, expected in d.items(): actual = getattr(o, key) if isinstance(expected, File): expected = get_file_contents(expected) actual = get_file_contents(actual) try: actual = actual.pk except AttributeError: pass self.assertEqual(expected, actual) _PACKAGE_PAYLOAD = { 'identifier': 'XP9KHM4BK9FZ7Q', 'name': 'Visual Studio Code', 'publisher': 'Microsoft', 'description': 'Free code editor.' } _VERSION_PAYLOAD = { 'version': '0.0.1' } _INSTALLER_PAYLOAD = { 'architecture': 'x64', 'type': 'exe', 'scope': 'user', 'file': MultiUploadInMemoryFile('file.exe', b'1') }