4 from unittest import TestCase
6 from datetime import datetime
10 GROUP_PREFIX = {'127.0.0.1:5000': {'group1': 'g1', 'group2': 'g2'}},
11 DURATION = {'127.0.0.1:5000':[3, 7, 14]}
14 class BasicTest(TestCase):
16 # functions to manipulate motions
17 def createVote(self, user, motion, vote):
19 '/motion/' + motion +'/vote',
20 environ_base={'USER_ROLES': user},
25 def createMotion(self, user, motiontitle, motioncontent, days, category):
28 environ_base={'USER_ROLES': user},
29 data=dict(title=motiontitle, content=motioncontent, days=days, category=category)
32 def cancelMotion(self, user, motion, reason):
34 '/motion/' + motion +'/cancel',
35 environ_base={'USER_ROLES': user},
36 data=dict(reason=reason)
39 def buildResultText(self, motiontext, yes, no, abstain):
40 return '<p>'+motiontext+'</p></p>\n <p>\nYes <span class=\"badge badge-pill badge-secondary\">'+str(yes)+'</span><br>'\
41 + '\nNo <span class=\"badge badge-pill badge-secondary\">'+str(no)+'</span><br>'\
42 + '\nAbstain <span class=\"badge badge-pill badge-secondary\">'+str(abstain)+'</span>'
44 # functions to clear database
46 with postgresql.open(app.config.get("DATABASE"), user=app.config.get("USER"), password=app.config.get("PASSWORD")) as db:
47 with app.open_resource('sql/schema.sql', mode='r') as f:
50 def db_sampledata(self):
51 with postgresql.open(app.config.get("DATABASE"), user=app.config.get("USER"), password=app.config.get("PASSWORD")) as db:
52 with app.open_resource('sql/sample_data.sql', mode='r') as f:
56 # no specific rights required
57 class GeneralTests(BasicTest):
60 app.config['TESTING'] = True
61 app.config['DEBUG'] = False
62 app.config.update(SERVER_NAME="127.0.0.1:5000")
69 self.app = app.test_client()
70 self.assertEqual(app.debug, False)
75 def test_main_page(self):
76 response = self.app.get('/', environ_base={'USER_ROLES': user}, follow_redirects=True)
77 self.assertEqual(response.status_code, 200)
79 def test_basic_results_data(self):
80 result = self.app.get('/', environ_base={'USER_ROLES': user}, follow_redirects=True)
82 #self.assertIn(str.encode('User: testuser'), result.data)
84 testtext= '<div class="motion card" id="motion-3">\n <div class="motion-title card-heading alert-warning">'\
85 + '\n <span class=\"title-text\">Motion C</span> (Canceled)\n <span class=\"motion-type\">group1</span>'\
86 + '\n <div># <a href=\"/motion/g1.20200402.003\" class=\"anchor\">g1.20200402.003</a></div>'\
87 + '\n <div class=\"date\">\n <div>Proposed: 2020-04-02 21:47:24 (UTC) by User A</div>'\
88 + '\n <div>Canceled: 2020-04-03 21:48:24 (UTC) by User A</div></div>\n </div>'\
89 + '\n <div class=\"card-body\">\n <p><p>A third motion</p></p>'\
90 + '\n <p>\nYes <span class=\"badge badge-pill badge-secondary\">1</span><br>'\
91 + '\nNo <span class=\"badge badge-pill badge-secondary\">0</span><br>'\
92 + '\nAbstain <span class=\"badge badge-pill badge-secondary\">0</span><br>\n </p>'\
93 + '\n <p>Cancelation reason: Entered with wrong text</p>\n </div>\n</div>'
94 self.assertIn(str.encode(testtext), result.data)
95 testtext= '<div class="motion card" id="motion-2">\n <div class="motion-title card-heading alert-danger">'\
96 + '\n <span class=\"title-text\">Motion B</span> (Finished)\n <span class=\"motion-type\">group1</span>'\
97 + '\n <div># <a href=\"/motion/g1.20200402.002\" class=\"anchor\">g1.20200402.002</a></div>'\
98 + '\n <div class=\"date\">\n <div>Proposed: 2020-04-02 21:41:26 (UTC) by User A</div>'\
99 + '\n <div>Votes until: 2020-04-04 21:41:26 (UTC)</div></div>\n </div>'\
100 + '\n <div class=\"card-body\">\n <p><p>A second motion</p></p>'\
101 + '\n <p>\nYes <span class=\"badge badge-pill badge-secondary\">1</span><br>'\
102 + '\nNo <span class=\"badge badge-pill badge-secondary\">2</span><br>'\
103 + '\nAbstain <span class=\"badge badge-pill badge-secondary\">0</span><br>\n </p>\n </div>\n</div>\n'
104 self.assertIn(str.encode(testtext), result.data)
105 testtext= '<div class=\"motion card\" id=\"motion-1\">\n <div class=\"motion-title card-heading alert-success\">'\
106 + '\n <span class=\"title-text\">Motion A</span> (Finished)\n <span class=\"motion-type\">group1</span>'\
107 + '\n <div># <a href=\"/motion/g1.20200402.001\" class=\"anchor\">g1.20200402.001</a></div>'\
108 + '\n <div class=\"date">\n <div>Proposed: 2020-04-02 21:40:33 (UTC) by User A</div>'\
109 + '\n <div>Votes until: 2020-04-02 21:40:33 (UTC)</div></div>\n </div>'\
110 + '\n <div class=\"card-body\">\n <p><p>My special motion</p></p>'\
111 + '\n <p>\nYes <span class=\"badge badge-pill badge-secondary\">2</span><br>'\
112 + '\nNo <span class=\"badge badge-pill badge-secondary\">1</span><br>'\
113 + '\nAbstain <span class=\"badge badge-pill badge-secondary\">0</span><br>\n </p>\n </div>\n</div>\n</div>'
114 self.assertIn(str.encode(testtext), result.data)
116 # start with second motion
117 result = self.app.get('/', environ_base={'USER_ROLES': user}, query_string=dict(start=2))
118 testtext= 'id=\"motion-3\">'
119 self.assertNotIn(str.encode(testtext), result.data)
120 testtext= 'id=\"motion-2">'
121 self.assertIn(str.encode(testtext), result.data)
122 testtext= 'id=\"motion-1\">'
123 self.assertIn(str.encode(testtext), result.data)
125 def test_basic_results_data_details(self):
126 motion='g1.20200402.002'
127 result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
128 testtext= '<p>A second motion</p></p>\n </div>\n</div>\n<a href=\"/?start=2#motion-2\" class=\"btn btn-primary\">Back</a>\n</body>'
129 self.assertIn(str.encode(testtext), result.data)
132 motion='g1.20200402.004'
133 response = self.createVote(user, motion, 'yes')
134 self.assertEqual(response.status_code, 403)
135 self.assertIn(str.encode('Forbidden'), response.data)
137 def test_no_user(self):
138 result = self.app.get('/', follow_redirects=True)
139 self.assertEqual(result.status_code, 500)
140 self.assertIn(str.encode('Server misconfigured'), result.data)
142 def test_user_invalid(self):
143 result = self.app.get('/', environ_base={'USER_ROLES': '<invalid>/'}, follow_redirects=True)
144 self.assertEqual(result.status_code, 403)
145 self.assertIn(str.encode('Access denied'), result.data)
147 def test_basic_env(self):
148 result = self.app.get('/', environ_base={'USER': 'testuser', 'ROLES':''}, follow_redirects=True)
149 testtext= 'id=\"motion-3\">'
150 self.assertIn(str.encode(testtext), result.data)
152 class VoterTests(BasicTest):
154 app.config['TESTING'] = True
155 app.config['DEBUG'] = False
156 app.config.update(SERVER_NAME="127.0.0.1:5000")
158 user='testuser/vote:*'
163 self.app = app.test_client()
164 self.assertEqual(app.debug, False)
169 def test_main_page(self):
170 response = self.app.get('/', environ_base={'USER_ROLES': user}, follow_redirects=True)
171 self.assertEqual(response.status_code, 200)
173 def test_home_data(self):
174 result = self.app.get('/', environ_base={'USER_ROLES': user})
175 self.assertNotIn("<select class=\"float form-control\" name=\"category\">", str(result.data) )
177 def test_vote_yes(self):
178 motion='g1.20200402.004'
179 response = self.createVote(user, motion, 'yes')
180 self.assertEqual(response.status_code, 302)
181 result = self.app.get('/', environ_base={'USER_ROLES': user})
182 resulttext=self.buildResultText('A fourth motion', 1, 0, 0)
183 result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
184 testtext= 'class=\"btn btn-success\" name=\"vote\" value="yes" id="vote-yes">yes</button>'
185 self.assertIn(str.encode(testtext), result.data)
186 testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"no\" id=\"vote-no\">no</button>'
187 self.assertIn(str.encode(testtext), result.data)
188 testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"abstain\" id=\"vote-abstain\">abstain</button>'
189 self.assertIn(str.encode(testtext), result.data)
191 def test_vote_no(self):
192 motion='g1.20200402.004'
193 response = self.createVote(user, motion, 'no')
194 self.assertEqual(response.status_code, 302)
195 result = self.app.get('/', environ_base={'USER_ROLES': user})
196 resulttext=self.buildResultText('A fourth motion', 0, 1, 0)
197 self.assertIn(str.encode(resulttext), result.data)
198 result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
199 testtext= 'class="btn btn-primary" name="vote\" value=\"yes\" id=\"vote-yes\">yes</button>'
200 self.assertIn(str.encode(testtext), result.data)
201 testtext= 'class=\"btn btn-success\" name=\"vote\" value=\"no\" id=\"vote-no\">no</button>'
202 self.assertIn(str.encode(testtext), result.data)
203 testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"abstain\" id=\"vote-abstain\">abstain</button>'
204 self.assertIn(str.encode(testtext), result.data)
206 def test_vote_abstain(self):
207 motion='g1.20200402.004'
208 response = self.createVote(user, motion, 'abstain')
209 self.assertEqual(response.status_code, 302)
210 result = self.app.get('/', environ_base={'USER_ROLES': user})
211 resulttext=self.buildResultText('A fourth motion', 0, 0, 1)
212 self.assertIn(str.encode(resulttext), result.data)
213 result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
214 testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"yes\" id=\"vote-yes\">yes</button>'
215 self.assertIn(str.encode(testtext), result.data)
216 testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"no\" id=\"vote-no\">no</button>'
217 self.assertIn(str.encode(testtext), result.data)
218 testtext= 'class=\"btn btn-success\" name=\"vote\" value=\"abstain\" id=\"vote-abstain\">abstain</button>'
219 self.assertIn(str.encode(testtext), result.data)
221 def test_vote_change(self):
222 motion='g1.20200402.004'
223 response = self.createVote(user, motion, 'yes')
224 self.assertEqual(response.status_code, 302)
225 result = self.app.get('/', environ_base={'USER_ROLES': user})
226 resulttext=self.buildResultText('A fourth motion', 1, 0, 0)
227 self.assertIn(str.encode(resulttext), result.data)
228 response = self.createVote(user, motion, 'no')
229 self.assertEqual(response.status_code, 302)
230 result = self.app.get('/', environ_base={'USER_ROLES': user})
231 resulttext=self.buildResultText('A fourth motion', 0, 1, 0)
232 self.assertIn(str.encode(resulttext), result.data)
233 response = self.createVote(user, motion, 'abstain')
234 self.assertEqual(response.status_code, 302)
235 result = self.app.get('/', environ_base={'USER_ROLES': user})
236 resulttext=self.buildResultText('A fourth motion', 0, 0, 1)
237 self.assertIn(str.encode(resulttext), result.data)
239 def test_vote_group(self):
240 motion='g1.20200402.004'
241 response = self.createVote(user, motion, 'yes')
242 self.assertEqual(response.status_code, 302)
244 motion='g1.20200402.004'
245 user1='testuser/vote:group1'
246 response = self.createVote(user1, motion, 'yes')
247 self.assertEqual(response.status_code, 302)
249 motion='g1.20200402.004'
250 user1='testuser/vote:group1 vote:group2'
251 response = self.createVote(user1, motion, 'yes')
252 self.assertEqual(response.status_code, 302)
254 def test_vote_wrong_group(self):
255 motion='g1.20200402.004'
256 user1='testuser/vote:group2'
257 response = self.createVote(user1, motion, 'yes')
258 self.assertEqual(response.status_code, 403)
259 self.assertIn(str.encode('Forbidden'), response.data)
261 def test_vote_closed(self):
262 motion='g1.20200402.002'
263 response = self.createVote(user, motion, 'abstain')
264 self.assertEqual(response.status_code, 500)
265 self.assertIn(str.encode('Error, motion deadline has passed'), response.data)
267 def test_vote_canceled(self):
268 motion='g1.20200402.003'
269 response = self.createVote(user, motion, 'abstain')
270 self.assertEqual(response.status_code, 500)
271 self.assertIn(str.encode('Error, motion deadline has passed'), response.data)
273 def test_vote_not_given(self):
274 motion='g1.30190402.001'
275 response = self.createVote(user, motion, 'abstain')
276 self.assertEqual(response.status_code, 404)
277 self.assertIn(str.encode('Error, Not found'), response.data)
279 def test_cancelMotion(self):
280 motion='g1.20200402.004'
282 response = self.cancelMotion(user, motion, reason)
283 self.assertEqual(response.status_code, 403)
284 self.assertIn(str.encode('Forbidden'), response.data)
286 def test_see_old_vote(self):
287 motion='g1.20200402.002'
288 result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
289 testtext= '<div>Proposed: 2020-04-02 21:41:26 (UTC) by User A</div>\n <div>Votes until: 2020-04-04 21:41:26 (UTC)</div></div>'\
290 + '\n </div>\n <div class="card-body">\n <p><p>A second motion</p></p>\n </div>\n</div>'\
291 + '\n<a href="/?start=2#motion-2" class="btn btn-primary">Back</a>'
292 self.assertIn(str.encode(testtext), result.data)
294 def test_createMotion(self):
297 response = self.createMotion(user, title, content, '3', 'group1')
298 self.assertEqual(response.status_code, 403)
299 self.assertIn(str.encode('Forbidden'), response.data)
301 class CreateMotionTests(BasicTest):
303 app.config['TESTING'] = True
304 app.config['DEBUG'] = False
305 app.config.update(SERVER_NAME="127.0.0.1:5000")
307 user='testuser/vote:* create:* cancel:*'
311 self.app = app.test_client()
312 self.assertEqual(app.debug, False)
314 # executed after each test
318 def test_main_page(self):
319 response = self.app.get('/', environ_base={'USER_ROLES': user}, follow_redirects=True)
320 self.assertEqual(response.status_code, 200)
322 def test_home_data(self):
323 result = self.app.get('/', environ_base={'USER_ROLES': user})
325 # assert the response data
326 self.assertIn(b'User: testuser', result.data)
327 self.assertIn("<select class=\"float form-control\" name=\"category\">", str(result.data) )
329 def test_createMotion(self):
332 response = self.createMotion(user, title, content, '3', 'group1')
333 self.assertEqual(response.status_code, 302)
334 result = self.app.get('/', environ_base={'USER_ROLES': user})
335 self.assertIn(str.encode(title), result.data)
336 self.assertIn(str.encode(content), result.data)
337 self.assertIn(str.encode('g1.'+datetime.today().strftime('%Y%m%d')+'.001'), result.data)
341 response = self.createMotion(user, title, content, '3', 'group1')
342 self.assertEqual(response.status_code, 302)
343 result = self.app.get('/', environ_base={'USER_ROLES': user})
344 self.assertIn(str.encode(title), result.data)
345 self.assertIn(str.encode(content), result.data)
346 self.assertIn(str.encode('g1.'+datetime.today().strftime('%Y%m%d')+'.002'), result.data)
350 response = self.createMotion(user, title, content, '3', 'group2')
351 self.assertEqual(response.status_code, 302)
352 result = self.app.get('/', environ_base={'USER_ROLES': user})
353 self.assertIn(str.encode(title), result.data)
354 self.assertIn(str.encode(content), result.data)
355 self.assertIn(str.encode('g2.'+datetime.today().strftime('%Y%m%d')+'.001'), result.data)
359 user1='testuser/vote:* create:group1 cancel:*'
360 response = self.createMotion(user1, title, content, '3', 'group1')
361 self.assertEqual(response.status_code, 302)
365 user1='testuser/vote:* create:group1 create:group2 cancel:*'
366 response = self.createMotion(user1, title, content, '3', 'group1')
367 self.assertEqual(response.status_code, 302)
370 def test_createMotionMarkdown(self):
371 title='Markdown Test'
372 content= 'MyMotionBody MD [text](https//domain.tld/link)'
373 response = self.createMotion(user, title, content, '3', 'group1')
374 self.assertEqual(response.status_code, 302)
375 result = self.app.get('/', environ_base={'USER_ROLES': user})
376 self.assertIn(str.encode(title), result.data)
377 self.assertIn(b'MyMotionBody MD <a href=\"https//domain.tld/link\">text</a>', result.data)
379 def test_createMotionMarkdownDirectLink(self):
380 title='Markdown Test Link'
381 content='MyMotionBody MD <a href=\"https//domain.tld/link\">direct</a'
382 response = self.createMotion(user, title, content, '3', 'group1')
383 self.assertEqual(response.status_code, 302)
384 result = self.app.get('/', environ_base={'USER_ROLES': user})
385 self.assertIn(str.encode(title), result.data)
386 self.assertIn(b'MyMotionBody MD <a href="https//domain.tld/link">direct</a', result.data)
388 def test_createMotionMarkdownCombined(self):
389 title='Markdown Test Link'
390 content='Body [combined](https//domain.tld/link) <a href=\"https//domain.tld/link\">combined1</a'
391 response = self.createMotion(user, title, content, '3', 'group1')
392 self.assertEqual(response.status_code, 302)
393 result = self.app.get('/', environ_base={'USER_ROLES': user})
394 self.assertIn(str.encode(title), result.data)
395 self.assertIn(b'Body <a href=\"https//domain.tld/link\">combined</a> <a href="https//domain.tld/link">combined1</a', result.data)
397 def test_createMotionWrongDayLength(self):
400 response = self.createMotion(user, title, content, '21', 'group1')
401 self.assertEqual(response.status_code, 500)
402 self.assertIn(str.encode('Error, invalid length'), response.data)
404 def test_createMotionWrongGroup(self):
407 response = self.createMotion(user, title, content, '3', 'test1')
408 self.assertEqual(response.status_code, 403)
409 self.assertIn(str.encode('Forbidden'), response.data)
411 user1='testuser/vote:* create:group1 cancel:*'
412 response = self.createMotion(user1, title, content, '3', 'group2')
413 self.assertEqual(response.status_code, 403)
414 self.assertIn(str.encode('Forbidden'), response.data)
416 def test_cancelMotion(self):
419 motion='g1.20200402.004'
421 response = self.cancelMotion(user, motion, reason)
422 self.assertEqual(response.status_code, 500)
423 self.assertIn(str.encode('Error, form requires reason'), response.data)
426 response = self.cancelMotion(user, motion, reason)
427 self.assertEqual(response.status_code, 302)
428 result = self.app.get('/', environ_base={'USER_ROLES': user})
429 self.assertIn(b'Cancelation reason: ' + str.encode(reason), result.data)
431 motion='g1.30190402.001'
433 response = self.cancelMotion(user, motion, reason)
434 self.assertEqual(response.status_code, 404)
435 self.assertIn(str.encode('Error, Not found'), response.data)
437 class AuditMotionTests(BasicTest):
439 app.config['TESTING'] = True
440 app.config['DEBUG'] = False
441 app.config.update(SERVER_NAME="127.0.0.1:5000")
443 user='testuser/audit:*'
448 self.app = app.test_client()
449 self.assertEqual(app.debug, False)
451 def test_see_old_vote(self):
452 motion='g1.20200402.002'
453 result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
454 testtext= '<div class="motion card" id="votes">\n <div class="card-heading text-white bg-info">\n Motion Votes\n </div>'\
455 + '\n <div class="card-body">\n <div>User A: yes</div>\n <div>User B: no</div>'\
456 + '\n <div>User C: no</div>\n </div>\n</div>\n<a href="/?start=2#motion-2" class="btn btn-primary">Back</a>'
457 self.assertIn(str.encode(testtext), result.data)
459 # executed after each test
464 if __name__ == "__main__":