diff --git a/compendium_v2/routes/survey_model.json b/compendium_v2/migrations/surveymodels/survey_model_2022.json similarity index 100% rename from compendium_v2/routes/survey_model.json rename to compendium_v2/migrations/surveymodels/survey_model_2022.json diff --git a/compendium_v2/routes/survey.py b/compendium_v2/routes/survey.py index daa3921c827ec8509988f37ee377daf243cd0249..7920a581d77c56240ccdbeab8c9348f57bb3890d 100644 --- a/compendium_v2/routes/survey.py +++ b/compendium_v2/routes/survey.py @@ -1,8 +1,6 @@ -import json import logging from enum import Enum -from pathlib import Path -from typing import Any, Optional, TypedDict, List, Dict +from typing import Any, TypedDict, List, Dict from flask import Blueprint, jsonify, request from sqlalchemy import select @@ -33,7 +31,7 @@ LIST_SURVEYS_RESPONSE_SCHEMA = { 'survey': { 'type': 'object', 'properties': { - 'year': {'type': 'string'}, + 'year': {'type': 'integer'}, 'status': {'type': 'string'}, 'responses': {'type': 'array', 'items': {'$ref': '#/definitions/response'}}, }, @@ -53,9 +51,9 @@ SURVEY_RESPONSE_SCHEMA = { 'model': {'type': 'object'}, 'data': {'type': 'object'}, 'page': {'type': 'number'}, - 'verification_status': {'type': 'array', 'items': {'type': 'string'}} + 'verification_status': {'type': 'object'} }, - 'required': ['year', 'status', 'responses'], + 'required': ['model', 'data', 'page', 'verification_status'], 'additionalProperties': False } @@ -239,7 +237,16 @@ def try_survey(year) -> Any: compendium_v2.routes.survey.SURVEY_RESPONSE_SCHEMA """ - return get_survey(year, all_visible=False) + survey = db.session.scalar(select(Survey).where(Survey.year == year)) + if not survey: + return "Survey not found", 404 + + return jsonify({ + "model": survey.survey, + "data": {}, + "page": 0, + "verification_status": {} + }) # TODO admin only @@ -256,16 +263,10 @@ def inspect_survey(year) -> Any: compendium_v2.routes.survey.SURVEY_RESPONSE_SCHEMA """ - return get_survey(year, all_visible=True) - - -def get_survey(year, all_visible) -> Any: survey = db.session.scalar(select(Survey).where(Survey.year == year)) if not survey: return "Survey not found", 404 - survey_json = prepare_survey_model(survey.survey, year, all_visible) - def visible_visitor(object, items): for key, value in items: if type(value) == dict: @@ -276,12 +277,11 @@ def get_survey(year, all_visible) -> Any: object['title'] = object['title'] + ' (visibleif: [' + value.replace('{', '#').replace('}', '#') + '])' object[key] = 'true' - if all_visible: - visible_visitor(survey, survey.items()) + visible_visitor(survey.survey, survey.survey.items()) return jsonify({ - "model": survey_json, - "data": None, + "model": survey.survey, + "data": {}, "page": 0, "verification_status": {} }) @@ -312,8 +312,7 @@ def load_survey(year, nren_name) -> Any: # TODO validation (if not admin) on year (is survey open?) and nren (logged in user is part of nren?) - survey_json = prepare_survey_model(survey.survey, year) - data: Optional[dict] = None + data = {} page = 0 verification_status = {} @@ -335,24 +334,13 @@ def load_survey(year, nren_name) -> Any: verification_status = {question_name: VerificationStatus.Unverified for question_name in data.keys()} return jsonify({ - "model": survey_json, + "model": survey.survey, "data": data, "page": page, "verification_status": verification_status }) -def prepare_survey_model(survey, year, all_visible=False): - if survey is None or survey == {}: - # TODO remove this at some point, its just convenient for now while we are changing the survey model a lot - # or is it really?? we can also keep the surveys on disk, which makes it really easy to diff, etc - p = Path(__file__).with_name('survey_model.json') - with p.open('r') as f: - survey = json.load(f) - - return survey - - @routes.route('/save/<int:year>/<string:nren_name>', methods=['POST']) @common.require_accepts_json def save_survey(year, nren_name) -> Any: diff --git a/survey-frontend/src/SurveyComponent.tsx b/survey-frontend/src/SurveyComponent.tsx index e07d016a896a7bcc67465ea296559dc8bdf0dbc9..cf20f9a36efc0640e10e11a44a5076ecf6aaee28 100644 --- a/survey-frontend/src/SurveyComponent.tsx +++ b/survey-frontend/src/SurveyComponent.tsx @@ -117,10 +117,9 @@ function SurveyComponent({ loadFrom, saveTo = '', readonly = false}) { survey.setVariable('surveyyear', year); survey.setVariable('previousyear', parseInt(year!) - 1); - if (json['data'] !== null) { - survey.data = json['data']; - survey.clearIncorrectValues(true); // TODO test if this really removes all old values and such - } + survey.data = json['data']; + survey.clearIncorrectValues(true); // TODO test if this really removes all old values and such + survey.currentPageNo = json['page']; survey.addNavigationItem({ diff --git a/test/conftest.py b/test/conftest.py index 8fdd0c86cec496325f0f7e6b8bb5a0b3420ba215..fa53adf02f5980e1f43bb42020b82baad7ab484e 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -4,8 +4,8 @@ import pytest import random import compendium_v2 -from compendium_v2.db import db, model -from compendium_v2.survey_db import model as survey_model +from compendium_v2.db import db, model, survey_model +from compendium_v2.survey_db import model as survey_db_model def _test_data_csv(filename): @@ -113,6 +113,31 @@ def test_staff_data(app): db.session.commit() +@pytest.fixture +def test_survey_data(app): + with app.app_context(): + nren_names = ['nren1', 'nren2', 'nren3', 'nren4'] + nren_dict = {nren_name: model.NREN(name=nren_name, country='country') for nren_name in nren_names} + db.session.add_all(nren_dict.values()) + + survey2021 = survey_model.Survey(year=2021, survey={}, status=survey_model.SurveyStatus.published) + survey2022 = survey_model.Survey( + year=2022, + survey={'part1': [{'title': 'ha', 'visibleIf': 'false'}]}, + status=survey_model.SurveyStatus.published + ) + db.session.add_all([survey2021, survey2022]) + + db.session.add(survey_model.SurveyResponse( + nren=nren_dict['nren1'], + survey=survey2022, + answers={}, + status=survey_model.ResponseStatus.checked + )) + + db.session.commit() + + @pytest.fixture def app(dummy_config): app = compendium_v2._create_app_with_db(dummy_config) @@ -123,7 +148,7 @@ def app(dummy_config): @pytest.fixture def app_with_survey_db(dummy_config): - dummy_config['SQLALCHEMY_BINDS'] = {survey_model.SURVEY_DB_BIND: dummy_config['SURVEY_DATABASE_URI']} + dummy_config['SQLALCHEMY_BINDS'] = {survey_db_model.SURVEY_DB_BIND: dummy_config['SURVEY_DATABASE_URI']} app = compendium_v2._create_app_with_db(dummy_config) with app.app_context(): db.create_all() diff --git a/test/test_survey.py b/test/test_survey.py new file mode 100644 index 0000000000000000000000000000000000000000..b4b4d4f6aa64d0d62bc353139cf131988732ac05 --- /dev/null +++ b/test/test_survey.py @@ -0,0 +1,123 @@ +import json +import jsonschema +from compendium_v2.routes.survey import LIST_SURVEYS_RESPONSE_SCHEMA, SURVEY_RESPONSE_SCHEMA, VerificationStatus + + +def test_survey_route_list_response(client, test_survey_data): + rv = client.get( + '/api/survey/list', + headers={'Accept': ['application/json']}) + assert rv.status_code == 200 + result = json.loads(rv.data.decode('utf-8')) + jsonschema.validate(result, LIST_SURVEYS_RESPONSE_SCHEMA) + assert result + + +def test_survey_route_new(client, test_survey_data): + rv = client.post( + '/api/survey/new', + headers={'Accept': ['application/json']}) + assert rv.status_code == 200 + result = json.loads(rv.data.decode('utf-8')) + assert result == {'success': True} + + rv = client.post( + '/api/survey/new', + headers={'Accept': ['application/json']}) + assert rv.status_code != 200 + + +def test_survey_route_open_close(client, test_survey_data): + rv = client.post( + '/api/survey/new', + headers={'Accept': ['application/json']}) + assert rv.status_code == 200 + result = json.loads(rv.data.decode('utf-8')) + assert result == {'success': True} + + rv = client.post( + '/api/survey/open/2023', + headers={'Accept': ['application/json']}) + assert rv.status_code == 200 + result = json.loads(rv.data.decode('utf-8')) + assert result == {'success': True} + + rv = client.post( + '/api/survey/open/2023', + headers={'Accept': ['application/json']}) + assert rv.status_code != 200 + + rv = client.post( + '/api/survey/close/2023', + headers={'Accept': ['application/json']}) + assert rv.status_code == 200 + result = json.loads(rv.data.decode('utf-8')) + assert result == {'success': True} + + rv = client.post( + '/api/survey/close/2023', + headers={'Accept': ['application/json']}) + assert rv.status_code != 200 + + +def test_survey_route_publish(client, test_survey_data): + rv = client.post( + '/api/survey/publish/2022', + headers={'Accept': ['application/json']}) + assert rv.status_code == 200 + result = json.loads(rv.data.decode('utf-8')) + assert result == {'success': True} + + +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): + 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 == 200 + result = json.loads(rv.data.decode('utf-8')) + assert result == {'success': True} + + 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'] == 3 + assert result['data'] == {'q1': 'yes', 'q2': ['no']} + assert result['verification_status'] == {'q1': VerificationStatus.Verified} + + 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'] == 0 + assert result['data'] == {'q1': 'yes', 'q2': ['no']} + assert result['verification_status'] == {'q1': VerificationStatus.Unverified, 'q2': VerificationStatus.Unverified}