Skip to content
Snippets Groups Projects
Commit 2279dd1a authored by Remco Tukker's avatar Remco Tukker
Browse files

show response status and cleanup

parent 6dea518b
No related branches found
No related tags found
1 merge request!57Feature/survey locking system
......@@ -46,6 +46,9 @@ class ResponseStatus(Enum):
checked = "checked"
RESPONSE_NOT_STARTED = "not started"
class Survey(db.Model):
__tablename__ = 'survey'
year: Mapped[int_pk]
......@@ -65,3 +68,15 @@ class SurveyResponse(db.Model):
locked_by: Mapped[uuid_nullable_fkUser]
locked_by_user: Mapped[User] = relationship(lazy='joined')
lock_uuid: Mapped[Optional[UUID]]
@property
def lock_description(self):
if self.locked_by_user:
return f"Locked by: {self.locked_by_user.fullname} ({self.locked_by_user.email})"
return ""
@property
def lock_username(self):
if self.locked_by_user:
return self.locked_by_user.fullname
return ""
......@@ -10,7 +10,7 @@ from sqlalchemy.orm import lazyload
from compendium_v2.db import db
from compendium_v2.db.model import NREN
from compendium_v2.db.survey_model import Survey, SurveyResponse, SurveyStatus, ResponseStatus
from compendium_v2.db.survey_model import Survey, SurveyResponse, SurveyStatus, ResponseStatus, RESPONSE_NOT_STARTED
from compendium_v2.routes import common
from compendium_v2.auth.session_management import admin_required, User
......@@ -24,11 +24,43 @@ SURVEY_RESPONSE_SCHEMA = {
'type': 'object',
'properties': {
'model': {'type': 'object'},
'locked_by': {'type': 'string'},
'data': {'type': 'object'},
'page': {'type': 'number'},
'verification_status': {'type': 'object'}
'verification_status': {'type': 'object'},
'mode': {'type': 'string'},
'status': {'type': 'string'},
},
'required': ['model', 'data', 'page', 'verification_status'],
'required': ['model', 'locked_by', 'data', 'page', 'verification_status', 'mode', 'status'],
'additionalProperties': False
}
SURVEY_LOCK_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'type': 'object',
'properties': {
'lock_uuid': {'type': 'string'},
'locked_by': {'type': 'string'},
'data': {'type': 'object'},
'verification_status': {'type': 'object'},
'mode': {'type': 'string'},
'status': {'type': 'string'},
},
'required': ['lock_uuid', 'locked_by', 'data', 'verification_status', 'mode', 'status'],
'additionalProperties': False
}
SURVEY_STATUS_RESPONSE_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'type': 'object',
'properties': {
'locked_by': {'type': 'string'},
'mode': {'type': 'string'},
'status': {'type': 'string'},
},
'required': ['locked_by', 'mode', 'status'],
'additionalProperties': False
}
......@@ -41,6 +73,11 @@ class VerificationStatus(str, Enum):
Edited = "edited" # a question for which last years answer was edited
class SurveyMode(str, Enum):
Display = "display"
Edit = "edit"
def check_access_nren(user: User, nren: str) -> bool:
if user.is_anonymous:
return False
......@@ -59,7 +96,7 @@ def try_survey(year) -> Any:
Get a survey without any associated nren for trying out the survey.
The survey will behave exactly as when an NREN opens it.
response will be formatted as:
response will either a 400 range code with a descriptive message or a 200 code with the following format:
.. asjson::
compendium_v2.routes.survey.SURVEY_RESPONSE_SCHEMA
......@@ -67,14 +104,16 @@ def try_survey(year) -> Any:
"""
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if not survey:
return {'success': False, 'message': 'Survey not found'}, 404
return {'message': 'Survey not found'}, 404
return {
"model": survey.survey,
"locked_by": "",
"data": {},
"page": 0,
"verification_status": {},
"mode": "edit"
"mode": SurveyMode.Edit,
"status": RESPONSE_NOT_STARTED
}
......@@ -86,7 +125,7 @@ def inspect_survey(year) -> Any:
Get a survey without any associated nren for inspecting all questions.
All questions are made visible and any VisibleIf condition added to the title.
response will be formatted as:
response will either a 400 range code with a descriptive message or a 200 code with the following format:
.. asjson::
compendium_v2.routes.survey.SURVEY_RESPONSE_SCHEMA
......@@ -94,7 +133,7 @@ def inspect_survey(year) -> Any:
"""
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if not survey:
return {'success': False, 'message': 'Survey not found'}, 404
return {'message': 'Survey not found'}, 404
def visible_visitor(object, items):
for key, value in items:
......@@ -110,10 +149,12 @@ def inspect_survey(year) -> Any:
return {
"model": survey.survey,
"locked_by": "",
"data": {},
"page": 0,
"verification_status": {},
"mode": "edit"
"mode": SurveyMode.Edit,
"status": RESPONSE_NOT_STARTED
}
......@@ -121,10 +162,7 @@ def get_response_data(response, year, nren_id):
data = {}
page = 0
verification_status = {}
locked_by = None
if response and response.locked_by_user:
locked_by = response.locked_by_user.fullname
locked_by = response.lock_username if response else ""
if response and response.answers:
data = response.answers["data"]
......@@ -153,7 +191,7 @@ def load_survey(year, nren_name) -> Any:
If the survey was not saved before and the survey was completed last year,
the data from last year will be prefilled.
response will be formatted as:
response will either a 400 range code with a descriptive message or a 200 code with the following format:
.. asjson::
compendium_v2.routes.survey.SURVEY_RESPONSE_SCHEMA
......@@ -161,14 +199,14 @@ def load_survey(year, nren_name) -> Any:
"""
nren = db.session.scalar(select(NREN).filter(NREN.name == nren_name))
if not nren:
return {'success': False, 'message': 'NREN not found'}, 404
return {'message': 'NREN not found'}, 404
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if not survey:
return {'success': False, 'message': 'Survey not found'}, 404
return {'message': 'Survey not found'}, 404
if not check_access_nren(current_user, nren):
return {'success': False, 'message': 'You do not have permissions to access this survey.'}, 403
return {'message': 'You do not have permissions to access this survey.'}, 403
response = db.session.scalar(
select(SurveyResponse).where(SurveyResponse.survey_year == year).where(SurveyResponse.nren_id == nren.id)
......@@ -182,7 +220,8 @@ def load_survey(year, nren_name) -> Any:
"data": data,
"page": page,
"verification_status": verification_status,
"mode": "display"
"mode": SurveyMode.Display,
"status": response.status.value if response else RESPONSE_NOT_STARTED
}
......@@ -190,16 +229,26 @@ def load_survey(year, nren_name) -> Any:
@common.require_accepts_json
@login_required
def lock_survey(year, nren_name) -> Any:
"""
Get the edit lock on a response. Creates the response if it doesnt exist yet.
It also includes the survey data to prevent race conditions.
response will either a 400 range code with a descriptive message or a 200 code with the following format:
.. asjson::
compendium_v2.routes.survey.SURVEY_LOCK_SCHEMA
"""
nren = db.session.scalar(select(NREN).filter(NREN.name == nren_name))
if not nren:
return {'success': False, 'message': 'NREN not found'}, 404
return {'message': 'NREN not found'}, 404
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if not survey:
return {'success': False, 'message': 'Survey not found'}, 404
return {'message': 'Survey not found'}, 404
if not check_access_nren(current_user, nren):
return {'success': False, 'message': 'You do not have permissions to access this survey.'}, 403
return {'message': 'You do not have permissions to access this survey.'}, 403
response = db.session.scalar(
select(SurveyResponse).where(SurveyResponse.survey_year == year)
......@@ -213,11 +262,11 @@ def lock_survey(year, nren_name) -> Any:
# it's possible this is the first time somebody opens the survey for edit, in
# that case we have to create the response:
if response is None:
response = SurveyResponse(survey_year=year, nren_id=nren.id, status=ResponseStatus.started)
response = SurveyResponse(survey_year=year, nren_id=nren.id, status=ResponseStatus.started, answers={})
db.session.add(response)
if response.locked_by is not None:
return {'success': False, 'message': 'This survey is already locked.'}, 403
return {'message': 'This survey is already locked.'}, 403
lock_uuid = uuid4()
response.lock_uuid = lock_uuid
......@@ -225,14 +274,13 @@ def lock_survey(year, nren_name) -> Any:
db.session.commit()
locked_by = response.locked_by_user.fullname
return {
'success': True,
'lock_uuid': lock_uuid,
'locked_by': locked_by,
'locked_by': response.lock_username,
'data': data,
'verification_status': verification_status,
"mode": "edit"
'mode': SurveyMode.Edit,
'status': response.status.value
}
......@@ -243,47 +291,60 @@ def save_survey(year, nren_name) -> Any:
"""
endpoint to save a survey response
:returns: ``{'success': True}`` or a 400 or 404 status with a descriptive message
expects the following entries in the json body:
"lock_uuid" to verify if the user has the edit lock;
"new_state" to distinguish the save/save and stop editing/complete actions;
"data", "page", "verification_status" which are stored in the db.
response will either a 400 range code with a descriptive message or a 200 code with the following format:
.. asjson::
compendium_v2.routes.survey.SURVEY_STATUS_RESPONSE_SCHEMA
"""
class NewState(str, Enum):
ReadOnly = "readonly"
Editing = "editing"
Completed = "completed"
nren = db.session.scalar(select(NREN).filter(NREN.name == nren_name))
if nren is None:
return {'success': False, 'message': 'NREN not found'}, 404
return {'message': 'NREN not found'}, 404
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if survey is None:
return {'success': False, 'message': 'Survey not found'}, 404
return {'message': 'Survey not found'}, 404
if not check_access_nren(current_user, nren):
return {'success': False, 'message': 'You do not have permission to edit this survey.'}, 403
return {'message': 'You do not have permission to edit this survey.'}, 403
if survey.status != SurveyStatus.open and not current_user.is_admin:
return {'success': False, 'message': 'Survey is closed'}, 400
return {'message': 'Survey is closed'}, 400
response = db.session.scalar(
select(SurveyResponse).where(SurveyResponse.survey_year == year).where(SurveyResponse.nren_id == nren.id)
)
if response is None:
return {'success': False, 'message': 'Survey response not found'}, 400
return {'message': 'Survey response not found'}, 400
save_survey = request.json
if not save_survey:
return {'success': False, 'message': 'Invalid Survey Format'}, 400
return {'message': 'Invalid Survey Format'}, 400
lock_uuid = save_survey["lock_uuid"]
if (lock_uuid != str(response.lock_uuid)):
return {'success': False, 'message': 'Somebody else is currently editing this survey.'}, 403
return {'message': 'Somebody else is currently editing this survey.'}, 403
new_state = save_survey["new_state"]
if (new_state == "editing"):
if (new_state == NewState.Editing):
pass
elif (new_state == "completed"):
elif (new_state == NewState.Completed):
response.locked_by = None
response.status = ResponseStatus.completed
elif (new_state == "readonly"):
elif (new_state == NewState.ReadOnly):
response.locked_by = None
else:
return {'success': False, 'message': 'Invalid new_state paramater'}, 400
return {'message': 'Invalid new_state paramater'}, 400
response.answers = {
"data": save_survey["data"],
......@@ -292,9 +353,8 @@ def save_survey(year, nren_name) -> Any:
}
db.session.commit()
mode = "edit" if new_state == "editing" else "display"
locked_by = response.locked_by_user.fullname if response.locked_by_user else None
return {'success': True, 'locked_by': locked_by, 'mode': mode}
mode = SurveyMode.Edit if new_state == NewState.Editing else SurveyMode.Display
return {'locked_by': response.lock_username, 'mode': mode, 'status': response.status.value}
@routes.route('/unlock/<int:year>/<string:nren_name>', methods=['POST'])
......@@ -304,29 +364,32 @@ def unlock_survey(year, nren_name) -> Any:
"""
endpoint to release a lock on a survey response without saving any data
:returns: ``{'success': True}`` or a 400 or 404 status with a descriptive message
response will either a 400 range code with a descriptive message or a 200 code with the following format:
.. asjson::
compendium_v2.routes.survey.SURVEY_STATUS_RESPONSE_SCHEMA
"""
nren = db.session.scalar(select(NREN).filter(NREN.name == nren_name))
if nren is None:
return {'success': False, 'message': 'NREN not found'}, 404
return {'message': 'NREN not found'}, 404
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if survey is None:
return {'success': False, 'message': 'Survey not found'}, 404
return {'message': 'Survey not found'}, 404
if not check_access_nren(current_user, nren):
return {'success': False, 'message': 'You do not have permission to edit this survey.'}, 403
return {'message': 'You do not have permission to edit this survey.'}, 403
response = db.session.scalar(
select(SurveyResponse).where(SurveyResponse.survey_year == year).where(SurveyResponse.nren_id == nren.id)
)
if response is None:
return {'success': False, 'message': 'Survey response not found'}, 404
return {'message': 'Survey response not found'}, 404
if current_user.id != response.locked_by and not current_user.is_admin:
return {'success': False, 'message': 'This survey was not locked by you.'}, 403
return {'message': 'This survey was not locked by you.'}, 403
response.locked_by = None # we leave lock_uuid, it will be overwritten when a new lock is acquired
db.session.commit()
return {'success': True, 'locked_by': None, 'mode': 'display'}
return {'locked_by': response.lock_username, 'mode': SurveyMode.Display, 'status': response.status.value}
......@@ -7,7 +7,7 @@ from sqlalchemy.orm import joinedload, load_only
from compendium_v2.db import db
from compendium_v2.db.model import NREN
from compendium_v2.db.survey_model import Survey, SurveyResponse, SurveyStatus, ResponseStatus
from compendium_v2.db.survey_model import Survey, SurveyResponse, SurveyStatus, ResponseStatus, RESPONSE_NOT_STARTED
from compendium_v2.routes import common
from compendium_v2.auth.session_management import admin_required
......@@ -24,8 +24,9 @@ LIST_SURVEYS_RESPONSE_SCHEMA = {
'properties': {
'nren': {'type': 'string'},
'status': {'type': 'string'},
'lock_description': {'type': 'string'},
},
'required': ['nren', 'status'],
'required': ['nren', 'status', 'lock_description'],
'additionalProperties': False
},
'survey': {
......@@ -73,11 +74,6 @@ def list_surveys() -> Any:
status: str
responses: List[Dict[str, str]]
def make_lock_description(locked_user):
if locked_user:
return f"Locked by: {locked_user.fullname} ({locked_user.email})"
return ""
entries: List[SurveyDict] = [
{
"year": entry.year,
......@@ -86,7 +82,7 @@ def list_surveys() -> Any:
{
"nren": r.nren.name,
"status": r.status.value,
"lock_description": make_lock_description(r.locked_by_user)
"lock_description": r.lock_description
}
for r in sorted(entry.responses, key=response_key)
]
......@@ -101,7 +97,7 @@ def list_surveys() -> Any:
continue
nrens_with_responses = set([r["nren"] for r in entry["responses"]])
for nren_name in sorted(nren_names.difference(nrens_with_responses), key=str.lower):
entry["responses"].append({"nren": nren_name, "status": "not started", "lock_description": ""})
entry["responses"].append({"nren": nren_name, "status": RESPONSE_NOT_STARTED, "lock_description": ""})
return entries
......@@ -117,17 +113,14 @@ def start_new_survey() -> Any:
"""
all_surveys = db.session.scalars(select(Survey).options(load_only(Survey.status)))
if any([survey.status != SurveyStatus.published for survey in all_surveys]):
return {
'success': False,
'message': 'All earlier surveys should be published before starting a new one'
}, 400
return {'message': 'All earlier surveys should be published before starting a new one'}, 400
last_survey = db.session.scalar(
select(Survey).order_by(Survey.year.desc()).limit(1)
)
if not last_survey:
return {'success': False, 'message': 'No surveys found'}, 404
return {'message': 'No surveys found'}, 404
new_year = last_survey.year + 1
new_survey = last_survey.survey
......@@ -149,14 +142,14 @@ def open_survey(year) -> Any:
"""
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if not survey:
return {'success': False, 'message': 'Survey not found'}, 404
return {'message': 'Survey not found'}, 404
if survey.status != SurveyStatus.closed:
return {'success': False, 'message': 'Survey is not closed and can therefore not be opened'}, 400
return {'message': 'Survey is not closed and can therefore not be opened'}, 400
all_surveys = db.session.scalars(select(Survey))
if any([s.status == SurveyStatus.open for s in all_surveys]):
return {'success': False, 'message': 'There already is an open survey'}, 400
return {'message': 'There already is an open survey'}, 400
survey.status = SurveyStatus.open
db.session.commit()
......@@ -175,10 +168,10 @@ def close_survey(year) -> Any:
"""
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if not survey:
return {'success': False, 'message': 'Survey not found'}, 404
return {'message': 'Survey not found'}, 404
if survey.status != SurveyStatus.open:
return {'success': False, 'message': 'Survey is not open and can therefore not be closed'}, 400
return {'message': 'Survey is not open and can therefore not be closed'}, 400
survey.status = SurveyStatus.closed
db.session.commit()
......@@ -197,17 +190,14 @@ def publish_survey(year) -> Any:
"""
survey = db.session.scalar(select(Survey).where(Survey.year == year))
if not survey:
return {'success': False, 'message': 'Survey not found'}, 404
return {'message': 'Survey not found'}, 404
if survey.status not in [SurveyStatus.closed, SurveyStatus.published]:
return {
'success': False,
'message': 'Survey is not closed or published and can therefore not be published'
}, 400
return {'message': 'Survey is not closed or published and can therefore not be published'}, 400
# TODO probably replace checked state with something else
if any([response.status != ResponseStatus.checked for response in survey.responses]):
return {'success': False, 'message': 'There are responses that arent checked yet'}, 400
return {'message': 'There are responses that arent checked yet'}, 400
# TODO call new survey_publisher with all responses and the year
......
......@@ -64,7 +64,8 @@ function SurveyContainerComponent({ loadFrom }) {
survey.showNavigationButtons = false;
survey.showTOC = false;
survey.mode = json['mode'];
survey.lockedBy = json['locked_by']
survey.lockedBy = json['locked_by'];
survey.status = json['status'];
setSurveyModel(survey);
}
......@@ -101,6 +102,7 @@ function SurveyContainerComponent({ loadFrom }) {
}
surveyModel.mode = json['mode'];
surveyModel.lockedBy = json['locked_by'];
surveyModel.status = json['status'];
} catch (e) {
return "Unknown Error: " + (e as Error).message;
}
......@@ -173,6 +175,7 @@ function SurveyContainerComponent({ loadFrom }) {
surveyModel.clearIncorrectValues(true);
surveyModel.mode = json['mode'];
surveyModel.lockedBy = json['locked_by']
surveyModel.status = json['status'];
lockUUID.current = json['lock_uuid'];
},
'releaseLock': async () => {
......@@ -184,6 +187,7 @@ function SurveyContainerComponent({ loadFrom }) {
}
surveyModel.mode = json['mode'];
surveyModel.lockedBy = json['locked_by'];
surveyModel.status = json['status'];
},
'validatePage': () => {
const validSurvey = validateWithAnswerVerification(surveyModel.validatePage.bind(surveyModel));
......@@ -196,17 +200,9 @@ function SurveyContainerComponent({ loadFrom }) {
return (
<Container className="survey-container">
<Toaster />
<Row className="survey-content">
<h2><span className="survey-title">{year} Compendium Survey</span><span className="survey-title-nren">{nren}</span></h2>
<span className="survey-description">How to complete this survey -
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam commodo tempus pulvinar. Aenean interdum lectus nec diam porttitor interdum.
Sed efficitur, magna ut dignissim consequat, purus lacus aliquam tortor, ut dignissim orci ligula ac ligula.</span>
</Row>
<Row>
<SurveyNavigationComponent surveyModel={surveyModel} surveyActions={surveyActions}>
<SurveyComponent surveyModel={surveyModel} verificationStatus={verificationStatus} />
</SurveyNavigationComponent>
</Row>
<SurveyNavigationComponent surveyModel={surveyModel} surveyActions={surveyActions} year={year} nren={nren}>
<SurveyComponent surveyModel={surveyModel} verificationStatus={verificationStatus} />
</SurveyNavigationComponent>
</Container>
);
}
......
......@@ -4,13 +4,14 @@ import { Container, Row } from "react-bootstrap";
import { userContext } from "./providers/UserProvider";
function SurveyNavigationComponent({ surveyModel, surveyActions, children }) {
function SurveyNavigationComponent({ surveyModel, surveyActions, year, nren, children }) {
// We use some state variables that are directly derived from the surveyModel to
// ensure React rerenders properly. It would honestly be just as easy to remove the state
// and force the rerender ourselves, because we know exactly when the rerender is necessary.
const [pageNo, setPageNo] = useState(0);
const [editing, setEditing] = useState(false);
const [lockedBy, setLockedBy] = useState("");
const [responseStatus, setResponseStatus] = useState("");
const { user: loggedInUser } = useContext(userContext);
// useCallback to keep the linter happy with the useEffect dependencies below
......@@ -18,6 +19,7 @@ function SurveyNavigationComponent({ surveyModel, surveyActions, children }) {
setEditing(surveyModel.mode == 'edit');
setLockedBy(surveyModel.lockedBy);
setPageNo(surveyModel.currentPageNo);
setResponseStatus(surveyModel.status);
}, [surveyModel]);
useEffect(() => {
......@@ -66,6 +68,12 @@ function SurveyNavigationComponent({ surveyModel, surveyActions, children }) {
return (
<Container>
<Row className="survey-content">
<h2><span className="survey-title">{year} Compendium Survey </span><span className="survey-title-nren"> {nren} </span><span> - {responseStatus}</span></h2>
<span className="survey-description">How to complete this survey -
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam commodo tempus pulvinar. Aenean interdum lectus nec diam porttitor interdum.
Sed efficitur, magna ut dignissim consequat, purus lacus aliquam tortor, ut dignissim orci ligula ac ligula.</span>
</Row>
<Row>
{renderExternalNavigation()}
</Row>
......
from sqlalchemy import select
from sqlalchemy.orm import lazyload
from compendium_v2.db import db
from compendium_v2.db.model import NREN
......@@ -28,7 +29,9 @@ def test_queries(app_with_survey_db, mocker):
with app_with_survey_db.app_context():
responses = db.session.scalars(select(SurveyResponse).order_by(SurveyResponse.nren_id)).all()
responses = db.session.scalars(
select(SurveyResponse).options(lazyload("*")).order_by(SurveyResponse.nren_id)
).all()
assert len(responses) == 1
assert responses[0].answers == {"data": {}, "page": 0, "verification_status": {}}
......
import json
import jsonschema
from compendium_v2.routes.response import SURVEY_RESPONSE_SCHEMA, VerificationStatus
def test_response_route_try_response(client, test_survey_data):
rv = client.get(
'/api/response/try/2022',
headers={'Accept': ['application/json']})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(result, SURVEY_RESPONSE_SCHEMA)
assert result
def test_response_route_inspect_response(client, test_survey_data):
rv = client.get(
'/api/response/inspect/2022',
headers={'Accept': ['application/json']})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(result, SURVEY_RESPONSE_SCHEMA)
assert result
def test_response_route_save_load_response(client, test_survey_data, mocked_user):
rv = client.post(
'/api/response/save/2021/nren2',
headers={'Accept': ['application/json']},
json={
'data': {'q1': 'yes', 'q2': ['no']},
'page': 3,
'verification_status': {'q1': VerificationStatus.Verified}
})
assert rv.status_code == 400
result = json.loads(rv.data.decode('utf-8'))
assert not result.get('success')
assert result.get('message') == 'Survey is closed'
rv = client.get(
'/api/survey/load/2021/nren2',
headers={'Accept': ['application/json']})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(result, SURVEY_RESPONSE_SCHEMA)
assert result['page'] == 0
assert result['data'] == {}
assert result['verification_status'] == {}
rv = client.post(
'/api/survey/save/2022/nren2',
headers={'Accept': ['application/json']},
json={
'data': {'q1': 'yes', 'q2': ['no']},
'page': 3,
'verification_status': {'q1': VerificationStatus.Verified}
})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
assert result.get('success')
rv = client.get(
'/api/survey/load/2022/nren2',
headers={'Accept': ['application/json']})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(result, SURVEY_RESPONSE_SCHEMA)
assert result['page'] == 3
assert result['data'] == {'q1': 'yes', 'q2': ['no']}
assert result['verification_status'] == {'q1': VerificationStatus.Verified}
......@@ -2,7 +2,7 @@ import json
import jsonschema
from compendium_v2.db import db
from compendium_v2.db.survey_model import Survey, SurveyStatus
from compendium_v2.routes.survey import LIST_SURVEYS_RESPONSE_SCHEMA, SURVEY_RESPONSE_SCHEMA, VerificationStatus
from compendium_v2.routes.survey import LIST_SURVEYS_RESPONSE_SCHEMA
def test_survey_route_list_response(client, test_survey_data):
......@@ -107,70 +107,3 @@ def test_survey_route_publish(app, client, test_survey_data, mocked_admin_user):
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
assert result.get('success')
def test_survey_route_try_response(client, test_survey_data):
rv = client.get(
'/api/survey/try/2022',
headers={'Accept': ['application/json']})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(result, SURVEY_RESPONSE_SCHEMA)
assert result
def test_survey_route_inspect_response(client, test_survey_data):
rv = client.get(
'/api/survey/inspect/2022',
headers={'Accept': ['application/json']})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(result, SURVEY_RESPONSE_SCHEMA)
assert result
def test_survey_route_save_load_response(client, test_survey_data, mocked_user):
rv = client.post(
'/api/survey/save/2021/nren2',
headers={'Accept': ['application/json']},
json={
'data': {'q1': 'yes', 'q2': ['no']},
'page': 3,
'verification_status': {'q1': VerificationStatus.Verified}
})
assert rv.status_code == 400
result = json.loads(rv.data.decode('utf-8'))
assert not result.get('success')
assert result.get('message') == 'Survey is closed'
rv = client.get(
'/api/survey/load/2021/nren2',
headers={'Accept': ['application/json']})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(result, SURVEY_RESPONSE_SCHEMA)
assert result['page'] == 0
assert result['data'] == {}
assert result['verification_status'] == {}
rv = client.post(
'/api/survey/save/2022/nren2',
headers={'Accept': ['application/json']},
json={
'data': {'q1': 'yes', 'q2': ['no']},
'page': 3,
'verification_status': {'q1': VerificationStatus.Verified}
})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
assert result.get('success')
rv = client.get(
'/api/survey/load/2022/nren2',
headers={'Accept': ['application/json']})
assert rv.status_code == 200
result = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(result, SURVEY_RESPONSE_SCHEMA)
assert result['page'] == 3
assert result['data'] == {'q1': 'yes', 'q2': ['no']}
assert result['verification_status'] == {'q1': VerificationStatus.Verified}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment