diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index ecd817f..a2470de 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -5,5 +5,14 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/limesurveyrc2api.iml b/.idea/limesurveyrc2api.iml
index 6c348c5..6a09009 100644
--- a/.idea/limesurveyrc2api.iml
+++ b/.idea/limesurveyrc2api.iml
@@ -1,14 +1,13 @@
-
-
+
-
+
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 7fb6195..2c50d4f 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..88d06b6
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,5 @@
+language: python
+python:
+ - "3.6"
+script:
+ - python -m unittest
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
index 912c83e..e9973ae
--- a/README.md
+++ b/README.md
@@ -40,6 +40,12 @@ It's just a start, so the list of implemented methods is shorter than not.
- Survey
+ list_surveys
+ list_questions
+ + delete_survey
+ + export_responses
+ + import_survey
+ + activate_survey
+ + activate_tokens
+ + list_groups
- Token
+ add_participants
+ delete_participants
@@ -47,6 +53,7 @@ It's just a start, so the list of implemented methods is shorter than not.
+ get_summary
+ invite_participants
+ list_participants
+ + remind_participants
### Error Handling
diff --git a/limesurveyrc2api/_survey.py b/limesurveyrc2api/_survey.py
old mode 100644
new mode 100755
index 01da5e9..0fd1a27
--- a/limesurveyrc2api/_survey.py
+++ b/limesurveyrc2api/_survey.py
@@ -1,6 +1,8 @@
+import warnings
from collections import OrderedDict
from limesurveyrc2api.exceptions import LimeSurveyError
-
+from os.path import splitext
+from base64 import b64encode
class _Survey(object):
@@ -76,3 +78,238 @@ def list_questions(self, survey_id,
else:
assert response_type is list
return response
+
+ def delete_survey(self, survey_id):
+ """ Delete a survey.
+
+ Parameters
+ :param survey_id: The ID of the Survey to be deleted.
+ :type: Integer
+ """
+ method = "delete_survey"
+ params = OrderedDict([
+ ("sSessionKey", self.api.session_key),
+ ("iSurveyID", survey_id)
+ ])
+ response = self.api.query(method=method, params=params)
+ response_type = type(response)
+
+ if response_type is dict and "status" in response:
+ status = response["status"]
+ error_messages = [
+ "No permission",
+ "Invalid session key"
+ ]
+ for message in error_messages:
+ if status == message:
+ raise LimeSurveyError(method, status)
+ else:
+ assert response_type is list
+ return response
+
+ def export_responses(self, survey_id, document_type, language_code=None,
+ completion_status='all', heading_type='code',
+ response_type='short', from_response_id=None,
+ to_response_id=None, fields=None):
+ """ Export responses in base64 encoded string.
+
+ Parameters
+ :param survey_id: Id of the Survey.
+ :type survey_id: Integer
+ :param document_type: Any format available by plugins
+ (e.g. pdf, csv, xls, doc, json)
+ :type document_type: String
+ :param language_code: (optional) The language to be used.
+ :type language_code: String
+ :param completion_status: (optional) 'complete', 'incomplete' or 'all'
+ :type completion_status: String
+ :param heading_type: (optional) 'code', 'full' or 'abbreviated'
+ :type heading_type: String
+ :param response_type: (optional) 'short' or 'long'
+ :type response_type: String
+ :param from_response_id: (optional)
+ :type from_response_id: Integer
+ :param to_response_id: (optional)
+ :type to_response_id: Integer
+ :param fields: (optional) Selected fields.
+ :type fields: Array
+ """
+ method = "export_responses"
+ params = OrderedDict([
+ ("sSessionKey", self.api.session_key),
+ ("iSurveyID", survey_id),
+ ("sDocumentType", document_type),
+ ("sLanguageCode", language_code),
+ ("sCompletionStatus", completion_status),
+ ("sHeadingType", heading_type),
+ ("sResponseType", response_type),
+ ("iFromResponseID", from_response_id),
+ ("iToResponseID", to_response_id),
+ ("aFields", fields)
+ ])
+ response = self.api.query(method=method, params=params)
+ response_type = type(response)
+
+ if response_type is dict and "status" in response:
+ status = response["status"]
+ error_messages = [
+ "Language code not found for this survey.",
+ "No Data, could not get max id.",
+ "No Data, survey table does not exist",
+ "No permission",
+ "Invalid session key"
+ ]
+ for message in error_messages:
+ if status == message:
+ raise LimeSurveyError(method, status)
+ else:
+ assert response_type is str
+ return response
+
+ def import_survey(self, path_to_import_survey, new_name=None,
+ dest_survey_id=None):
+ """ Import a survey. Allowed formats: lss, csv, txt or lsa
+
+ Parameters
+ :param path_to_import_survey: Path to survey as file to copy.
+ :type path_to_import_survey: String
+ :param new_name: (optional) The optional new name of the survey
+ Important! Seems only to work if lss file is given!
+ :type new_name: String
+ :param dest_survey_id: (optional) This is the new ID of the survey -
+ if already used a random one will be taken instead
+ :type dest_survey_id: Integer
+ """
+ import_datatype = splitext(path_to_import_survey)[1][1:]
+ # TODO: Naming seems only to work with lss files - why?
+ if import_datatype != 'lss' and new_name:
+ warnings.warn("New naming seems only to work with lss files",
+ RuntimeWarning)
+ # encode import data
+ with open(path_to_import_survey, 'rb') as f:
+ # import data must be a base 64 encoded string
+ import_data = b64encode(f.read())
+ # decoding needed because json.dumps() in method get of
+ # class LimeSurvey can not encode bytes
+ import_data = import_data.decode('ascii')
+
+ method = "import_survey"
+ params = OrderedDict([
+ ("sSessionKey", self.api.session_key),
+ ("sImportData", import_data),
+ ("sImportDataType", import_datatype),
+ ("sNewSurveyName", new_name),
+ ("DestSurveyID", dest_survey_id)
+ ])
+ response = self.api.query(method=method, params=params)
+ response_type = type(response)
+
+ if response_type is dict and "status" in response:
+ status = response["status"]
+ error_messages = [
+ "Error: ...", # TODO: Unclear what might be returned here
+ "Invalid extension",
+ "No permission",
+ "Invalid session key"
+ ]
+ for message in error_messages:
+ if status == message:
+ raise LimeSurveyError(method, status)
+ else:
+ assert response_type is int # the new survey id
+ return response
+
+ def activate_survey(self, survey_id):
+ """ Activate an existing survey.
+
+ Parameters
+ :param survey_id: Id of the Survey to be activated.
+ :type survey_id: Integer
+ """
+ method = "activate_survey"
+ params = OrderedDict([
+ ("sSessionKey", self.api.session_key),
+ ("iSurveyID", survey_id)
+ ])
+ response = self.api.query(method=method, params=params)
+ response_type = type(response)
+
+ if response_type is dict and "status" in response:
+ status = response["status"]
+ error_messages = [
+ "Error: Invalid survey ID",
+ "Error: ...", # TODO: what could be output of ActivateResults?
+ "No permission",
+ "Invalid session key"
+ ]
+ for message in error_messages:
+ if status == message:
+ raise LimeSurveyError(method, status)
+ else:
+ assert response_type is list
+ return response
+
+ def activate_tokens(self, survey_id, attribute_fields=[]):
+ """
+
+ Parameters
+ :param survey_id: ID of the Survey where a participants table will
+ be created for.
+ :type survey_id: Integer
+ :param attribute_fields: An array of integer describing any additional
+ attribute fiields.
+ :type attribute_fields: Array
+ """
+ method = "activate_tokens"
+ params = OrderedDict([
+ ("sSessionKey", self.api.session_key),
+ ("iSurveyId", survey_id),
+ ("aAttributeFields", attribute_fields)
+ ])
+ response = self.api.query(method=method, params=params)
+ response_type = type(response)
+
+ if response_type is dict and "status" in response:
+ status = response["status"]
+ error_messages = [
+ "Error: Invalid survey ID",
+ "Survey participants table could not be created",
+ "No permission",
+ "Invalid session key"
+ ]
+ for message in error_messages:
+ if status == message:
+ raise LimeSurveyError(method, status)
+ else:
+ assert response_type is list
+ return response
+
+ def list_groups(self, survey_id):
+ """ Return the ids and all attributes of groups belonging to survey.
+
+ Parameters
+ :param survey_id: ID of the survey containing the groups.
+ :rtype survey_id: Integer
+ """
+ method = "list_groups"
+ params = OrderedDict([
+ ("sSessionKey", self.api.session_key),
+ ("iSurveyID", survey_id)
+ ])
+ response = self.api.query(method=method, params=params)
+ response_type = type(response)
+
+ if response_type is dict and "status" in response:
+ status = response["status"]
+ error_messages = [
+ "Error: Invalid survey ID",
+ "No groups found",
+ "No permission"
+ "Invalid S ession key" # typo in remotecontrol_handle.php
+ ]
+ for message in error_messages:
+ if status == message:
+ raise LimeSurveyError(method, status)
+ else:
+ assert response_type is list
+ return response
diff --git a/limesurveyrc2api/_token.py b/limesurveyrc2api/_token.py
index 0496e51..5da0856 100644
--- a/limesurveyrc2api/_token.py
+++ b/limesurveyrc2api/_token.py
@@ -13,7 +13,7 @@ def add_participants(
Add participants to the specified survey.
Parameters
- :param survey_id: ID of survey to delete participants from.
+ :param survey_id: ID of survey to add participants.
:type survey_id: Integer
:param participant_data: List of participant detail dictionaries.
:type participant_data: List[Dict]
@@ -277,6 +277,45 @@ def list_participants(
assert response_type is list
return response
- def remind_participants(self):
- # TODO
- raise NotImplementedError
+ def remind_participants(self, survey_id, min_days_between=None,
+ max_reminders=None, token_ids=False):
+ """ Send a reminder to participants in a survey.
+
+ Returns result of sending.
+
+ Parameters
+ :param survey_id: ID of the Survey that participants belong.
+ :type survey_id: Integer
+ :param min_days_between: (optional) Days from last reminder.
+ :type min_days_between: Integer
+ :param max_reminders: (optional) Maximum reminders count.
+ :type max_reminders: Integer
+ :param token_ids: (optional filter) IDs of the participant to remind.
+ :type token_ids: array
+ """
+ method = "remind_participants"
+ params = OrderedDict([
+ ("sSessionKey", self.api.session_key),
+ ("iSurveyID", survey_id),
+ ("iMinDaysBetween", min_days_between),
+ ("iMaxReminders", max_reminders),
+ ("aTokenIds", token_ids)
+ ])
+ response = self.api.query(method=method, params=params)
+ response_type = type(response)
+
+ if response_type is dict and "status" in response:
+ status = response["status"]
+ error_messages = [
+ "Error: No survey participants table",
+ "Error: No candidate tokens",
+ "Error: Invalid survey ID",
+ "No permission",
+ "Invalid Session Key"
+ ]
+ for message in error_messages:
+ if status == message:
+ raise LimeSurveyError(method, status)
+ else:
+ assert response_type is list
+ return response
diff --git a/setup.py b/setup.py
old mode 100644
new mode 100755
index 095bede..b90461e
--- a/setup.py
+++ b/setup.py
@@ -6,8 +6,8 @@
version=__version__,
description="LimeSurvey RC2 API Web Services Client",
url="https://github.com/lindsay-stevens",
- author="Lindsay Stevens",
- author_email="lindsay.stevens.au@gmail.com",
+ author="Lindsay Stevens, forked by Elke Schaechtele",
+ author_email="lindsay.stevens.au@gmail.com, elke.schaechtele@web.de",
packages=["limesurveyrc2api"],
test_suite="tests",
include_package_data=True,
diff --git a/tests/config.ini b/tests/config.ini
new file mode 100755
index 0000000..f850e63
--- /dev/null
+++ b/tests/config.ini
@@ -0,0 +1,5 @@
+[test]
+url = http://localhost/index.php/admin/remotecontrol
+username = admin
+password = admin
+survey_id = 369411
diff --git a/tests/config.ini.tmpl b/tests/config.ini.tmpl
index e68d213..da4c81c 100644
--- a/tests/config.ini.tmpl
+++ b/tests/config.ini.tmpl
@@ -2,3 +2,4 @@
url = http://localhost/limesurvey/index.php/admin/remotecontrol
username = admin
password = admin
+survey_id = 0
diff --git a/tests/fixtures/an_other_questionnaire_different_fileformat.lsa b/tests/fixtures/an_other_questionnaire_different_fileformat.lsa
new file mode 100644
index 0000000..e05df2a
Binary files /dev/null and b/tests/fixtures/an_other_questionnaire_different_fileformat.lsa differ
diff --git a/tests/fixtures/same_questionnaire_different_fileformat.txt b/tests/fixtures/same_questionnaire_different_fileformat.txt
new file mode 100644
index 0000000..7d0b11a
--- /dev/null
+++ b/tests/fixtures/same_questionnaire_different_fileformat.txt
@@ -0,0 +1,69 @@
+class type/scale name relevance text help language validation mandatory other default same_default allowed_filetypes alphasort answer_width array_filter array_filter_exclude array_filter_style assessment_value category_separator choice_title code_filter commented_checkbox commented_checkbox_auto cssclass date_format date_max date_min display_columns display_rows display_type dropdown_dates dropdown_dates_minute_step dropdown_dates_month_style dropdown_prefix dropdown_prepostfix dropdown_separators dropdown_size dualscale_headerA dualscale_headerB em_validation_q em_validation_q_tip em_validation_sq em_validation_sq_tip equals_num_value equation exclude_all_others exclude_all_others_auto hidden hide_tip input_boxes label_input_columns location_city location_country location_defaultcoordinates location_mapheight location_mapservice location_mapwidth location_mapzoom location_nodefaultfromip location_postal location_state max_answers max_filesize max_num_of_files max_num_value max_num_value_n max_subquestions maximum_chars min_answers min_num_of_files min_num_value min_num_value_n multiflexible_checkbox multiflexible_max multiflexible_min multiflexible_step num_value_int_only numbers_only other_comment_mandatory other_numbers_only other_replace_text page_break parent_order prefix printable_help public_statistics random_group random_order rank_title repeat_headings reverse samechoiceheight samelistheight scale_export show_comment show_grand_total show_title show_totals showpopups slider_accuracy slider_custom_handle slider_default slider_handle slider_layout slider_max slider_middlestart slider_min slider_orientation slider_rating slider_reset slider_separator slider_showminmax statistics_graphtype statistics_showgraph statistics_showmap suffix text_input_columns text_input_width time_limit time_limit_action time_limit_countdown_message time_limit_disable_next time_limit_disable_prev time_limit_message time_limit_message_delay time_limit_message_style time_limit_timer_style time_limit_warning time_limit_warning_2 time_limit_warning_2_display_time time_limit_warning_2_message time_limit_warning_2_style time_limit_warning_display_time time_limit_warning_message time_limit_warning_style use_dropdown value_range_allows_missing
+S sid 772267
+S owner_id 1
+S admin Administrator
+S active N
+S adminemail user@example.com
+S anonymized N
+S format G
+S savetimings N
+S template ubuntu_orange
+S language en
+S datestamp N
+S usecookie N
+S allowregister N
+S allowsave Y
+S autonumber_start 0
+S autoredirect N
+S allowprev N
+S printanswers N
+S ipaddr N
+S refurl N
+S datecreated 2018-01-04
+S publicstatistics N
+S publicgraphs N
+S listpublic N
+S htmlemail Y
+S sendconfirmation Y
+S tokenanswerspersistence N
+S assessments N
+S usecaptcha N
+S usetokens N
+S bounce_email user@example.com
+S tokenlength 15
+S showxquestions Y
+S showgroupinfo B
+S shownoanswer N
+S showqnumcode X
+S bounceprocessing N
+S showwelcome Y
+S showprogress Y
+S questionindex 0
+S navigationdelay 0
+S nokeyboard N
+S alloweditaftercompletion N
+SL surveyls_title A Rather Interesting Questionnaire for Testing en
+SL surveyls_email_invite_subj Invitation to participate in a survey en
+SL surveyls_email_invite Dear {FIRSTNAME},
you have been invited to participate in a survey.
The survey is titled: "{SURVEYNAME}"
"{SURVEYDESCRIPTION}"
To participate, please click on the link below.
Sincerely,
{ADMINNAME} ({ADMINEMAIL})
---------------------------------------------- Click here to do the survey: {SURVEYURL}
If you do not want to participate in this survey and don't want to receive any more invitations please click the following link: {OPTOUTURL}
If you are blacklisted but want to participate in this survey and want to receive invitations please click the following link: {OPTINURL} en
+SL surveyls_email_remind_subj Reminder to participate in a survey en
+SL surveyls_email_remind Dear {FIRSTNAME},
Recently we invited you to participate in a survey.
We note that you have not yet completed the survey, and wish to remind you that the survey is still available should you wish to take part.
The survey is titled: "{SURVEYNAME}"
"{SURVEYDESCRIPTION}"
To participate, please click on the link below.
Sincerely,
{ADMINNAME} ({ADMINEMAIL})
---------------------------------------------- Click here to do the survey: {SURVEYURL}
If you do not want to participate in this survey and don't want to receive any more invitations please click the following link: {OPTOUTURL} en
+SL surveyls_email_register_subj Survey registration confirmation en
+SL surveyls_email_register Dear {FIRSTNAME},
You, or someone using your email address, have registered to participate in an online survey titled {SURVEYNAME}.
To complete this survey, click on the following URL:
{SURVEYURL}
If you have any questions about this survey, or if you did not register to participate and believe this email is in error, please contact {ADMINNAME} at {ADMINEMAIL}. en
+SL surveyls_email_confirm_subj Confirmation of your participation in our survey en
+SL surveyls_email_confirm Dear {FIRSTNAME},
this email is to confirm that you have completed the survey titled {SURVEYNAME} and your response has been saved. Thank you for participating.
If you have any further questions about this email, please contact {ADMINNAME} on {ADMINEMAIL}.
Sincerely,
{ADMINNAME} en
+SL surveyls_dateformat 9 en
+SL email_admin_notification_subj Response submission for survey {SURVEYNAME} en
+SL email_admin_notification Hello,
A new response was submitted for your survey '{SURVEYNAME}'.
Click the following link to reload the survey: {RELOADURL}
Click the following link to see the individual response: {VIEWRESPONSEURL}
Click the following link to edit the individual response: {EDITRESPONSEURL}
View statistics by clicking here: {STATISTICSURL} en
+SL email_admin_responses_subj Response submission for survey {SURVEYNAME} with results en
+SL email_admin_responses Hello,
A new response was submitted for your survey '{SURVEYNAME}'.
Click the following link to reload the survey: {RELOADURL}
Click the following link to see the individual response: {VIEWRESPONSEURL}
Click the following link to edit the individual response: {EDITRESPONSEURL}
View statistics by clicking here: {STATISTICSURL}
The following answers were given by the participant: {ANSWERTABLE} en
+SL surveyls_numberformat 0 en
+G G0 Demographics 1 Some questions about you. en
+Q T cob 1 What is your country of birth? en N 1 1 12 1
+Q N yob 1 What is your year of birth? en N 1 2050 4 1850 1 12
+Q L fcol 1 What is your favourite colour? en Y 1 1 1 1
+SQ 0 other 1 Other en
+A 0 1 Red en
+A 0 2 Green en
+A 0 3 Blue en
+A 0 4 Yellow en
+A 0 5 Orange en
\ No newline at end of file
diff --git a/tests/fixtures/same_questionnaire_different_fileformat.xml b/tests/fixtures/same_questionnaire_different_fileformat.xml
new file mode 100644
index 0000000..0f606ad
--- /dev/null
+++ b/tests/fixtures/same_questionnaire_different_fileformat.xml
@@ -0,0 +1,73 @@
+
+
+ A Rather Interesting Questionnaire for Testing
+
+
+
+
+ title
+ Demographics
+ self
+
+
+ before
+ Some questions about you.
+ self
+
+
+ What is your country of birth?
+
+
+ longtext
+ 40
+
+
+
+
+
+ What is your year of birth?
+
+
+ integer
+ 4
+
+
+
+
+
+ What is your favourite colour?
+
+
+
+
+ 1
+
+
+
+ 2
+
+
+
+ 3
+
+
+
+ 4
+
+
+
+ 5
+
+
+
+ -oth-
+
+ Other
+ 24
+
+
+
+
+
+
+
diff --git a/tests/test__survey.py b/tests/test__survey.py
old mode 100644
new mode 100755
index 78743c1..93fee4e
--- a/tests/test__survey.py
+++ b/tests/test__survey.py
@@ -29,3 +29,86 @@ def test_list_questions_failure(self):
with self.assertRaises(LimeSurveyError) as ctx:
self.api.survey.list_questions(self.survey_id_invalid)
self.assertIn("Error: Invalid survey ID", ctx.exception.message)
+
+ def test_delete_survey_success(self):
+ """ Deleting a Survey should return status OK. """
+ s = 'tests/fixtures/a_rather_interesting_questionnaire_for_testing.lss'
+ new_survey_id = self.api.survey.import_survey(s, new_name='delete_me')
+ result = self.api.survey.delete_survey(new_survey_id)
+ self.assertEqual("OK", result["status"])
+
+ def test_export_responses_success_different_document_types(self):
+ """ Should return requested file as base64 encoded string. """
+ for extension in ['pdf', 'csv', 'xls', 'doc', 'json']:
+ result = self.api.survey.export_responses(
+ self.survey_id, document_type=extension)
+ self.assertIs(type(result), str)
+
+ # TODO: add tests for other parameters of export_responses
+
+ def test_import_survey_success_lss(self):
+ """ Importing a survey should return the id of the new survey. """
+ valid_files = [
+ 'tests/fixtures/a_rather_interesting_questionnaire_for_testing.lss',
+ 'tests/fixtures/an_other_questionnaire_different_fileformat.lsa',
+ 'tests/fixtures/same_questionnaire_different_fileformat.txt'
+ ]
+ new_survey_ids = [] # for deleting after test
+ for file in valid_files:
+ new_name = 'copy_test_%s' % file[-3:]
+ result = self.api.survey.import_survey(file, new_name)
+ self.assertIs(int, type(result))
+ new_survey_ids.append(result)
+ for new_survey_id in new_survey_ids: # delete new surveys
+ self.api.survey.delete_survey(new_survey_id)
+
+ def test_import_survey_failure_invalid_file_extension(self):
+ """ Survey with invalid file extension should raise an error. """
+ invalid = 'tests/fixtures/same_questionnaire_different_fileformat.xml'
+ with self.assertRaises(LimeSurveyError) as ctx:
+ self.api.survey.import_survey(invalid)
+ self.assertIn("Invalid extension", ctx.exception.message)
+
+ def test_activate_survey_success(self):
+ """ In case of success result of activation as array is returned. """
+ non_active_survey_path = (
+ 'tests/fixtures/same_questionnaire_different_fileformat.txt')
+ non_active_survey_id = self.api.survey.import_survey(
+ non_active_survey_path)
+ result = self.api.survey.activate_survey(non_active_survey_id)
+ self.assertEqual(result['status'], 'OK')
+ # TODO: if get_survey_properties is implemented check active status
+ # clean up
+ self.api.survey.delete_survey(non_active_survey_id)
+
+ def test_activate_tokens_success(self):
+ """ In case of success return response. """
+ new_survey_path = (
+ 'tests/fixtures/same_questionnaire_different_fileformat.txt')
+ new_survey_id = self.api.survey.import_survey(new_survey_path)
+ response = self.api.survey.activate_tokens(new_survey_id)
+ self.assertEqual(response['status'], 'OK')
+ # clean up
+ self.api.survey.delete_survey(new_survey_id)
+
+ def test_activate_tokens_failure(self):
+ """ A wrong survey_id should raise an exception. """
+ with self.assertRaises(LimeSurveyError) as ctx:
+ self.api.survey.activate_tokens(self.survey_id_invalid)
+ self.assertIn("Error: Invalid survey ID", ctx.exception.message)
+
+ # TODO: test for attributeFields
+
+ def test_list_groups_success(self):
+ """ Listing groups for a survey should return a group list. """
+ response = self.api.survey.list_groups(self.survey_id)
+ for group in response:
+ self.assertEqual(group["sid"], self.survey_id)
+ self.assertIsNotNone(group["group_name"])
+ self.assertIsNotNone(group["gid"])
+
+ def test_list_groups_failure(self):
+ """Listing questions for an invalid survey should return an error."""
+ with self.assertRaises(LimeSurveyError) as ctx:
+ self.api.survey.list_questions(self.survey_id_invalid)
+ self.assertIn("Error: Invalid survey ID", ctx.exception.message)
diff --git a/tests/test__token.py b/tests/test__token.py
index 115f90f..8e6c376 100644
--- a/tests/test__token.py
+++ b/tests/test__token.py
@@ -159,6 +159,8 @@ def setUpClass(cls):
cls.added_tokens = cls.api.token.add_participants(
survey_id=cls.survey_id, participant_data=cls.participants)
cls.token_ids = [x["tid"] for x in cls.added_tokens]
+ cls.api.token.invite_participants(survey_id=cls.survey_id,
+ token_ids=cls.token_ids)
@classmethod
def tearDownClass(cls):
@@ -236,7 +238,7 @@ def test_list_participants_return_type_success(self):
"""Querying for participants should return attrs with expected types."""
result = self.api.token.list_participants(survey_id=self.survey_id)[0]
return_types_top = [
- ("tid", int),
+ ("tid", str),
("token", str),
("participant_info", dict),
]
@@ -282,7 +284,7 @@ def test_list_participants_success(self):
result = self.api.token.list_participants(survey_id=self.survey_id)
result_token_ids = [x["tid"] for x in result]
for token_id in self.token_ids:
- self.assertIn(int(token_id), result_token_ids)
+ self.assertIn(token_id, result_token_ids)
def test_list_participants_conditions_failure(self):
"""Querying with a condition matching none should result in an error."""
@@ -290,3 +292,42 @@ def test_list_participants_conditions_failure(self):
self.api.token.list_participants(
survey_id=self.survey_id, conditions={"email": "not_an_email"})
self.assertIn("No survey participants found.", lse.exception.message)
+
+ def test_remind_participants_success(self):
+ """ Should return array of result of each email send action and a
+ count of invitations left to send in status key. """
+ with CapturingAiosmtpdServer() as cas:
+ result = self.api.token.remind_participants(
+ survey_id=self.survey_id)
+ overall_status = result.pop("status")
+ self.assertEqual("0 left to send", overall_status)
+ for token_id, email_info in result.items():
+ self.assertEqual("OK", email_info.get("status"))
+ # TODO: cas.messages is [] here too...
+
+ def test_remind_participants_success_individual_token(self):
+ """ If token_ids is specified only remind those tokens. """
+ remind_tokens = self.token_ids[:1]
+ with CapturingAiosmtpdServer() as cas:
+ result = self.api.token.remind_participants(
+ self.survey_id, token_ids=remind_tokens)
+ self.assertEqual(len(result), 2) # token and general status
+ self.assertIn(remind_tokens[0], result)
+
+ def test_remind_participants_failure_min_days_between(self):
+ """ Only remind participants if min days between last reminder. """
+ with CapturingAiosmtpdServer():
+ self.api.token.remind_participants(self.survey_id)
+ with self.assertRaises(LimeSurveyError) as lse:
+ self.api.token.remind_participants(
+ survey_id=self.survey_id, min_days_between=1)
+ self.assertIn("Error: No candidate tokens", lse.exception.message)
+
+ def test_remind_participants_failure_max_reminders_count(self):
+ """ Only remind participants if min days between last reminder. """
+ with CapturingAiosmtpdServer():
+ self.api.token.remind_participants(self.survey_id)
+ with self.assertRaises(LimeSurveyError) as lse:
+ self.api.token.remind_participants(
+ survey_id=self.survey_id, max_reminders=1)
+ self.assertIn("Error: No candidate tokens", lse.exception.message)
diff --git a/tests/test_limesurvey.py b/tests/test_limesurvey.py
index afc193a..e099f2d 100644
--- a/tests/test_limesurvey.py
+++ b/tests/test_limesurvey.py
@@ -28,9 +28,12 @@ def setUpClass(cls):
username=cls.username)
cls.session_key = None
cls.api.open(password=cls.password)
-
- surveys = sorted(cls.api.survey.list_surveys(), key=itemgetter("sid"))
- cls.survey_id = surveys[0].get("sid")
+ if not confparser['test']['survey_id']:
+ surveys = sorted(cls.api.survey.list_surveys(),
+ key=itemgetter("sid"))
+ cls.survey_id = surveys[0].get("sid")
+ else:
+ cls.survey_id = confparser['test']['survey_id']
cls.survey_id_invalid = -1
@classmethod