1# Copyright (C) 2016-2020 by the Free Software Foundation, Inc. 2# 3# This file is part of GNU Mailman. 4# 5# GNU Mailman is free software: you can redistribute it and/or modify it under 6# the terms of the GNU General Public License as published by the Free 7# Software Foundation, either version 3 of the License, or (at your option) 8# any later version. 9# 10# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT 11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 13# more details. 14# 15# You should have received a copy of the GNU General Public License along with 16# GNU Mailman. If not, see <https://www.gnu.org/licenses/>. 17 18"""Test REST header matches.""" 19 20import unittest 21 22from mailman.app.lifecycle import create_list 23from mailman.database.transaction import transaction 24from mailman.interfaces.mailinglist import IHeaderMatchList 25from mailman.testing.helpers import call_api 26from mailman.testing.layers import RESTLayer 27from urllib.error import HTTPError 28 29 30class TestHeaderMatches(unittest.TestCase): 31 layer = RESTLayer 32 33 def setUp(self): 34 with transaction(): 35 self._mlist = create_list('ant@example.com') 36 37 def test_get_missing_header_match(self): 38 with self.assertRaises(HTTPError) as cm: 39 call_api('http://localhost:9001/3.0/lists/ant.example.com' 40 '/header-matches/0') 41 self.assertEqual(cm.exception.code, 404) 42 self.assertEqual(cm.exception.reason, 43 'No header match at this position: 0') 44 45 def test_delete_missing_header_match(self): 46 with self.assertRaises(HTTPError) as cm: 47 call_api('http://localhost:9001/3.0/lists/ant.example.com' 48 '/header-matches/0', 49 method='DELETE') 50 self.assertEqual(cm.exception.code, 404) 51 self.assertEqual(cm.exception.reason, 52 'No header match at this position: 0') 53 54 def test_add_duplicate(self): 55 header_matches = IHeaderMatchList(self._mlist) 56 with transaction(): 57 header_matches.append('header', 'pattern') 58 with self.assertRaises(HTTPError) as cm: 59 call_api('http://localhost:9001/3.0/lists/ant.example.com' 60 '/header-matches', { 61 'header': 'header', 62 'pattern': 'pattern', 63 }, method='POST') 64 self.assertEqual(cm.exception.code, 400) 65 self.assertEqual(cm.exception.reason, 66 'This header match already exists') 67 68 def test_header_match_on_missing_list(self): 69 with self.assertRaises(HTTPError) as cm: 70 call_api('http://localhost:9001/3.0/lists/bee.example.com' 71 '/header-matches/') 72 self.assertEqual(cm.exception.code, 404) 73 74 def test_add_bad_regexp(self): 75 with self.assertRaises(HTTPError) as cm: 76 call_api('http://localhost:9001/3.0/lists/ant.example.com' 77 '/header-matches', { 78 'header': 'header', 79 'pattern': '+invalid', 80 }) 81 self.assertEqual(cm.exception.code, 400) 82 self.assertEqual( 83 cm.exception.reason, 84 'Invalid Parameter "pattern":' 85 ' Expected a valid regexp, got +invalid.') 86 87 def test_patch_bad_regexp(self): 88 header_matches = IHeaderMatchList(self._mlist) 89 with transaction(): 90 header_matches.append('header', 'pattern') 91 with self.assertRaises(HTTPError) as cm: 92 call_api('http://localhost:9001/3.0/lists/ant.example.com' 93 '/header-matches/0', { 94 'header': 'header', 95 'pattern': '+invalid', 96 }, method='PATCH') 97 self.assertEqual(cm.exception.code, 400) 98 self.assertEqual( 99 cm.exception.reason, 100 'Invalid Parameter "pattern":' 101 ' Expected a valid regexp, got +invalid.') 102 self.assertEqual(cm.exception.reason, 103 'Invalid Parameter "pattern": ' 104 'Expected a valid regexp, got +invalid.') 105 106 def test_add_header_match(self): 107 _, resp = call_api('http://localhost:9001/3.0/lists/ant.example.com' 108 '/header-matches', { 109 'header': 'header-1', 110 'pattern': '^Yes', 111 'action': 'hold', 112 'tag': 'tag1', 113 }, 114 method='POST') 115 self.assertEqual(resp.status_code, 201) 116 header_matches = IHeaderMatchList(self._mlist) 117 self.assertEqual( 118 [(match.header, match.pattern, match.chain, match.tag) 119 for match in header_matches], 120 [('header-1', '^Yes', 'hold', 'tag1')]) 121 122 def test_add_header_match_with_no_action(self): 123 _, resp = call_api('http://localhost:9001/3.0/lists/ant.example.com' 124 '/header-matches', { 125 'header': 'header-1', 126 'pattern': '^Yes', 127 'action': '', 128 'tag': 'tag1', 129 }, 130 method='POST') 131 self.assertEqual(resp.status_code, 201) 132 header_matches = IHeaderMatchList(self._mlist) 133 self.assertEqual( 134 [(match.header, match.pattern, match.chain, match.tag) 135 for match in header_matches], 136 [('header-1', '^Yes', None, 'tag1')]) 137 138 def test_update_header_match_with_action(self): 139 header_matches = IHeaderMatchList(self._mlist) 140 with transaction(): 141 header_matches.append('header-1', '^Yes', 'hold', 'tag1') 142 _, resp = call_api('http://localhost:9001/3.0/lists/ant.example.com' 143 '/header-matches/0', { 144 'action': '' 145 }, 146 method='PATCH') 147 self.assertEqual(resp.status_code, 204) 148 self.assertEqual( 149 [(match.header, match.pattern, match.chain, match.tag) 150 for match in header_matches], 151 [('header-1', '^Yes', None, 'tag1')]) 152 153 def test_update_header_match_with_no_action(self): 154 header_matches = IHeaderMatchList(self._mlist) 155 with transaction(): 156 header_matches.append('header-1', '^Yes', 'hold', 'tag1') 157 _, resp = call_api('http://localhost:9001/3.0/lists/ant.example.com' 158 '/header-matches/0', { 159 'pattern': '^No' 160 }, 161 method='PATCH') 162 self.assertEqual(resp.status_code, 204) 163 self.assertEqual( 164 [(match.header, match.pattern, match.chain, match.tag) 165 for match in header_matches], 166 [('header-1', '^No', 'hold', 'tag1')]) 167 168 def test_get_header_match_by_tag(self): 169 header_matches = IHeaderMatchList(self._mlist) 170 with transaction(): 171 header_matches.append('header-1', 'pattern-1') 172 header_matches.append( 173 'header-2', 'pattern-2', chain='hold', tag='tag') 174 header_matches.append('header-3', 'pattern-3', chain='accept') 175 176 content, resp = call_api( 177 'http://localhost:9001/3.0/lists/ant.example.com' 178 '/header-matches/find', {'tag': 'tag'} 179 ) 180 self.assertEqual(resp.status_code, 200) 181 self.assertIsNotNone(content) 182 self.assertEqual(len(content['entries']), 1) 183 self.assertEqual(content['entries'][0]['header'], 'header-2') 184 self.assertEqual(content['entries'][0]['pattern'], 'pattern-2') 185 self.assertEqual(content['entries'][0]['action'], 'hold') 186 187 def test_get_header_match_empty(self): 188 with self.assertRaises(HTTPError) as cm: 189 call_api('http://localhost:9001/3.0/lists/ant.example.com' 190 '/header-matches/find', {'tag': 'tag'}) 191 self.assertEqual(cm.exception.code, 404) 192 self.assertEqual( 193 cm.exception.reason, 194 'Cound not find any HeaderMatch for provided search options.') 195