From a463475d5446b6ec9e3de332afba730ebb019bea Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 15:50:29 +1000 Subject: [PATCH 01/21] Add a minimal TaxBenefitSystem Fixture in tests/web_api/conftest.py - this minimal TaxBenefitSystem has the same Entities as the `openfisca_country_template` - it one mock Parameter and one mock Variable --- tests/web_api/conftest.py | 72 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/web_api/conftest.py diff --git a/tests/web_api/conftest.py b/tests/web_api/conftest.py new file mode 100644 index 0000000000..1cd1895028 --- /dev/null +++ b/tests/web_api/conftest.py @@ -0,0 +1,72 @@ +from http.client import OK, NOT_FOUND +import pytest +from unittest.mock import patch + +from openfisca_core.entities import build_entity +from openfisca_core.parameters import ParameterNode +from openfisca_core.periods import MONTH, ETERNITY +from openfisca_core.taxbenefitsystems import TaxBenefitSystem +from openfisca_core.variables import Variable + + +from openfisca_core.entities import build_entity + +Household = build_entity( + key = "household", + plural = "households", + label = "All the people in a family or group who live together in the same place.", + doc = "[REMOVED FOR TEST]", + roles = [ + { + "key": "parent", + "plural": "parents", + "label": "Parents", + "max": 2, + "subroles": ["first_parent", "second_parent"], + "doc": "The one or two adults in charge of the household.", + }, + { + "key": "child", + "plural": "children", + "label": "Child", + "doc": "Other individuals living in the household.", + }, + ], + ) + +Person = build_entity( + key = "person", + plural = "persons", + label = "An individual. The minimal legal entity on which a legislation might be applied.", + doc = "[REMOVED FOR TEST]", + is_person = True, + ) + +@pytest.fixture(scope="module") +def entities(): + return [Household, Person] + + +@pytest.fixture() +def test_tax_benefit_system(entities): + tax_benefit_system = TaxBenefitSystem(entities) + + # At least one ParameterNode must be defined, or else `openfisca_web_api.app.create_app()` will fail + tax_benefit_system.parameters = ParameterNode(name="mockParameterNode", + data={'amount': {'values': { + "2015-01-01": {'value': 550}, + "2016-01-01": {'value': 600} + }} + }) + + # At least one Variable must be defined, or else `openfisca_web_api.app.create_app()` will fail + class initial_variable(Variable): + value_type = float + entity = Person + definition_period = MONTH + label = "Basic income provided to adults" + reference = "https://law.gov.example/basic_income" # Always use the most official source + + tax_benefit_system.add_variable(initial_variable) + + return tax_benefit_system \ No newline at end of file From eca32deb83b8bb39f74cb78cd50a991b149c3fa3 Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 15:50:29 +1000 Subject: [PATCH 02/21] Create `test_client` fixture in tests/web_api/test_variables - In this fixture you can instantiate the variables that will be needed by your tests, add them to the TaxBenefitSystem, and create an App Client --- tests/web_api/test_variables.py | 165 ++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/tests/web_api/test_variables.py b/tests/web_api/test_variables.py index 53d4a8948f..2a64671f4f 100644 --- a/tests/web_api/test_variables.py +++ b/tests/web_api/test_variables.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from datetime import date from http.client import OK, NOT_FOUND import json import re @@ -7,11 +8,175 @@ import pytest from . import subject +from openfisca_core.indexed_enums import Enum +from openfisca_core.periods import MONTH, ETERNITY +from openfisca_core.variables import Variable +from openfisca_web_api.app import create_app +from tests.web_api.conftest import test_tax_benefit_system, Person, Household def assert_items_equal(x, y): assert set(x) == set(y) +GITHUB_URL_REGEX = r'^https://github\.com/openfisca/openfisca-core/blob/\d+\.\d+\.\d+((.dev|rc)\d+)?/tests/web_api/(.)+\.py#L\d+-L\d+$' + +@pytest.fixture(scope="module") +def test_client(test_tax_benefit_system): + + ### + # In this section you define the mock Variables you will need for tests in this module + class birth(Variable): + value_type = date + default_value = date(1970, 1, 1) # By default, if no value is set for a simulation, we consider the people involved in a simulation to be born on the 1st of Jan 1970. + entity = Person + label = "Birth date" + definition_period = ETERNITY # This variable cannot change over time. + reference = "https://en.wiktionary.org/wiki/birthdate" + + + class income_tax(Variable): + value_type = float + entity = Person + definition_period = MONTH + label = "Income tax" + reference = "https://law.gov.example/income_tax" # Always use the most official source + + def formula(person, period, parameters): + """ + Income tax. + + The formula to compute the income tax for a given person at a given period + """ + return person("salary", period) * parameters(period).taxes.income_tax_rate + + + class age(Variable): + value_type = int + entity = Person + definition_period = MONTH + label = "Person's age (in years)" + + def formula(person, period, _parameters): + """ + Person's age (in years). + + A person's age is computed according to its birth date. + """ + birth = person("birth", period) + birth_year = birth.astype("datetime64[Y]").astype(int) + 1970 + birth_month = birth.astype("datetime64[M]").astype(int) % 12 + 1 + birth_day = (birth - birth.astype("datetime64[M]") + 1).astype(int) + + is_birthday_past = (birth_month < period.start.month) + (birth_month == period.start.month) * (birth_day <= period.start.day) + + return (period.start.year - birth_year) - where(is_birthday_past, 0, 1) # If the birthday is not passed this year, subtract one year + + + class housing_allowance(Variable): + value_type = float + entity = Household + definition_period = MONTH + label = "Housing allowance" + reference = "https://law.gov.example/housing_allowance" # Always use the most official source + end = "2016-11-30" # This allowance was removed on the 1st of Dec 2016. Calculating it before this date will always return the variable default value, 0. + unit = "currency-EUR" + documentation = """ + This allowance was introduced on the 1st of Jan 1980. + It disappeared in Dec 2016. + """ + + def formula_1980(household, period, parameters): + """ + Housing allowance. + + This allowance was introduced on the 1st of Jan 1980. + Calculating it before this date will always return the variable default value, 0. + + To compute this allowance, the 'rent' value must be provided for the same month, + but 'housing_occupancy_status' is not necessary. + """ + return household("rent", period) * parameters(period).benefits.housing_allowance + + + class HousingOccupancyStatus(Enum): + __order__ = "owner tenant free_lodger homeless" + owner = "Owner" + tenant = "Tenant" + free_lodger = "Free lodger" + homeless = "Homeless" + + + class housing_occupancy_status(Variable): + value_type = Enum + possible_values = HousingOccupancyStatus + default_value = HousingOccupancyStatus.tenant + entity = Household + definition_period = MONTH + label = "Legal housing situation of the household concerning their main residence" + + + class basic_income(Variable): + value_type = float + entity = Person + definition_period = MONTH + label = "Basic income provided to adults" + reference = "https://law.gov.example/basic_income" # Always use the most official source + + def formula_2016_12(person, period, parameters): + """ + Basic income provided to adults. + + Since Dec 1st 2016, the basic income is provided to any adult, without considering their income. + """ + age_condition = person("age", period) >= parameters(period).general.age_of_majority + return age_condition * parameters(period).benefits.basic_income # This '*' is a vectorial 'if'. See https://openfisca.org/doc/coding-the-legislation/25_vectorial_computing.html#control-structures + + def formula_2015_12(person, period, parameters): + """ + Basic income provided to adults. + + From Dec 1st 2015 to Nov 30 2016, the basic income is provided to adults who have no income. + Before Dec 1st 2015, the basic income does not exist in the law, and calculating it returns its default value, which is 0. + """ + age_condition = person("age", period) >= parameters(period).general.age_of_majority + salary_condition = person("salary", period) == 0 + return age_condition * salary_condition * parameters(period).benefits.basic_income # The '*' is also used as a vectorial 'and'. See https://openfisca.org/doc/coding-the-legislation/25_vectorial_computing.html#boolean-operations + + + class pension(Variable): + value_type = float + entity = Person + definition_period = MONTH + label = "Pension for the elderly. Pension attribuée aux personnes âgées. تقاعد." + reference = ["https://fr.wikipedia.org/wiki/Retraite_(économie)", "https://ar.wikipedia.org/wiki/تقاعد"] + + def formula(person, period, parameters): + """ + Pension for the elderly. + + A person's pension depends on their birth date. + In French: retraite selon l'âge. + In Arabic: تقاعد. + """ + age_condition = person("age", period) >= parameters(period).general.age_of_retirement + return age_condition + + ### + # Add the Variables above to the `test_tax_benefit_system` fixture + test_tax_benefit_system.add_variable(birth) + test_tax_benefit_system.add_variable(income_tax) + test_tax_benefit_system.add_variable(age) + test_tax_benefit_system.add_variable(housing_allowance) + test_tax_benefit_system.add_variable(housing_occupancy_status) + test_tax_benefit_system.add_variable(basic_income) + test_tax_benefit_system.add_variable(pension) + + ### + # Create the test API client + app = create_app(test_tax_benefit_system) + return app.test_client() + + # /variables variables_response = subject.get('/variables') From 6166bd2b54af9eb15cfb7c8ae2fe2e168cf0f57b Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 15:50:29 +1000 Subject: [PATCH 03/21] Fix // Change fixture scope to "package" to prevent recreating fixture objects for every single test --- tests/web_api/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/web_api/conftest.py b/tests/web_api/conftest.py index 1cd1895028..8335298764 100644 --- a/tests/web_api/conftest.py +++ b/tests/web_api/conftest.py @@ -42,12 +42,12 @@ is_person = True, ) -@pytest.fixture(scope="module") +@pytest.fixture(scope="package") def entities(): return [Household, Person] -@pytest.fixture() +@pytest.fixture(scope="package") def test_tax_benefit_system(entities): tax_benefit_system = TaxBenefitSystem(entities) From c1abb72560dc9714047ce5fda1c9b5814b3ea97f Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 15:50:29 +1000 Subject: [PATCH 04/21] Refactor all existing tests to use Fixtures instead of importing from "openfisca_country_template" --- tests/web_api/test_variables.py | 87 +++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 37 deletions(-) diff --git a/tests/web_api/test_variables.py b/tests/web_api/test_variables.py index 2a64671f4f..47c7efa4f2 100644 --- a/tests/web_api/test_variables.py +++ b/tests/web_api/test_variables.py @@ -6,7 +6,6 @@ import re import pytest -from . import subject from openfisca_core.indexed_enums import Enum from openfisca_core.periods import MONTH, ETERNITY @@ -179,15 +178,17 @@ def formula(person, period, parameters): # /variables -variables_response = subject.get('/variables') -GITHUB_URL_REGEX = r'^https://github\.com/openfisca/country-template/blob/\d+\.\d+\.\d+((.dev|rc)\d+)?/openfisca_country_template/variables/(.)+\.py#L\d+-L\d+$' +@pytest.fixture(scope="module") +def variables_response(test_client): + variables_response = test_client.get("/variables") + return variables_response -def test_return_code(): +def test_return_code(variables_response): assert variables_response.status_code == OK -def test_response_data(): +def test_response_data(variables_response): variables = json.loads(variables_response.data.decode('utf-8')) assert variables['birth'] == { 'description': 'Birth date', @@ -198,20 +199,22 @@ def test_response_data(): # /variable/ -def test_error_code_non_existing_variable(): - response = subject.get('/variable/non_existing_variable') +def test_error_code_non_existing_variable(test_client): + response = test_client.get('/variable/non_existing_variable') assert response.status_code == NOT_FOUND -input_variable_response = subject.get('/variable/birth') -input_variable = json.loads(input_variable_response.data.decode('utf-8')) +@pytest.fixture(scope="module") +def input_variable_response(test_client): + input_variable_response = test_client.get('/variable/birth') + return input_variable_response -def test_return_code_existing_input_variable(): +def test_return_code_existing_input_variable(input_variable_response): assert input_variable_response.status_code == OK -def check_input_variable_value(key, expected_value): +def check_input_variable_value(key, expected_value, input_variable={}): assert input_variable[key] == expected_value @@ -223,23 +226,25 @@ def check_input_variable_value(key, expected_value): ('entity', 'person'), ('references', ['https://en.wiktionary.org/wiki/birthdate']), ]) -def test_input_variable_value(expected_values): - check_input_variable_value(*expected_values) +def test_input_variable_value(expected_values, input_variable_response): + input_variable = json.loads(input_variable_response.data.decode('utf-8')) + check_input_variable_value(*expected_values, input_variable=input_variable) -def test_input_variable_github_url(): - assert re.match(GITHUB_URL_REGEX, input_variable['source']) +def test_input_variable_github_url(test_client): + input_variable_response = test_client.get('/variable/income_tax') + input_variable = json.loads(input_variable_response.data.decode('utf-8')) -variable_response = subject.get('/variable/income_tax') -variable = json.loads(variable_response.data.decode('utf-8')) + assert re.match(GITHUB_URL_REGEX, input_variable['source']) -def test_return_code_existing_variable(): +def test_return_code_existing_variable(test_client): + variable_response = test_client.get('/variable/income_tax') assert variable_response.status_code == OK -def check_variable_value(key, expected_value): +def check_variable_value(key, expected_value, variable={}): assert variable[key] == expected_value @@ -250,11 +255,15 @@ def check_variable_value(key, expected_value): ('definitionPeriod', 'MONTH'), ('entity', 'person'), ]) -def test_variable_value(expected_values): - check_variable_value(*expected_values) +def test_variable_value(expected_values, test_client): + variable_response = test_client.get('/variable/income_tax') + variable = json.loads(variable_response.data.decode('utf-8')) + check_variable_value(*expected_values, variable=variable) -def test_variable_formula_github_link(): +def test_variable_formula_github_link(test_client): + variable_response = test_client.get('/variable/income_tax') + variable = json.loads(variable_response.data.decode('utf-8')) assert re.match(GITHUB_URL_REGEX, variable['formulas']['0001-01-01']['source']) @@ -264,22 +273,22 @@ def test_variable_formula_content(): assert "return person(\"salary\", period) * parameters(period).taxes.income_tax_rate" in content -def test_null_values_are_dropped(): - variable_response = subject.get('/variable/age') +def test_null_values_are_dropped(test_client): + variable_response = test_client.get('/variable/age') variable = json.loads(variable_response.data.decode('utf-8')) assert 'references' not in variable.keys() -def test_variable_with_start_and_stop_date(): - response = subject.get('/variable/housing_allowance') +def test_variable_with_start_and_stop_date(test_client): + response = test_client.get('/variable/housing_allowance') variable = json.loads(response.data.decode('utf-8')) assert_items_equal(variable['formulas'], ['1980-01-01', '2016-12-01']) assert variable['formulas']['2016-12-01'] is None assert 'formula' in variable['formulas']['1980-01-01']['content'] -def test_variable_with_enum(): - response = subject.get('/variable/housing_occupancy_status') +def test_variable_with_enum(test_client): + response = test_client.get('/variable/housing_occupancy_status') variable = json.loads(response.data.decode('utf-8')) assert variable['valueType'] == 'String' assert variable['defaultValue'] == 'tenant' @@ -291,19 +300,23 @@ def test_variable_with_enum(): 'tenant': 'Tenant'} -dated_variable_response = subject.get('/variable/basic_income') -dated_variable = json.loads(dated_variable_response.data.decode('utf-8')) +@pytest.fixture(scope="module") +def dated_variable_response(test_client): + dated_variable_response = test_client.get('/variable/basic_income') + return dated_variable_response -def test_return_code_existing_dated_variable(): +def test_return_code_existing_dated_variable(dated_variable_response): assert dated_variable_response.status_code == OK -def test_dated_variable_formulas_dates(): +def test_dated_variable_formulas_dates(dated_variable_response): + dated_variable = json.loads(dated_variable_response.data.decode('utf-8')) assert_items_equal(dated_variable['formulas'], ['2016-12-01', '2015-12-01']) -def test_dated_variable_formulas_content(): +def test_dated_variable_formulas_content(dated_variable_response): + dated_variable = json.loads(dated_variable_response.data.decode('utf-8')) formula_code_2016 = dated_variable['formulas']['2016-12-01']['content'] formula_code_2015 = dated_variable['formulas']['2015-12-01']['content'] @@ -313,13 +326,13 @@ def test_dated_variable_formulas_content(): assert "return" in formula_code_2015 -def test_variable_encoding(): - variable_response = subject.get('/variable/pension') +def test_variable_encoding(test_client): + variable_response = test_client.get('/variable/pension') assert variable_response.status_code == OK -def test_variable_documentation(): - response = subject.get('/variable/housing_allowance') +def test_variable_documentation(test_client): + response = test_client.get('/variable/housing_allowance') variable = json.loads(response.data.decode('utf-8')) assert variable['documentation'] == "This allowance was introduced on the 1st of Jan 1980.\nIt disappeared in Dec 2016." From a328923b08c811b5b4bbe94e0afd7ffea5b254db Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 15:50:29 +1000 Subject: [PATCH 05/21] Style // fix flake8 --- tests/web_api/conftest.py | 19 ++++++++----------- tests/web_api/test_variables.py | 23 ++++++++++------------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/tests/web_api/conftest.py b/tests/web_api/conftest.py index 8335298764..864e76655e 100644 --- a/tests/web_api/conftest.py +++ b/tests/web_api/conftest.py @@ -1,16 +1,12 @@ -from http.client import OK, NOT_FOUND import pytest -from unittest.mock import patch from openfisca_core.entities import build_entity from openfisca_core.parameters import ParameterNode -from openfisca_core.periods import MONTH, ETERNITY +from openfisca_core.periods import MONTH from openfisca_core.taxbenefitsystems import TaxBenefitSystem from openfisca_core.variables import Variable -from openfisca_core.entities import build_entity - Household = build_entity( key = "household", plural = "households", @@ -42,6 +38,7 @@ is_person = True, ) + @pytest.fixture(scope="package") def entities(): return [Household, Person] @@ -52,13 +49,13 @@ def test_tax_benefit_system(entities): tax_benefit_system = TaxBenefitSystem(entities) # At least one ParameterNode must be defined, or else `openfisca_web_api.app.create_app()` will fail - tax_benefit_system.parameters = ParameterNode(name="mockParameterNode", + tax_benefit_system.parameters = ParameterNode(name="mockParameterNode", data={'amount': {'values': { - "2015-01-01": {'value': 550}, - "2016-01-01": {'value': 600} - }} + "2015-01-01": {'value': 550}, + "2016-01-01": {'value': 600} + }} }) - + # At least one Variable must be defined, or else `openfisca_web_api.app.create_app()` will fail class initial_variable(Variable): value_type = float @@ -69,4 +66,4 @@ class initial_variable(Variable): tax_benefit_system.add_variable(initial_variable) - return tax_benefit_system \ No newline at end of file + return tax_benefit_system diff --git a/tests/web_api/test_variables.py b/tests/web_api/test_variables.py index 47c7efa4f2..6290e55f91 100644 --- a/tests/web_api/test_variables.py +++ b/tests/web_api/test_variables.py @@ -3,6 +3,7 @@ from datetime import date from http.client import OK, NOT_FOUND import json +from numpy import where import re import pytest @@ -11,7 +12,8 @@ from openfisca_core.periods import MONTH, ETERNITY from openfisca_core.variables import Variable from openfisca_web_api.app import create_app -from tests.web_api.conftest import test_tax_benefit_system, Person, Household +from .conftest import Person, Household + def assert_items_equal(x, y): assert set(x) == set(y) @@ -19,9 +21,10 @@ def assert_items_equal(x, y): GITHUB_URL_REGEX = r'^https://github\.com/openfisca/openfisca-core/blob/\d+\.\d+\.\d+((.dev|rc)\d+)?/tests/web_api/(.)+\.py#L\d+-L\d+$' + @pytest.fixture(scope="module") def test_client(test_tax_benefit_system): - + ### # In this section you define the mock Variables you will need for tests in this module class birth(Variable): @@ -32,7 +35,6 @@ class birth(Variable): definition_period = ETERNITY # This variable cannot change over time. reference = "https://en.wiktionary.org/wiki/birthdate" - class income_tax(Variable): value_type = float entity = Person @@ -48,7 +50,6 @@ def formula(person, period, parameters): """ return person("salary", period) * parameters(period).taxes.income_tax_rate - class age(Variable): value_type = int entity = Person @@ -70,7 +71,6 @@ def formula(person, period, _parameters): return (period.start.year - birth_year) - where(is_birthday_past, 0, 1) # If the birthday is not passed this year, subtract one year - class housing_allowance(Variable): value_type = float entity = Household @@ -96,7 +96,6 @@ def formula_1980(household, period, parameters): """ return household("rent", period) * parameters(period).benefits.housing_allowance - class HousingOccupancyStatus(Enum): __order__ = "owner tenant free_lodger homeless" owner = "Owner" @@ -104,7 +103,6 @@ class HousingOccupancyStatus(Enum): free_lodger = "Free lodger" homeless = "Homeless" - class housing_occupancy_status(Variable): value_type = Enum possible_values = HousingOccupancyStatus @@ -113,7 +111,6 @@ class housing_occupancy_status(Variable): definition_period = MONTH label = "Legal housing situation of the household concerning their main residence" - class basic_income(Variable): value_type = float entity = Person @@ -141,7 +138,6 @@ def formula_2015_12(person, period, parameters): salary_condition = person("salary", period) == 0 return age_condition * salary_condition * parameters(period).benefits.basic_income # The '*' is also used as a vectorial 'and'. See https://openfisca.org/doc/coding-the-legislation/25_vectorial_computing.html#boolean-operations - class pension(Variable): value_type = float entity = Person @@ -159,7 +155,7 @@ def formula(person, period, parameters): """ age_condition = person("age", period) >= parameters(period).general.age_of_retirement return age_condition - + ### # Add the Variables above to the `test_tax_benefit_system` fixture test_tax_benefit_system.add_variable(birth) @@ -173,7 +169,7 @@ def formula(person, period, parameters): ### # Create the test API client app = create_app(test_tax_benefit_system) - return app.test_client() + return app.test_client() # /variables @@ -184,6 +180,7 @@ def variables_response(test_client): variables_response = test_client.get("/variables") return variables_response + def test_return_code(variables_response): assert variables_response.status_code == OK @@ -214,7 +211,7 @@ def test_return_code_existing_input_variable(input_variable_response): assert input_variable_response.status_code == OK -def check_input_variable_value(key, expected_value, input_variable={}): +def check_input_variable_value(key, expected_value, input_variable=None): assert input_variable[key] == expected_value @@ -244,7 +241,7 @@ def test_return_code_existing_variable(test_client): assert variable_response.status_code == OK -def check_variable_value(key, expected_value, variable={}): +def check_variable_value(key, expected_value, variable=None): assert variable[key] == expected_value From 72d83fbe073a85eed952a78e0a279746d716c739 Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 15:50:29 +1000 Subject: [PATCH 06/21] Changed pytest scope of pytest.fixture test_tax_benefit_system to "module". Thanks @cesco-fran for the recommendation! --- tests/web_api/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/web_api/conftest.py b/tests/web_api/conftest.py index 864e76655e..77c4e2499e 100644 --- a/tests/web_api/conftest.py +++ b/tests/web_api/conftest.py @@ -44,7 +44,7 @@ def entities(): return [Household, Person] -@pytest.fixture(scope="package") +@pytest.fixture(scope="module") def test_tax_benefit_system(entities): tax_benefit_system = TaxBenefitSystem(entities) From 3e6f5558260b857f4ba93f31a8158577eae08507 Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 15:50:30 +1000 Subject: [PATCH 07/21] Fix failing test caused by #987 refactoring of `test_variables.test_variable_formula_content` --- tests/web_api/test_variables.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/web_api/test_variables.py b/tests/web_api/test_variables.py index 6290e55f91..52d6c5dfe7 100644 --- a/tests/web_api/test_variables.py +++ b/tests/web_api/test_variables.py @@ -264,7 +264,9 @@ def test_variable_formula_github_link(test_client): assert re.match(GITHUB_URL_REGEX, variable['formulas']['0001-01-01']['source']) -def test_variable_formula_content(): +def test_variable_formula_content(test_client): + variable_response = test_client.get('/variable/income_tax') + variable = json.loads(variable_response.data.decode('utf-8')) content = variable['formulas']['0001-01-01']['content'] assert "def formula(person, period, parameters):" in content assert "return person(\"salary\", period) * parameters(period).taxes.income_tax_rate" in content From d79eec0f6807d08e63c3585c2fef1e3352a233df Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 16:43:10 +1000 Subject: [PATCH 08/21] Refactoring due to modularisation works on #997 --- tests/web_api/conftest.py | 69 ----------- tests/web_api/test_variables.py | 199 ++++++-------------------------- 2 files changed, 34 insertions(+), 234 deletions(-) delete mode 100644 tests/web_api/conftest.py diff --git a/tests/web_api/conftest.py b/tests/web_api/conftest.py deleted file mode 100644 index 77c4e2499e..0000000000 --- a/tests/web_api/conftest.py +++ /dev/null @@ -1,69 +0,0 @@ -import pytest - -from openfisca_core.entities import build_entity -from openfisca_core.parameters import ParameterNode -from openfisca_core.periods import MONTH -from openfisca_core.taxbenefitsystems import TaxBenefitSystem -from openfisca_core.variables import Variable - - -Household = build_entity( - key = "household", - plural = "households", - label = "All the people in a family or group who live together in the same place.", - doc = "[REMOVED FOR TEST]", - roles = [ - { - "key": "parent", - "plural": "parents", - "label": "Parents", - "max": 2, - "subroles": ["first_parent", "second_parent"], - "doc": "The one or two adults in charge of the household.", - }, - { - "key": "child", - "plural": "children", - "label": "Child", - "doc": "Other individuals living in the household.", - }, - ], - ) - -Person = build_entity( - key = "person", - plural = "persons", - label = "An individual. The minimal legal entity on which a legislation might be applied.", - doc = "[REMOVED FOR TEST]", - is_person = True, - ) - - -@pytest.fixture(scope="package") -def entities(): - return [Household, Person] - - -@pytest.fixture(scope="module") -def test_tax_benefit_system(entities): - tax_benefit_system = TaxBenefitSystem(entities) - - # At least one ParameterNode must be defined, or else `openfisca_web_api.app.create_app()` will fail - tax_benefit_system.parameters = ParameterNode(name="mockParameterNode", - data={'amount': {'values': { - "2015-01-01": {'value': 550}, - "2016-01-01": {'value': 600} - }} - }) - - # At least one Variable must be defined, or else `openfisca_web_api.app.create_app()` will fail - class initial_variable(Variable): - value_type = float - entity = Person - definition_period = MONTH - label = "Basic income provided to adults" - reference = "https://law.gov.example/basic_income" # Always use the most official source - - tax_benefit_system.add_variable(initial_variable) - - return tax_benefit_system diff --git a/tests/web_api/test_variables.py b/tests/web_api/test_variables.py index 52d6c5dfe7..ecfed71d00 100644 --- a/tests/web_api/test_variables.py +++ b/tests/web_api/test_variables.py @@ -1,175 +1,44 @@ -# -*- coding: utf-8 -*- - -from datetime import date -from http.client import OK, NOT_FOUND +from http import client import json -from numpy import where -import re - import pytest +import re -from openfisca_core.indexed_enums import Enum -from openfisca_core.periods import MONTH, ETERNITY -from openfisca_core.variables import Variable -from openfisca_web_api.app import create_app -from .conftest import Person, Household +from openfisca_web_api import app def assert_items_equal(x, y): assert set(x) == set(y) -GITHUB_URL_REGEX = r'^https://github\.com/openfisca/openfisca-core/blob/\d+\.\d+\.\d+((.dev|rc)\d+)?/tests/web_api/(.)+\.py#L\d+-L\d+$' +GITHUB_URL_REGEX = r'^https://github\.com/openfisca/country-template/blob/\d+\.\d+\.\d+((.dev|rc)\d+)?/openfisca_country_template/variables/(.)+\.py#L\d+-L\d+$' @pytest.fixture(scope="module") -def test_client(test_tax_benefit_system): - - ### - # In this section you define the mock Variables you will need for tests in this module - class birth(Variable): - value_type = date - default_value = date(1970, 1, 1) # By default, if no value is set for a simulation, we consider the people involved in a simulation to be born on the 1st of Jan 1970. - entity = Person - label = "Birth date" - definition_period = ETERNITY # This variable cannot change over time. - reference = "https://en.wiktionary.org/wiki/birthdate" - - class income_tax(Variable): - value_type = float - entity = Person - definition_period = MONTH - label = "Income tax" - reference = "https://law.gov.example/income_tax" # Always use the most official source - - def formula(person, period, parameters): - """ - Income tax. - - The formula to compute the income tax for a given person at a given period - """ - return person("salary", period) * parameters(period).taxes.income_tax_rate - - class age(Variable): - value_type = int - entity = Person - definition_period = MONTH - label = "Person's age (in years)" - - def formula(person, period, _parameters): - """ - Person's age (in years). - - A person's age is computed according to its birth date. - """ - birth = person("birth", period) - birth_year = birth.astype("datetime64[Y]").astype(int) + 1970 - birth_month = birth.astype("datetime64[M]").astype(int) % 12 + 1 - birth_day = (birth - birth.astype("datetime64[M]") + 1).astype(int) - - is_birthday_past = (birth_month < period.start.month) + (birth_month == period.start.month) * (birth_day <= period.start.day) - - return (period.start.year - birth_year) - where(is_birthday_past, 0, 1) # If the birthday is not passed this year, subtract one year - - class housing_allowance(Variable): - value_type = float - entity = Household - definition_period = MONTH - label = "Housing allowance" - reference = "https://law.gov.example/housing_allowance" # Always use the most official source - end = "2016-11-30" # This allowance was removed on the 1st of Dec 2016. Calculating it before this date will always return the variable default value, 0. - unit = "currency-EUR" - documentation = """ - This allowance was introduced on the 1st of Jan 1980. - It disappeared in Dec 2016. - """ - - def formula_1980(household, period, parameters): - """ - Housing allowance. - - This allowance was introduced on the 1st of Jan 1980. - Calculating it before this date will always return the variable default value, 0. - - To compute this allowance, the 'rent' value must be provided for the same month, - but 'housing_occupancy_status' is not necessary. - """ - return household("rent", period) * parameters(period).benefits.housing_allowance - - class HousingOccupancyStatus(Enum): - __order__ = "owner tenant free_lodger homeless" - owner = "Owner" - tenant = "Tenant" - free_lodger = "Free lodger" - homeless = "Homeless" - - class housing_occupancy_status(Variable): - value_type = Enum - possible_values = HousingOccupancyStatus - default_value = HousingOccupancyStatus.tenant - entity = Household - definition_period = MONTH - label = "Legal housing situation of the household concerning their main residence" - - class basic_income(Variable): - value_type = float - entity = Person - definition_period = MONTH - label = "Basic income provided to adults" - reference = "https://law.gov.example/basic_income" # Always use the most official source - - def formula_2016_12(person, period, parameters): - """ - Basic income provided to adults. - - Since Dec 1st 2016, the basic income is provided to any adult, without considering their income. - """ - age_condition = person("age", period) >= parameters(period).general.age_of_majority - return age_condition * parameters(period).benefits.basic_income # This '*' is a vectorial 'if'. See https://openfisca.org/doc/coding-the-legislation/25_vectorial_computing.html#control-structures - - def formula_2015_12(person, period, parameters): - """ - Basic income provided to adults. - - From Dec 1st 2015 to Nov 30 2016, the basic income is provided to adults who have no income. - Before Dec 1st 2015, the basic income does not exist in the law, and calculating it returns its default value, which is 0. - """ - age_condition = person("age", period) >= parameters(period).general.age_of_majority - salary_condition = person("salary", period) == 0 - return age_condition * salary_condition * parameters(period).benefits.basic_income # The '*' is also used as a vectorial 'and'. See https://openfisca.org/doc/coding-the-legislation/25_vectorial_computing.html#boolean-operations - - class pension(Variable): - value_type = float - entity = Person - definition_period = MONTH - label = "Pension for the elderly. Pension attribuée aux personnes âgées. تقاعد." - reference = ["https://fr.wikipedia.org/wiki/Retraite_(économie)", "https://ar.wikipedia.org/wiki/تقاعد"] - - def formula(person, period, parameters): - """ - Pension for the elderly. - - A person's pension depends on their birth date. - In French: retraite selon l'âge. - In Arabic: تقاعد. - """ - age_condition = person("age", period) >= parameters(period).general.age_of_retirement - return age_condition - - ### - # Add the Variables above to the `test_tax_benefit_system` fixture - test_tax_benefit_system.add_variable(birth) - test_tax_benefit_system.add_variable(income_tax) - test_tax_benefit_system.add_variable(age) - test_tax_benefit_system.add_variable(housing_allowance) - test_tax_benefit_system.add_variable(housing_occupancy_status) - test_tax_benefit_system.add_variable(basic_income) - test_tax_benefit_system.add_variable(pension) - - ### +def test_client(tax_benefit_system): + """ This module-scoped fixture creates an API client for the TBS defined in the `tax_benefit_system` + fixture. This `tax_benefit_system` is mutable, so you can add/update variables. Example: + + ``` + from openfisca_country_template import entities + from openfisca_core import periods + from openfisca_core.variables import Variable + ... + + class new_variable(Variable): + value_type = float + entity = entities.Person + definition_period = periods.MONTH + label = "New variable" + reference = "https://law.gov.example/new_variable" # Always use the most official source + + tax_benefit_system.add_variable(new_variable) + flask_app = app.create_app(tax_benefit_system) + ``` + """ + # Create the test API client - app = create_app(test_tax_benefit_system) - return app.test_client() + flask_app = app.create_app(tax_benefit_system) + return flask_app.test_client() # /variables @@ -182,7 +51,7 @@ def variables_response(test_client): def test_return_code(variables_response): - assert variables_response.status_code == OK + assert variables_response.status_code == client.OK def test_response_data(variables_response): @@ -198,7 +67,7 @@ def test_response_data(variables_response): def test_error_code_non_existing_variable(test_client): response = test_client.get('/variable/non_existing_variable') - assert response.status_code == NOT_FOUND + assert response.status_code == client.NOT_FOUND @pytest.fixture(scope="module") @@ -208,7 +77,7 @@ def input_variable_response(test_client): def test_return_code_existing_input_variable(input_variable_response): - assert input_variable_response.status_code == OK + assert input_variable_response.status_code == client.OK def check_input_variable_value(key, expected_value, input_variable=None): @@ -238,7 +107,7 @@ def test_input_variable_github_url(test_client): def test_return_code_existing_variable(test_client): variable_response = test_client.get('/variable/income_tax') - assert variable_response.status_code == OK + assert variable_response.status_code == client.OK def check_variable_value(key, expected_value, variable=None): @@ -306,7 +175,7 @@ def dated_variable_response(test_client): def test_return_code_existing_dated_variable(dated_variable_response): - assert dated_variable_response.status_code == OK + assert dated_variable_response.status_code == client.OK def test_dated_variable_formulas_dates(dated_variable_response): @@ -327,7 +196,7 @@ def test_dated_variable_formulas_content(dated_variable_response): def test_variable_encoding(test_client): variable_response = test_client.get('/variable/pension') - assert variable_response.status_code == OK + assert variable_response.status_code == client.OK def test_variable_documentation(test_client): From fd3dc4d0145c918ab1bbb740e9fd36564499d48b Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 16:57:47 +1000 Subject: [PATCH 09/21] Modularise `test_calculate.py` --- tests/web_api/test_calculate.py | 155 ++++++++++++++++++-------------- 1 file changed, 90 insertions(+), 65 deletions(-) diff --git a/tests/web_api/test_calculate.py b/tests/web_api/test_calculate.py index 06d8b84af8..e212405715 100644 --- a/tests/web_api/test_calculate.py +++ b/tests/web_api/test_calculate.py @@ -1,28 +1,53 @@ -# -*- coding: utf-8 -*- - -import os +import copy +import dpath import json -from http.client import OK, BAD_REQUEST, NOT_FOUND, INTERNAL_SERVER_ERROR -from copy import deepcopy - +from http import client +import os import pytest -import dpath from openfisca_country_template.situation_examples import couple -from . import subject +from openfisca_web_api import app + + +@pytest.fixture(scope="module") +def test_client(tax_benefit_system): + """ This module-scoped fixture creates an API client for the TBS defined in the `tax_benefit_system` + fixture. This `tax_benefit_system` is mutable, so you can add/update variables. Example: + + ``` + from openfisca_country_template import entities + from openfisca_core import periods + from openfisca_core.variables import Variable + ... + + class new_variable(Variable): + value_type = float + entity = entities.Person + definition_period = periods.MONTH + label = "New variable" + reference = "https://law.gov.example/new_variable" # Always use the most official source + + tax_benefit_system.add_variable(new_variable) + flask_app = app.create_app(tax_benefit_system) + ``` + """ + + # Create the test API client + flask_app = app.create_app(tax_benefit_system) + return flask_app.test_client() -def post_json(data = None, file = None): +def post_json(_client, data = None, file = None): if file: file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'assets', file) with open(file_path, 'r') as file: data = file.read() - return subject.post('/calculate', data = data, content_type = 'application/json') + return _client.post('/calculate', data = data, content_type = 'application/json') -def check_response(data, expected_error_code, path_to_check, content_to_check): - response = post_json(data) +def check_response(_client, data, expected_error_code, path_to_check, content_to_check): + response = post_json(_client, data) assert response.status_code == expected_error_code json_response = json.loads(response.data.decode('utf-8')) if path_to_check: @@ -31,32 +56,32 @@ def check_response(data, expected_error_code, path_to_check, content_to_check): @pytest.mark.parametrize("test", [ - ('{"a" : "x", "b"}', BAD_REQUEST, 'error', 'Invalid JSON'), - ('["An", "array"]', BAD_REQUEST, 'error', 'Invalid type'), - ('{"persons": {}}', BAD_REQUEST, 'persons', 'At least one person'), - ('{"persons": {"bob": {}}, "unknown_entity": {}}', BAD_REQUEST, 'unknown_entity', 'entities are not found',), - ('{"persons": {"bob": {}}, "households": {"dupont": {"parents": {}}}}', BAD_REQUEST, 'households/dupont/parents', 'type',), - ('{"persons": {"bob": {"unknown_variable": {}}}}', NOT_FOUND, 'persons/bob/unknown_variable', 'You tried to calculate or to set',), - ('{"persons": {"bob": {"housing_allowance": {}}}}', BAD_REQUEST, 'persons/bob/housing_allowance', "You tried to compute the variable 'housing_allowance' for the entity 'persons'",), - ('{"persons": {"bob": {"salary": 4000 }}}', BAD_REQUEST, 'persons/bob/salary', 'period',), - ('{"persons": {"bob": {"salary": {"2017-01": "toto"} }}}', BAD_REQUEST, 'persons/bob/salary/2017-01', 'expected type number',), - ('{"persons": {"bob": {"salary": {"2017-01": {}} }}}', BAD_REQUEST, 'persons/bob/salary/2017-01', 'expected type number',), - ('{"persons": {"bob": {"age": {"2017-01": "toto"} }}}', BAD_REQUEST, 'persons/bob/age/2017-01', 'expected type integer',), - ('{"persons": {"bob": {"birth": {"2017-01": "toto"} }}}', BAD_REQUEST, 'persons/bob/birth/2017-01', 'Can\'t deal with date',), - ('{"persons": {"bob": {}}, "households": {"household": {"parents": ["unexpected_person_id"]}}}', BAD_REQUEST, 'households/household/parents', 'has not been declared in persons',), - ('{"persons": {"bob": {}}, "households": {"household": {"parents": ["bob", "bob"]}}}', BAD_REQUEST, 'households/household/parents', 'has been declared more than once',), - ('{"persons": {"bob": {}}, "households": {"household": {"parents": ["bob", {}]}}}', BAD_REQUEST, 'households/household/parents/1', 'Invalid type',), - ('{"persons": {"bob": {"salary": {"invalid period": 2000 }}}}', BAD_REQUEST, 'persons/bob/salary', 'Expected a period',), - ('{"persons": {"bob": {"salary": {"invalid period": null }}}}', BAD_REQUEST, 'persons/bob/salary', 'Expected a period',), - ('{"persons": {"bob": {"basic_income": {"2017": 2000 }}}, "households": {"household": {"parents": ["bob"]}}}', BAD_REQUEST, 'persons/bob/basic_income/2017', '"basic_income" can only be set for one month',), - ('{"persons": {"bob": {"salary": {"ETERNITY": 2000 }}}, "households": {"household": {"parents": ["bob"]}}}', BAD_REQUEST, 'persons/bob/salary/ETERNITY', 'salary is only defined for months',), - ('{"persons": {"alice": {}, "bob": {}, "charlie": {}}, "households": {"_": {"parents": ["alice", "bob", "charlie"]}}}', BAD_REQUEST, 'households/_/parents', 'at most 2 parents in a household',), + ('{"a" : "x", "b"}', client.BAD_REQUEST, 'error', 'Invalid JSON'), + ('["An", "array"]', client.BAD_REQUEST, 'error', 'Invalid type'), + ('{"persons": {}}', client.BAD_REQUEST, 'persons', 'At least one person'), + ('{"persons": {"bob": {}}, "unknown_entity": {}}', client.BAD_REQUEST, 'unknown_entity', 'entities are not found',), + ('{"persons": {"bob": {}}, "households": {"dupont": {"parents": {}}}}', client.BAD_REQUEST, 'households/dupont/parents', 'type',), + ('{"persons": {"bob": {"unknown_variable": {}}}}', client.NOT_FOUND, 'persons/bob/unknown_variable', 'You tried to calculate or to set',), + ('{"persons": {"bob": {"housing_allowance": {}}}}', client.BAD_REQUEST, 'persons/bob/housing_allowance', "You tried to compute the variable 'housing_allowance' for the entity 'persons'",), + ('{"persons": {"bob": {"salary": 4000 }}}', client.BAD_REQUEST, 'persons/bob/salary', 'period',), + ('{"persons": {"bob": {"salary": {"2017-01": "toto"} }}}', client.BAD_REQUEST, 'persons/bob/salary/2017-01', 'expected type number',), + ('{"persons": {"bob": {"salary": {"2017-01": {}} }}}', client.BAD_REQUEST, 'persons/bob/salary/2017-01', 'expected type number',), + ('{"persons": {"bob": {"age": {"2017-01": "toto"} }}}', client.BAD_REQUEST, 'persons/bob/age/2017-01', 'expected type integer',), + ('{"persons": {"bob": {"birth": {"2017-01": "toto"} }}}', client.BAD_REQUEST, 'persons/bob/birth/2017-01', 'Can\'t deal with date',), + ('{"persons": {"bob": {}}, "households": {"household": {"parents": ["unexpected_person_id"]}}}', client.BAD_REQUEST, 'households/household/parents', 'has not been declared in persons',), + ('{"persons": {"bob": {}}, "households": {"household": {"parents": ["bob", "bob"]}}}', client.BAD_REQUEST, 'households/household/parents', 'has been declared more than once',), + ('{"persons": {"bob": {}}, "households": {"household": {"parents": ["bob", {}]}}}', client.BAD_REQUEST, 'households/household/parents/1', 'Invalid type',), + ('{"persons": {"bob": {"salary": {"invalid period": 2000 }}}}', client.BAD_REQUEST, 'persons/bob/salary', 'Expected a period',), + ('{"persons": {"bob": {"salary": {"invalid period": null }}}}', client.BAD_REQUEST, 'persons/bob/salary', 'Expected a period',), + ('{"persons": {"bob": {"basic_income": {"2017": 2000 }}}, "households": {"household": {"parents": ["bob"]}}}', client.BAD_REQUEST, 'persons/bob/basic_income/2017', '"basic_income" can only be set for one month',), + ('{"persons": {"bob": {"salary": {"ETERNITY": 2000 }}}, "households": {"household": {"parents": ["bob"]}}}', client.BAD_REQUEST, 'persons/bob/salary/ETERNITY', 'salary is only defined for months',), + ('{"persons": {"alice": {}, "bob": {}, "charlie": {}}, "households": {"_": {"parents": ["alice", "bob", "charlie"]}}}', client.BAD_REQUEST, 'households/_/parents', 'at most 2 parents in a household',), ]) -def test_responses(test): - check_response(*test) +def test_responses(test_client, test): + check_response(test_client, *test) -def test_basic_calculation(): +def test_basic_calculation(test_client): simulation_json = json.dumps({ "persons": { "bill": { @@ -101,8 +126,8 @@ def test_basic_calculation(): } }) - response = post_json(simulation_json) - assert response.status_code == OK + response = post_json(test_client, simulation_json) + assert response.status_code == client.OK response_json = json.loads(response.data.decode('utf-8')) assert dpath.get(response_json, 'persons/bill/basic_income/2017-12') == 600 # Universal basic income assert dpath.get(response_json, 'persons/bill/income_tax/2017-12') == 300 # 15% of the salary @@ -112,7 +137,7 @@ def test_basic_calculation(): assert dpath.get(response_json, 'households/first_household/housing_tax/2017') == 3000 -def test_enums_sending_identifier(): +def test_enums_sending_identifier(test_client): simulation_json = json.dumps({ "persons": { "bill": {} @@ -133,13 +158,13 @@ def test_enums_sending_identifier(): } }) - response = post_json(simulation_json) - assert response.status_code == OK + response = post_json(test_client, simulation_json) + assert response.status_code == client.OK response_json = json.loads(response.data.decode('utf-8')) assert dpath.get(response_json, 'households/_/housing_tax/2017') == 0 -def test_enum_output(): +def test_enum_output(test_client): simulation_json = json.dumps({ "persons": { "bill": {}, @@ -154,13 +179,13 @@ def test_enum_output(): } }) - response = post_json(simulation_json) - assert response.status_code == OK + response = post_json(test_client, simulation_json) + assert response.status_code == client.OK response_json = json.loads(response.data.decode('utf-8')) assert dpath.get(response_json, "households/_/housing_occupancy_status/2017-01") == "tenant" -def test_enum_wrong_value(): +def test_enum_wrong_value(test_client): simulation_json = json.dumps({ "persons": { "bill": {}, @@ -175,15 +200,15 @@ def test_enum_wrong_value(): } }) - response = post_json(simulation_json) - assert response.status_code == BAD_REQUEST + response = post_json(test_client, simulation_json) + assert response.status_code == client.BAD_REQUEST response_json = json.loads(response.data.decode('utf-8')) message = "Possible values are ['owner', 'tenant', 'free_lodger', 'homeless']" text = dpath.get(response_json, "households/_/housing_occupancy_status/2017-01") assert message in text -def test_encoding_variable_value(): +def test_encoding_variable_value(test_client): simulation_json = json.dumps({ "persons": { "toto": {} @@ -202,15 +227,15 @@ def test_encoding_variable_value(): }) # No UnicodeDecodeError - response = post_json(simulation_json) - assert response.status_code == BAD_REQUEST, response.data.decode('utf-8') + response = post_json(test_client, simulation_json) + assert response.status_code == client.BAD_REQUEST, response.data.decode('utf-8') response_json = json.loads(response.data.decode('utf-8')) message = "'Locataire ou sous-locataire d‘un logement loué vide non-HLM' is not a known value for 'housing_occupancy_status'. Possible values are " text = dpath.get(response_json, 'households/_/housing_occupancy_status/2017-07') assert message in text -def test_encoding_entity_name(): +def test_encoding_entity_name(test_client): simulation_json = json.dumps({ "persons": { "O‘Ryan": {}, @@ -227,17 +252,17 @@ def test_encoding_entity_name(): }) # No UnicodeDecodeError - response = post_json(simulation_json) + response = post_json(test_client, simulation_json) response_json = json.loads(response.data.decode('utf-8')) # In Python 3, there is no encoding issue. - if response.status_code != OK: + if response.status_code != client.OK: message = "'O‘Ryan' is not a valid ASCII value." text = response_json['error'] assert message in text -def test_encoding_period_id(): +def test_encoding_period_id(test_client): simulation_json = json.dumps({ "persons": { "bill": { @@ -268,8 +293,8 @@ def test_encoding_period_id(): }) # No UnicodeDecodeError - response = post_json(simulation_json) - assert response.status_code == BAD_REQUEST + response = post_json(test_client, simulation_json) + assert response.status_code == client.BAD_REQUEST response_json = json.loads(response.data.decode('utf-8')) # In Python 3, there is no encoding issue. @@ -279,17 +304,17 @@ def test_encoding_period_id(): assert message in text -def test_str_variable(): - new_couple = deepcopy(couple) +def test_str_variable(test_client): + new_couple = copy.deepcopy(couple) new_couple['households']['_']['postal_code'] = {'2017-01': None} simulation_json = json.dumps(new_couple) - response = subject.post('/calculate', data = simulation_json, content_type = 'application/json') + response = test_client.post('/calculate', data = simulation_json, content_type = 'application/json') - assert response.status_code == OK + assert response.status_code == client.OK -def test_periods(): +def test_periods(test_client): simulation_json = json.dumps({ "persons": { "bill": {} @@ -307,8 +332,8 @@ def test_periods(): } }) - response = post_json(simulation_json) - assert response.status_code == OK + response = post_json(test_client, simulation_json) + assert response.status_code == client.OK response_json = json.loads(response.data.decode('utf-8')) @@ -319,7 +344,7 @@ def test_periods(): assert monthly_variable == {'2017-01': 'tenant'} -def test_gracefully_handle_unexpected_errors(): +def test_gracefully_handle_unexpected_errors(test_client): """ Context ======== @@ -358,8 +383,8 @@ def test_gracefully_handle_unexpected_errors(): } }) - response = post_json(simulation_json) - assert response.status_code == INTERNAL_SERVER_ERROR + response = post_json(test_client, simulation_json) + assert response.status_code == client.INTERNAL_SERVER_ERROR error = json.loads(response.data)["error"] assert f"Unable to compute variable '{variable}' for period {period}" in error From f906dbf3098e4670c9eee4108ba91af393b635f2 Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 16:57:54 +1000 Subject: [PATCH 10/21] Modularise `test_entities.py` --- tests/web_api/test_entities.py | 52 +++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/tests/web_api/test_entities.py b/tests/web_api/test_entities.py index de5600599f..8eb19831c8 100644 --- a/tests/web_api/test_entities.py +++ b/tests/web_api/test_entities.py @@ -1,24 +1,56 @@ # -*- coding: utf-8 -*- -from http.client import OK +from http import client import json -import openfisca_country_template -from . import subject +import pytest + +from openfisca_country_template import entities + +from openfisca_web_api import app -entities_response = subject.get('/entities') # /entities -def test_return_code(): - assert entities_response.status_code == OK +@pytest.fixture(scope="module") +def test_client(tax_benefit_system): + """ This module-scoped fixture creates an API client for the TBS defined in the `tax_benefit_system` + fixture. This `tax_benefit_system` is mutable, so you can add/update variables. Example: + + ``` + from openfisca_country_template import entities + from openfisca_core import periods + from openfisca_core.variables import Variable + ... + + class new_variable(Variable): + value_type = float + entity = entities.Person + definition_period = periods.MONTH + label = "New variable" + reference = "https://law.gov.example/new_variable" # Always use the most official source + + tax_benefit_system.add_variable(new_variable) + flask_app = app.create_app(tax_benefit_system) + ``` + """ + + # Create the test API client + flask_app = app.create_app(tax_benefit_system) + return flask_app.test_client() + + +def test_return_code(test_client): + entities_response = test_client.get('/entities') + assert entities_response.status_code == client.OK -def test_response_data(): - entities = json.loads(entities_response.data.decode('utf-8')) - test_documentation = openfisca_country_template.entities.Household.doc.strip() +def test_response_data(test_client): + entities_response = test_client.get('/entities') + entities_dict = json.loads(entities_response.data.decode('utf-8')) + test_documentation = entities.Household.doc.strip() - assert entities['household'] == { + assert entities_dict['household'] == { 'description': 'All the people in a family or group who live together in the same place.', 'documentation': test_documentation, 'plural': 'households', From e70283be113b0a18a8b6f4059faf0f3aaf73160c Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 17:07:06 +1000 Subject: [PATCH 11/21] Move `test_client` fixture to `/tests/fixtures/` directory to reduce duplication --- conftest.py | 1 + tests/fixtures/appclient.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/fixtures/appclient.py diff --git a/conftest.py b/conftest.py index ae14b71c8a..3adc794111 100644 --- a/conftest.py +++ b/conftest.py @@ -1,4 +1,5 @@ pytest_plugins = [ + "tests.fixtures.appclient", "tests.fixtures.entities", "tests.fixtures.simulations", "tests.fixtures.taxbenefitsystems", diff --git a/tests/fixtures/appclient.py b/tests/fixtures/appclient.py new file mode 100644 index 0000000000..32d9e71aee --- /dev/null +++ b/tests/fixtures/appclient.py @@ -0,0 +1,31 @@ +import pytest + +from openfisca_web_api import app + + +@pytest.fixture(scope="module") +def test_client(tax_benefit_system): + """ This module-scoped fixture creates an API client for the TBS defined in the `tax_benefit_system` + fixture. This `tax_benefit_system` is mutable, so you can add/update variables. Example: + + ``` + from openfisca_country_template import entities + from openfisca_core import periods + from openfisca_core.variables import Variable + ... + + class new_variable(Variable): + value_type = float + entity = entities.Person + definition_period = periods.MONTH + label = "New variable" + reference = "https://law.gov.example/new_variable" # Always use the most official source + + tax_benefit_system.add_variable(new_variable) + flask_app = app.create_app(tax_benefit_system) + ``` + """ + + # Create the test API client + flask_app = app.create_app(tax_benefit_system) + return flask_app.test_client() From b09f50e14fc05e3762964372af85b27a40eab77f Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 17:07:41 +1000 Subject: [PATCH 12/21] Remove `test_client` from individual test modules since it's in "tests/fixtures" now --- tests/web_api/test_calculate.py | 30 ------------------------------ tests/web_api/test_entities.py | 31 ------------------------------- tests/web_api/test_variables.py | 30 ------------------------------ 3 files changed, 91 deletions(-) diff --git a/tests/web_api/test_calculate.py b/tests/web_api/test_calculate.py index e212405715..1ada0006d5 100644 --- a/tests/web_api/test_calculate.py +++ b/tests/web_api/test_calculate.py @@ -7,36 +7,6 @@ from openfisca_country_template.situation_examples import couple -from openfisca_web_api import app - - -@pytest.fixture(scope="module") -def test_client(tax_benefit_system): - """ This module-scoped fixture creates an API client for the TBS defined in the `tax_benefit_system` - fixture. This `tax_benefit_system` is mutable, so you can add/update variables. Example: - - ``` - from openfisca_country_template import entities - from openfisca_core import periods - from openfisca_core.variables import Variable - ... - - class new_variable(Variable): - value_type = float - entity = entities.Person - definition_period = periods.MONTH - label = "New variable" - reference = "https://law.gov.example/new_variable" # Always use the most official source - - tax_benefit_system.add_variable(new_variable) - flask_app = app.create_app(tax_benefit_system) - ``` - """ - - # Create the test API client - flask_app = app.create_app(tax_benefit_system) - return flask_app.test_client() - def post_json(_client, data = None, file = None): if file: diff --git a/tests/web_api/test_entities.py b/tests/web_api/test_entities.py index 8eb19831c8..6f8153ed37 100644 --- a/tests/web_api/test_entities.py +++ b/tests/web_api/test_entities.py @@ -2,44 +2,13 @@ from http import client import json -import pytest from openfisca_country_template import entities -from openfisca_web_api import app - # /entities -@pytest.fixture(scope="module") -def test_client(tax_benefit_system): - """ This module-scoped fixture creates an API client for the TBS defined in the `tax_benefit_system` - fixture. This `tax_benefit_system` is mutable, so you can add/update variables. Example: - - ``` - from openfisca_country_template import entities - from openfisca_core import periods - from openfisca_core.variables import Variable - ... - - class new_variable(Variable): - value_type = float - entity = entities.Person - definition_period = periods.MONTH - label = "New variable" - reference = "https://law.gov.example/new_variable" # Always use the most official source - - tax_benefit_system.add_variable(new_variable) - flask_app = app.create_app(tax_benefit_system) - ``` - """ - - # Create the test API client - flask_app = app.create_app(tax_benefit_system) - return flask_app.test_client() - - def test_return_code(test_client): entities_response = test_client.get('/entities') assert entities_response.status_code == client.OK diff --git a/tests/web_api/test_variables.py b/tests/web_api/test_variables.py index ecfed71d00..4581608aa8 100644 --- a/tests/web_api/test_variables.py +++ b/tests/web_api/test_variables.py @@ -3,8 +3,6 @@ import pytest import re -from openfisca_web_api import app - def assert_items_equal(x, y): assert set(x) == set(y) @@ -13,34 +11,6 @@ def assert_items_equal(x, y): GITHUB_URL_REGEX = r'^https://github\.com/openfisca/country-template/blob/\d+\.\d+\.\d+((.dev|rc)\d+)?/openfisca_country_template/variables/(.)+\.py#L\d+-L\d+$' -@pytest.fixture(scope="module") -def test_client(tax_benefit_system): - """ This module-scoped fixture creates an API client for the TBS defined in the `tax_benefit_system` - fixture. This `tax_benefit_system` is mutable, so you can add/update variables. Example: - - ``` - from openfisca_country_template import entities - from openfisca_core import periods - from openfisca_core.variables import Variable - ... - - class new_variable(Variable): - value_type = float - entity = entities.Person - definition_period = periods.MONTH - label = "New variable" - reference = "https://law.gov.example/new_variable" # Always use the most official source - - tax_benefit_system.add_variable(new_variable) - flask_app = app.create_app(tax_benefit_system) - ``` - """ - - # Create the test API client - flask_app = app.create_app(tax_benefit_system) - return flask_app.test_client() - - # /variables From f3dbee3beecb0d55f5967620922a6672a0265ec7 Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 17:10:50 +1000 Subject: [PATCH 13/21] Modularise test_headers.py --- tests/web_api/test_headers.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/web_api/test_headers.py b/tests/web_api/test_headers.py index e1239cf8a9..54bbfd0df8 100644 --- a/tests/web_api/test_headers.py +++ b/tests/web_api/test_headers.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- -from . import distribution, subject +from . import distribution -parameters_response = subject.get('/parameters') - -def test_package_name_header(): +def test_package_name_header(test_client): + parameters_response = test_client.get('/parameters') assert parameters_response.headers.get('Country-Package') == distribution.key -def test_package_version_header(): +def test_package_version_header(test_client): + parameters_response = test_client.get('/parameters') assert parameters_response.headers.get('Country-Package-Version') == distribution.version From eddd68042c9758e92a601e20919b0205c087e0a6 Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 17:11:13 +1000 Subject: [PATCH 14/21] Fix import styles --- tests/web_api/test_helpers.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/web_api/test_helpers.py b/tests/web_api/test_helpers.py index d36d399e25..cb049a0822 100644 --- a/tests/web_api/test_helpers.py +++ b/tests/web_api/test_helpers.py @@ -1,7 +1,8 @@ import os +from openfisca_web_api.loader import parameters + from openfisca_core.parameters import load_parameter_file -from openfisca_web_api.loader.parameters import build_api_values_history, get_value dir_path = os.path.join(os.path.dirname(__file__), 'assets') @@ -16,7 +17,7 @@ def test_build_api_values_history(): '2015-01-01': 0.04, '2013-01-01': 0.03, } - assert build_api_values_history(parameter) == values + assert parameters.build_api_values_history(parameter) == values def test_build_api_values_history_with_stop_date(): @@ -30,23 +31,23 @@ def test_build_api_values_history_with_stop_date(): '2013-01-01': 0.03, } - assert build_api_values_history(parameter) == values + assert parameters.build_api_values_history(parameter) == values def test_get_value(): values = {'2013-01-01': 0.03, '2017-01-01': 0.02, '2015-01-01': 0.04} - assert get_value('2013-01-01', values) == 0.03 - assert get_value('2014-01-01', values) == 0.03 - assert get_value('2015-02-01', values) == 0.04 - assert get_value('2016-12-31', values) == 0.04 - assert get_value('2017-01-01', values) == 0.02 - assert get_value('2018-01-01', values) == 0.02 + assert parameters.get_value('2013-01-01', values) == 0.03 + assert parameters.get_value('2014-01-01', values) == 0.03 + assert parameters.get_value('2015-02-01', values) == 0.04 + assert parameters.get_value('2016-12-31', values) == 0.04 + assert parameters.get_value('2017-01-01', values) == 0.02 + assert parameters.get_value('2018-01-01', values) == 0.02 def test_get_value_with_none(): values = {'2015-01-01': 0.04, '2017-01-01': None} - assert get_value('2016-12-31', values) == 0.04 - assert get_value('2017-01-01', values) is None - assert get_value('2011-01-01', values) is None + assert parameters.get_value('2016-12-31', values) == 0.04 + assert parameters.get_value('2017-01-01', values) is None + assert parameters.get_value('2011-01-01', values) is None From 8e7fec0959c0c67be7406e18de88cbd67f3b36ab Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 17:19:39 +1000 Subject: [PATCH 15/21] Modularise test_parameters.py --- tests/web_api/test_parameters.py | 85 ++++++++++++++++---------------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/tests/web_api/test_parameters.py b/tests/web_api/test_parameters.py index a3b58df7c6..a1e3fd7de1 100644 --- a/tests/web_api/test_parameters.py +++ b/tests/web_api/test_parameters.py @@ -1,23 +1,22 @@ -# -*- coding: utf-8 -*- - -from http.client import OK, NOT_FOUND +from http import client import json +import pytest import re -import pytest -from . import tax_benefit_system, subject # /parameters -parameters_response = subject.get('/parameters') + GITHUB_URL_REGEX = r'^https://github\.com/openfisca/country-template/blob/\d+\.\d+\.\d+((.dev|rc)\d+)?/openfisca_country_template/parameters/(.)+\.yaml$' -def test_return_code(): - assert parameters_response.status_code == OK +def test_return_code(test_client): + parameters_response = test_client.get('/parameters') + assert parameters_response.status_code == client.OK -def test_response_data(): +def test_response_data(test_client): + parameters_response = test_client.get('/parameters') parameters = json.loads(parameters_response.data.decode('utf-8')) assert parameters['taxes.income_tax_rate'] == { @@ -29,23 +28,23 @@ def test_response_data(): # /parameter/ -def test_error_code_non_existing_parameter(): - response = subject.get('/parameter/non/existing.parameter') - assert response.status_code == NOT_FOUND +def test_error_code_non_existing_parameter(test_client): + response = test_client.get('/parameter/non/existing.parameter') + assert response.status_code == client.NOT_FOUND -def test_return_code_existing_parameter(): - response = subject.get('/parameter/taxes/income_tax_rate') - assert response.status_code == OK +def test_return_code_existing_parameter(test_client): + response = test_client.get('/parameter/taxes/income_tax_rate') + assert response.status_code == client.OK -def test_legacy_parameter_route(): - response = subject.get('/parameter/taxes.income_tax_rate') - assert response.status_code == OK +def test_legacy_parameter_route(test_client): + response = test_client.get('/parameter/taxes.income_tax_rate') + assert response.status_code == client.OK -def test_parameter_values(): - response = subject.get('/parameter/taxes/income_tax_rate') +def test_parameter_values(test_client): + response = test_client.get('/parameter/taxes/income_tax_rate') parameter = json.loads(response.data) assert sorted(list(parameter.keys())), ['description', 'id', 'metadata', 'source', 'values'] assert parameter['id'] == 'taxes.income_tax_rate' @@ -56,7 +55,7 @@ def test_parameter_values(): assert 'taxes/income_tax_rate.yaml' in parameter['source'] # 'documentation' attribute exists only when a value is defined - response = subject.get('/parameter/benefits/housing_allowance') + response = test_client.get('/parameter/benefits/housing_allowance') parameter = json.loads(response.data) assert sorted(list(parameter.keys())), ['description', 'documentation', 'id', 'metadata', 'source' == 'values'] assert ( @@ -65,9 +64,9 @@ def test_parameter_values(): ) -def test_parameter_node(): - response = subject.get('/parameter/benefits') - assert response.status_code == OK +def test_parameter_node(tax_benefit_system, test_client): + response = test_client.get('/parameter/benefits') + assert response.status_code == client.OK parameter = json.loads(response.data) assert sorted(list(parameter.keys())), ['description', 'documentation', 'id', 'metadata', 'source' == 'subparams'] assert parameter['documentation'] == ( @@ -86,14 +85,14 @@ def test_parameter_node(): ), parameter['subparams']['basic_income']['description'] -def test_stopped_parameter_values(): - response = subject.get('/parameter/benefits/housing_allowance') +def test_stopped_parameter_values(test_client): + response = test_client.get('/parameter/benefits/housing_allowance') parameter = json.loads(response.data) assert parameter['values'] == {'2016-12-01': None, '2010-01-01': 0.25} -def test_scale(): - response = subject.get('/parameter/taxes/social_security_contribution') +def test_scale(test_client): + response = test_client.get('/parameter/taxes/social_security_contribution') parameter = json.loads(response.data) assert sorted(list(parameter.keys())), ['brackets', 'description', 'id', 'metadata' == 'source'] assert parameter['brackets'] == { @@ -105,25 +104,25 @@ def test_scale(): } -def check_code(route, code): - response = subject.get(route) +def check_code(_client, route, code): + response = _client.get(route) assert response.status_code == code @pytest.mark.parametrize("expected_code", [ - ('/parameters/', OK), - ('/parameter', NOT_FOUND), - ('/parameter/', NOT_FOUND), - ('/parameter/with-ÜNı©ød€', NOT_FOUND), - ('/parameter/with%20url%20encoding', NOT_FOUND), - ('/parameter/taxes/income_tax_rate/', OK), - ('/parameter/taxes/income_tax_rate/too-much-nesting', NOT_FOUND), - ('/parameter//taxes/income_tax_rate/', NOT_FOUND), + ('/parameters/', client.OK), + ('/parameter', client.NOT_FOUND), + ('/parameter/', client.NOT_FOUND), + ('/parameter/with-ÜNı©ød€', client.NOT_FOUND), + ('/parameter/with%20url%20encoding', client.NOT_FOUND), + ('/parameter/taxes/income_tax_rate/', client.OK), + ('/parameter/taxes/income_tax_rate/too-much-nesting', client.NOT_FOUND), + ('/parameter//taxes/income_tax_rate/', client.NOT_FOUND), ]) -def test_routes_robustness(expected_code): - check_code(*expected_code) +def test_routes_robustness(test_client, expected_code): + check_code(test_client, *expected_code) -def test_parameter_encoding(): - parameter_response = subject.get('/parameter/general/age_of_retirement') - assert parameter_response.status_code == OK +def test_parameter_encoding(test_client): + parameter_response = test_client.get('/parameter/general/age_of_retirement') + assert parameter_response.status_code == client.OK From d9fe83aad255bf63e8d9f5875bb420b57c906714 Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 17:20:03 +1000 Subject: [PATCH 16/21] Modularise test_spec.py --- tests/web_api/test_spec.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/tests/web_api/test_spec.py b/tests/web_api/test_spec.py index dd59b588bd..5e19752119 100644 --- a/tests/web_api/test_spec.py +++ b/tests/web_api/test_spec.py @@ -1,27 +1,25 @@ -# -*- coding: utf-8 -*- - -import json -from http.client import OK - import dpath -from . import subject +import json +import pytest +from http import client def assert_items_equal(x, y): assert sorted(x) == sorted(y) -openAPI_response = subject.get('/spec') - - -def test_return_code(): - assert openAPI_response.status_code == OK +def test_return_code(test_client): + openAPI_response = test_client.get('/spec') + assert openAPI_response.status_code == client.OK -body = json.loads(openAPI_response.data.decode('utf-8')) +@pytest.fixture(scope="module") +def body(test_client): + openAPI_response = test_client.get('/spec') + return json.loads(openAPI_response.data.decode('utf-8')) -def test_paths(): +def test_paths(body): assert_items_equal( body['paths'], ["/parameter/{parameterID}", @@ -35,7 +33,7 @@ def test_paths(): ) -def test_entity_definition(): +def test_entity_definition(body): assert 'parents' in dpath.get(body, 'definitions/Household/properties') assert 'children' in dpath.get(body, 'definitions/Household/properties') assert 'salary' in dpath.get(body, 'definitions/Person/properties') @@ -43,7 +41,7 @@ def test_entity_definition(): assert 'number' == dpath.get(body, 'definitions/Person/properties/salary/additionalProperties/type') -def test_situation_definition(): +def test_situation_definition(body): situation_input = body['definitions']['SituationInput'] situation_output = body['definitions']['SituationOutput'] for situation in situation_input, situation_output: @@ -53,5 +51,5 @@ def test_situation_definition(): assert "#/definitions/Person" == dpath.get(situation, '/properties/persons/additionalProperties/$ref') -def test_host(): +def test_host(body): assert 'http' not in body['host'] From 9cd824b7e74939d3ec730e3cc99112163fb04732 Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 17:22:48 +1000 Subject: [PATCH 17/21] Modularise test_trace.py --- tests/web_api/test_trace.py | 44 +++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/tests/web_api/test_trace.py b/tests/web_api/test_trace.py index 143e33e549..b59fbdb5f0 100644 --- a/tests/web_api/test_trace.py +++ b/tests/web_api/test_trace.py @@ -1,23 +1,19 @@ -# -*- coding: utf-8 -*- - +import copy +import dpath +from http import client import json -from copy import deepcopy -from http.client import OK -import dpath from openfisca_country_template.situation_examples import single, couple -from . import subject - def assert_items_equal(x, y): assert set(x) == set(y) -def test_trace_basic(): +def test_trace_basic(test_client): simulation_json = json.dumps(single) - response = subject.post('/trace', data = simulation_json, content_type = 'application/json') - assert response.status_code == OK + response = test_client.post('/trace', data = simulation_json, content_type = 'application/json') + assert response.status_code == client.OK response_json = json.loads(response.data.decode('utf-8')) disposable_income_value = dpath.util.get(response_json, 'trace/disposable_income<2017-01>/value') assert isinstance(disposable_income_value, list) @@ -31,19 +27,19 @@ def test_trace_basic(): assert_items_equal(basic_income_dep, ['age<2017-01>']) -def test_trace_enums(): - new_single = deepcopy(single) +def test_trace_enums(test_client): + new_single = copy.deepcopy(single) new_single['households']['_']['housing_occupancy_status'] = {"2017-01": None} simulation_json = json.dumps(new_single) - response = subject.post('/trace', data = simulation_json, content_type = 'application/json') + response = test_client.post('/trace', data = simulation_json, content_type = 'application/json') response_json = json.loads(response.data) housing_status = dpath.util.get(response_json, 'trace/housing_occupancy_status<2017-01>/value') assert housing_status[0] == 'tenant' # The default value -def test_entities_description(): +def test_entities_description(test_client): simulation_json = json.dumps(couple) - response = subject.post('/trace', data = simulation_json, content_type = 'application/json') + response = test_client.post('/trace', data = simulation_json, content_type = 'application/json') response_json = json.loads(response.data.decode('utf-8')) assert_items_equal( dpath.util.get(response_json, 'entitiesDescription/persons'), @@ -51,9 +47,9 @@ def test_entities_description(): ) -def test_root_nodes(): +def test_root_nodes(test_client): simulation_json = json.dumps(couple) - response = subject.post('/trace', data = simulation_json, content_type = 'application/json') + response = test_client.post('/trace', data = simulation_json, content_type = 'application/json') response_json = json.loads(response.data.decode('utf-8')) assert_items_equal( dpath.util.get(response_json, 'requestedCalculations'), @@ -61,22 +57,22 @@ def test_root_nodes(): ) -def test_str_variable(): - new_couple = deepcopy(couple) +def test_str_variable(test_client): + new_couple = copy.deepcopy(couple) new_couple['households']['_']['postal_code'] = {'2017-01': None} simulation_json = json.dumps(new_couple) - response = subject.post('/trace', data = simulation_json, content_type = 'application/json') + response = test_client.post('/trace', data = simulation_json, content_type = 'application/json') - assert response.status_code == OK + assert response.status_code == client.OK -def test_trace_parameters(): - new_couple = deepcopy(couple) +def test_trace_parameters(test_client): + new_couple = copy.deepcopy(couple) new_couple['households']['_']['housing_tax'] = {'2017': None} simulation_json = json.dumps(new_couple) - response = subject.post('/trace', data = simulation_json, content_type = 'application/json') + response = test_client.post('/trace', data = simulation_json, content_type = 'application/json') response_json = json.loads(response.data.decode('utf-8')) assert len(dpath.util.get(response_json, 'trace/housing_tax<2017>/parameters')) > 0 From aa3157c04dd07165dab8fb25405db6a20aa356b5 Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 17:26:28 +1000 Subject: [PATCH 18/21] Remove `tax_benefit_system` and `subject` from "tests/web_api/__init__.py" since we now use the fixtures defined in "tests/fixtures" --- tests/web_api/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/web_api/__init__.py b/tests/web_api/__init__.py index c98d824b4a..8098c2a5a2 100644 --- a/tests/web_api/__init__.py +++ b/tests/web_api/__init__.py @@ -1,10 +1,4 @@ -# -*- coding: utf-8 -*- - import pkg_resources -from openfisca_web_api.app import create_app -from openfisca_core.scripts import build_tax_benefit_system TEST_COUNTRY_PACKAGE_NAME = 'openfisca_country_template' distribution = pkg_resources.get_distribution(TEST_COUNTRY_PACKAGE_NAME) -tax_benefit_system = build_tax_benefit_system(TEST_COUNTRY_PACKAGE_NAME, extensions = None, reforms = None) -subject = create_app(tax_benefit_system).test_client() From 390f194b295228f297c14b11ab5948ea50f0a986 Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 17:56:55 +1000 Subject: [PATCH 19/21] bump version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 601c0e7493..3411528df7 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ setup( name = 'OpenFisca-Core', - version = '35.3.5', + version = '35.3.6', author = 'OpenFisca Team', author_email = 'contact@openfisca.org', classifiers = [ From b1dfd14c02a370b33e6217f4956e52d1208c4c8d Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Thu, 15 Apr 2021 18:01:01 +1000 Subject: [PATCH 20/21] Update CHANGELOG.md --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 897aef89a5..5994be09c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +### 35.3.6 [#984](https://github.com/openfisca/openfisca-core/pull/984) + +#### Technical changes + +- In web_api tests, extract `test_client` to a fixture reusable by all the tests in the test suite. + - To mitigate possible performance issues, by default the fixture is initialised once per test module. + - This follows the same approach as [#997](https://github.com/openfisca/openfisca-core/pull/997) + + ### 35.3.5 [#997](https://github.com/openfisca/openfisca-core/pull/997) #### Technical changes From 0e42b47e8cfd47f2cc5027c7f7ba6b636228141b Mon Sep 17 00:00:00 2001 From: Ram Parameswaran Date: Fri, 16 Apr 2021 09:52:52 +1000 Subject: [PATCH 21/21] Style // remove redundant underscore from `_client` argument --- tests/web_api/test_calculate.py | 8 ++++---- tests/web_api/test_parameters.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/web_api/test_calculate.py b/tests/web_api/test_calculate.py index 1ada0006d5..061c9e3e5d 100644 --- a/tests/web_api/test_calculate.py +++ b/tests/web_api/test_calculate.py @@ -8,16 +8,16 @@ from openfisca_country_template.situation_examples import couple -def post_json(_client, data = None, file = None): +def post_json(client, data = None, file = None): if file: file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'assets', file) with open(file_path, 'r') as file: data = file.read() - return _client.post('/calculate', data = data, content_type = 'application/json') + return client.post('/calculate', data = data, content_type = 'application/json') -def check_response(_client, data, expected_error_code, path_to_check, content_to_check): - response = post_json(_client, data) +def check_response(client, data, expected_error_code, path_to_check, content_to_check): + response = post_json(client, data) assert response.status_code == expected_error_code json_response = json.loads(response.data.decode('utf-8')) if path_to_check: diff --git a/tests/web_api/test_parameters.py b/tests/web_api/test_parameters.py index a1e3fd7de1..efef6ffcd2 100644 --- a/tests/web_api/test_parameters.py +++ b/tests/web_api/test_parameters.py @@ -104,8 +104,8 @@ def test_scale(test_client): } -def check_code(_client, route, code): - response = _client.get(route) +def check_code(client, route, code): + response = client.get(route) assert response.status_code == code