From 9184b80d43570bddc21ed4fb521a12a936f5f9cb Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Tue, 7 May 2019 19:31:39 +0200
Subject: [PATCH] cook: add unit tests for cook.py (#32886)

---
 hobo/environment/management/commands/cook.py |   2 -
 tests/test_cook.py                           | 382 ++++++++++++++++++-
 tests_schemas/conftest.py                    |  27 ++
 tests_schemas/example_env.json               | 226 +++++++++++
 tests_schemas/example_recipe.json            |  62 +++
 tests_schemas/test_cook.py                   |  28 +-
 6 files changed, 704 insertions(+), 23 deletions(-)
 create mode 100644 tests_schemas/example_env.json
 create mode 100644 tests_schemas/example_recipe.json

diff --git a/hobo/environment/management/commands/cook.py b/hobo/environment/management/commands/cook.py
index 2b6d41b..1c4f7d6 100644
--- a/hobo/environment/management/commands/cook.py
+++ b/hobo/environment/management/commands/cook.py
@@ -188,8 +188,6 @@ class Command(BaseCommand):
                     service_type=obj_type,
                     service_pk=obj.id,
                     defaults={'label': label or variable_name})
-            variable.service_type = obj_type
-            variable.service_pk = obj.id
             if label:
                 variable.label = label
             value = variables[variable_name].get('value')
diff --git a/tests/test_cook.py b/tests/test_cook.py
index 1f46a35..ce9bf11 100644
--- a/tests/test_cook.py
+++ b/tests/test_cook.py
@@ -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()
+
+    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')
 
-    mocked_open.side_effect = [StringIO.StringIO(receipe)]
-    command.run_cook('recipe_me.json')
-    assert str(command.create_authentic.mock_calls) == expected_a2
+    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')]
diff --git a/tests_schemas/conftest.py b/tests_schemas/conftest.py
index cdc4f79..a5fe8f0 100644
--- a/tests_schemas/conftest.py
+++ b/tests_schemas/conftest.py
@@ -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)
diff --git a/tests_schemas/example_env.json b/tests_schemas/example_env.json
new file mode 100644
index 0000000..d3dab1a
--- /dev/null
+++ b/tests_schemas/example_env.json
@@ -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"
+    }
+}
\ No newline at end of file
diff --git a/tests_schemas/example_recipe.json b/tests_schemas/example_recipe.json
new file mode 100644
index 0000000..85f7f10
--- /dev/null
+++ b/tests_schemas/example_recipe.json
@@ -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"
+    }
+}
diff --git a/tests_schemas/test_cook.py b/tests_schemas/test_cook.py
index e0ab2f1..97cbb34 100644
--- a/tests_schemas/test_cook.py
+++ b/tests_schemas/test_cook.py
@@ -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)
-- 
2.20.1

