diff --git a/heptapod/gitlab/tests/test_hooks.py b/heptapod/gitlab/tests/test_hooks_inner.py
similarity index 43%
rename from heptapod/gitlab/tests/test_hooks.py
rename to heptapod/gitlab/tests/test_hooks_inner.py
index 53fdbb7ab32802b108552f0e6016d2c6b928838a_aGVwdGFwb2QvZ2l0bGFiL3Rlc3RzL3Rlc3RfaG9va3MucHk=..32ea9bcd47568019561e70ee2927520a69416a11_aGVwdGFwb2QvZ2l0bGFiL3Rlc3RzL3Rlc3RfaG9va3NfaW5uZXIucHk= 100644
--- a/heptapod/gitlab/tests/test_hooks.py
+++ b/heptapod/gitlab/tests/test_hooks_inner.py
@@ -4,6 +4,6 @@
 # GNU General Public License version 2 or any later version.
 #
 # SPDX-License-Identifier: GPL-2.0-or-later
-import io
-import json
+"""Tests for hooks that don't need neither a repo, nor to fake requests.
+"""
 import pytest
@@ -9,3 +9,2 @@
 import pytest
-import requests
 
@@ -11,7 +10,4 @@
 
-from heptapod.testhelpers import (
-    LocalRepoWrapper,
-    )
 from .. import (
     format_alert_message,
     format_warnings,
@@ -19,7 +15,6 @@
     git_hook_format_changes,
     parse_broadcast_msg,
     parse_actor,
-    Hook,
     InvalidActor,
     )
 
@@ -23,6 +18,7 @@
     InvalidActor,
     )
 
+
 ZERO_SHA = "0" * 20
 
 
@@ -129,325 +125,3 @@
             assert '?query=something' in line
     assert seen_view, "Did not get message to view existing MR"
     assert seen_create, "Did not get message to create new MR"
-
-
-def make_repo(base_dir, name):
-    secret = base_dir.join('secret')
-    secret.write("TEHTOKEN")
-    wrapper = LocalRepoWrapper.init(
-        base_dir.join(name),
-        config=dict(heptapod={'gitlab-shell': str(base_dir),
-                              'gitlab-internal-api-secret-file': str(secret),
-                              }),
-    )
-    # by default, ui.environ IS os.environ (shared) but that isn't true
-    # in WSGI contexts. In that case, it is a plain dict, the WSGI environment
-    # indeed. For these tests, we need them to be independent.
-    wrapper.repo.ui.environ = {}
-    return wrapper
-
-
-def request_recorder(records, responses):
-    def request(hook, meth, subpath, params, **kwargs):
-        records.append((meth, subpath, params, kwargs))
-        resp = responses.pop(0)
-        if isinstance(resp, Exception):
-            raise resp
-        return resp
-
-    return request
-
-
-def test_post_receive_params(tmpdir):
-    wrapper = make_repo(tmpdir, 'proj.hg')
-    repo = wrapper.repo
-    hook = Hook('post-receive', repo)
-
-    repo.ui.environ.update(
-        HEPTAPOD_USERINFO_ID='23',
-        HEPTAPOD_PROJECT_ID='1024',
-    )
-    changes = {
-        'branch/newbr': (ZERO_SHA, "beef1234dead" + "a" * 8),
-        'topic/default/zz': ("23cafe45" + "0" * 12, "1234" + "b" * 16),
-    }
-
-    params = hook.post_receive_params(changes)
-    assert params['identifier'] == 'user-23'
-    assert params['changes'].splitlines() == [
-        '23cafe45000000000000 1234bbbbbbbbbbbbbbbb topic/default/zz',
-        '00000000000000000000 beef1234deadaaaaaaaa branch/newbr']
-    assert params['gl_repository'] == 'project-1024'
-
-
-def test_pre_receive_params(tmpdir):
-    wrapper = make_repo(tmpdir, 'proj.hg')
-    repo = wrapper.repo
-    hook = Hook('post-receive', repo)
-
-    repo.ui.environ.update(
-        HEPTAPOD_USERINFO_ID='2',
-        HEPTAPOD_PROJECT_ID='3145926',
-    )
-    changes = {
-        'topic/default/with-a-dash': (ZERO_SHA, "beef1234dead" + "a" * 8),
-        'branch/default': ("23cafe45" + "0" * 12, "1234" + "b" * 16),
-    }
-    params = hook.pre_receive_params(changes)
-
-    assert params['user_id'] == '2'
-    assert params['changes'].splitlines() == [
-        '23cafe45000000000000 1234bbbbbbbbbbbbbbbb branch/default',
-        '00000000000000000000 beef1234deadaaaaaaaa topic/default/with-a-dash']
-    assert params['gl_repository'] == 'project-3145926'
-    assert params['project'] == repo.root
-
-
-def test_post_receive_handle_response(tmpdir):
-    wrapper = make_repo(tmpdir, 'proj.hg')
-    repo = wrapper.repo
-    hook = Hook('post-receive', repo)
-
-    out = hook.post_receive_handle_response(
-        # example taken from the functional tests
-        {u'broadcast_message': None,
-         u'reference_counter_decreased': True,
-         u'merge_request_urls': [
-             {u'url': u'http://localhost:3000/root/'
-              'test_project_1583330760_9216309/merge_requests/1',
-              u'branch_name': u'topic/default/antelope',
-              u'new_merge_request': False}
-         ]})
-    out = [l.strip() for l in out.splitlines() if l.strip()]
-    assert out == [
-        'View merge request for topic/default/antelope:',
-        'http://localhost:3000/root/test_project_1583330760_9216309/'
-        'merge_requests/1']
-
-    broadcast = "Check out the new tutorial"
-    out = hook.post_receive_handle_response(
-        {u'broadcast_message': broadcast,
-         u'reference_counter_decreased': True,
-         })
-    out = (l.strip() for l in out.splitlines() if l.strip())
-    assert any(broadcast in l for l in out)
-
-    project_created = "Congrats!"
-    out = hook.post_receive_handle_response(
-        {u'project_created_message': project_created,
-         u'reference_counter_decreased': True,
-         })
-    out = (l.strip() for l in out.splitlines() if l.strip())
-    assert any(project_created in l for l in out)
-
-    # GitLab internal API gives warnings as a unique string
-    # see lib/api/helpers/internal_helpers.rb
-    warnings = "You probably shouldn't have"
-    out = hook.post_receive_handle_response(
-        {u'warnings': warnings,
-         u'reference_counter_decreased': True,
-         })
-    out = (l.strip() for l in out.splitlines() if l.strip())
-    assert any(warnings in l for l in out)
-
-
-def make_response(status_code, body):
-    """Body is bytes"""
-    resp = requests.models.Response()
-    resp.status_code = status_code
-    resp.raw = io.BytesIO(body)
-    return resp
-
-
-def make_json_response(obj, status_code=200):
-    return make_response(status_code, json.dumps(obj).encode())
-
-
-def test_integration_post_receive(tmpdir, monkeypatch):
-    records = []
-    responses = [make_json_response(
-        {u'broadcast_message': None,
-         u'reference_counter_decreased': True,
-         u'merge_request_urls': [
-             {u'url': u'http://localhost:3000/root/'
-              'test_project_1583330760_9216309/merge_requests/1',
-              u'branch_name': u'topic/default/antelope',
-              u'new_merge_request': False}
-         ]}),
-                 requests.exceptions.Timeout("1 ns is too much ;-)"),
-                 ]
-
-    monkeypatch.setattr(Hook, 'gitlab_internal_api_request',
-                        request_recorder(records, responses))
-
-    repo_wrapper = make_repo(tmpdir, 'repo.hg')
-    repo = repo_wrapper.repo
-
-    repo.ui.environ.update(
-        HEPTAPOD_USERINFO_ID='23',
-        HEPTAPOD_PROJECT_ID='1024',
-    )
-
-    hook = Hook('post-receive', repo_wrapper.repo)
-    changes = {
-        'branch/newbr': (ZERO_SHA, "beef1234dead" + "a" * 8),
-        'topic/default/zz': ("23cafe45" + "0" * 12, "1234" + "b" * 16),
-    }
-
-    return_code, out, err = hook(changes)
-    assert return_code == 0
-    out = [l.strip() for l in out.splitlines() if l.strip()]
-    assert out == [
-        'View merge request for topic/default/antelope:',
-        'http://localhost:3000/root/test_project_1583330760_9216309/'
-        'merge_requests/1']
-
-    return_code, out, err = hook(changes)
-    assert return_code == 1
-    assert err == 'GitLab is unreachable'
-
-
-def test_integration_pre_receive(tmpdir, monkeypatch):
-    records = []
-    responses = [make_json_response({u'message': "yes, granted!",
-                                     u'status': True,
-                                     },
-                                    status_code=200),
-                 make_json_response({u'status': True},
-                                    status_code=200),
-                 make_json_response({u'message': "go away!",
-                                     u'status': False,
-                                     },
-                                    status_code=401),
-                 make_response(504, b"Bad gateway :-("),
-                 requests.exceptions.ConnectionError("Failed"),
-                 ]
-    monkeypatch.setattr(Hook, 'gitlab_internal_api_request',
-                        request_recorder(records, responses))
-
-    repo_wrapper = make_repo(tmpdir, 'repo.hg')
-    repo = repo_wrapper.repo
-
-    repo.ui.environ.update(
-        HEPTAPOD_USERINFO_ID='23',
-        HEPTAPOD_PROJECT_ID='1024',
-    )
-
-    hook = Hook('pre-receive', repo_wrapper.repo)
-    changes = {
-        'branch/newbr': (ZERO_SHA, "beef1234dead" + "a" * 8),
-        'topic/default/zz': ("23cafe45" + "0" * 12, "1234" + "b" * 16),
-    }
-
-    return_code, out, err = hook(changes)
-    assert return_code == 0
-    assert out == 'yes, granted!'
-
-    return_code, out, err = hook(changes)
-    assert return_code == 0
-    assert out == ''
-
-    return_code, out, err = hook(changes)
-    assert return_code == 1
-    assert err == 'go away!'
-
-    return_code, out, err = hook(changes)
-    assert return_code == 1
-    assert err == 'API is not accessible'
-
-    return_code, out, err = hook(changes)
-    assert return_code == 1
-    assert err == 'GitLab is unreachable'
-
-
-def test_empty_changes(tmpdir):
-    hook = Hook('post-receive', make_repo(tmpdir, 'repo.hg').repo)
-    code, out, err = hook(())
-    assert code == 0
-    assert not out
-    assert not err
-
-
-def test_unknown_hook(tmpdir):
-    # that's a typo, the most likely way for it to happen
-    name = 'post-recieve'
-
-    repo = make_repo(tmpdir, 'repo.hg').repo
-    hook = Hook(name, repo)
-    changes = {
-        'branch/newbr': (ZERO_SHA, "beef1234dead" + "a" * 8),
-        'topic/default/zz': ("23cafe45" + "0" * 12, "1234" + "b" * 16),
-    }
-
-    repo.ui.environ.update(
-        HEPTAPOD_USERINFO_ID='23',
-        HEPTAPOD_PROJECT_ID='1024',
-    )
-    with pytest.raises(NotImplementedError) as exc_info:
-        hook(changes)
-    assert exc_info.value.args[0] == name
-
-
-def test_missing_secret(tmpdir):
-    repo = LocalRepoWrapper.init(tmpdir).repo
-    shell = tmpdir.join('heptapod-shell').ensure(dir=True)
-    repo.ui.setconfig(b'heptapod', b'gitlab-shell', str(shell).encode())
-    with pytest.raises(RuntimeError) as exc_info:
-        Hook('post-receive', repo)
-    assert "secret-file" in exc_info.value.args[0]
-
-    # defaulting to gitlab-shell secret if available
-    shell.join('.gitlab_shell_secret').write('myseekret\n')
-    hook = Hook('post-receive', repo)
-    assert hook.gitlab_secret == 'myseekret'
-
-
-def test_hook_missing_user(tmpdir):
-    wrapper = make_repo(tmpdir, 'proj2.hg')
-    repo = wrapper.repo
-    hook = Hook('post-something', repo)
-
-    repo.ui.environ['HEPTAPOD_PROJECT_ID'] = '1024'
-    with pytest.raises(ValueError) as exc_info:
-        hook.environ()
-    assert 'User id' in exc_info.value.args[0]
-
-    # and that gets catched, does not break the process
-    code, out, err = hook(dict(master=(0, 1)))
-    assert code != 0
-
-
-def test_hook_missing_project(tmpdir):
-    wrapper = make_repo(tmpdir, 'proj2.hg')
-    repo = wrapper.repo
-    hook = Hook('post-something', repo)
-
-    repo.ui.environ['HEPTAPOD_USERINFO_ID'] = '3'
-    with pytest.raises(ValueError) as exc_info:
-        hook.environ()
-    assert 'Project id' in exc_info.value.args[0]
-
-    # and that gets catched, does not break the process
-    code, out, err = hook(dict(master=(0, 1)))
-    assert code != 0
-
-
-def test_hook_skip(tmpdir):
-    wrapper = make_repo(tmpdir, 'proj2.hg')
-    repo = wrapper.repo
-    repo.ui.environ['HEPTAPOD_SKIP_GITLAB_HOOK'] = 'pre-rcv'
-    hook = Hook('pre-rcv', repo)
-    assert hook(dict(master=(0, 1))) == (0, '', '')
-
-
-def test_hook_skip_all(tmpdir):
-    wrapper = make_repo(tmpdir, 'proj2.hg')
-    repo = wrapper.repo
-    repo.ui.environ['HEPTAPOD_SKIP_ALL_GITLAB_HOOKS'] = 'no'
-    hook = Hook('pre-rcv', repo)
-    # it's been called and failed because of lack of User id
-    assert hook(dict(master=(0, 1)))[0] == 1
-
-    repo.ui.environ['HEPTAPOD_SKIP_ALL_GITLAB_HOOKS'] = 'yes'
-    hook = Hook('pre-rcv', repo)
-    assert hook(dict(master=(0, 1))) == (0, '', '')
diff --git a/heptapod/gitlab/tests/test_hooks_integration.py b/heptapod/gitlab/tests/test_hooks_integration.py
new file mode 100644
index 0000000000000000000000000000000000000000..32ea9bcd47568019561e70ee2927520a69416a11_aGVwdGFwb2QvZ2l0bGFiL3Rlc3RzL3Rlc3RfaG9va3NfaW50ZWdyYXRpb24ucHk=
--- /dev/null
+++ b/heptapod/gitlab/tests/test_hooks_integration.py
@@ -0,0 +1,359 @@
+# Copyright 2019-2020 Georges Racinet <georges.racinet@octobus.net>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+"""Highest level tests for GitLab hook support.
+
+These tests instantiate `Hook` and thus need a real repository.
+Some use the API like in the hg-git wrapper, and thus need to monkey patch
+a request recorder in.
+"""
+import io
+import json
+import pytest
+import requests
+
+from heptapod.testhelpers import (
+    LocalRepoWrapper,
+    )
+from .. import (
+    Hook,
+    )
+
+ZERO_SHA = "0" * 20
+
+
+def make_repo(base_dir, name):
+    secret = base_dir.join('secret')
+    secret.write("TEHTOKEN")
+    wrapper = LocalRepoWrapper.init(
+        base_dir.join(name),
+        config=dict(heptapod={'gitlab-shell': str(base_dir),
+                              'gitlab-internal-api-secret-file': str(secret),
+                              }),
+    )
+    # by default, ui.environ IS os.environ (shared) but that isn't true
+    # in WSGI contexts. In that case, it is a plain dict, the WSGI environment
+    # indeed. For these tests, we need them to be independent.
+    wrapper.repo.ui.environ = {}
+    return wrapper
+
+
+def request_recorder(records, responses):
+    """Record sent requests and treat each from the given responses.
+
+    :param responses: a list of :class:`request.Response` instances or
+                      :class:`Exception` instances. In the latter case,
+                      the exception is raised instead of returning the
+                      response.
+    :param records: list to which the recorder appends the request that is
+                    being sent.
+    """
+    def request(hook, meth, subpath, params, **kwargs):
+        records.append((meth, subpath, params, kwargs))
+        resp = responses.pop(0)
+        if isinstance(resp, Exception):
+            raise resp
+        return resp
+
+    return request
+
+
+def make_response(status_code, body):
+    """Create a :class:`request.Response` instance.
+
+    :param body: must be `bytes`.
+    """
+    resp = requests.models.Response()
+    resp.status_code = status_code
+    resp.raw = io.BytesIO(body)
+    return resp
+
+
+def make_json_response(obj, status_code=200):
+    return make_response(status_code, json.dumps(obj).encode())
+
+
+def test_post_receive_params(tmpdir):
+    wrapper = make_repo(tmpdir, 'proj.hg')
+    repo = wrapper.repo
+    hook = Hook('post-receive', repo)
+
+    repo.ui.environ.update(
+        HEPTAPOD_USERINFO_ID='23',
+        HEPTAPOD_PROJECT_ID='1024',
+    )
+    changes = {
+        'branch/newbr': (ZERO_SHA, "beef1234dead" + "a" * 8),
+        'topic/default/zz': ("23cafe45" + "0" * 12, "1234" + "b" * 16),
+    }
+
+    params = hook.post_receive_params(changes)
+    assert params['identifier'] == 'user-23'
+    assert params['changes'].splitlines() == [
+        '23cafe45000000000000 1234bbbbbbbbbbbbbbbb topic/default/zz',
+        '00000000000000000000 beef1234deadaaaaaaaa branch/newbr']
+    assert params['gl_repository'] == 'project-1024'
+
+
+def test_pre_receive_params(tmpdir):
+    wrapper = make_repo(tmpdir, 'proj.hg')
+    repo = wrapper.repo
+    hook = Hook('post-receive', repo)
+
+    repo.ui.environ.update(
+        HEPTAPOD_USERINFO_ID='2',
+        HEPTAPOD_PROJECT_ID='3145926',
+    )
+    changes = {
+        'topic/default/with-a-dash': (ZERO_SHA, "beef1234dead" + "a" * 8),
+        'branch/default': ("23cafe45" + "0" * 12, "1234" + "b" * 16),
+    }
+    params = hook.pre_receive_params(changes)
+
+    assert params['user_id'] == '2'
+    assert params['changes'].splitlines() == [
+        '23cafe45000000000000 1234bbbbbbbbbbbbbbbb branch/default',
+        '00000000000000000000 beef1234deadaaaaaaaa topic/default/with-a-dash']
+    assert params['gl_repository'] == 'project-3145926'
+    assert params['project'] == repo.root
+
+
+def test_post_receive_handle_response(tmpdir):
+    wrapper = make_repo(tmpdir, 'proj.hg')
+    repo = wrapper.repo
+    hook = Hook('post-receive', repo)
+
+    out = hook.post_receive_handle_response(
+        # example taken from the functional tests
+        {u'broadcast_message': None,
+         u'reference_counter_decreased': True,
+         u'merge_request_urls': [
+             {u'url': u'http://localhost:3000/root/'
+              'test_project_1583330760_9216309/merge_requests/1',
+              u'branch_name': u'topic/default/antelope',
+              u'new_merge_request': False}
+         ]})
+    out = [l.strip() for l in out.splitlines() if l.strip()]
+    assert out == [
+        'View merge request for topic/default/antelope:',
+        'http://localhost:3000/root/test_project_1583330760_9216309/'
+        'merge_requests/1']
+
+    broadcast = "Check out the new tutorial"
+    out = hook.post_receive_handle_response(
+        {u'broadcast_message': broadcast,
+         u'reference_counter_decreased': True,
+         })
+    out = (l.strip() for l in out.splitlines() if l.strip())
+    assert any(broadcast in l for l in out)
+
+    project_created = "Congrats!"
+    out = hook.post_receive_handle_response(
+        {u'project_created_message': project_created,
+         u'reference_counter_decreased': True,
+         })
+    out = (l.strip() for l in out.splitlines() if l.strip())
+    assert any(project_created in l for l in out)
+
+    # GitLab internal API gives warnings as a unique string
+    # see lib/api/helpers/internal_helpers.rb
+    warnings = "You probably shouldn't have"
+    out = hook.post_receive_handle_response(
+        {u'warnings': warnings,
+         u'reference_counter_decreased': True,
+         })
+    out = (l.strip() for l in out.splitlines() if l.strip())
+    assert any(warnings in l for l in out)
+
+
+def test_integration_post_receive(tmpdir, monkeypatch):
+    records = []
+    responses = [make_json_response(
+        {u'broadcast_message': None,
+         u'reference_counter_decreased': True,
+         u'merge_request_urls': [
+             {u'url': u'http://localhost:3000/root/'
+              'test_project_1583330760_9216309/merge_requests/1',
+              u'branch_name': u'topic/default/antelope',
+              u'new_merge_request': False}
+         ]}),
+                 requests.exceptions.Timeout("1 ns is too much ;-)"),
+                 ]
+
+    monkeypatch.setattr(Hook, 'gitlab_internal_api_request',
+                        request_recorder(records, responses))
+
+    repo_wrapper = make_repo(tmpdir, 'repo.hg')
+    repo = repo_wrapper.repo
+
+    repo.ui.environ.update(
+        HEPTAPOD_USERINFO_ID='23',
+        HEPTAPOD_PROJECT_ID='1024',
+    )
+
+    hook = Hook('post-receive', repo_wrapper.repo)
+    changes = {
+        'branch/newbr': (ZERO_SHA, "beef1234dead" + "a" * 8),
+        'topic/default/zz': ("23cafe45" + "0" * 12, "1234" + "b" * 16),
+    }
+
+    return_code, out, err = hook(changes)
+    assert return_code == 0
+    out = [l.strip() for l in out.splitlines() if l.strip()]
+    assert out == [
+        'View merge request for topic/default/antelope:',
+        'http://localhost:3000/root/test_project_1583330760_9216309/'
+        'merge_requests/1']
+
+    return_code, out, err = hook(changes)
+    assert return_code == 1
+    assert err == 'GitLab is unreachable'
+
+
+def test_integration_pre_receive(tmpdir, monkeypatch):
+    records = []
+    responses = [make_json_response({u'message': "yes, granted!",
+                                     u'status': True,
+                                     },
+                                    status_code=200),
+                 make_json_response({u'status': True},
+                                    status_code=200),
+                 make_json_response({u'message': "go away!",
+                                     u'status': False,
+                                     },
+                                    status_code=401),
+                 make_response(504, b"Bad gateway :-("),
+                 requests.exceptions.ConnectionError("Failed"),
+                 ]
+    monkeypatch.setattr(Hook, 'gitlab_internal_api_request',
+                        request_recorder(records, responses))
+
+    repo_wrapper = make_repo(tmpdir, 'repo.hg')
+    repo = repo_wrapper.repo
+
+    repo.ui.environ.update(
+        HEPTAPOD_USERINFO_ID='23',
+        HEPTAPOD_PROJECT_ID='1024',
+    )
+
+    hook = Hook('pre-receive', repo_wrapper.repo)
+    changes = {
+        'branch/newbr': (ZERO_SHA, "beef1234dead" + "a" * 8),
+        'topic/default/zz': ("23cafe45" + "0" * 12, "1234" + "b" * 16),
+    }
+
+    return_code, out, err = hook(changes)
+    assert return_code == 0
+    assert out == 'yes, granted!'
+
+    return_code, out, err = hook(changes)
+    assert return_code == 0
+    assert out == ''
+
+    return_code, out, err = hook(changes)
+    assert return_code == 1
+    assert err == 'go away!'
+
+    return_code, out, err = hook(changes)
+    assert return_code == 1
+    assert err == 'API is not accessible'
+
+    return_code, out, err = hook(changes)
+    assert return_code == 1
+    assert err == 'GitLab is unreachable'
+
+
+def test_empty_changes(tmpdir):
+    hook = Hook('post-receive', make_repo(tmpdir, 'repo.hg').repo)
+    code, out, err = hook(())
+    assert code == 0
+    assert not out
+    assert not err
+
+
+def test_unknown_hook(tmpdir):
+    # that's a typo, the most likely way for it to happen
+    name = 'post-recieve'
+
+    repo = make_repo(tmpdir, 'repo.hg').repo
+    hook = Hook(name, repo)
+    changes = {
+        'branch/newbr': (ZERO_SHA, "beef1234dead" + "a" * 8),
+        'topic/default/zz': ("23cafe45" + "0" * 12, "1234" + "b" * 16),
+    }
+
+    repo.ui.environ.update(
+        HEPTAPOD_USERINFO_ID='23',
+        HEPTAPOD_PROJECT_ID='1024',
+    )
+    with pytest.raises(NotImplementedError) as exc_info:
+        hook(changes)
+    assert exc_info.value.args[0] == name
+
+
+def test_missing_secret(tmpdir):
+    repo = LocalRepoWrapper.init(tmpdir).repo
+    shell = tmpdir.join('heptapod-shell').ensure(dir=True)
+    repo.ui.setconfig(b'heptapod', b'gitlab-shell', str(shell).encode())
+    with pytest.raises(RuntimeError) as exc_info:
+        Hook('post-receive', repo)
+    assert "secret-file" in exc_info.value.args[0]
+
+    # defaulting to gitlab-shell secret if available
+    shell.join('.gitlab_shell_secret').write('myseekret\n')
+    hook = Hook('post-receive', repo)
+    assert hook.gitlab_secret == 'myseekret'
+
+
+def test_hook_missing_user(tmpdir):
+    wrapper = make_repo(tmpdir, 'proj2.hg')
+    repo = wrapper.repo
+    hook = Hook('post-something', repo)
+
+    repo.ui.environ['HEPTAPOD_PROJECT_ID'] = '1024'
+    with pytest.raises(ValueError) as exc_info:
+        hook.environ()
+    assert 'User id' in exc_info.value.args[0]
+
+    # and that gets catched, does not break the process
+    code, out, err = hook(dict(master=(0, 1)))
+    assert code != 0
+
+
+def test_hook_missing_project(tmpdir):
+    wrapper = make_repo(tmpdir, 'proj2.hg')
+    repo = wrapper.repo
+    hook = Hook('post-something', repo)
+
+    repo.ui.environ['HEPTAPOD_USERINFO_ID'] = '3'
+    with pytest.raises(ValueError) as exc_info:
+        hook.environ()
+    assert 'Project id' in exc_info.value.args[0]
+
+    # and that gets catched, does not break the process
+    code, out, err = hook(dict(master=(0, 1)))
+    assert code != 0
+
+
+def test_hook_skip(tmpdir):
+    wrapper = make_repo(tmpdir, 'proj2.hg')
+    repo = wrapper.repo
+    repo.ui.environ['HEPTAPOD_SKIP_GITLAB_HOOK'] = 'pre-rcv'
+    hook = Hook('pre-rcv', repo)
+    assert hook(dict(master=(0, 1))) == (0, '', '')
+
+
+def test_hook_skip_all(tmpdir):
+    wrapper = make_repo(tmpdir, 'proj2.hg')
+    repo = wrapper.repo
+    repo.ui.environ['HEPTAPOD_SKIP_ALL_GITLAB_HOOKS'] = 'no'
+    hook = Hook('pre-rcv', repo)
+    # it's been called and failed because of lack of User id
+    assert hook(dict(master=(0, 1)))[0] == 1
+
+    repo.ui.environ['HEPTAPOD_SKIP_ALL_GITLAB_HOOKS'] = 'yes'
+    hook = Hook('pre-rcv', repo)
+    assert hook(dict(master=(0, 1))) == (0, '', '')