diff --git a/web_attachment_size_limit/__manifest__.py b/web_attachment_size_limit/__manifest__.py index e924e9b..365a972 100644 --- a/web_attachment_size_limit/__manifest__.py +++ b/web_attachment_size_limit/__manifest__.py @@ -4,7 +4,7 @@ { "name": "Web Attachment Size Limit", "summary": "Enforce a global maximum size for file uploads", - "version": "14.0.1.0.0", + "version": "14.0.1.0.1", "category": "Web", "website": "https://numigi.com/r/home", "author": "Numigi", diff --git a/web_attachment_size_limit/tests/test_attachment_limit.py b/web_attachment_size_limit/tests/test_attachment_limit.py index 3ad4c59..7f22311 100644 --- a/web_attachment_size_limit/tests/test_attachment_limit.py +++ b/web_attachment_size_limit/tests/test_attachment_limit.py @@ -1,106 +1,80 @@ # © Numigi (tm) and all its contributors (https://numigi.com/r/home) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -# import io -# import json -# from odoo.tests.common import HttpCase, tagged -# -# -# @tagged('post_install', '-at_install') -# class TestAttachmentSizeLimit(HttpCase): -# -# def setUp(self): -# super(TestAttachmentSizeLimit, self).setUp() -# # Authenticate as admin to have upload rights and establish session -# self.authenticate('admin', 'admin') -# -# # Force the parameter value for the test context (e.g., 100 bytes) -# self.env['ir.config_parameter'].sudo().set_param( -# 'web.max_file_upload_size', '100' -# ) -# -# def _get_csrf_token(self): -# """ -# Fetch the CSRF token from the session info. -# This is required for controllers protected by -# @http.route(..., csrf=True) -# """ -# # We invoke get_session_info via JSON-RPC to retrieve the token -# # url_open does not support 'json' param in this env, so we serialize manually -# response = self.url_open( -# '/web/session/get_session_info', -# data=json.dumps({}), -# headers={'Content-Type': 'application/json'} -# ) -# return response.json().get('result', {}).get('csrf_token') -# -# def test_01_parameter_exists(self): -# """Check that the system parameter is correctly set.""" -# param = self.env['ir.config_parameter'].sudo().get_param( -# 'web.max_file_upload_size' -# ) -# self.assertEqual( -# param, '100', "The parameter value should be 100 for this test." -# ) -# -# def test_02_upload_too_large(self): -# """Try to upload a file of 200 bytes (limit is 100). Should fail.""" -# -# csrf_token = self._get_csrf_token() -# -# file_content = b'x' * 200 -# files = { -# 'ufile': ('big_file.txt', io.BytesIO(file_content), 'text/plain'), -# } -# # We must send the model/id and CSRF token as data fields -# data = { -# 'model': 'res.users', -# 'id': str(self.env.user.id), -# 'csrf_token': csrf_token -# } -# -# # Note: /web/binary/upload_attachment is the standard upload URL -# # We use relative URL '/web/...' so Odoo uses the correct testing port -# response = self.url_open( -# '/web/binary/upload_attachment', data=data, files=files -# ) -# -# response_content = response.content.decode('utf-8') -# -# self.assertIn( -# "File too large", -# response_content, -# "The upload should have been blocked with an error message. " -# "Response: %s" % response_content -# ) -# -# def test_03_upload_success(self): -# """Try to upload a file of 50 bytes (limit is 100). Should succeed.""" -# -# csrf_token = self._get_csrf_token() -# -# file_content = b'x' * 50 -# files = { -# 'ufile': ( -# 'small_file.txt', io.BytesIO(file_content), 'text/plain' -# ), -# } -# data = { -# 'model': 'res.users', -# 'id': str(self.env.user.id), -# 'csrf_token': csrf_token -# } -# -# response = self.url_open( -# '/web/binary/upload_attachment', data=data, files=files -# ) -# response_content = response.content.decode('utf-8') -# -# self.assertNotIn( -# "error", response_content, -# "Valid upload should not return an error." -# ) -# self.assertIn( -# "small_file.txt", response_content, -# "The filename should be in the response." -# ) +import io +import json + +from odoo.tests.common import HttpCase, tagged + + +@tagged('-at_install', 'post_install') +class TestAttachmentSizeLimit(HttpCase): + """Tests HTTP for web_attachment_size_limit""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + + # Définir une limite faible pour les tests (100 bytes) + cls.env['ir.config_parameter'].sudo().set_param( + 'web_attachment_size_limit.max_upload_size', '100' + ) + + # Utilisateur courant + cls.user = cls.env.user + + def _upload_file(self, content: bytes, filename='test.txt'): + """Helper pour uploader un fichier via le contrôleur web""" + files = { + 'ufile': (filename, io.BytesIO(content), 'text/plain'), + } + data = { + 'model': 'res.users', + 'id': str(self.user.id), + } + + return self.url_open( + '/web/binary/upload_attachment', + data=data, + files=files, + ) + + def test_02_upload_too_large(self): + """Upload > limite (200 bytes). Doit échouer.""" + file_content = b'x' * 200 + + response = self._upload_file( + file_content, + filename='too_big.txt' + ) + + self.assertEqual( + response.status_code, 413, + 'Upload should be rejected with HTTP 413' + ) + + payload = json.loads(response.text) + self.assertIn('error', payload) + self.assertIn('exceeds', payload['error'].lower()) + + def test_03_upload_success(self): + """Upload < limite (50 bytes). Doit réussir.""" + file_content = b'x' * 50 + + response = self._upload_file( + file_content, + filename='small_file.txt' + ) + + self.assertEqual( + response.status_code, 200, + 'Upload should succeed' + ) + + payload = json.loads(response.text) + self.assertIn('id', payload) + + attachment = self.env['ir.attachment'].browse(payload['id']) + self.assertTrue(attachment.exists()) + self.assertEqual(attachment.res_model, 'res.users') + self.assertEqual(attachment.res_id, self.user.id) diff --git a/web_visual_company_switcher/controllers/company_switcher.py b/web_visual_company_switcher/controllers/company_switcher.py index 1aca275..ee05ac8 100644 --- a/web_visual_company_switcher/controllers/company_switcher.py +++ b/web_visual_company_switcher/controllers/company_switcher.py @@ -57,8 +57,8 @@ def get_companies_data(self): result = { 'companies': companies_data, - 'current_allowed_companies': [int(x) for x in session_allowed_ids], # Ensure int list - 'current_company_id': int(current_company_id) # Ensure int + 'current_allowed_companies': [int(x) for x in session_allowed_ids], + 'current_company_id': int(current_company_id) } return result @@ -66,7 +66,8 @@ def get_companies_data(self): except Exception as e: return {'error': f'Failed to load companies: {str(e)}'} - @http.route('/web/visual_company_switcher/switch_company', type='json', auth='user', csrf=True) + @http.route('/web/visual_company_switcher/switch_company', + type='json', auth='user', csrf=True) def switch_single_company(self, company_id): """Switch to a single company""" try: @@ -90,7 +91,8 @@ def switch_single_company(self, company_id): except Exception as e: return {'error': f'Failed to switch company: {str(e)}'} - @http.route('/web/visual_company_switcher/switch_companies', type='json', auth='user', csrf=True) + @http.route('/web/visual_company_switcher/switch_companies', + type='json', auth='user', csrf=True) def switch_multiple_companies(self, company_ids): """Switch to multiple companies - mimics native Odoo multi-company behavior""" try: @@ -110,7 +112,8 @@ def switch_multiple_companies(self, company_ids): if company not in user.company_ids: return {'error': f'Access denied to company {company.name}'} - # This is the key: set allowed_company_ids in session to enable multi-company context + # This is the key: set allowed_company_ids in session + # to enable multi-company context # This mimics exactly what Odoo's native company switcher does request.session['allowed_company_ids'] = company_ids