Starting to add new functioning tests, beginning with dependency checker
parent
b2face2680
commit
7cb9dbbb2e
|
@ -22,7 +22,7 @@ The old versions are tagged as follows:
|
||||||
* tag 2.0 - original updated trunk from Seantis version.
|
* tag 2.0 - original updated trunk from Seantis version.
|
||||||
* tag 2.5 - contains the original Seantis version & all PRs merged in as of 12/09/15. It's considered to be the backwards compatible version of the repository.
|
* tag 2.5 - contains the original Seantis version & all PRs merged in as of 12/09/15. It's considered to be the backwards compatible version of the repository.
|
||||||
|
|
||||||
The new version is the current trunk and is dubbed v3.0.
|
The new version is the current trunk and is dubbed v3.0. v3.0 should be considered a new project & thus will contain backwards incompatible changes. When possible, we'll try and backport fixes to the v2.x branches, but it will not be a priority.
|
||||||
|
|
||||||
About this Manual
|
About this Manual
|
||||||
-----------------
|
-----------------
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
from questionnaire.models import Question, Answer
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
def check_actual_answers_against_expression(check_answer, actual_answer, check_question):
|
||||||
|
|
||||||
|
# Numeric Value Expressions
|
||||||
|
if check_answer[0:1] in "<>":
|
||||||
|
try:
|
||||||
|
actual_answer = float(actual_answer)
|
||||||
|
if check_answer[1:2] == "=":
|
||||||
|
check_value = float(check_answer[2:])
|
||||||
|
else:
|
||||||
|
check_value = float(check_answer[1:])
|
||||||
|
except:
|
||||||
|
logging.error("ERROR: must use numeric values with < <= => > checks (%r)" % check_question)
|
||||||
|
return False
|
||||||
|
if check_answer.startswith("<="):
|
||||||
|
return actual_answer <= check_value
|
||||||
|
if check_answer.startswith(">="):
|
||||||
|
return actual_answer >= check_value
|
||||||
|
if check_answer.startswith("<"):
|
||||||
|
return actual_answer < check_value
|
||||||
|
if check_answer.startswith(">"):
|
||||||
|
return actual_answer > check_value
|
||||||
|
|
||||||
|
# Convert answer to list if not already one
|
||||||
|
if type(actual_answer) != type(list()):
|
||||||
|
actual_answer = [actual_answer]
|
||||||
|
|
||||||
|
# Negative Value Expressions
|
||||||
|
if check_answer.startswith("!"):
|
||||||
|
for actual_answer in actual_answer:
|
||||||
|
if actual_answer == '':
|
||||||
|
return False
|
||||||
|
if check_answer[1:].strip() == actual_answer.strip():
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
return
|
||||||
|
|
||||||
|
# Positive Value Expressions
|
||||||
|
for actual_answer in actual_answer:
|
||||||
|
if check_answer.strip() == actual_answer.strip():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def dep_check(expr, runinfo, answerdict):
|
||||||
|
"""
|
||||||
|
Given a comma separated question number and expression, determine if the
|
||||||
|
provided answer to the question number satisfies the expression.
|
||||||
|
|
||||||
|
If the expression starts with >, >=, <, or <=, compare the rest of
|
||||||
|
the expression numerically and return False if it's not able to be
|
||||||
|
converted to an integer.
|
||||||
|
|
||||||
|
If the expression starts with !, return true if the rest of the expression
|
||||||
|
does not match the answer.
|
||||||
|
|
||||||
|
Otherwise return true if the expression matches the answer.
|
||||||
|
|
||||||
|
If there is no comma and only a question number, it checks if the answer
|
||||||
|
is "yes"
|
||||||
|
|
||||||
|
When looking up the answer, it first checks if it's in the answerdict,
|
||||||
|
then it checks runinfo's cookies, then it does a database lookup to find
|
||||||
|
the answer.
|
||||||
|
|
||||||
|
The use of the comma separator is purely historical.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if hasattr(runinfo, 'questionset'):
|
||||||
|
questionnaire = runinfo.questionset.questionnaire
|
||||||
|
elif hasattr(runinfo, 'questionnaire'):
|
||||||
|
questionnaire = runinfo.questionnaire
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
# Parse expression
|
||||||
|
if "," not in expr:
|
||||||
|
expr = expr + ",yes"
|
||||||
|
|
||||||
|
check_questionnum, check_answer = expr.split(",", 1)
|
||||||
|
|
||||||
|
# Get question to check
|
||||||
|
try:
|
||||||
|
check_question = Question.objects.get(number=check_questionnum,
|
||||||
|
questionset__questionnaire=questionnaire)
|
||||||
|
except Question.DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Parse & load actual answer(s) from user
|
||||||
|
if check_question in answerdict:
|
||||||
|
# test for membership in multiple choice questions
|
||||||
|
# FIXME: only checking answerdict
|
||||||
|
for k, v in answerdict[check_question].items():
|
||||||
|
if not k.startswith('multiple_'):
|
||||||
|
continue
|
||||||
|
if check_answer.startswith("!"):
|
||||||
|
if check_answer[1:].strip() == v.strip():
|
||||||
|
return False
|
||||||
|
elif check_answer.strip() == v.strip():
|
||||||
|
return True
|
||||||
|
actual_answer = answerdict[check_question].get('ANSWER', '')
|
||||||
|
elif hasattr(runinfo, 'get_cookie') and runinfo.get_cookie(check_questionnum, False):
|
||||||
|
actual_answer = runinfo.get_cookie(check_questionnum)
|
||||||
|
else:
|
||||||
|
# retrieve from database
|
||||||
|
ansobj = Answer.objects.filter(question=check_question,
|
||||||
|
runid=runinfo.runid, subject=runinfo.subject)
|
||||||
|
if ansobj:
|
||||||
|
actual_answer = ansobj[0].split_answer()[0]
|
||||||
|
logging.warn("Put `store` in checks field for question %s" % check_questionnum)
|
||||||
|
else:
|
||||||
|
actual_answer = None
|
||||||
|
|
||||||
|
if not actual_answer:
|
||||||
|
if check_question.getcheckdict():
|
||||||
|
actual_answer = check_question.getcheckdict().get('default')
|
||||||
|
|
||||||
|
if actual_answer is None:
|
||||||
|
actual_answer = u''
|
||||||
|
if type(actual_answer) == type(list()):
|
||||||
|
actual_answer = actual_answer[0]
|
||||||
|
|
||||||
|
return check_actual_answers_against_expression(check_answer, actual_answer, check_question)
|
|
@ -0,0 +1,191 @@
|
||||||
|
"""
|
||||||
|
Basic Test Suite for Questionnaire Application
|
||||||
|
|
||||||
|
Unfortunately Django 1.0 only has TestCase and not TransactionTestCase
|
||||||
|
so we can't test that a submitted page with an error does not have any
|
||||||
|
answers submitted to the DB.
|
||||||
|
"""
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.client import Client
|
||||||
|
from questionnaire.models import *
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
|
class TypeTest(TestCase):
|
||||||
|
fixtures = ( 'testQuestions.yaml', )
|
||||||
|
urls = 'questionnaire.test_urls'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.ansdict1 = {
|
||||||
|
'questionset_id' : '1',
|
||||||
|
'question_1' : 'Open Answer 1',
|
||||||
|
'question_2' : 'Open Answer 2\r\nMultiline',
|
||||||
|
'question_3' : 'yes',
|
||||||
|
'question_4' : 'dontknow',
|
||||||
|
'question_5' : 'yes',
|
||||||
|
'question_5_comment' : 'this comment is required because of required-yes check',
|
||||||
|
'question_6' : 'no',
|
||||||
|
'question_6_comment' : 'this comment is required because of required-no check',
|
||||||
|
'question_7' : '5',
|
||||||
|
'question_8_unit' : 'week',
|
||||||
|
'question_8' : '2',
|
||||||
|
}
|
||||||
|
self.ansdict2 = {
|
||||||
|
'questionset_id' : '2',
|
||||||
|
'question_9' : 'q9_choice1', # choice
|
||||||
|
'question_10' : '_entry_', # choice-freeform
|
||||||
|
'question_10_comment' : 'my freeform',
|
||||||
|
'question_11_multiple_2' : 'q11_choice2', # choice-multiple
|
||||||
|
'question_11_multiple_4' : 'q11_choice4', # choice-multiple
|
||||||
|
'question_12_multiple_1' : 'q12_choice1',# choice-multiple-freeform
|
||||||
|
'question_12_more_1' : 'blah', # choice-multiple-freeform
|
||||||
|
}
|
||||||
|
runinfo = self.runinfo = RunInfo.objects.get(runid='test:test')
|
||||||
|
self.runid = runinfo.runid
|
||||||
|
self.subject_id = runinfo.subject_id
|
||||||
|
|
||||||
|
|
||||||
|
def test010_redirect(self):
|
||||||
|
"Check redirection from generic questionnaire to questionset"
|
||||||
|
response = self.client.get('/q/test:test/')
|
||||||
|
self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/')
|
||||||
|
|
||||||
|
|
||||||
|
def test020_get_questionset_1(self):
|
||||||
|
"Get first page of Questions"
|
||||||
|
response = self.client.get('/q/test:test/1/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.template[0].name, 'questionnaire/questionset.html')
|
||||||
|
|
||||||
|
|
||||||
|
def test030_language_setting(self):
|
||||||
|
"Set the language and confirm it is set in DB"
|
||||||
|
response = self.client.get('/q/test:test/1/', {"lang" : "en"})
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/')
|
||||||
|
response = self.client.get('/q/test:test/1/')
|
||||||
|
assert "Don't Know" in response.content
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
runinfo = RunInfo.objects.get(runid='test:test')
|
||||||
|
self.assertEqual(runinfo.subject.language, 'en')
|
||||||
|
response = self.client.get('/q/test:test/1/', {"lang" : "de"})
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/')
|
||||||
|
response = self.client.get('/q/test:test/1/')
|
||||||
|
assert "Weiss nicht" in response.content
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
runinfo = RunInfo.objects.get(runid='test:test')
|
||||||
|
self.assertEqual(runinfo.subject.language, 'de')
|
||||||
|
|
||||||
|
|
||||||
|
def test040_missing_question(self):
|
||||||
|
"Post questions with a mandatory field missing"
|
||||||
|
c = self.client
|
||||||
|
ansdict = self.ansdict1.copy()
|
||||||
|
del ansdict['question_3']
|
||||||
|
response = c.post('/q/test:test/1/', ansdict)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
errors = response.context[-1]['errors']
|
||||||
|
self.assertEqual(len(errors), 1) and errors.has_key('3')
|
||||||
|
|
||||||
|
|
||||||
|
def test050_missing_question(self):
|
||||||
|
"Post questions with a mandatory field missing"
|
||||||
|
c = self.client
|
||||||
|
ansdict = self.ansdict1.copy()
|
||||||
|
del ansdict['question_5_comment']
|
||||||
|
# first set language to english
|
||||||
|
response = self.client.get('/q/test:test/1/', {"lang" : "en"})
|
||||||
|
response = c.post('/q/test:test/1/', ansdict)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.context[-1]['errors']), 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test060_successful_questionnaire(self):
|
||||||
|
"POST complete answers for QuestionSet 1"
|
||||||
|
c = self.client
|
||||||
|
ansdict1 = self.ansdict1
|
||||||
|
runinfo = RunInfo.objects.get(runid='test:test')
|
||||||
|
runid = runinfo.random = runinfo.runid = '1real'
|
||||||
|
runinfo.save()
|
||||||
|
|
||||||
|
response = c.get('/q/1real/1/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.template[0].name, 'questionnaire/questionset.html')
|
||||||
|
response = c.post('/q/1real/', ansdict1)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response['Location'], 'http://testserver/q/1real/2/')
|
||||||
|
"POST complete answers for QuestionSet 2"
|
||||||
|
c = self.client
|
||||||
|
|
||||||
|
ansdict2 = self.ansdict2
|
||||||
|
response = c.get('/q/1real/2/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response.template[0].name, 'questionnaire/questionset.html')
|
||||||
|
response = c.post('/q/1real/', ansdict2)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response['Location'], 'http://testserver/')
|
||||||
|
|
||||||
|
self.assertEqual(RunInfo.objects.filter(runid='1real').count(), 0)
|
||||||
|
|
||||||
|
# TODO: The format of these answers seems very strange to me. It was
|
||||||
|
# simpler before I changed it to get the test to work.
|
||||||
|
# I'll have to revisit this once I figure out how this is meant to work
|
||||||
|
# for now it is more important to me that all tests pass
|
||||||
|
|
||||||
|
dbvalues = {
|
||||||
|
'1' : u'["%s"]' % ansdict1['question_1'],
|
||||||
|
'2' : u'["%s"]' % ansdict1['question_2'],
|
||||||
|
'3' : u'["%s"]' % ansdict1['question_3'],
|
||||||
|
'4' : u'["%s"]' % ansdict1['question_4'],
|
||||||
|
'5' : u'["%s", ["%s"]]' % (ansdict1['question_5'], ansdict1['question_5_comment']),
|
||||||
|
'6' : u'["%s", ["%s"]]' % (ansdict1['question_6'], ansdict1['question_6_comment']),
|
||||||
|
'7' : u'[%s]' % ansdict1['question_7'],
|
||||||
|
'8' : u'%s; %s' % (ansdict1['question_8'], ansdict1['question_8_unit']),
|
||||||
|
'9' : u'["q9_choice1"]',
|
||||||
|
'10' : u'[["my freeform"]]',
|
||||||
|
'11' : u'["q11_choice2", "q11_choice4"]',
|
||||||
|
'12' : u'["q12_choice1", ["blah"]]',
|
||||||
|
}
|
||||||
|
for k, v in dbvalues.items():
|
||||||
|
ans = Answer.objects.get(runid=runid, subject__id=self.subject_id,
|
||||||
|
question__number=k)
|
||||||
|
|
||||||
|
v = v.replace('\r', '\\r').replace('\n', '\\n')
|
||||||
|
self.assertEqual(ans.answer, v)
|
||||||
|
|
||||||
|
def test070_tags(self):
|
||||||
|
c = self.client
|
||||||
|
|
||||||
|
# the first questionset in questionnaire 2 is always shown,
|
||||||
|
# but one of its 2 questions is tagged with testtag
|
||||||
|
with_tags = c.get('/q/test:withtags/1/')
|
||||||
|
|
||||||
|
# so we'll get two questions shown if the run is tagged
|
||||||
|
self.assertEqual(with_tags.status_code, 200)
|
||||||
|
self.assertEqual(len(with_tags.context['qlist']), 2)
|
||||||
|
|
||||||
|
# one question, if the run is not tagged
|
||||||
|
without_tags = c.get('/q/test:withouttags/1/')
|
||||||
|
|
||||||
|
self.assertEqual(without_tags.status_code, 200)
|
||||||
|
self.assertEqual(len(without_tags.context['qlist']), 1)
|
||||||
|
|
||||||
|
# the second questionset is only shown if the run is tagged
|
||||||
|
with_tags = c.get('/q/test:withtags/2/')
|
||||||
|
|
||||||
|
self.assertEqual(with_tags.status_code, 200)
|
||||||
|
self.assertEqual(len(with_tags.context['qlist']), 1)
|
||||||
|
|
||||||
|
# meaning it'll be skipped on the untagged run
|
||||||
|
without_tags = c.get('/q/test.withouttags/2/')
|
||||||
|
|
||||||
|
self.assertEqual(without_tags.status_code, 302) # redirect
|
||||||
|
|
||||||
|
# the progress values of the first questionset should reflect
|
||||||
|
# the fact that in one run there's only one questionset
|
||||||
|
with_tags = c.get('/q/test:withtags/1/')
|
||||||
|
without_tags = c.get('/q/test:withouttags/1/')
|
||||||
|
|
||||||
|
self.assertEqual(with_tags.context['progress'], 50)
|
||||||
|
self.assertEqual(without_tags.context['progress'], 100)
|
|
@ -1,191 +1,47 @@
|
||||||
"""
|
|
||||||
Basic Test Suite for Questionnaire Application
|
|
||||||
|
|
||||||
Unfortunately Django 1.0 only has TestCase and not TransactionTestCase
|
|
||||||
so we can't test that a submitted page with an error does not have any
|
|
||||||
answers submitted to the DB.
|
|
||||||
"""
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import Client
|
|
||||||
from questionnaire.models import *
|
|
||||||
from datetime import datetime
|
|
||||||
import os
|
|
||||||
|
|
||||||
class TypeTest(TestCase):
|
from dependency_checker import check_actual_answers_against_expression
|
||||||
fixtures = ( 'testQuestions.yaml', )
|
from .models import Question
|
||||||
urls = 'questionnaire.test_urls'
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.ansdict1 = {
|
|
||||||
'questionset_id' : '1',
|
|
||||||
'question_1' : 'Open Answer 1',
|
|
||||||
'question_2' : 'Open Answer 2\r\nMultiline',
|
|
||||||
'question_3' : 'yes',
|
|
||||||
'question_4' : 'dontknow',
|
|
||||||
'question_5' : 'yes',
|
|
||||||
'question_5_comment' : 'this comment is required because of required-yes check',
|
|
||||||
'question_6' : 'no',
|
|
||||||
'question_6_comment' : 'this comment is required because of required-no check',
|
|
||||||
'question_7' : '5',
|
|
||||||
'question_8_unit' : 'week',
|
|
||||||
'question_8' : '2',
|
|
||||||
}
|
|
||||||
self.ansdict2 = {
|
|
||||||
'questionset_id' : '2',
|
|
||||||
'question_9' : 'q9_choice1', # choice
|
|
||||||
'question_10' : '_entry_', # choice-freeform
|
|
||||||
'question_10_comment' : 'my freeform',
|
|
||||||
'question_11_multiple_2' : 'q11_choice2', # choice-multiple
|
|
||||||
'question_11_multiple_4' : 'q11_choice4', # choice-multiple
|
|
||||||
'question_12_multiple_1' : 'q12_choice1',# choice-multiple-freeform
|
|
||||||
'question_12_more_1' : 'blah', # choice-multiple-freeform
|
|
||||||
}
|
|
||||||
runinfo = self.runinfo = RunInfo.objects.get(runid='test:test')
|
|
||||||
self.runid = runinfo.runid
|
|
||||||
self.subject_id = runinfo.subject_id
|
|
||||||
|
|
||||||
|
|
||||||
def test010_redirect(self):
|
class QuestionSetTests(TestCase):
|
||||||
"Check redirection from generic questionnaire to questionset"
|
def test_dependencies_for_multiple_choice_question(self):
|
||||||
response = self.client.get('/q/test:test/')
|
check_question = Question()
|
||||||
self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/')
|
self.assertTrue(check_actual_answers_against_expression('3B', ['3B', '3E'], check_question))
|
||||||
|
|
||||||
|
def test_dependencies_for_multiple_choice_question_false(self):
|
||||||
|
check_question = Question()
|
||||||
|
self.assertFalse(check_actual_answers_against_expression('3C', ['3B', '3E'], check_question))
|
||||||
|
|
||||||
def test020_get_questionset_1(self):
|
def test_dependencies_for_multiple_choice_question_negation(self):
|
||||||
"Get first page of Questions"
|
check_question = Question()
|
||||||
response = self.client.get('/q/test:test/1/')
|
self.assertTrue(check_actual_answers_against_expression('!3C', ['3B', '3E'], check_question))
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response.template[0].name, 'questionnaire/questionset.html')
|
|
||||||
|
|
||||||
|
def test_dependencies_for_multiple_choice_question_negation_false(self):
|
||||||
|
check_question = Question()
|
||||||
|
self.assertFalse(check_actual_answers_against_expression('!3C', ['3C', '3E'], check_question))
|
||||||
|
|
||||||
def test030_language_setting(self):
|
def test_dependencies_for_single_choice_question(self):
|
||||||
"Set the language and confirm it is set in DB"
|
check_question = Question()
|
||||||
response = self.client.get('/q/test:test/1/', {"lang" : "en"})
|
self.assertTrue(check_actual_answers_against_expression('3B', '3B', check_question))
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertFalse(check_actual_answers_against_expression('3C', '3B', check_question))
|
||||||
self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/')
|
self.assertTrue(check_actual_answers_against_expression('!3C', '3B', check_question))
|
||||||
response = self.client.get('/q/test:test/1/')
|
self.assertTrue(check_actual_answers_against_expression('!3C', '', check_question))
|
||||||
assert "Don't Know" in response.content
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
runinfo = RunInfo.objects.get(runid='test:test')
|
|
||||||
self.assertEqual(runinfo.subject.language, 'en')
|
|
||||||
response = self.client.get('/q/test:test/1/', {"lang" : "de"})
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
self.assertEqual(response['Location'], 'http://testserver/q/test:test/1/')
|
|
||||||
response = self.client.get('/q/test:test/1/')
|
|
||||||
assert "Weiss nicht" in response.content
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
runinfo = RunInfo.objects.get(runid='test:test')
|
|
||||||
self.assertEqual(runinfo.subject.language, 'de')
|
|
||||||
|
|
||||||
|
def test_dependencies_for_numeric_checks(self):
|
||||||
|
check_question = Question()
|
||||||
|
self.assertTrue(check_actual_answers_against_expression('>5.6', '6', check_question))
|
||||||
|
self.assertFalse(check_actual_answers_against_expression('>5.6', '3.6', check_question))
|
||||||
|
self.assertFalse(check_actual_answers_against_expression('>5.6', '5.6', check_question))
|
||||||
|
|
||||||
def test040_missing_question(self):
|
self.assertTrue(check_actual_answers_against_expression('>=5.6', '6', check_question))
|
||||||
"Post questions with a mandatory field missing"
|
self.assertFalse(check_actual_answers_against_expression('>=5.6', '3.6', check_question))
|
||||||
c = self.client
|
self.assertTrue(check_actual_answers_against_expression('>=5.6', '5.6', check_question))
|
||||||
ansdict = self.ansdict1.copy()
|
|
||||||
del ansdict['question_3']
|
|
||||||
response = c.post('/q/test:test/1/', ansdict)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
errors = response.context[-1]['errors']
|
|
||||||
self.assertEqual(len(errors), 1) and errors.has_key('3')
|
|
||||||
|
|
||||||
|
self.assertTrue(check_actual_answers_against_expression('<5.6', '4.6', check_question))
|
||||||
|
self.assertFalse(check_actual_answers_against_expression('<5.6', '8.6', check_question))
|
||||||
|
self.assertFalse(check_actual_answers_against_expression('<5.6', '5.6', check_question))
|
||||||
|
|
||||||
def test050_missing_question(self):
|
self.assertTrue(check_actual_answers_against_expression('<=5.6', '3.6', check_question))
|
||||||
"Post questions with a mandatory field missing"
|
self.assertFalse(check_actual_answers_against_expression('<=5.6', '9.6', check_question))
|
||||||
c = self.client
|
self.assertTrue(check_actual_answers_against_expression('<=5.6', '5.6', check_question))
|
||||||
ansdict = self.ansdict1.copy()
|
|
||||||
del ansdict['question_5_comment']
|
|
||||||
# first set language to english
|
|
||||||
response = self.client.get('/q/test:test/1/', {"lang" : "en"})
|
|
||||||
response = c.post('/q/test:test/1/', ansdict)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(len(response.context[-1]['errors']), 1)
|
|
||||||
|
|
||||||
|
|
||||||
def test060_successful_questionnaire(self):
|
|
||||||
"POST complete answers for QuestionSet 1"
|
|
||||||
c = self.client
|
|
||||||
ansdict1 = self.ansdict1
|
|
||||||
runinfo = RunInfo.objects.get(runid='test:test')
|
|
||||||
runid = runinfo.random = runinfo.runid = '1real'
|
|
||||||
runinfo.save()
|
|
||||||
|
|
||||||
response = c.get('/q/1real/1/')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response.template[0].name, 'questionnaire/questionset.html')
|
|
||||||
response = c.post('/q/1real/', ansdict1)
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
self.assertEqual(response['Location'], 'http://testserver/q/1real/2/')
|
|
||||||
"POST complete answers for QuestionSet 2"
|
|
||||||
c = self.client
|
|
||||||
|
|
||||||
ansdict2 = self.ansdict2
|
|
||||||
response = c.get('/q/1real/2/')
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertEqual(response.template[0].name, 'questionnaire/questionset.html')
|
|
||||||
response = c.post('/q/1real/', ansdict2)
|
|
||||||
self.assertEqual(response.status_code, 302)
|
|
||||||
self.assertEqual(response['Location'], 'http://testserver/')
|
|
||||||
|
|
||||||
self.assertEqual(RunInfo.objects.filter(runid='1real').count(), 0)
|
|
||||||
|
|
||||||
# TODO: The format of these answers seems very strange to me. It was
|
|
||||||
# simpler before I changed it to get the test to work.
|
|
||||||
# I'll have to revisit this once I figure out how this is meant to work
|
|
||||||
# for now it is more important to me that all tests pass
|
|
||||||
|
|
||||||
dbvalues = {
|
|
||||||
'1' : u'["%s"]' % ansdict1['question_1'],
|
|
||||||
'2' : u'["%s"]' % ansdict1['question_2'],
|
|
||||||
'3' : u'["%s"]' % ansdict1['question_3'],
|
|
||||||
'4' : u'["%s"]' % ansdict1['question_4'],
|
|
||||||
'5' : u'["%s", ["%s"]]' % (ansdict1['question_5'], ansdict1['question_5_comment']),
|
|
||||||
'6' : u'["%s", ["%s"]]' % (ansdict1['question_6'], ansdict1['question_6_comment']),
|
|
||||||
'7' : u'[%s]' % ansdict1['question_7'],
|
|
||||||
'8' : u'%s; %s' % (ansdict1['question_8'], ansdict1['question_8_unit']),
|
|
||||||
'9' : u'["q9_choice1"]',
|
|
||||||
'10' : u'[["my freeform"]]',
|
|
||||||
'11' : u'["q11_choice2", "q11_choice4"]',
|
|
||||||
'12' : u'["q12_choice1", ["blah"]]',
|
|
||||||
}
|
|
||||||
for k, v in dbvalues.items():
|
|
||||||
ans = Answer.objects.get(runid=runid, subject__id=self.subject_id,
|
|
||||||
question__number=k)
|
|
||||||
|
|
||||||
v = v.replace('\r', '\\r').replace('\n', '\\n')
|
|
||||||
self.assertEqual(ans.answer, v)
|
|
||||||
|
|
||||||
def test070_tags(self):
|
|
||||||
c = self.client
|
|
||||||
|
|
||||||
# the first questionset in questionnaire 2 is always shown,
|
|
||||||
# but one of its 2 questions is tagged with testtag
|
|
||||||
with_tags = c.get('/q/test:withtags/1/')
|
|
||||||
|
|
||||||
# so we'll get two questions shown if the run is tagged
|
|
||||||
self.assertEqual(with_tags.status_code, 200)
|
|
||||||
self.assertEqual(len(with_tags.context['qlist']), 2)
|
|
||||||
|
|
||||||
# one question, if the run is not tagged
|
|
||||||
without_tags = c.get('/q/test:withouttags/1/')
|
|
||||||
|
|
||||||
self.assertEqual(without_tags.status_code, 200)
|
|
||||||
self.assertEqual(len(without_tags.context['qlist']), 1)
|
|
||||||
|
|
||||||
# the second questionset is only shown if the run is tagged
|
|
||||||
with_tags = c.get('/q/test:withtags/2/')
|
|
||||||
|
|
||||||
self.assertEqual(with_tags.status_code, 200)
|
|
||||||
self.assertEqual(len(with_tags.context['qlist']), 1)
|
|
||||||
|
|
||||||
# meaning it'll be skipped on the untagged run
|
|
||||||
without_tags = c.get('/q/test.withouttags/2/')
|
|
||||||
|
|
||||||
self.assertEqual(without_tags.status_code, 302) # redirect
|
|
||||||
|
|
||||||
# the progress values of the first questionset should reflect
|
|
||||||
# the fact that in one run there's only one questionset
|
|
||||||
with_tags = c.get('/q/test:withtags/1/')
|
|
||||||
without_tags = c.get('/q/test:withouttags/1/')
|
|
||||||
|
|
||||||
self.assertEqual(with_tags.context['progress'], 50)
|
|
||||||
self.assertEqual(without_tags.context['progress'], 100)
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ from questionnaire.parsers import *
|
||||||
from questionnaire.emails import _send_email, send_emails
|
from questionnaire.emails import _send_email, send_emails
|
||||||
from questionnaire.utils import numal_sort, split_numal
|
from questionnaire.utils import numal_sort, split_numal
|
||||||
from questionnaire.request_cache import request_cache
|
from questionnaire.request_cache import request_cache
|
||||||
|
from questionnaire.dependency_checker import dep_check
|
||||||
from questionnaire import profiler
|
from questionnaire import profiler
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
@ -915,104 +916,6 @@ def has_tag(tag, runinfo):
|
||||||
return tag in (t.strip() for t in runinfo.tags.split(','))
|
return tag in (t.strip() for t in runinfo.tags.split(','))
|
||||||
|
|
||||||
|
|
||||||
def dep_check(expr, runinfo, answerdict):
|
|
||||||
"""
|
|
||||||
Given a comma separated question number and expression, determine if the
|
|
||||||
provided answer to the question number satisfies the expression.
|
|
||||||
|
|
||||||
If the expression starts with >, >=, <, or <=, compare the rest of
|
|
||||||
the expression numerically and return False if it's not able to be
|
|
||||||
converted to an integer.
|
|
||||||
|
|
||||||
If the expression starts with !, return true if the rest of the expression
|
|
||||||
does not match the answer.
|
|
||||||
|
|
||||||
Otherwise return true if the expression matches the answer.
|
|
||||||
|
|
||||||
If there is no comma and only a question number, it checks if the answer
|
|
||||||
is "yes"
|
|
||||||
|
|
||||||
When looking up the answer, it first checks if it's in the answerdict,
|
|
||||||
then it checks runinfo's cookies, then it does a database lookup to find
|
|
||||||
the answer.
|
|
||||||
|
|
||||||
The use of the comma separator is purely historical.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if hasattr(runinfo, 'questionset'):
|
|
||||||
questionnaire = runinfo.questionset.questionnaire
|
|
||||||
elif hasattr(runinfo, 'questionnaire'):
|
|
||||||
questionnaire = runinfo.questionnaire
|
|
||||||
else:
|
|
||||||
assert False
|
|
||||||
|
|
||||||
if "," not in expr:
|
|
||||||
expr = expr + ",yes"
|
|
||||||
|
|
||||||
check_questionnum, check_answer = expr.split(",", 1)
|
|
||||||
try:
|
|
||||||
check_question = Question.objects.get(number=check_questionnum,
|
|
||||||
questionset__questionnaire=questionnaire)
|
|
||||||
except Question.DoesNotExist:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if check_question in answerdict:
|
|
||||||
# test for membership in multiple choice questions
|
|
||||||
# FIXME: only checking answerdict
|
|
||||||
for k, v in answerdict[check_question].items():
|
|
||||||
if not k.startswith('multiple_'):
|
|
||||||
continue
|
|
||||||
if check_answer.startswith("!"):
|
|
||||||
if check_answer[1:].strip() == v.strip():
|
|
||||||
return False
|
|
||||||
elif check_answer.strip() == v.strip():
|
|
||||||
return True
|
|
||||||
actual_answer = answerdict[check_question].get('ANSWER', '')
|
|
||||||
elif hasattr(runinfo, 'get_cookie') and runinfo.get_cookie(check_questionnum, False):
|
|
||||||
actual_answer = runinfo.get_cookie(check_questionnum)
|
|
||||||
else:
|
|
||||||
# retrieve from database
|
|
||||||
ansobj = Answer.objects.filter(question=check_question,
|
|
||||||
runid=runinfo.runid, subject=runinfo.subject)
|
|
||||||
if ansobj:
|
|
||||||
actual_answer = ansobj[0].split_answer()[0]
|
|
||||||
logging.warn("Put `store` in checks field for question %s" % check_questionnum)
|
|
||||||
else:
|
|
||||||
actual_answer = None
|
|
||||||
|
|
||||||
if not actual_answer:
|
|
||||||
if check_question.getcheckdict():
|
|
||||||
actual_answer = check_question.getcheckdict().get('default')
|
|
||||||
|
|
||||||
if actual_answer is None:
|
|
||||||
actual_answer = u''
|
|
||||||
if type(actual_answer) == type(list()):
|
|
||||||
actual_answer = actual_answer[0]
|
|
||||||
if check_answer[0:1] in "<>":
|
|
||||||
try:
|
|
||||||
actual_answer = float(actual_answer)
|
|
||||||
if check_answer[1:2] == "=":
|
|
||||||
check_value = float(check_answer[2:])
|
|
||||||
else:
|
|
||||||
check_value = float(check_answer[1:])
|
|
||||||
except:
|
|
||||||
logging.error("ERROR: must use numeric values with < <= => > checks (%r)" % check_question)
|
|
||||||
return False
|
|
||||||
if check_answer.startswith("<="):
|
|
||||||
return actual_answer <= check_value
|
|
||||||
if check_answer.startswith(">="):
|
|
||||||
return actual_answer >= check_value
|
|
||||||
if check_answer.startswith("<"):
|
|
||||||
return actual_answer < check_value
|
|
||||||
if check_answer.startswith(">"):
|
|
||||||
return actual_answer > check_value
|
|
||||||
if check_answer.startswith("!"):
|
|
||||||
if actual_answer == '':
|
|
||||||
return False
|
|
||||||
return check_answer[1:].strip() != actual_answer.strip()
|
|
||||||
return check_answer.strip() == actual_answer.strip()
|
|
||||||
|
|
||||||
|
|
||||||
@permission_required("questionnaire.management")
|
@permission_required("questionnaire.management")
|
||||||
def send_email(request, runinfo_id):
|
def send_email(request, runinfo_id):
|
||||||
if request.method != "POST":
|
if request.method != "POST":
|
||||||
|
|
Loading…
Reference in New Issue