442 lines
16 KiB
Python
442 lines
16 KiB
Python
import json
|
|
import os
|
|
from unittest.mock import Mock, call, mock_open, patch
|
|
|
|
import pytest
|
|
from django.contrib.auth.models import User
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.core.management import call_command
|
|
from django.core.management.base import CommandError
|
|
|
|
from hobo.environment import models as environment_models
|
|
from hobo.environment.management.commands.cook import Command
|
|
from hobo.environment.models import (
|
|
Authentic,
|
|
BiJoe,
|
|
Chrono,
|
|
Combo,
|
|
Fargo,
|
|
Hobo,
|
|
Lingo,
|
|
Passerelle,
|
|
Variable,
|
|
Wcs,
|
|
Welco,
|
|
)
|
|
from hobo.profile.models import AttributeDefinition
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mocked_notify_agents():
|
|
with patch('hobo.environment.management.commands.cook.notify_agents') as mocked_notify_agents:
|
|
yield mocked_notify_agents
|
|
|
|
|
|
@pytest.fixture
|
|
def command():
|
|
return Command()
|
|
|
|
|
|
@pytest.fixture
|
|
def cook(command, tmpdir):
|
|
class Cook:
|
|
counter = 0
|
|
|
|
def __call__(self, recipe, **kwargs):
|
|
path = tmpdir / f'recipe-{self.counter}.json'
|
|
self.counter += 1
|
|
path.write_text(json.dumps(recipe), 'utf-8')
|
|
call_command(command, str(path), **kwargs)
|
|
|
|
return Cook()
|
|
|
|
|
|
def test_check_action(command, monkeypatch):
|
|
"""no exception raised if url are available"""
|
|
command.server_action = 'mock a server_action handler (ex: hobo-create)'
|
|
action, action_args = 'server-action', {'url': 'https://test.org/'}
|
|
|
|
monkeypatch.setattr(environment_models, 'is_resolvable', lambda x: True)
|
|
monkeypatch.setattr(environment_models, 'has_valid_certificate', lambda x: True)
|
|
command.check_action(action, action_args)
|
|
assert True
|
|
|
|
|
|
def test_check_action_unknown_action(command, monkeypatch):
|
|
"""raise CommandError"""
|
|
command.server_action = 'mock a server_action handler (ex: hobo-create)'
|
|
action, action_args = 'not-a-server-action', {'url': 'https://test.org/'}
|
|
|
|
monkeypatch.setattr(environment_models, 'is_resolvable', lambda x: True)
|
|
monkeypatch.setattr(environment_models, '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_providing_port(command):
|
|
command.server_action = 'mock a server_action handler (ex: hobo-create)'
|
|
action, action_args = 'server-action', {'url': 'https://test.org:443/'}
|
|
|
|
with pytest.raises(CommandError) as e_info:
|
|
command.check_action(action, action_args)
|
|
assert 'providing port is not supported on service url https://test.org:443/' in str(e_info.value)
|
|
|
|
|
|
def test_check_action_unresolvable(command, monkeypatch):
|
|
"""raise CommandError"""
|
|
command.server_action = 'mock a server_action handler (ex: hobo-create)'
|
|
action, action_args = 'server-action', {'url': 'https://test.org/'}
|
|
|
|
monkeypatch.setattr(environment_models, 'is_resolvable', lambda x: False)
|
|
monkeypatch.setattr(environment_models, '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(command, monkeypatch):
|
|
"""raise CommandError"""
|
|
command.server_action = 'mock a server_action handler (ex: hobo-create)'
|
|
action, action_args = 'server-action', {'url': 'https://test.org/'}
|
|
|
|
monkeypatch.setattr(environment_models, 'is_resolvable', lambda x: True)
|
|
monkeypatch.setattr(environment_models, '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_check_action_disabled_service(command, cook, db):
|
|
recipe = {'steps': [{'create-bijoe': {'url': 'https://entrouvert.org/', 'title': 'Statistiques'}}]}
|
|
with pytest.raises(CommandError, match='service class "BiJoe" is disabled'):
|
|
cook(recipe)
|
|
command.create_bijoe = Mock()
|
|
cook(recipe, permissive=True)
|
|
|
|
|
|
def test_handle(command):
|
|
kwargs = {'verbosity': 0, 'timeout': 'timeout value', 'permissive': 'permissive value'}
|
|
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')]
|
|
|
|
|
|
def test_run_cook(command, cook, mocked_notify_agents, db):
|
|
command.must_notify = True
|
|
command.check_action = Mock()
|
|
command.create_hobo = Mock()
|
|
command.wait_operationals = Mock()
|
|
|
|
recipe = {'steps': [{'create-hobo': {'url': 'https://entrouvert.org/'}}]}
|
|
cook(recipe, timeout=42, permissive=False)
|
|
|
|
assert command.check_action.mock_calls == [call('create-hobo', {'url': 'https://entrouvert.org/'})]
|
|
assert command.create_hobo.mock_calls == [call(url='https://entrouvert.org/')]
|
|
assert mocked_notify_agents.call_count == 2
|
|
assert command.wait_operationals.mock_calls == [call(timeout=42)]
|
|
|
|
|
|
def test_having_several_action_args(command, cook, db):
|
|
command.create_authentic = Mock()
|
|
recipe = {'steps': [{'create-authentic': {'url': 'https://entrouvert.org/', 'title': 'Connexion'}}]}
|
|
cook(recipe, permissive=True)
|
|
assert command.create_authentic.mock_calls == [call(title='Connexion', url='https://entrouvert.org/')]
|
|
|
|
|
|
def test_load_variables_from(command, cook, tmpdir, db):
|
|
"""load variables from a file"""
|
|
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': []}},
|
|
],
|
|
}
|
|
cook(recipe, permissive=True)
|
|
|
|
assert command.create_hobo.mock_calls == [call(url='https://entrouvert.org/')]
|
|
assert command.create_authentic.mock_calls == [call(url='https://foo1/')]
|
|
assert command.create_combo.mock_calls == [call(not_a_string=[], url='https://bar2/')]
|
|
|
|
|
|
def test_wait_operationals(command, 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])
|
|
|
|
# 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(command, db):
|
|
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(command, db):
|
|
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(command, db):
|
|
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(command, db):
|
|
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(command, db):
|
|
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(command, db):
|
|
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.create_site = Mock()
|
|
tenant = Mock()
|
|
tenant.schema_name = 'public'
|
|
tenant.get_directory = Mock(return_value='/foo')
|
|
mocked_connection.tenant = 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 call().write('http://entrouvert.org/and_much') in mocked_open.mock_calls
|
|
|
|
|
|
@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.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.tenant = 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, '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.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(command, db):
|
|
# 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, dummy = Authentic.objects.get_or_create(slug='slug1', defaults={'title': 'bar'})
|
|
obj2, dummy = 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, command):
|
|
mocked_connection.tenant = 'the tenant'
|
|
command.set_theme('the theme')
|
|
|
|
assert mocked_set_theme.mock_calls == [call('the theme')]
|
|
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, command):
|
|
mocked_connection.tenant = 'the tenant'
|
|
mocked_connection.set_tenant = Mock()
|
|
command.run_cook = Mock()
|
|
command.cook('a-recipe-file.json')
|
|
|
|
assert command.run_cook.mock_calls == [call('a-recipe-file.json')]
|
|
assert mocked_connection.set_tenant.mock_calls == [call('the tenant')]
|