#!/usr/bin/env python3 """ Tests for the security input validation on PUT /api/config. Validates that domain and cell_name fields reject injection characters while allowing legitimate values (multi-label domains, hyphens, etc.). """ import sys import json import unittest from pathlib import Path from unittest.mock import patch, MagicMock api_dir = Path(__file__).parent.parent / 'api' sys.path.insert(0, str(api_dir)) from app import app def _put(client, payload): return client.put( '/api/config', data=json.dumps(payload), content_type='application/json', ) class TestDomainValidation(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() def test_domain_with_newline_returns_400(self): r = _put(self.client, {'domain': 'cell\nnewline'}) self.assertEqual(r.status_code, 400) data = json.loads(r.data) self.assertIn('error', data) def test_domain_with_opening_brace_returns_400(self): r = _put(self.client, {'domain': 'cell{injection}'}) self.assertEqual(r.status_code, 400) data = json.loads(r.data) self.assertIn('error', data) def test_domain_with_semicolon_returns_400(self): r = _put(self.client, {'domain': 'cell;rm -rf /'}) self.assertEqual(r.status_code, 400) data = json.loads(r.data) self.assertIn('error', data) def test_domain_with_space_returns_400(self): r = _put(self.client, {'domain': 'my cell'}) self.assertEqual(r.status_code, 400) data = json.loads(r.data) self.assertIn('error', data) def test_domain_multilabel_with_dot_returns_200(self): # Multi-label names like 'cell.local' or 'home.lan' must be accepted. r = _put(self.client, {'domain': 'cell.local'}) # The endpoint may also return non-400 on 500 if downstream fails, # but the validation itself must not reject dots. self.assertNotEqual(r.status_code, 400) def test_domain_simple_word_returns_200(self): r = _put(self.client, {'domain': 'myhome'}) self.assertNotEqual(r.status_code, 400) def test_domain_with_hyphen_returns_200(self): r = _put(self.client, {'domain': 'my-cell'}) self.assertNotEqual(r.status_code, 400) def test_domain_with_at_sign_returns_400(self): r = _put(self.client, {'domain': 'cell@evil.com'}) self.assertEqual(r.status_code, 400) self.assertIn('error', json.loads(r.data)) def test_domain_with_slash_returns_400(self): r = _put(self.client, {'domain': 'cell/etc'}) self.assertEqual(r.status_code, 400) self.assertIn('error', json.loads(r.data)) class TestCellNameValidation(unittest.TestCase): def setUp(self): app.config['TESTING'] = True self.client = app.test_client() def test_cell_name_with_space_returns_400(self): r = _put(self.client, {'cell_name': 'my cell'}) self.assertEqual(r.status_code, 400) data = json.loads(r.data) self.assertIn('error', data) def test_cell_name_with_dot_returns_400(self): # cell_name is a single hostname component — dots are not allowed r = _put(self.client, {'cell_name': 'my.cell'}) self.assertEqual(r.status_code, 400) data = json.loads(r.data) self.assertIn('error', data) def test_cell_name_with_newline_returns_400(self): r = _put(self.client, {'cell_name': 'cell\nevil'}) self.assertEqual(r.status_code, 400) data = json.loads(r.data) self.assertIn('error', data) def test_cell_name_with_semicolon_returns_400(self): r = _put(self.client, {'cell_name': 'cell;drop'}) self.assertEqual(r.status_code, 400) data = json.loads(r.data) self.assertIn('error', data) def test_cell_name_valid_hyphenated_returns_200(self): r = _put(self.client, {'cell_name': 'valid-name'}) self.assertNotEqual(r.status_code, 400) def test_cell_name_simple_alpha_returns_200(self): r = _put(self.client, {'cell_name': 'mycell'}) self.assertNotEqual(r.status_code, 400) def test_cell_name_with_digits_returns_200(self): r = _put(self.client, {'cell_name': 'cell01'}) self.assertNotEqual(r.status_code, 400) def test_cell_name_with_brace_returns_400(self): r = _put(self.client, {'cell_name': 'cell{x}'}) self.assertEqual(r.status_code, 400) data = json.loads(r.data) self.assertIn('error', data) if __name__ == '__main__': unittest.main()