hobo/tests/test_cook.py

446 lines
16 KiB
Python

import json
import os
from io import StringIO
import pytest
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import CommandError
from mock import Mock, call, mock_open, patch
from hobo.environment.management.commands.cook import Command
from hobo.environment.models import (
Authentic,
BiJoe,
Chrono,
Combo,
Fargo,
Hobo,
Lingo,
Passerelle,
ServiceBase,
Variable,
Wcs,
Welco,
)
from hobo.profile.models import AttributeDefinition
def test_check_action(monkeypatch):
"""no exception raised if url are available"""
command = Command()
command.server_action = 'mock a server_action handler (ex: hobo-create)'
action, action_args = 'server-action', {u'url': u'https://test.org/'}
monkeypatch.setattr(ServiceBase, 'is_resolvable', lambda x: True)
monkeypatch.setattr(ServiceBase, 'has_valid_certificate', lambda x: True)
command.check_action(action, action_args)
assert True
def test_check_action_unknown_action(monkeypatch):
"""raise CommandError"""
command = Command()
command.server_action = 'mock a server_action handler (ex: hobo-create)'
action, action_args = 'not-a-server-action', {u'url': u'https://test.org/'}
monkeypatch.setattr(ServiceBase, 'is_resolvable', lambda x: True)
monkeypatch.setattr(ServiceBase, 'has_valid_certificate', lambda x: True)
with pytest.raises(CommandError) as e_info:
command.check_action(action, action_args)
assert 'Unknown action not-a-server-action' in str(e_info.value)
def test_check_action_unresolvable(monkeypatch):
"""raise CommandError"""
command = Command()
command.server_action = 'mock a server_action handler (ex: hobo-create)'
action, action_args = 'server-action', {u'url': u'https://test.org/'}
monkeypatch.setattr(ServiceBase, 'is_resolvable', lambda x: False)
monkeypatch.setattr(ServiceBase, 'has_valid_certificate', lambda x: True)
with pytest.raises(CommandError) as e_info:
command.check_action(action, action_args)
assert 'test.org is not resolvable in URL https://test.org/' in str(e_info.value)
def test_check_action_invalid_certificat(monkeypatch):
"""raise CommandError"""
command = Command()
command.server_action = 'mock a server_action handler (ex: hobo-create)'
action, action_args = 'server-action', {u'url': u'https://test.org/'}
monkeypatch.setattr(ServiceBase, 'is_resolvable', lambda x: True)
monkeypatch.setattr(ServiceBase, 'has_valid_certificate', lambda x: False)
with pytest.raises(CommandError) as e_info:
command.check_action(action, action_args)
assert 'no valid certificate for https://test.org/' in str(e_info.value)
def test_handle():
kwargs = {'verbosity': 0, 'timeout': 'timeout value', 'permissive': 'permissive value'}
command = Command()
command.run_cook = Mock()
command.handle('recipe.json', **kwargs)
assert command.verbosity == 0
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()
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.terminal_width = 80
command.wait_operationals(2)
assert True
# not operational
obj2.last_operational_success_timestamp = None
with pytest.raises(CommandError, match='timeout waiting for url2'):
command.wait_operationals(0.6)
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'
assert var.auto is False
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"}'
command.set_variable('varauto', ['bar', 'baz'], auto=True)
var = Variable.objects.get(name='varauto')
assert var.auto is True
command.set_variable('meta_description', 'blah blah') # in AUTO_VARIABLES
var = Variable.objects.get(name='meta_description')
assert var.auto is True
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.timeout = 42
command.wait_operationals = Mock()
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_bijoe('url', 'title')
command.create_lingo('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(BiJoe, 'url', 'title', None, '', None),
call(Lingo, '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')]