Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a463475
Add a minimal TaxBenefitSystem Fixture in tests/web_api/conftest.py
RamParameswaran Apr 15, 2021
eca32de
Create `test_client` fixture in tests/web_api/test_variables
RamParameswaran Apr 15, 2021
6166bd2
Fix // Change fixture scope to "package" to prevent recreating fixtur…
RamParameswaran Apr 15, 2021
c1abb72
Refactor all existing tests to use Fixtures instead of importing from…
RamParameswaran Apr 15, 2021
a328923
Style // fix flake8
RamParameswaran Apr 15, 2021
72d83fb
Changed pytest scope of pytest.fixture test_tax_benefit_system to "mo…
RamParameswaran Apr 15, 2021
3e6f555
Fix failing test caused by #987 refactoring of `test_variables.test_v…
RamParameswaran Apr 15, 2021
d79eec0
Refactoring due to modularisation works on #997
RamParameswaran Apr 15, 2021
fd3dc4d
Modularise `test_calculate.py`
RamParameswaran Apr 15, 2021
f906dbf
Modularise `test_entities.py`
RamParameswaran Apr 15, 2021
e70283b
Move `test_client` fixture to `/tests/fixtures/` directory to reduce …
RamParameswaran Apr 15, 2021
b09f50e
Remove `test_client` from individual test modules since it's in "test…
RamParameswaran Apr 15, 2021
f3dbee3
Modularise test_headers.py
RamParameswaran Apr 15, 2021
eddd680
Fix import styles
RamParameswaran Apr 15, 2021
8e7fec0
Modularise test_parameters.py
RamParameswaran Apr 15, 2021
d9fe83a
Modularise test_spec.py
RamParameswaran Apr 15, 2021
9cd824b
Modularise test_trace.py
RamParameswaran Apr 15, 2021
aa3157c
Remove `tax_benefit_system` and `subject` from "tests/web_api/__init_…
RamParameswaran Apr 15, 2021
390f194
bump version
RamParameswaran Apr 15, 2021
b1dfd14
Update CHANGELOG.md
RamParameswaran Apr 15, 2021
0e42b47
Style // remove redundant underscore from `_client` argument
RamParameswaran Apr 15, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pytest_plugins = [
"tests.fixtures.appclient",
"tests.fixtures.entities",
"tests.fixtures.simulations",
"tests.fixtures.taxbenefitsystems",
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

setup(
name = 'OpenFisca-Core',
version = '35.3.5',
version = '35.3.6',
author = 'OpenFisca Team',
author_email = '[email protected]',
classifiers = [
Expand Down
31 changes: 31 additions & 0 deletions tests/fixtures/appclient.py
Original file line number Diff line number Diff line change
@@ -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()
6 changes: 0 additions & 6 deletions tests/web_api/__init__.py
Original file line number Diff line number Diff line change
@@ -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()
127 changes: 61 additions & 66 deletions tests/web_api/test_calculate.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
# -*- 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


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:
Expand All @@ -31,32 +26,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": {
Expand Down Expand Up @@ -101,8 +96,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
Expand All @@ -112,7 +107,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": {}
Expand All @@ -133,13 +128,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": {},
Expand All @@ -154,13 +149,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": {},
Expand All @@ -175,15 +170,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": {}
Expand All @@ -202,15 +197,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": {},
Expand All @@ -227,17 +222,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": {
Expand Down Expand Up @@ -268,8 +263,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.
Expand All @@ -279,17 +274,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": {}
Expand All @@ -307,8 +302,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'))

Expand All @@ -319,7 +314,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
========
Expand Down Expand Up @@ -358,8 +353,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
21 changes: 11 additions & 10 deletions tests/web_api/test_entities.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
# -*- coding: utf-8 -*-

from http.client import OK
from http import client
import json
import openfisca_country_template
from . import subject

entities_response = subject.get('/entities')
from openfisca_country_template import entities


# /entities


def test_return_code():
assert entities_response.status_code == OK
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',
Expand Down
10 changes: 5 additions & 5 deletions tests/web_api/test_headers.py
Original file line number Diff line number Diff line change
@@ -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
Loading