cook: add unit tests for cook.py (#32886)

This commit is contained in:
Nicolas Roche 2019-05-27 19:35:36 +02:00
parent 918a53f5cf
commit 2afc250a97
5 changed files with 704 additions and 21 deletions

View File

@ -1,11 +1,18 @@
import pytest
import mock
import os
import json
import StringIO
import pytest
from mock import call, mock_open, patch, Mock
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import CommandError
from hobo.environment.models import ServiceBase
from hobo.environment.models import (Authentic, BiJoe, Chrono, Combo, Corbo, Fargo,
Hobo, Passerelle, ServiceBase, Variable, Wcs, Welco)
from hobo.environment.management.commands.cook import Command
from hobo.profile.models import AttributeDefinition
def test_check_action(monkeypatch):
@ -55,23 +62,360 @@ def test_check_action_invalid_certificat(monkeypatch):
command.check_action(action, action_args)
assert 'has no valid certificate' in str(e_info.value)
@mock.patch('hobo.environment.management.commands.cook.open')
def test_having_several_action_args(mocked_open):
"""load variables from a file"""
receipe = """{
"steps": [
{"create-authentic": {
"url": "https://entrouvert.org/",
"title": "Connexion"
}}
]
}"""
expected_a2 = "[call(title=u'Connexion', url=u'https://entrouvert.org/')]"
def test_handle():
kwargs = {'verbosity': 'verbosity value',
'timeout': 'timeout value',
'permissive': 'permissive value'}
command = Command()
command.run_cook = Mock()
command.handle('recipe.json', **kwargs)
assert command.verbosity == 'verbosity value'
assert command.timeout == 'timeout value'
assert command.permissive == 'permissive value'
assert command.run_cook.mock_calls == [call('recipe.json')]
@patch('hobo.environment.management.commands.cook.notify_agents')
def test_run_cook(mocked_notify_agents, tmpdir):
command = Command()
command.permissive = False
command.must_notify = True
command.timeout = 42
command.check_action = Mock()
command.create_hobo = Mock()
mocked_notify_agents = Mock()
command.wait_operationals = Mock()
recipe = {'steps': [{'create-hobo': {'url': 'https://entrouvert.org/'}}]}
recipe_path = os.path.join(str(tmpdir), 'recipe.json')
with open(recipe_path, 'w') as handler:
handler.write(json.dumps(recipe))
command.run_cook(recipe_path)
assert command.check_action.mock_calls == [
call(u'create-hobo', {u'url': u'https://entrouvert.org/'})]
assert command.create_hobo.mock_calls == [call(url=u'https://entrouvert.org/')]
assert mocked_notify_agents.mock_calls == []
assert command.wait_operationals.mock_calls == [call(timeout=42)]
def test_having_several_action_args(tmpdir):
command = Command()
command.permissive = True
command.create_authentic = mock.MagicMock()
command.create_authentic = Mock()
mocked_open.side_effect = [StringIO.StringIO(receipe)]
command.run_cook('recipe_me.json')
assert str(command.create_authentic.mock_calls) == expected_a2
recipe = {'steps': [{'create-authentic': {
'url': 'https://entrouvert.org/',
'title': 'Connexion'
}}]}
recipe_path = os.path.join(str(tmpdir), 'recipe.json')
with open(recipe_path, 'w') as handler:
handler.write(json.dumps(recipe))
command.run_cook(recipe_path)
assert command.create_authentic.mock_calls == [
call(title=u'Connexion', url=u'https://entrouvert.org/')]
def test_load_variables_from(db, tmpdir):
"""load variables from a file"""
command = Command()
command.permissive = True
command.create_hobo = Mock()
command.create_authentic = Mock()
command.create_combo = Mock()
variables = {'foo': 'foo1', 'bar': 'bar1'}
variables_path = os.path.join(str(tmpdir), 'variable.json')
with open(variables_path, 'w') as handler:
handler.write(json.dumps(variables))
recipe = {
'load-variables-from': variables_path,
'variables': {
'domain': 'entrouvert.org',
'bar': 'bar2'
},
'steps':[
{'create-hobo': {'url': 'https://${domain}/'}},
{'create-authentic': {'url': 'https://${foo}/'}},
{'create-combo': {'url': 'https://${bar}/', 'not_a_string': []}}
]}
recipe_path = os.path.join(str(tmpdir), 'recipe.json')
with open(recipe_path, 'w') as handler:
handler.write(json.dumps(recipe))
command.run_cook(recipe_path)
assert command.create_hobo.mock_calls == [call(url=u'https://entrouvert.org/')]
assert command.create_authentic.mock_calls == [call(url=u'https://foo1/')]
assert command.create_combo.mock_calls == [
call(not_a_string=[], url=u'https://bar2/')]
def test_wait_operationals(db, monkeypatch):
service1 = Mock()
service2 = Mock()
obj1 = Mock()
obj2 = Mock()
def do_nothing(*args, **kwargs):
pass
obj1.check_operational = do_nothing
obj2.check_operational = do_nothing
obj1.base_url = 'url1'
obj2.base_url = 'url2'
service1.objects.all = Mock(return_value=[obj1])
service2.objects.all = Mock(return_value=[obj2])
monkeypatch.setattr(
'hobo.environment.management.commands.cook.AVAILABLE_SERVICES',
[service1, service2])
command = Command()
# already operational
obj1.last_operational_success_timestamp = 'some date'
obj2.last_operational_success_timestamp = 'some date'
command.wait_operationals(2)
assert True
# not operational
obj2.last_operational_success_timestamp = None
with pytest.raises(CommandError) as e_info:
command.wait_operationals(.6)
assert str(e_info).find('CommandError: timeout waiting for url2') != -1
def test_set_variable(db):
command = Command()
command.set_variable('foo', 'bar')
var = Variable.objects.get(name='foo')
assert var.value == 'bar'
assert var.label == 'foo'
command.set_variable('foo', 'bar', label='FOO')
var = Variable.objects.get(name='foo')
assert var.value == 'bar'
assert var.label == 'FOO'
command.set_variable('foo', ['bar', 'baz'])
var = Variable.objects.get(name='foo')
assert var.value == '["bar", "baz"]'
command.set_variable('foo', {'key1': 'bar', 'key2': 'baz'})
var = Variable.objects.get(name='foo')
ordered_dump = json.dumps(json.loads(var.value), sort_keys=True)
assert ordered_dump == '{"key1": "bar", "key2": "baz"}'
def test_set_attribute(db):
command = Command()
command.set_attribute('foo_name', 'foo_label')
values = AttributeDefinition.objects.filter(name='foo_name')
assert values.count() == 1
assert values[0].label == 'foo_label'
assert values[0].disabled is False
command.set_attribute('foo_name', 'foo_label', disabled=True)
values = AttributeDefinition.objects.filter(name='foo_name')
assert values.count() == 1
assert values[0].label == 'foo_label'
assert values[0].disabled is True
def test_disable_attribute(db):
command = Command()
command.set_attribute('foo_name', 'foo_label')
values = AttributeDefinition.objects.filter(name='foo_name')
assert values.count() == 1
assert values[0].label == 'foo_label'
assert values[0].disabled is False
command.disable_attribute('foo_name')
values = AttributeDefinition.objects.filter(name='foo_name')
assert values[0].disabled is True
command.disable_attribute('not_defined')
values = AttributeDefinition.objects.filter(name='not_defined')
assert values.count() == 0
def test_enable_attribute(db):
command = Command()
command.set_attribute('foo_name', 'foo_label', disabled=True)
values = AttributeDefinition.objects.filter(name='foo_name')
assert values.count() == 1
assert values[0].label == 'foo_label'
assert values[0].disabled is True
command.enable_attribute('foo_name')
values = AttributeDefinition.objects.filter(name='foo_name')
assert values[0].disabled is False
command.enable_attribute('not_defined')
values = AttributeDefinition.objects.filter(name='not_defined')
assert values.count() == 0
def test_create_superuser(db):
command = Command()
command.create_superuser()
assert User.objects.count() == 1
user = User.objects.all()[0]
assert user.username == 'admin'
assert user.is_superuser is True
def test_create_site(db):
command = Command()
base_url = 'http://entrouvert.org'
title = 'site title'
slug = None
template_name = ''
variables = {'foo': {'label': 'FOO', 'value': {'key': 'bar'}}}
command.create_site(Combo, base_url, title, slug, template_name,
variables)
# Combo object
assert Combo.objects.count() == 1
combo = Combo.objects.all()[0]
assert combo.title == title
assert combo.base_url == base_url + '/'
assert combo.template_name == ''
# ContentType object
obj_type = ContentType.objects.get_for_model(Combo)
# Variables
var = Variable.objects.get(name='foo')
assert var.label == 'FOO'
assert var.value == '{"key": "bar"}'
assert var.service_type == obj_type
assert var.service_pk == combo.id
with pytest.raises(CommandError) as e_info:
command.create_site(Combo, 'unvalid_url', 'site title', 'a slug', '', '')
assert 'Enter a valid URL.' in str(e_info.value)
@patch('hobo.environment.management.commands.cook.connection')
@patch('hobo.environment.management.commands.cook.call_command')
@patch('hobo.environment.management.commands.cook.TenantMiddleware')
def test_create_hobo_primary(mocked_TenantMiddleware, mocked_call_command,
mocked_connection):
command = Command()
command.create_site = Mock()
tenant = Mock()
tenant.schema_name = 'public'
tenant.get_directory = Mock(return_value='/foo')
mocked_connection.get_tenant = Mock(return_value=tenant)
mocked_connection.set_tenant = Mock()
mocked_TenantMiddleware.get_tenant_by_hostname = Mock(return_value=tenant)
mocked_call_command.side_effect = CommandError
mocked_open = mock_open()
with patch('hobo.environment.management.commands.cook.open', mocked_open,
create=True):
command.create_hobo('http://entrouvert.org/and_much')
assert command.create_site.mock_calls == []
assert mocked_call_command.mock_calls == [
call('create_hobo_tenant', 'entrouvert.org')]
assert len(mocked_connection.set_tenant.mock_calls) == 1
assert mocked_open.mock_calls == [
call('/foo/base_url', 'w'),
call().write('http://entrouvert.org/and_much'),
call().close()]
@patch('hobo.environment.management.commands.cook.connection')
@patch('hobo.environment.management.commands.cook.call_command')
@patch('hobo.environment.management.commands.cook.TenantMiddleware')
def test_create_hobo_not_primary(mocked_TenantMiddleware, mocked_call_command,
mocked_connection):
command = Command()
command.create_site = Mock()
tenant = Mock()
tenant.schema_name = 'other than public'
tenant.get_directory = Mock(return_value='/foo')
mocked_connection.get_tenant = Mock(return_value=tenant)
mocked_connection.set_tenant = Mock()
mocked_TenantMiddleware.get_tenant_by_hostname = Mock(return_value=tenant)
mocked_open = mock_open()
with patch('hobo.environment.management.commands.cook.open', mocked_open,
create=True):
command.create_hobo('http://entrouvert.org/and_much')
assert command.create_site.mock_calls == [
call(Hobo, 'http://entrouvert.org/and_much', None, u'hobo-none',
template_name='', variables=None)]
assert mocked_call_command.mock_calls == []
assert len(mocked_connection.set_tenant.mock_calls) == 1
assert mocked_open.mock_calls == []
def test_create_services():
command = Command()
command.create_site = Mock()
command.create_authentic('url', 'title')
command.create_combo('url', 'title')
command.create_wcs('url', 'title')
command.create_passerelle('url', 'title')
command.create_fargo('url', 'title')
command.create_welco('url', 'title')
command.create_chrono('url', 'title')
command.create_corbo('url', 'title')
command.create_bijoe('url', 'title')
assert len(command.create_site.mock_calls) == 9
assert command.create_site.mock_calls == [
call(Authentic, 'url', 'title', None, '', None),
call(Combo, 'url', 'title', None, '', None),
call(Wcs, 'url', 'title', None, '', None),
call(Passerelle, 'url', 'title', None, '', None),
call(Fargo, 'url', 'title', None, '', None),
call(Welco, 'url', 'title', None, '', None),
call(Chrono, 'url', 'title', None, '', None),
call(Corbo, 'url', 'title', None, '', None),
call(BiJoe, 'url', 'title', None, '', None)]
def test_set_idp(db):
command = Command()
# exceptions maybe we should handle into cook.py ?
with pytest.raises(Authentic.DoesNotExist,
match='Authentic matching query does not exist'):
command.set_idp('url')
with pytest.raises(IndexError, match='list index out of range'):
command.set_idp()
# objects sorted on title: [obj1, obj2]
obj1, ignored = Authentic.objects.get_or_create(
slug='slug1', defaults={'title': 'bar'})
obj2, ignored = Authentic.objects.get_or_create(
slug='slug2', defaults={'title': 'foo', 'base_url': 'http://example.org'})
assert obj1.use_as_idp_for_self is False
assert obj2.use_as_idp_for_self is False
# set first
command.set_idp('')
obj = Authentic.objects.get(title='bar')
assert obj.use_as_idp_for_self is True
# set using url
command.set_idp('http://example.org/')
obj = Authentic.objects.get(title='foo')
assert obj.use_as_idp_for_self is True
@patch('hobo.environment.management.commands.cook.set_theme')
@patch('hobo.environment.management.commands.cook.connection')
@patch('hobo.agent.common.management.commands.hobo_deploy.Command.configure_theme')
def test_set_theme(mocked_configure_theme, mocked_connection, mocked_set_theme):
mocked_connection.get_tenant = Mock(return_value='the tenant')
command = Command()
command.set_theme('the theme')
assert mocked_set_theme.mock_calls == [call('the theme')]
assert len(mocked_connection.get_tenant.mock_calls) == 1
assert mocked_configure_theme.mock_calls == [
call({'variables': {'theme': 'the theme'}}, 'the tenant')]
@patch('hobo.environment.management.commands.cook.connection')
def test_cook(mocked_connection):
mocked_connection.get_tenant = Mock(return_value='the tenant')
mocked_connection.set_tenant = Mock()
command = Command()
command.run_cook = Mock()
command.cook('a-recipe-file.json')
assert len(mocked_connection.get_tenant.mock_calls) == 1
assert command.run_cook.mock_calls == [call('a-recipe-file.json')]
assert mocked_connection.set_tenant.mock_calls == [call('the tenant')]

View File

@ -1,3 +1,6 @@
import os
import shutil
import pytest
from django.core.management import call_command
@ -47,3 +50,27 @@ def fake_notify(monkeypatch):
monkeypatch.setattr(Command, 'wait_operationals', fake_wait)
return services
@pytest.fixture
def fake_themes(settings, tmpdir):
THEMES="""
[
{
"id": "publik",
"label": "Publik",
"variables": {
"css_variant": "publik",
"no_extra_js": false,
"theme_color": "#E80E89"
}
}
]
"""
base_dir = str(tmpdir.mkdir('themes'))
settings.THEMES_DIRECTORY = base_dir
themes_dir = os.path.join(base_dir, 'publik-base')
os.mkdir(themes_dir)
with open(os.path.join(themes_dir, 'themes.json'), 'w') as handler:
handler.write(THEMES)

View File

@ -0,0 +1,226 @@
{
"profile": {
"fields": [
{
"asked_on_registration": false,
"description": "",
"disabled": false,
"kind": "title",
"label": "Civilit\u00e9",
"name": "title",
"required": false,
"searchable": false,
"user_editable": true,
"user_visible": true
},
{
"asked_on_registration": true,
"description": "",
"disabled": false,
"kind": "string",
"label": "Pr\u00e9nom",
"name": "first_name",
"required": true,
"searchable": false,
"user_editable": true,
"user_visible": true
},
{
"asked_on_registration": true,
"description": "",
"disabled": false,
"kind": "string",
"label": "Nom",
"name": "last_name",
"required": true,
"searchable": false,
"user_editable": true,
"user_visible": true
},
{
"asked_on_registration": false,
"description": "",
"disabled": false,
"kind": "email",
"label": "Adresse \u00e9lectronique",
"name": "email",
"required": true,
"searchable": false,
"user_editable": true,
"user_visible": true
},
{
"asked_on_registration": false,
"description": "",
"disabled": false,
"kind": "string",
"label": "Adresse",
"name": "address",
"required": false,
"searchable": false,
"user_editable": true,
"user_visible": true
},
{
"asked_on_registration": false,
"description": "",
"disabled": false,
"kind": "string",
"label": "Code postal",
"name": "zipcode",
"required": false,
"searchable": false,
"user_editable": true,
"user_visible": true
},
{
"asked_on_registration": false,
"description": "",
"disabled": false,
"kind": "string",
"label": "Commune",
"name": "city",
"required": false,
"searchable": false,
"user_editable": true,
"user_visible": true
},
{
"asked_on_registration": false,
"description": "",
"disabled": true,
"kind": "string",
"label": "Pays",
"name": "country",
"required": false,
"searchable": false,
"user_editable": true,
"user_visible": true
},
{
"asked_on_registration": false,
"description": "",
"disabled": true,
"kind": "birthdate",
"label": "Date de naissance",
"name": "birthdate",
"required": false,
"searchable": false,
"user_editable": true,
"user_visible": true
},
{
"asked_on_registration": false,
"description": "",
"disabled": false,
"kind": "string",
"label": "T\u00e9l\u00e9phone",
"name": "phone",
"required": false,
"searchable": false,
"user_editable": true,
"user_visible": true
},
{
"asked_on_registration": false,
"description": "",
"disabled": false,
"kind": "string",
"label": "Mobile",
"name": "mobile",
"required": false,
"searchable": false,
"user_editable": true,
"user_visible": true
}
]
},
"services": [
{
"backoffice-menu-url": "https://hobo-instance-name.dev.signalpublik.com/menu.json",
"base_url": "https://hobo-instance-name.dev.signalpublik.com/",
"saml-sp-metadata-url": "https://hobo-instance-name.dev.signalpublik.com/accounts/mellon/metadata/",
"service-id": "hobo",
"slug": "hobo",
"title": "Hobo"
},
{
"backoffice-menu-url": "https://connexion-instance-name.dev.signalpublik.com/manage/menu.json",
"base_url": "https://connexion-instance-name.dev.signalpublik.com/",
"id": 1,
"saml-idp-metadata-url": "https://connexion-instance-name.dev.signalpublik.com/idp/saml2/metadata",
"secondary": false,
"secret_key": "k_a)vo)a&8xugbzjl#%^s8vfkm2+#yhz#if4m+xu!qqv=04x9q",
"service-id": "authentic",
"service-label": "Authentic",
"slug": "idp",
"template_name": "signal-publik",
"title": "Connexion",
"variables": {}
},
{
"backoffice-menu-url": "https://demarches-instance-name.dev.signalpublik.com/backoffice/menu.json",
"base_url": "https://demarches-instance-name.dev.signalpublik.com/",
"id": 1,
"saml-sp-metadata-url": "https://demarches-instance-name.dev.signalpublik.com/saml/metadata",
"secondary": false,
"secret_key": "uhipz^y38a*w#rrnio_-i=+7p47aq#$+dntm*i@nz(y)n57153",
"service-id": "wcs",
"service-label": "w.c.s.",
"slug": "eservices",
"template_name": "",
"title": "D\u00e9marches",
"variables": {}
},
{
"backoffice-menu-url": "https://passerelle-instance-name.dev.signalpublik.com/manage/menu.json",
"base_url": "https://passerelle-instance-name.dev.signalpublik.com/",
"id": 1,
"saml-sp-metadata-url": "https://passerelle-instance-name.dev.signalpublik.com/accounts/mellon/metadata/",
"secondary": false,
"secret_key": "vz&g(p1bhzw35iltrrl$^6013*+q80l&l4)b)tsr=+ko__js_v",
"service-id": "passerelle",
"service-label": "Passerelle",
"slug": "passerelle",
"template_name": "signal-publik",
"title": "Passerelle",
"variables": {}
},
{
"backoffice-menu-url": "https://instance-name.dev.signalpublik.com/manage/menu.json",
"base_url": "https://instance-name.dev.signalpublik.com/",
"id": 1,
"saml-sp-metadata-url": "https://instance-name.dev.signalpublik.com/accounts/mellon/metadata/",
"secondary": false,
"secret_key": "^0!psa-ijq4*va0a4&_)solvils#hig2vtof(%3iy#!6p5!f6e",
"service-id": "combo",
"service-label": "Combo",
"slug": "portal",
"template_name": "signal-publik-portal-user",
"title": "Compte citoyen",
"variables": {}
},
{
"backoffice-menu-url": "https://agents-instance-name.dev.signalpublik.com/manage/menu.json",
"base_url": "https://agents-instance-name.dev.signalpublik.com/",
"id": 2,
"saml-sp-metadata-url": "https://agents-instance-name.dev.signalpublik.com/accounts/mellon/metadata/",
"secondary": false,
"secret_key": "m1&vql=pm-clw)0wcnk=q4g1-#flrus!dui$gr$7ug2%xw@ko$",
"service-id": "combo",
"service-label": "Combo",
"slug": "portal-agent",
"template_name": "signal-publik-portal-agent",
"title": "Portail agent",
"variables": {}
}
],
"timestamp": "1558975192.98",
"users": [],
"variables": {
"css_variant": "publik",
"no_extra_js": false,
"theme": "publik",
"theme_color": "#E80E89"
}
}

View File

@ -0,0 +1,62 @@
{
"steps": [
{
"create-hobo": {
"url": "https://${hobo}/",
"primary": true
}
},
{
"create-authentic": {
"title": "Connexion",
"url": "https://${authentic}/",
"template_name": "signal-publik"
}
},
{
"set-idp": {}
},
{
"create-combo": {
"template_name": "signal-publik-portal-user",
"title": "Compte citoyen",
"url": "https://${combo}/"
}
},
{
"create-combo": {
"slug": "portal-agent",
"template_name": "signal-publik-portal-agent",
"title": "Portail agent",
"url": "https://${combo_agent}/"
}
},
{
"create-wcs": {
"title": "D\u00e9marches",
"url": "https://${wcs}/"
}
},
{
"create-passerelle": {
"title": "Passerelle",
"template_name": "signal-publik",
"url": "https://${passerelle}/"
}
},
{
"set-theme": {
"theme": "publik"
}
}
],
"variables": {
"authentic": "connexion-instance-name.dev.signalpublik.com",
"combo": "instance-name.dev.signalpublik.com",
"combo_agent": "agents-instance-name.dev.signalpublik.com",
"fargo": "porte-documents-instance-name.dev.signalpublik.com",
"hobo": "hobo-instance-name.dev.signalpublik.com",
"passerelle": "passerelle-instance-name.dev.signalpublik.com",
"wcs": "demarches-instance-name.dev.signalpublik.com"
}
}

View File

@ -1,8 +1,10 @@
import json
import pytest
from django.core.management import call_command
from django.core.management import call_command, load_command_class
from django.core.management.base import CommandError
from hobo.deploy.utils import get_hobo_json
from hobo.environment.models import ServiceBase
@ -10,7 +12,7 @@ def test_cook(db, fake_notify, monkeypatch):
monkeypatch.setattr(ServiceBase, 'is_resolvable', lambda x: True)
monkeypatch.setattr(ServiceBase, 'has_valid_certificate', lambda x: True)
call_command('cook', 'tests_schemas/recipe.json')
assert(len(fake_notify) == 3)
assert len(fake_notify) == 3
def test_cook_unresolvable(db, fake_notify, monkeypatch):
@ -18,3 +20,25 @@ def test_cook_unresolvable(db, fake_notify, monkeypatch):
with pytest.raises(CommandError) as e_info:
call_command('cook', 'tests_schemas/recipe.json')
assert 'is not resolvable' in str(e_info.value)
def test_cook_example(db, fake_notify, monkeypatch, fake_themes):
"""hobo/cook (before rabbitmq) scenario having templates.
the resulting JSON may be helpfull to manually invoque hobo-deploy (after rabbitmq)
"""
monkeypatch.setattr(ServiceBase, 'is_resolvable', lambda x: True)
monkeypatch.setattr(ServiceBase, 'has_valid_certificate', lambda x: True)
call_command('cook', 'tests_schemas/example_recipe.json')
# notify_agents was call
assert len(fake_notify) == 6
# here is the JSON environment spread by notify_agents
environment = get_hobo_json()
# below JSON file was created by this instruction
#json.dump(environment, open('tests_schemas/example_env.json', 'w'),
# sort_keys=True, indent=4, separators=(',', ': '))
expected = json.load(open('tests_schemas/example_env.json', 'r'))
assert json.dumps(environment, sort_keys=True), json.dumps(expected, sort_keys=True)