import json import os from pathlib import Path import tempfile import unittest from test_catalog_api import fixture, load_service class BannerTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory(dir=os.environ.get('TMPDIR')) self.path = fixture(self.temp.name) self.api = load_service() self.catalog = self.api.Catalog(self.path) def tearDown(self): self.catalog.close() self.temp.cleanup() def test_local_config_reloads_safe_bounded_ads_without_admin_endpoint(self): config = Path(self.temp.name) / 'banners.json' self.catalog.close() self.catalog = self.api.Catalog(self.path, banner_config=config) self.assertEqual(self.catalog.dispatch('/api/banners/'), {'banners': []}) ad = {'title_en': 'Fixture sponsor', 'title_fa': 'حامی آزمایشی', 'subtitle_en': 'Test only', 'target_url': 'https://sponsor.example/offer', 'image_url': 'https://sponsor.example/banner.jpg', 'secret': 'not public'} config.write_text(json.dumps({'banners': [ad]})) result = self.catalog.dispatch('/api/banners/')['banners'] self.assertEqual(len(result), 1) self.assertEqual(result[0]['title_fa'], ad['title_fa']) self.assertEqual(result[0]['image_url'], ad['image_url']) self.assertNotIn('secret', result[0]) config.write_text('{invalid') self.assertEqual(self.catalog.dispatch('/api/banners/'), {'banners': []}) for target in ['http://evil.example/', 'javascript:alert(1)', '//evil.example/', 'https://a:b@evil.example/', 'https://evil.example:444/', 'https://evil.example/ bad', 'https://evil.example/\\\\bad', 'https://evil.example/%0abad', 'https://', 'https://-bad.example/', 'https://bad..example/', 'https://bad.example./', 'https://evil.example/%zz', 'https://evil.example/' + 'x' * 2048]: config.write_text(json.dumps({'banners': [{**ad, 'target_url': target}, ad]})) self.assertEqual(len(self.catalog.dispatch('/api/banners/')['banners']), 1, target) for row in [None, True, 5, {}, {**ad, 'title_en': 'x' * 81}, {**ad, 'subtitle_en': 'x' * 181}, {**ad, 'title_fa': 'bad\u202etext'}, {**ad, 'image_url': 'http://evil.example/banner.jpg'}]: config.write_text(json.dumps({'banners': [row, ad]})) self.assertEqual(len(self.catalog.dispatch('/api/banners/')['banners']), 1, row) config.write_text(json.dumps({'banners': [ad] * 30})) self.assertEqual(len(self.catalog.dispatch('/api/banners/')['banners']), 5) config.write_text(json.dumps({'banners': [{}] * 20 + [ad]})) self.assertEqual(self.catalog.dispatch('/api/banners/'), {'banners': []}) config.write_text(' ' * 65537) self.assertEqual(self.catalog.dispatch('/api/banners/'), {'banners': []}) for value in [None, [], {'banners': {}}, {'banners': []}]: config.write_text(json.dumps(value)) self.assertEqual(self.catalog.dispatch('/api/banners/'), {'banners': []}) for route in ['/api/banners/admin/', '/api/banners/config/']: with self.assertRaises(self.api.ApiError) as error: self.catalog.dispatch(route) self.assertEqual(error.exception.status, 404) def test_http_banner_endpoint_is_json_no_store_and_rejects_writes(self): from http.client import HTTPConnection import threading server = self.api.make_server(self.catalog, port=0) worker = threading.Thread(target=server.serve_forever, daemon=True) worker.start() try: for method, expected in [('GET', 200), ('HEAD', 200), ('POST', 405), ('PUT', 405)]: client = HTTPConnection('127.0.0.1', server.server_port, timeout=5) client.request(method, '/api/banners/') response = client.getresponse() self.assertEqual(response.status, expected) self.assertEqual(response.getheader('Cache-Control'), 'no-store') self.assertIn('application/json', response.getheader('Content-Type')) body = response.read() if method == 'GET': self.assertEqual(json.loads(body), {'banners': []}) if method == 'HEAD': self.assertEqual(body, b'') client.close() finally: server.shutdown() server.server_close() worker.join(timeout=5) def test_read_only_banner_route_defaults_to_empty_ad_list(self): self.assertEqual(self.catalog.dispatch('/api/banners/'), {'banners': []}) self.assertEqual(self.catalog.dispatch('/api/banners'), {'banners': []}) with self.assertRaises(self.api.ApiError) as error: self.catalog.dispatch('/api/banners/?url=https://evil.example/') self.assertEqual(error.exception.status, 400) if __name__ == '__main__': unittest.main()