diff --git a/.docker-base/Dockerfile b/.docker-base/Dockerfile
index 592922471aa83d5175b1fbc1d355be8b547966a1..3476e1193312f57e86778cd51422e7fc954e6e18 100644
--- a/.docker-base/Dockerfile
+++ b/.docker-base/Dockerfile
@@ -18,8 +18,3 @@ COPY wait-for-it.sh /opt/caosdb/wait-for-it.sh
 WORKDIR /opt/caosdb
 RUN mkdir -p /opt/caosdb/build_docker/
 CMD /bin/bash
-
-# python client
-ADD https://gitlab.com/api/v4/projects/13656973/repository/branches/dev \
-	  pylib_version.json
-RUN pip3 install git+https://gitlab.com/caosdb/caosdb-pylib 
diff --git a/.docker/Dockerfile b/.docker/Dockerfile
index 3ab23751df44a9df91e3dccabfbd2904aa795ecb..174d42e4c49132927b98184c12955013f2ac13ab 100644
--- a/.docker/Dockerfile
+++ b/.docker/Dockerfile
@@ -1,20 +1,32 @@
 FROM debian:latest
 RUN apt-get update && \
-	 apt-get install \
-	 curl \
-	 git \
-	 openjdk-11-jdk-headless \
-	 python-autopep8 \
-	 python3-pip \
-	 tox \
-	 -y
+    apt-get install \
+    curl \
+    git \
+    openjdk-11-jdk-headless \
+    python-autopep8 \
+    python3-pip \
+    tox \
+    -y
 COPY .docker/wait-for-it.sh /wait-for-it.sh
 ARG PYLIB=dev
 ADD https://gitlab.com/api/v4/projects/13656973/repository/commits/${PYLIB} \
-	pylib_version.json
+    pylib_version.json
 RUN git clone https://gitlab.com/caosdb/caosdb-pylib.git && \
-   cd caosdb-pylib && git checkout ${PYLIB} && pip3 install .
+    cd caosdb-pylib && git checkout ${PYLIB} && pip3 install .
 COPY . /git
-RUN rm -r /git/.git && mv /git/.docker/pycaosdb.ini /git 
+
+# Delete .git because it is huge.
+RUN rm -r /git/.git
+
+# Install pycaosdb.ini for the tests
+RUN mv /git/.docker/tester_pycaosdb.ini /git/pycaosdb.ini
+
+
 WORKDIR /git
-CMD /wait-for-it.sh caosdb-server:10443 -t 500 -- tox
+# wait for server,
+CMD /wait-for-it.sh caosdb-server:10443 -t 500 -- \
+    # ... install pycaosdb.ini the server-side scripts
+    cp /git/.docker/sss_pycaosdb.ini /scripting/home/.pycaosdb.ini && \
+    # ... and run tests
+    tox
diff --git a/.docker/docker-compose.yml b/.docker/docker-compose.yml
index 1d0cd73a6453dfede236f857b8f560adac5f67ff..efaa0b27ddc0827cb5db9e01552de53eaaf81246 100644
--- a/.docker/docker-compose.yml
+++ b/.docker/docker-compose.yml
@@ -22,16 +22,21 @@ services:
         target: /opt/caosdb/mnt/extroot
       - type: volume
         source: scripting
-        target: /opt/caosdb/git/caosdb-server/scripting/bin
+        target: /opt/caosdb/git/caosdb-server/scripting
+      - type: volume
+        source: authtoken
+        target: /opt/caosdb/git/caosdb-server/authtoken
     ports:
       # - "from_outside:from_inside"
       - "10443:10443"
       - "10080:10080"
     environment:
       DEBUG: 1
+      CAOSDB_CONFIG_AUTHTOKEN_CONFIG: "conf/core/authtoken.example.yaml"
 volumes:
   scripting:
   extroot:
+  authtoken:
 networks:
   caosnet:
     driver: bridge
diff --git a/.docker/pycaosdb.ini b/.docker/pycaosdb.ini
deleted file mode 100644
index 29846d6f976db29f8bc05d29240a2b4a18fa6ec5..0000000000000000000000000000000000000000
--- a/.docker/pycaosdb.ini
+++ /dev/null
@@ -1,23 +0,0 @@
-[IntegrationTests]
-test_server_side_scripting.bin_dir=/scripting-bin/
-
-# location of the files from the pyinttest perspective
-test_files.test_insert_files_in_dir.local=/extroot/test_insert_files_in_dir/
-# location of the files from the caosdb_servers perspective
-test_files.test_insert_files_in_dir.server=/opt/caosdb/mnt/extroot/test_insert_files_in_dir/
-
-
-[Connection]
-url=https://caosdb-server:10443/
-username=admin
-cacert=/cert/caosdb.cert.pem
-#cacert=/etc/ssl/cert.pem
-debug=0
-
-passwordmethod=plain
-password=caosdb
-
-ssl_insecure=True
-timeout=500
-[Container]
-debug=0
diff --git a/.docker/run.sh b/.docker/run.sh
index 00c7618f87295fbe67c7c485a9204b46adb1a438..b0e1a716f28516b83043fb3fdb6594515a0bafd4 100755
--- a/.docker/run.sh
+++ b/.docker/run.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
 
-docker-compose -f tester.yml run tester 
+docker-compose -f tester.yml run tester
 rv=$?
 echo $rv > result
diff --git a/.docker/sss_pycaosdb.ini b/.docker/sss_pycaosdb.ini
new file mode 100644
index 0000000000000000000000000000000000000000..de2867f8dc66b3e81f10f35e40c36f9cb8591604
--- /dev/null
+++ b/.docker/sss_pycaosdb.ini
@@ -0,0 +1,9 @@
+; this is the pycaosdb.ini for the server-side-scripting home.
+[Connection]
+url = https://caosdb-server:10443
+cacert = /opt/caosdb/cert/caosdb.cert.pem
+debug = 0
+timeout = 5000
+
+[Misc]
+sendmail = /usr/local/bin/sendmail_to_file
diff --git a/.docker/tester.yml b/.docker/tester.yml
index ad192de54dbb438e7f441663dee2c70e076e9d50..83db879c6072bfdea7b3212c833116b96bb54d0c 100644
--- a/.docker/tester.yml
+++ b/.docker/tester.yml
@@ -13,10 +13,14 @@ services:
         target: /extroot
       - type: volume
         source: scripting
-        target: /scripting-bin
+        target: /scripting
+      - type: volume
+        source: authtoken
+        target: /authtoken
 networks:
   docker_caosnet:
     external: true
 volumes:
   scripting:
   extroot:
+  authtoken:
diff --git a/.docker/tester_pycaosdb.ini b/.docker/tester_pycaosdb.ini
new file mode 100644
index 0000000000000000000000000000000000000000..38247085d7c6ba681f7ed9b1553d76818109711a
--- /dev/null
+++ b/.docker/tester_pycaosdb.ini
@@ -0,0 +1,31 @@
+; pycaosdb.ini for pytest test suites.
+
+[IntegrationTests]
+; location of the scripting bin dir which is used for the test scripts from the
+; server's perspective.
+test_server_side_scripting.bin_dir.server = scripting/bin-debug/
+; location of the scripting bin dir which is used for the test scripts from the
+; pyinttest's perspective.
+test_server_side_scripting.bin_dir.local = /scripting/bin-debug/
+
+; location of the files from the pyinttest perspective
+test_files.test_insert_files_in_dir.local = /extroot/test_insert_files_in_dir/
+; location of the files from the caosdb_servers perspective
+test_files.test_insert_files_in_dir.server = /opt/caosdb/mnt/extroot/test_insert_files_in_dir/
+
+; location of the one-time tokens from the pyinttest's perspective
+test_authentication.admin_token_crud = /authtoken/admin_token_crud.txt
+test_authentication.admin_token_expired = /authtoken/admin_token_expired.txt
+test_authentication.admin_token_3_attempts = /authtoken/admin_token_3_attempts.txt
+
+
+[Connection]
+url = https://caosdb-server:10443/
+username = admin
+cacert = /cert/caosdb.cert.pem
+debug = 0
+
+passwordmethod = plain
+password = caosdb
+
+timeout = 500
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b3c51e8d8b235afe9ffa688cb1b4a6a8399e0e3c..f5adb2885b56d998e9b47fe48a4258290acbdb6f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -21,6 +21,7 @@
 variables:
    CI_REGISTRY_IMAGE: $CI_REGISTRY/caosdb/caosdb-pyinttest/testenv:latest
    CI_REGISTRY_IMAGE_BASE: $CI_REGISTRY/caosdb/caosdb-pyinttest/base:latest
+   DEPLOY_REF: dev
 
 stages:
   - setup
@@ -42,24 +43,27 @@ stages:
 # |             +-(caosdb-server)-------------------+ |
 # |             |                                   | |
 # |             | /opt/caosdb                       | |
-# |       .---->|  + /git/caosdb-server/scripting/  | |
-# |      | .--->|  + /mnt/extroot                   | |
-# |      | | .->|  + /cert                          | |
-# |      | | |  |                                   | |
-# |      | | |  +-----------------------------------+ |
-# |      | | |                                        |
-# |      | | |  filesystem:                           |
-# |      | | *-    /cert -----------.                 |
-# |      | |                        |                 |
-# |      | |    volumes:            |                 |
-# |      .  *--    extroot  ------. |                 |
-# |       *----    scripting  --. | |                 |
-# |                             | | |                 |
-# | +-(caosdb-pyinttest)---+    | | |                 |
-# | |                      |    | | |                 |
-# | | /scripting           |<---* | |                 |
-# | | /extroot             |<----*  .                 |
-# | | /cert                |<------*                  |
+# |    .------->|  + /git/caosdb-server/scripting/  | |
+# |    | .----->|  + /git/caosdb-server/authtoken/  | |
+# |    | | .--->|  + /mnt/extroot                   | |
+# |    | | | .->|  + /cert                          | |
+# |    | | | |  |                                   | |
+# |    | | | |  +-----------------------------------+ |
+# |    | | | |                                        |
+# |    | | | |    filesystem:                         |
+# |    | | |  *---  /cert -----------.                |
+# |    | | |                          |               |
+# |    | | |      volumes:            |               |
+# |    | |  *-----  extroot  ------.  |               |
+# |    |  *-------  scripting  --.  | |               |
+# |     *---------  authtoken  -. | | |               |
+# |                             | | | |               |
+# | +-(caosdb-pyinttest)---+    | | | |               |
+# | |                      |    | | | |               |
+# | | /authtoken           |<---* | | |               |
+# | | /scripting           |<----*  | |               |
+# | | /extroot             |<------*  |               |
+# | | /cert                |<--------*                |
 # | |                      |                          |
 # | +----------------------+                          |
 # +---------------------------------------------------+
@@ -69,9 +73,8 @@ stages:
 # pipeline and a certificate is created in there. The certificat is then
 # available in mounted directories in the server and pyinttest containers.
 #
-# Two additional volumes in the root docker are shared by the caosdb-server and
-# the caosdb-pyintest containers.
-# These volumes are inteded to be used for testing server-side scripting and
+# Additional volumes in the root docker are shared by the caosdb-server and the caosdb-pyintest
+# containers.  These volumes are intended to be used for testing server-side scripting and
 # file-system features.
 #
 services:
@@ -81,7 +84,9 @@ test:
   tags: [docker]
   stage: test
   image: $CI_REGISTRY_IMAGE_BASE
+  needs: ["cert"]
   script:
+      - ls /
       - echo $F_BRANCH
       - echo $CAOSDB_TAG
       - echo $CI_COMMIT_REF_NAME
@@ -92,23 +97,27 @@ test:
       - if ! echo "$F_BRANCH" | grep -c "^f-" ; then
           F_BRANCH=dev;
         fi
+
+      - echo $F_BRANCH
+      - echo $CI_COMMIT_REF_NAME
+      - echo $CI_REGISTRY_IMAGE
+
+      - docker login -u gitlab+deploy-token-ci-pull -p $TOKEN_CI_PULL $CI_REGISTRY_INDISCALE
       - if [[ "$CAOSDB_TAG" == "" ]]; then
           if echo "$F_BRANCH" | grep -c "^f-" ; then
-            CAOSDB_TAG=dev_F_${F_BRANCH}-latest;
+            CAOSDB_TAG=${DEPLOY_REF}_F_${F_BRANCH}-latest;
+            docker pull $CI_REGISTRY_INDISCALE/caosdb/src/caosdb-deploy:$CAOSDB_TAG || CAOSDB_TAG=${DEPLOY_REF}-latest ;
           else
-            CAOSDB_TAG=dev-latest;
+            CAOSDB_TAG=${DEPLOY_REF}-latest;
           fi;
         fi
-        # TODO default $CAOSDB_TAG to dev_F_$F_BRANCH-latest
-      - echo $F_BRANCH
+      - docker pull $CI_REGISTRY_INDISCALE/caosdb/src/caosdb-deploy:$CAOSDB_TAG || CAOSDB_TAG=dev-latest ;
       - echo $CAOSDB_TAG
-      - echo $CI_COMMIT_REF_NAME
 
       - time docker load < /image-cache/caosdb-pyint-testenv-${CI_COMMIT_REF_NAME}.tar || true
       - time docker load < /image-cache/mariadb-${F_BRANCH}.tar || true
       - time docker load < /image-cache/caosdb-${F_BRANCH}.tar || true
       - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
-      - docker login -u gitlab+deploy-token-ci-pull -p $TOKEN_CI_PULL $CI_REGISTRY_INDISCALE
       - docker pull $CI_REGISTRY_IMAGE
       - cd .docker
         # here the server and the mysql backend docker are being started
@@ -134,6 +143,7 @@ build-testenv:
   tags: [cached-dind]
   image: docker:19.03
   stage: setup
+  needs: []
   script:
       - df -h
       - command -v wget
@@ -165,6 +175,7 @@ cert:
   tags: [docker]
   stage: cert
   image: $CI_REGISTRY_IMAGE
+  needs: ["build-testenv"]
   artifacts:
     paths:
       - .docker/cert/
@@ -175,6 +186,7 @@ cert:
 style:
   tags: [docker]
   stage: style
+  needs: ["build-testenv"]
   image: $CI_REGISTRY_IMAGE
   script:
       - autopep8 -r --diff --exit-code .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5919bd25f0a537915c9b1ebba301f3e754f0f7d6..684d597db3cf505eb7da728414213107081b49c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added (for new features)
 
-- Tests for [#62](https://gitlab.com/caosdb/caosdb-server/-/issues/62)
+* Tests for One-time Authentication Tokens
+* Test for [caosdb-pylib#31](https://gitlab.com/caosdb/caosdb-pylib/-/issues/31)
+* Tests for [caosdb-server#62](https://gitlab.com/caosdb/caosdb-server/-/issues/62)
   in caosdb-server-project, i.e., renaming of a RecordType that should
   be reflected in properties with that RT as datatype.
 
@@ -20,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed (for any bug fixes)
 
-- Tests for NaN Double Values (see https://gitlab.com/caosdb/caosdb-server/issues/41)
+* Tests for NaN Double Values (see https://gitlab.com/caosdb/caosdb-server/issues/41)
 * Tests for name queries. [caosdb-server#51](https://gitlab.com/caosdb/caosdb-server/-/issues/51)
 
 ### Security (in case of vulnerabilities)
diff --git a/resources/ok_anonymous b/resources/ok_anonymous
new file mode 100755
index 0000000000000000000000000000000000000000..fdc5d87618d9c6b229febd4e25ec7d7ec03347cb
--- /dev/null
+++ b/resources/ok_anonymous
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo "ok_anonymous"
diff --git a/resources/simple_script.py b/resources/simple_script.py
deleted file mode 100755
index 71bd9c05b4e86133cc356e1c15359701642a9486..0000000000000000000000000000000000000000
--- a/resources/simple_script.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# ** header v3.0
-# This file is a part of the CaosDB Project.
-#
-# Copyright (C) 2018 Research Group Biomedical Physics,
-# Max-Planck-Institute for Dynamics and Self-Organization Göttingen
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <https://www.gnu.org/licenses/>.
-#
-# ** end header
-#
-"""server_side_script.py.
-
-An example which implements a minimal server-side script.
-
-1) This script expects to find a *.txt file in the .upload_files dir which is
-printed to stdout.
-
-2) It executes a "Count stars" query and prints the result to stdout.
-
-3) It will return with code 0 if everything is ok, or with any code that is
-specified with the commandline option --exit
-"""
-
-import sys
-from os import listdir
-from caosdb import configure_connection, execute_query
-
-
-# parse --auth-token option and configure connection
-CODE = 0
-QUERY = "COUNT stars"
-for arg in sys.argv:
-    if arg.startswith("--auth-token="):
-        auth_token = arg[13:]
-        configure_connection(auth_token=auth_token)
-    if arg.startswith("--exit="):
-        CODE = int(arg[7:])
-    if arg.startswith("--query="):
-        QUERY = arg[8:]
-
-
-############################################################
-# 1 # find and print *.txt file ############################
-############################################################
-
-try:
-    for fname in listdir(".upload_files"):
-        if fname.endswith(".txt"):
-            with open(".upload_files/{}".format(fname)) as f:
-                print(f.read())
-except FileNotFoundError:
-    pass
-
-
-############################################################
-# 2 # query "COUNT stars" ##################################
-############################################################
-
-RESULT = execute_query(QUERY)
-print(RESULT)
-
-############################################################
-# 3 ########################################################
-############################################################
-
-sys.exit(CODE)
diff --git a/tests/test_administration.py b/tests/test_administration.py
index 50aa906d00bf13f13d5a5569a8042013e65138e0..768214e05a10845be405d014294e6af69471ea48 100644
--- a/tests/test_administration.py
+++ b/tests/test_administration.py
@@ -27,7 +27,7 @@
 """
 
 from caosdb import (administration as admin, get_config)
-from nose.tools import assert_true, assert_equal, assert_is_not_none, with_setup, assert_raises
+from nose.tools import assert_true, assert_equal, assert_is_not_none, assert_raises
 from caosdb.exceptions import (ClientErrorException, TransactionError,
                                AuthorizationException, LoginFailedException)
 from caosdb.connection.connection import get_connection, configure_connection
@@ -96,12 +96,10 @@ def test_set_server_property():
     assert admin.get_server_property("AUTH_OPTIONAL") == "FALSE"
 
 
-@with_setup(setup, teardown)
 def test_insert_role_success():
-    assert_true(admin._insert_role(name=test_role, description=test_role_desc))
+    assert admin._insert_role(name=test_role, description=test_role_desc)
 
 
-@with_setup(setup, teardown)
 def test_insert_role_failure_permission():
     switch_to_normal_user()
     with assert_raises(AuthorizationException) as cm:
@@ -111,7 +109,6 @@ def test_insert_role_failure_permission():
         "You are not permitted to insert a new role.")
 
 
-@with_setup(setup, teardown)
 def test_insert_role_failure_name_duplicates():
     test_insert_role_success()
     with assert_raises(ClientErrorException) as cm:
@@ -121,7 +118,6 @@ def test_insert_role_failure_name_duplicates():
         "Role name is already in use. Choose a different name.")
 
 
-@with_setup(setup, teardown)
 def test_update_role_success():
     test_insert_role_success()
     assert_is_not_none(
@@ -131,7 +127,6 @@ def test_update_role_success():
             "asdf"))
 
 
-@with_setup(setup, teardown)
 def test_update_role_failure_permissions():
     test_insert_role_success()
     switch_to_normal_user()
@@ -142,20 +137,17 @@ def test_update_role_failure_permissions():
         "You are not permitted to update this role.")
 
 
-@with_setup(setup, teardown)
 def test_update_role_failure_non_existing():
     with assert_raises(TransactionError) as cm:
         admin._update_role(name=test_role, description=test_role_desc + "asdf")
     assert_equal(cm.exception.msg, "Role does not exist.")
 
 
-@with_setup(setup, teardown)
 def test_delete_role_success():
     test_insert_role_success()
     assert_true(admin._delete_role(name=test_role))
 
 
-@with_setup(setup, teardown)
 def test_delete_role_failure_permissions():
     test_insert_role_success()
     switch_to_normal_user()
@@ -166,21 +158,18 @@ def test_delete_role_failure_permissions():
         "You are not permitted to delete this role.")
 
 
-@with_setup(setup, teardown)
 def test_delete_role_failure_non_existing():
     with assert_raises(TransactionError) as cm:
         admin._delete_role(name=test_role)
     assert_equal(cm.exception.msg, "Role does not exist.")
 
 
-@with_setup(setup, teardown)
 def test_retrieve_role_success():
     test_insert_role_success()
     r = admin._retrieve_role(test_role)
     assert_is_not_none(r)
 
 
-@with_setup(setup, teardown)
 def test_retrieve_role_failure_permission():
     test_insert_role_success()
     switch_to_normal_user()
@@ -191,14 +180,12 @@ def test_retrieve_role_failure_permission():
         "You are not permitted to retrieve this role.")
 
 
-@with_setup(setup, teardown)
 def test_retrieve_role_failure_non_existing():
     with assert_raises(TransactionError) as cm:
         admin._retrieve_role(name=test_role)
     assert_equal(cm.exception.msg, "Role does not exist.")
 
 
-@with_setup(setup, teardown)
 def test_set_permissions_success():
     test_insert_role_success()
     assert_true(
@@ -210,7 +197,6 @@ def test_set_permissions_success():
                     "BLA:BLA:BLA")]))
 
 
-@with_setup(setup, teardown)
 def test_set_permissions_failure_permissions():
     test_insert_role_success()
     switch_to_normal_user()
@@ -224,7 +210,6 @@ def test_set_permissions_failure_permissions():
         "You are not permitted to set this role's permissions.")
 
 
-@with_setup(setup, teardown)
 def test_set_permissions_failure_non_existing():
     with assert_raises(TransactionError) as cm:
         admin._set_permissions(
@@ -234,7 +219,6 @@ def test_set_permissions_failure_non_existing():
     assert_equal(cm.exception.msg, "Role does not exist.")
 
 
-@with_setup(setup, teardown)
 def test_get_permissions_success():
     test_set_permissions_success()
     r = admin._get_permissions(role=test_role)
@@ -242,7 +226,6 @@ def test_get_permissions_success():
     assert_is_not_none(r)
 
 
-@with_setup(setup, teardown)
 def test_get_permissions_failure_permissions():
     test_set_permissions_success()
     switch_to_normal_user()
@@ -253,14 +236,12 @@ def test_get_permissions_failure_permissions():
         "You are not permitted to retrieve this role's permissions.")
 
 
-@with_setup(setup, teardown)
 def test_get_permissions_failure_non_existing():
     with assert_raises(TransactionError) as cm:
         admin._get_permissions(role="non-existing-role")
     assert_equal(cm.exception.msg, "Role does not exist.")
 
 
-@with_setup(setup, teardown)
 def test_get_roles_success():
     test_insert_role_success()
     r = admin._get_roles(username=test_user)
@@ -268,7 +249,6 @@ def test_get_roles_success():
     return r
 
 
-@with_setup(setup, teardown)
 def test_get_roles_failure_permissions():
     test_insert_role_success()
     switch_to_normal_user()
@@ -279,14 +259,12 @@ def test_get_roles_failure_permissions():
         "You are not permitted to retrieve this user's roles.")
 
 
-@with_setup(setup, teardown)
 def test_get_roles_failure_non_existing():
     with assert_raises(TransactionError) as cm:
         admin._get_roles(username="non-existing-user")
     assert_equal(cm.exception.msg, "User does not exist.")
 
 
-@with_setup(setup, teardown)
 def test_set_roles_success():
     roles_old = test_get_roles_success()
     roles = {test_role}
@@ -296,7 +274,6 @@ def test_set_roles_success():
     assert_is_not_none(admin._set_roles(username=test_user, roles=roles_old))
 
 
-@with_setup(setup, teardown)
 def test_set_roles_success_with_warning():
     test_insert_role_success()
     roles = {test_role}
@@ -305,7 +282,6 @@ def test_set_roles_success_with_warning():
     assert_is_not_none(admin._set_roles(username=test_user, roles=[]))
 
 
-@with_setup(setup, teardown)
 def test_set_roles_failure_permissions():
     roles_old = test_get_roles_success()
     roles = {test_role}
@@ -333,7 +309,6 @@ def test_set_roles_failure_non_existing_user():
     assert_equal(cm.exception.msg, "User does not exist.")
 
 
-@with_setup(setup, teardown)
 def test_insert_user_success():
     admin._insert_user(
         name=test_user + "2",
@@ -343,7 +318,6 @@ def test_insert_user_success():
         entity=None)
 
 
-@with_setup(setup, teardown)
 def test_insert_user_failure_permissions():
     switch_to_normal_user()
     with assert_raises(AuthorizationException) as cm:
@@ -358,7 +332,6 @@ def test_insert_user_failure_permissions():
         "You are not permitted to insert a new user.")
 
 
-@with_setup(setup, teardown)
 def test_insert_user_failure_name_in_use():
     test_insert_user_success()
     with assert_raises(ClientErrorException) as cm:
@@ -366,13 +339,11 @@ def test_insert_user_failure_name_in_use():
     assert_equal(cm.exception.msg, "User name is already in use.")
 
 
-@with_setup(setup, teardown)
 def test_delete_user_success():
     test_insert_user_success()
     assert_is_not_none(admin._delete_user(name=test_user + "2"))
 
 
-@with_setup(setup, teardown)
 def test_delete_user_failure_permissions():
     test_insert_user_success()
     switch_to_normal_user()
@@ -383,14 +354,12 @@ def test_delete_user_failure_permissions():
         "You are not permitted to delete this user.")
 
 
-@with_setup(setup, teardown)
 def test_delete_user_failure_non_existing():
     with assert_raises(TransactionError) as cm:
         admin._delete_user(name="non_existing_user")
     assert_equal(cm.exception.msg, "User does not exist.")
 
 
-@with_setup(setup, teardown)
 def test_update_user_success_status():
     assert_is_not_none(
         admin._insert_user(
@@ -408,7 +377,6 @@ def test_update_user_success_status():
         entity=None)
 
 
-@with_setup(setup, teardown)
 def test_update_user_success_email():
     assert_is_not_none(
         admin._insert_user(
@@ -426,7 +394,6 @@ def test_update_user_success_email():
         entity=None)
 
 
-@with_setup(setup, teardown)
 def test_update_user_success_entity():
     assert_is_not_none(
         admin._insert_user(
@@ -439,7 +406,6 @@ def test_update_user_success_entity():
                        status=None, email=None, entity="21")
 
 
-@with_setup(setup, teardown)
 def test_update_user_success_password():
     assert_is_not_none(
         admin._insert_user(
@@ -457,7 +423,6 @@ def test_update_user_success_password():
         entity=None)
 
 
-@with_setup(setup, teardown)
 def test_update_user_failure_permissions_status():
     assert_is_not_none(
         admin._insert_user(
@@ -480,7 +445,6 @@ def test_update_user_failure_permissions_status():
         "You are not permitted to update this user.")
 
 
-@with_setup(setup, teardown)
 def test_update_user_failure_permissions_email():
     assert_is_not_none(
         admin._insert_user(
@@ -503,7 +467,6 @@ def test_update_user_failure_permissions_email():
         "You are not permitted to update this user.")
 
 
-@with_setup(setup, teardown)
 def test_update_user_failure_permissions_entity():
     assert_is_not_none(
         admin._insert_user(
@@ -526,7 +489,6 @@ def test_update_user_failure_permissions_entity():
         "You are not permitted to update this user.")
 
 
-@with_setup(setup, teardown)
 def test_update_user_failure_permissions_password():
     assert_is_not_none(
         admin._insert_user(
@@ -549,7 +511,6 @@ def test_update_user_failure_permissions_password():
         "You are not permitted to update this user.")
 
 
-@with_setup(setup, teardown)
 def test_update_user_failure_non_existing_user():
     with assert_raises(TransactionError) as cm:
         admin._update_user(
@@ -562,7 +523,6 @@ def test_update_user_failure_non_existing_user():
     assert_equal(cm.exception.msg, "User does not exist.")
 
 
-@with_setup(setup, teardown)
 def test_update_user_failure_non_existing_entity():
     assert_is_not_none(
         admin._insert_user(
@@ -582,13 +542,11 @@ def test_update_user_failure_non_existing_entity():
     assert_equal(cm.exception.msg, "Entity does not exist.")
 
 
-@with_setup(setup, teardown)
 def test_retrieve_user_success():
     test_insert_user_success()
     assert_is_not_none(admin._retrieve_user(realm=None, name=test_user + "2"))
 
 
-@with_setup(setup, teardown)
 def test_retrieve_user_failure_permissions():
     test_insert_user_success()
     switch_to_normal_user()
@@ -599,14 +557,12 @@ def test_retrieve_user_failure_permissions():
         "You are not permitted to retrieve this user.")
 
 
-@with_setup(setup, teardown)
 def test_retrieve_user_failure_non_existing():
     with assert_raises(TransactionError) as cm:
         admin._retrieve_user(realm=None, name="non_existing")
     assert_equal(cm.exception.msg, "User does not exist.")
 
 
-@with_setup(setup, teardown)
 def test_login_with_inactive_user_failure():
     assert_is_not_none(
         admin._insert_user(
diff --git a/tests/test_affiliation.py b/tests/test_affiliation.py
index 813c6ffb03b8031b0a42139efd6e8edfac61806a..0b9f011d04708f68dd21e9059dac6bc23a5a159d 100644
--- a/tests/test_affiliation.py
+++ b/tests/test_affiliation.py
@@ -48,10 +48,9 @@ file_path = "testfile.dat"
 
 
 def setup():
-    try:
-        db.execute_query("FIND Test*").delete()
-    except Exception as e:
-        print(e)
+    d = db.execute_query("FIND ENTITY WITH ID > 99")
+    if len(d) > 0:
+        d.delete()
     db.RecordType(name=recty_name).insert()
     db.Record(name=rec_name).add_parent(name=recty_name).insert()
     db.File(name=file_name, file=file_path, path="testfile.dat").insert()
diff --git a/tests/test_authentication.py b/tests/test_authentication.py
index dc862c4e1c358376a039a8a50d0863fca2d5a112..ce2a095bab5f6b2d70afbea1a2a96af624c71389 100644
--- a/tests/test_authentication.py
+++ b/tests/test_authentication.py
@@ -5,6 +5,8 @@
 #
 # Copyright (C) 2018 Research Group Biomedical Physics,
 # Max-Planck-Institute for Dynamics and Self-Organization Göttingen
+# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+# Copyright (C) 2020 Timm Fitschen <t.fitschen@indiscale.com>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
@@ -21,40 +23,45 @@
 #
 # ** end header
 #
-"""Created on 20.01.2015.
-
-@author: tf
-"""
 
 import os
+import time
 from sys import hexversion
 from urllib.parse import urlparse
 from http.client import HTTPSConnection
 import ssl
-from subprocess import call, check_output
+from subprocess import call
 from lxml import etree
-from pytest import skip
+from pytest import raises, mark
 from caosdb.exceptions import LoginFailedException
-import caosdb as h
-from nose.tools import (assert_false, assert_true, assert_is_none,
-                        assert_raises, assert_equal, assert_is_not_none,
-                        nottest, with_setup)
-from caosdb.connection.connection import _Connection
+import caosdb as db
+from .test_server_side_scripting import request
+
+
+_USED_OTA_TOKEN = set()
 
 
 def setup():
-    try:
-        h.execute_query("FIND Test*").delete()
-    except Exception as e:
-        print(e)
+    db.configure_connection()
+
+    # deactivate anonymous user
+    db.administration.set_server_property("AUTH_OPTIONAL", "FALSE")
+    d = db.execute_query("FIND Test*")
+    if len(d) > 0:
+        d.delete()
+
+
+def teardown():
+    setup()
 
 
+@mark.skipif(
+    not db.get_config().has_option("Connection", "password_method")
+    or not db.get_config().get("Connection", "password_method") == "pass",
+    reason="password_method is not pass")
 def test_pass():
-    if not h.get_config().has_option("Connection", "password_method") or not h.get_config(
-    ).get("Connection", "password_method") == "pass":
-        skip()
-    assert call(["pass", h.get_config().get("Connection",
-                                            "password_identifier")]) == 0
+    assert call(["pass", db.get_config().get("Connection",
+                                             "password_identifier")]) == 0
 
 
 def test_https_support():
@@ -65,9 +72,9 @@ def test_https_support():
     context.verify_mode = ssl.CERT_REQUIRED
     if hasattr(context, "check_hostname"):
         context.check_hostname = True
-    context.load_verify_locations(h.get_config().get("Connection", "cacert"))
+    context.load_verify_locations(db.get_config().get("Connection", "cacert"))
 
-    url = h.get_config().get("Connection", "url")
+    url = db.get_config().get("Connection", "url")
     fullurl = urlparse(url)
 
     http_con = HTTPSConnection(
@@ -79,27 +86,35 @@ def test_https_support():
 
 
 def test_login_via_post_form_data_failure():
-    with assert_raises(LoginFailedException) as cm:
-        h.get_connection().post_form_data(
+    with raises(LoginFailedException):
+        db.get_connection().post_form_data(
             "login", {
-                "username": h.get_config().get("Connection", "username"),
+                "username": db.get_config().get("Connection", "username"),
                 "password": "wrongpassphrase"
             })
 
 
+def test_anonymous_not_returning_auth_token():
+    # activate anonymous user
+    db.administration.set_server_property("AUTH_OPTIONAL", "TRUE")
+
+    response = request(method="GET", headers={}, path="Entity")
+    assert response.getheader("Set-Cookie") is None  # no auth token returned
+
+
 def test_anonymous_setter():
-    """ this test verifies that the "test_login_while_anonymous_is_active" is
+    """This test verifies that the "test_login_while_anonymous_is_active" is
     effective."""
 
     # activate anonymous user
-    h.administration.set_server_property("AUTH_OPTIONAL", "TRUE")
+    db.administration.set_server_property("AUTH_OPTIONAL", "TRUE")
 
     # connect without auth-token
     context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
     context.verify_mode = ssl.CERT_REQUIRED
-    context.load_verify_locations(h.get_config().get("Connection", "cacert"))
+    context.load_verify_locations(db.get_config().get("Connection", "cacert"))
 
-    url = h.get_config().get("Connection", "url")
+    url = db.get_config().get("Connection", "url")
     fullurl = urlparse(url)
 
     http_con = HTTPSConnection(
@@ -113,15 +128,14 @@ def test_anonymous_setter():
     assert xml.xpath("/Response/UserInfo/Roles/Role")[0].text == "anonymous"
 
 
-@with_setup(setup, setup)
 def test_login_while_anonymous_is_active():
     # activate anonymous user
-    h.administration.set_server_property("AUTH_OPTIONAL", "TRUE")
+    db.administration.set_server_property("AUTH_OPTIONAL", "TRUE")
 
     # logout
-    h.get_connection()._logout()
+    db.get_connection()._logout()
 
-    body = h.get_connection().retrieve(
+    body = db.get_connection().retrieve(
         entity_uri_segments=["Entity"],
         reconnect=True).read()
     xml = etree.fromstring(body)
@@ -129,3 +143,176 @@ def test_login_while_anonymous_is_active():
     # pylib did the login even though the anonymous user is active
     assert xml.xpath(
         "/Response/UserInfo/Roles/Role")[0].text == "administration"
+
+
+def test_authtoken_config():
+    assert db.administration.get_server_property(
+        "AUTHTOKEN_CONFIG") == "conf/core/authtoken.example.yaml"
+
+
+def get_one_time_token(testcase):
+    username = "anonymous"
+    realm = "OneTimeAuthenticationToken"
+    roles = ["administration"]
+    permissions = []
+    filename = db.get_config().get("IntegrationTests",
+                                   "test_authentication.{}".format(testcase))
+    assert os.path.isdir(os.path.split(filename)[0])
+    assert os.path.isfile(filename)
+    with open(filename, "r") as f:
+        auth_token = f.read()
+    while auth_token in _USED_OTA_TOKEN:
+        # wait until the server has renewed the token
+        time.sleep(1)
+        with open(filename, "r") as f:
+            auth_token = f.read()
+    else:
+        _USED_OTA_TOKEN.add(auth_token)
+
+    assert auth_token.startswith('["O","{re}","{us}",["{rol}"],{perm},'.format(
+        re=realm, us=username, rol=",".join(roles), perm=permissions))
+    return auth_token
+
+
+def test_one_time_token():
+    assert db.Info().user_info.roles == ["administration"]
+    assert db.Info().user_info.name == db.get_config().get("Connection", "username")
+
+    auth_token = get_one_time_token("admin_token_crud")
+    db.configure_connection(password_method="auth_token",
+                            auth_token=auth_token)
+
+    assert db.Info().user_info.roles == ["administration"]
+    assert db.Info().user_info.name == "anonymous"
+    assert db.Info().user_info.realm == "OneTimeAuthenticationToken"
+
+    db.configure_connection()
+
+    assert db.Info().user_info.roles == ["administration"]
+    assert db.Info().user_info.name == db.get_config().get("Connection",
+                                                           "username")
+
+
+def test_one_time_token_invalid():
+    auth_token = get_one_time_token("admin_token_crud")
+    auth_token = auth_token.replace("[]", '["permission"]')
+
+    db.configure_connection(password_method="auth_token",
+                            auth_token=auth_token)
+    with raises(db.LoginFailedException) as lfe:
+        db.Info()
+    assert lfe.value.args[0] == (
+        "The authentication token is expired or you have been logged out otherwise. The auth_token "
+        "authenticator cannot log in again. You must provide a new authentication token.")
+
+    # also raises exception when anonymous is enabled
+    db.configure_connection()
+    db.administration.set_server_property("AUTH_OPTIONAL", "TRUE")
+    db.configure_connection(password_method="auth_token",
+                            auth_token=auth_token)
+    with raises(db.LoginFailedException) as lfe:
+        db.Info()
+    assert lfe.value.args[0] == (
+        "The authentication token is expired or you have been logged out otherwise. The auth_token "
+        "authenticator cannot log in again. You must provide a new authentication token.")
+
+
+def test_one_time_token_expired():
+    auth_token = get_one_time_token("admin_token_expired")
+    db.configure_connection(password_method="auth_token",
+                            auth_token=auth_token)
+    with raises(db.LoginFailedException) as lfe:
+        db.Info()
+    assert lfe.value.args[0] == (
+        "The authentication token is expired or you have been logged out otherwise. The auth_token "
+        "authenticator cannot log in again. You must provide a new authentication token.")
+
+    # also raises exception when anonymous is enabled
+    db.configure_connection()
+    db.administration.set_server_property("AUTH_OPTIONAL", "TRUE")
+    db.configure_connection(password_method="auth_token",
+                            auth_token=auth_token)
+    with raises(db.LoginFailedException) as lfe:
+        db.Info()
+    assert lfe.value.args[0] == (
+        "The authentication token is expired or you have been logged out otherwise. The auth_token "
+        "authenticator cannot log in again. You must provide a new authentication token.")
+
+
+def test_one_time_token_3_attempts():
+    auth_token = get_one_time_token("admin_token_3_attempts")
+
+    # 1st
+    db.configure_connection(password_method="auth_token",
+                            auth_token=auth_token)
+    assert db.get_connection()._authenticator.auth_token == auth_token
+    assert db.Info().user_info.roles == ["administration"]
+    assert db.get_connection()._authenticator.auth_token != auth_token
+    assert db.Info().user_info.name == "anonymous"
+    assert db.Info().user_info.realm == "OneTimeAuthenticationToken"
+
+    # 2nd
+    db.configure_connection(password_method="auth_token",
+                            auth_token=auth_token)
+    assert db.get_connection()._authenticator.auth_token == auth_token
+    assert db.Info().user_info.roles == ["administration"]
+    assert db.get_connection()._authenticator.auth_token != auth_token
+    assert db.Info().user_info.name == "anonymous"
+    assert db.Info().user_info.realm == "OneTimeAuthenticationToken"
+
+    # 3rd
+    db.configure_connection(password_method="auth_token",
+                            auth_token=auth_token)
+    assert db.get_connection()._authenticator.auth_token == auth_token
+    assert db.Info().user_info.roles == ["administration"]
+    assert db.get_connection()._authenticator.auth_token != auth_token
+    assert db.Info().user_info.name == "anonymous"
+    assert db.Info().user_info.realm == "OneTimeAuthenticationToken"
+
+    # 4th
+    db.configure_connection(password_method="auth_token",
+                            auth_token=auth_token)
+    assert db.get_connection()._authenticator.auth_token == auth_token
+    with raises(db.LoginFailedException) as lfe:
+        db.Info()
+    assert lfe.value.args[0] == (
+        "The authentication token is expired or you have been logged out otherwise. The auth_token "
+        "authenticator cannot log in again. You must provide a new authentication token.")
+
+    db.configure_connection()
+    db.administration.set_server_property("AUTH_OPTIONAL", "TRUE")
+
+    # 5th attempt, also raises error when anonymous user is enabled
+    db.configure_connection(password_method="auth_token",
+                            auth_token=auth_token)
+    assert db.get_connection()._authenticator.auth_token == auth_token
+    with raises(db.LoginFailedException) as lfe:
+        db.Info()
+    assert lfe.value.args[0] == (
+        "The authentication token is expired or you have been logged out otherwise. The auth_token "
+        "authenticator cannot log in again. You must provide a new authentication token.")
+
+
+def test_crud_with_one_time_token():
+    auth_token = get_one_time_token("admin_token_crud")
+    db.configure_connection(password_method="auth_token",
+                            auth_token=auth_token)
+
+    # CREATE
+    rt = db.RecordType(name="TestRT")
+    rt.insert()
+    assert rt.id == db.execute_query("FIND TestRT", unique=True).id
+
+    # UPDATE
+    assert db.execute_query("FIND TestRT", unique=True).description is None
+    rt.description = "new desc"
+    rt.update()
+    assert rt.description == db.execute_query(
+        "FIND TestRT", unique=True).description
+
+    # RETRIEVE
+    rt.retrieve()
+    assert rt.id == db.execute_query("FIND TestRT", unique=True).id
+
+    rt.delete()
+    assert len(db.execute_query("FIND TestRT")) == 0
diff --git a/tests/test_properties.py b/tests/test_properties.py
new file mode 100644
index 0000000000000000000000000000000000000000..9219501f09a5ee5eff4b89a78f17f8e02b842151
--- /dev/null
+++ b/tests/test_properties.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+#
+# ** header v3.0
+# This file is a part of the CaosDB Project.
+#
+# Copyright (c) 2020 IndiScale GmbH <www.indiscale.com>
+# Copyright (c) 2020 Timm Fitschen <t.fitschen@indiscale.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# ** end header
+import pytest
+
+import caosdb as db
+from caosdb.common.utils import xml2str
+
+
+def setup_module():
+    d = db.execute_query("FIND Test*")
+    if len(d) > 0:
+        d.delete()
+
+
+def setup():
+    setup_module()
+
+
+def teardown():
+    setup_module()
+
+
+@pytest.mark.xfail(reason="""see pylib issue #31""")
+def test_add_properties_with_wrong_role():
+    p = db.Property(name="TestProperty1", datatype=db.TEXT).insert()
+    rt = db.RecordType(name="TestRT1").add_property("TestProperty1").insert()
+
+    wrong_role = db.Record(name="TestRT1").retrieve()
+    right_role = db.RecordType(name="TestRT1").retrieve()
+    rec = db.Record(name="fail").add_property(wrong_role)
+    rec2 = db.Record(name="ok").add_property(right_role)
+
+    assert wrong_role.get_property("TestProperty1") is not None
+    assert str(wrong_role.get_property("TestProperty1")) == str(
+        right_role.get_property("TestProperty1"))
+
+    xml = rec2.to_xml()
+    assert not xml.xpath("/Record/Property/Property")
+
+    xml = rec.to_xml()
+    # TODO this fails because the Record is treated differently than the
+    # RecordType.
+    assert not xml.xpath("/Record/Property/Property")
diff --git a/tests/test_server_side_scripting.py b/tests/test_server_side_scripting.py
index 672f50a4c39378a05460e6f93f50568fb6df3e05..45bb6f7bf1e4075080454ad292fd74da7f601cf7 100644
--- a/tests/test_server_side_scripting.py
+++ b/tests/test_server_side_scripting.py
@@ -26,33 +26,66 @@
 Integration tests for the implementation of the server-side-scripting api.
 """
 from __future__ import print_function, unicode_literals
-from pytest import raises, mark
+import os
+from pytest import raises
+import json
 from lxml import etree
-from caosdb import get_connection, get_config
+from http.client import HTTPSConnection
+import ssl
+from caosdb import get_connection, get_config, Info, execute_query, RecordType
 from caosdb.exceptions import (EntityDoesNotExistError, ClientErrorException)
 from caosdb.connection.encode import MultipartParam, multipart_encode
+from caosdb.connection.utils import urlencode, urlparse
 from caosdb import administration as admin
+from caosdb.utils.server_side_scripting import run_server_side_script
 
-_TEST_SCRIPTS = ["not_executable", "ok", "err", "simple_script.py"]
-_SERVER_SIDE_SCRIPTING_BIN_DIR = get_config().get(
+_TEST_SCRIPTS = ["not_executable", "ok", "err", "ok_anonymous"]
+_SERVER_SIDE_SCRIPTING_BIN_DIR_LOCAL = get_config().get(
     "IntegrationTests",
-    "test_server_side_scripting.bin_dir")
+    "test_server_side_scripting.bin_dir.local")
+_SERVER_SIDE_SCRIPTING_BIN_DIR_SERVER = get_config().get(
+    "IntegrationTests",
+    "test_server_side_scripting.bin_dir.server")
 _TEST_SCRIPTS_DIR = "./resources/"
 _REMOVE_FILES_AFTERWARDS = []
+_ORIGINAL_SERVER_SCRIPTING_BIN_DIR = ""
+
+
+def clean_database():
+    admin._set_permissions("anonymous", [])
+    d = execute_query("FIND ENTITY WITH ID > 99")
+    if len(d) > 0:
+        d.delete()
+
+
+def setup():
+    clean_database()
+
+
+def teardown():
+    admin.set_server_property("SERVER_SIDE_SCRIPTING_BIN_DIR",
+                              _ORIGINAL_SERVER_SCRIPTING_BIN_DIR)
+    clean_database()
 
 
 def setup_module():
+    global _ORIGINAL_SERVER_SCRIPTING_BIN_DIR
+    _ORIGINAL_SERVER_SCRIPTING_BIN_DIR = admin.get_server_property(
+        "SERVER_SIDE_SCRIPTING_BIN_DIR")
+    clean_database()
+
     from os import makedirs
     from os.path import join, isdir, exists
     from shutil import copyfile, copymode
-    print("bin: " + str(_SERVER_SIDE_SCRIPTING_BIN_DIR))
+    print("bin dir (local): " + str(_SERVER_SIDE_SCRIPTING_BIN_DIR_LOCAL))
+    print("bin dir (server): " + str(_SERVER_SIDE_SCRIPTING_BIN_DIR_SERVER))
     print("tests scripts: " + str(_TEST_SCRIPTS))
-    if not exists(_SERVER_SIDE_SCRIPTING_BIN_DIR):
-        makedirs(_SERVER_SIDE_SCRIPTING_BIN_DIR)
-        _REMOVE_FILES_AFTERWARDS.append(_SERVER_SIDE_SCRIPTING_BIN_DIR)
-    assert isdir(_SERVER_SIDE_SCRIPTING_BIN_DIR)
+    if not exists(_SERVER_SIDE_SCRIPTING_BIN_DIR_LOCAL):
+        makedirs(_SERVER_SIDE_SCRIPTING_BIN_DIR_LOCAL)
+        _REMOVE_FILES_AFTERWARDS.append(_SERVER_SIDE_SCRIPTING_BIN_DIR_LOCAL)
+    assert isdir(_SERVER_SIDE_SCRIPTING_BIN_DIR_LOCAL)
     for script_file in _TEST_SCRIPTS:
-        target = join(_SERVER_SIDE_SCRIPTING_BIN_DIR, script_file)
+        target = join(_SERVER_SIDE_SCRIPTING_BIN_DIR_LOCAL, script_file)
         src = join(_TEST_SCRIPTS_DIR, script_file)
         copyfile(src, target)
         copymode(src, target)
@@ -69,6 +102,7 @@ def teardown_module():
                 rmtree(obsolete)
             else:
                 remove(obsolete)
+    clean_database()
 
 
 def test_call_script_non_existing():
@@ -79,15 +113,8 @@ def test_call_script_non_existing():
 
 
 def test_call_script_not_executable():
-    props = admin.get_server_properties()
-    print(props)
-
-    import os
-    dirpath = os.getcwd()
-    print("PWD={}".format(dirpath))
-    print("ls ./\n{}".format(os.listdir("./")))
-    print("ls ../\n{}".format(os.listdir("../")))
-
+    admin.set_server_property("SERVER_SIDE_SCRIPTING_BIN_DIR",
+                              _SERVER_SIDE_SCRIPTING_BIN_DIR_SERVER)
     form = dict()
     form["call"] = "not_executable"
     with raises(ClientErrorException) as exc_info:
@@ -96,6 +123,8 @@ def test_call_script_not_executable():
 
 
 def test_call_ok():
+    admin.set_server_property("SERVER_SIDE_SCRIPTING_BIN_DIR",
+                              _SERVER_SIDE_SCRIPTING_BIN_DIR_SERVER)
     form = dict()
     form["call"] = "ok"
     r = get_connection().post_form_data("scripting", form)
@@ -107,6 +136,8 @@ def test_call_ok():
 
 
 def test_call_err():
+    admin.set_server_property("SERVER_SIDE_SCRIPTING_BIN_DIR",
+                              _SERVER_SIDE_SCRIPTING_BIN_DIR_SERVER)
     form = dict()
     form["call"] = "err"
     r = get_connection().post_form_data("scripting", form)
@@ -117,29 +148,234 @@ def test_call_err():
     assert xml.xpath("/Response/script/stderr")[0].text == "err"
 
 
-@mark.skip(reason="need to setup .pycaosdb.ini in home dirs of sss within docker")
-def test_simple_sss():
+def test_run_server_side_script_with_file_as_positional_param():
+    RecordType("TestRT").insert()
+    _REMOVE_FILES_AFTERWARDS.append("test_file.txt")
+    with open("test_file.txt", "w") as f:
+        f.write("this is a test")
+
+    response = run_server_side_script("administration/diagnostics.py",
+                                      "pos0",
+                                      "pos1",
+                                      exit="123",
+                                      query="COUNT TestRT",
+                                      files={"-p2": "test_file.txt"})
+    assert response.stderr is None
+    assert response.code == 123
+    assert response.call == ('administration/diagnostics.py '
+                             '--exit=123 --query=COUNT TestRT '
+                             'pos0 pos1 .upload_files/test_file.txt')
+
+    json_data = json.loads(response.stdout)
+    assert "caosdb" in json_data
+    assert "query" in json_data["caosdb"]
+    assert json_data["caosdb"]["query"] == ["COUNT TestRT", "1"]
+    assert "./.upload_files/test_file.txt" in json_data["files"]
+
+
+def test_run_server_side_script_with_additional_file():
+    RecordType("TestRT").insert()
+    _REMOVE_FILES_AFTERWARDS.append("test_file.txt")
+    with open("test_file.txt", "w") as f:
+        f.write("this is a test")
+
+    response = run_server_side_script("administration/diagnostics.py",
+                                      "pos0",
+                                      "pos1",
+                                      exit="123",
+                                      query="COUNT TestRT",
+                                      files={"dummykey": "test_file.txt"})
+    assert response.stderr is None
+    assert response.code == 123
+    assert response.call == ('administration/diagnostics.py '
+                             '--exit=123 --query=COUNT TestRT '
+                             'pos0 pos1')
+
+    json_data = json.loads(response.stdout)
+    assert json_data["caosdb"]["query"] == ["COUNT TestRT", "1"]
+    assert "./.upload_files/test_file.txt" in json_data["files"]
+
+
+def test_diagnostics_basic():
+    RecordType("TestRT").insert()
+
     form = dict()
-    form["call"] = "simple_script.py"
+    form["call"] = "administration/diagnostics.py"
     form["-Oexit"] = "123"
-    r = get_connection().post_form_data("scripting", form)
-    xml = etree.parse(r)
+    form["-Oquery"] = "COUNT TestRT"
+
+    response = get_connection().post_form_data("scripting", form)
+    xml = etree.parse(response)
     print(etree.tostring(xml))
-    assert xml.xpath("/Response/script/@code")[0] == "123"
 
+    assert response.status == 200  # ok
+    assert response.getheader("Content-Type") == 'text/xml; charset=UTF-8'
+
+    diagnostics = xml.xpath("/Response/script/stdout")[0].text
+    assert diagnostics is not None
+    diagnostics = json.loads(diagnostics)
+    assert diagnostics["python_version"] is not None
+    assert diagnostics["import"]["caosdb"][0] is True, ("caosdb not installed"
+                                                        " in server's python path")
+    assert diagnostics["auth_token"] is not None
+    EXC_ERR = ("There shouldn't be any exception during the diagnostics of "
+               "the interaction of the server-side script and the server.")
+    assert "exception" not in diagnostics["caosdb"], EXC_ERR
+
+    assert "query" in diagnostics["caosdb"]
+    assert diagnostics["caosdb"]["query"][0] == "COUNT TestRT"
+    assert diagnostics["caosdb"]["query"][1] == "1", ("The RecordType should "
+                                                      "have been found.")
+    assert xml.xpath("/Response/script/@code")[0] == "123", ("The script "
+                                                             "should exit "
+                                                             "with code 123.")
+    assert xml.xpath("/Response/script/call")[0].text.startswith(
+        "administration/diagnostics.py")
+    assert xml.xpath("/Response/script/stderr")[0].text is None
+
+
+def test_diagnostics_with_file_upload():
+    RecordType("TestRT").insert()
     _REMOVE_FILES_AFTERWARDS.append("test_file.txt")
     with open("test_file.txt", "w") as f:
         f.write("this is a test")
+
     parts = []
     parts.append(MultipartParam.from_file(paramname="txt_file",
                                           filename="test_file.txt"))
-    parts.append(MultipartParam("call", "simple_script.py"))
+    parts.append(MultipartParam("call", "administration/diagnostics.py"))
     body, headers = multipart_encode(parts)
-    r = get_connection().insert(["scripting"], body=body, headers=headers)
-    xml = etree.parse(r)
-    print(etree.tostring(xml).decode("utf-8"))
+    response = get_connection().insert(
+        ["scripting"], body=body, headers=headers)
+    assert response.status == 200  # ok
+    assert response.getheader("Content-Type") == 'text/xml; charset=UTF-8'
+
+    xml = etree.parse(response)
+    print(etree.tostring(xml))
     assert xml.xpath("/Response/script/@code")[0] == "0"
-    assert xml.xpath(
-        "/Response/script/call")[0].text.startswith("simple_script.py")
-    assert "this is a test" in xml.xpath("/Response/script/stdout")[0].text
+
+    diagnostics = xml.xpath("/Response/script/stdout")[0].text
+    assert diagnostics is not None
+    diagnostics = json.loads(diagnostics)
+    assert diagnostics["python_version"] is not None
+    assert diagnostics["import"]["caosdb"][0] is True, ("caosdb not installed"
+                                                        " in server's python path")
+    assert diagnostics["auth_token"] is not None
+    assert "exception" not in diagnostics["caosdb"], ("There shouldn't be any "
+                                                      "exception during the "
+                                                      "diagnostics of the "
+                                                      "interaction of the "
+                                                      "server-side "
+                                                      "script and the server.")
+
+    assert xml.xpath("/Response/script/@code")[0] == "0", ("The script "
+                                                           "should exit "
+                                                           "with code 0.")
+    assert xml.xpath("/Response/script/call")[0].text.startswith(
+        "administration/diagnostics.py")
+    assert xml.xpath("/Response/script/stderr")[0].text is None
+
+
+def test_call_as_anonymous_with_administration_role():
+    assert Info().user_info.roles == ["administration"]
+
+    # activate anonymous user
+    admin.set_server_property("AUTH_OPTIONAL", "TRUE")
+    # give permission to call diagnostics.py
+    admin._set_permissions(
+        "anonymous", [
+            admin.PermissionRule(
+                "Grant", "SCRIPTING:EXECUTE:administration:diagnostics.py")])
+
+    form = dict()
+    form["call"] = "administration/diagnostics.py"
+
+    headers = {"Content-Type": "application/x-www-form-urlencoded"}
+    response = request(method="POST", headers=headers, path="scripting",
+                       body=urlencode(form))
+
+    xml = etree.parse(response)
+
+    assert response.getheader("Set-Cookie") is None  # no auth token returned
+    assert response.getheader("Content-Type") == 'text/xml; charset=UTF-8'
+
+    assert response.status == 200  # ok
+    assert xml.xpath("/Response/script/@code")[0] == "0"
+    diagnostics = xml.xpath("/Response/script/stdout")[0].text
+    assert diagnostics is not None
+    diagnostics = json.loads(diagnostics)
+    assert diagnostics["python_version"] is not None
+    assert diagnostics["import"]["caosdb"][0] is True, ("caosdb not installed"
+                                                        " in server's python path")
+    assert diagnostics["auth_token"] is not None
+    assert "exception" not in diagnostics["caosdb"]
+
+
+def request(method, headers, path, body=None):
+    """ Connect without auth-token. This is clumsy, bc the pylib is not
+    intended to be used as anonymous user.
+    """
+    context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
+    context.verify_mode = ssl.CERT_REQUIRED
+    if hasattr(context, "check_hostname"):
+        context.check_hostname = True
+    context.load_verify_locations(get_config().get("Connection", "cacert"))
+
+    url = get_config().get("Connection", "url")
+    fullurl = urlparse(url)
+
+    http_con = HTTPSConnection(
+        str(fullurl.netloc), timeout=200, context=context)
+    http_con.request(method=method, headers=headers, url=str(fullurl.path) +
+                     path, body=body)
+    return http_con.getresponse()
+
+
+def test_anonymous_script_calling_not_permitted():
+    form = dict()
+    form["call"] = "ok"
+
+    # activate anonymous user
+    admin.set_server_property("AUTH_OPTIONAL", "TRUE")
+
+    # remove all anonymous permissions
+    admin._set_permissions("anonymous", [
+        admin.PermissionRule("Grant", "SCRIPTING:EXECUTE:ok"),
+        admin.PermissionRule("Deny", "*")
+    ])
+
+    headers = {"Content-Type": "application/x-www-form-urlencoded"}
+    response = request(method="POST", headers=headers, path="scripting",
+                       body=urlencode(form))
+    assert response.status == 403  # forbidden
+    assert response.getheader("Set-Cookie") is None  # no auth token returned
+
+
+def test_anonymous_script_calling_success():
+    admin.set_server_property("SERVER_SIDE_SCRIPTING_BIN_DIR",
+                              _SERVER_SIDE_SCRIPTING_BIN_DIR_SERVER)
+    form = dict()
+    form["call"] = "ok_anonymous"
+
+    # activate anonymous user
+    admin.set_server_property("AUTH_OPTIONAL", "TRUE")
+    # give permission to call ok_anonymous
+    admin._set_permissions("anonymous",
+                           [admin.PermissionRule("Grant", "SCRIPTING:EXECUTE:ok_anonymous")])
+
+    headers = {"Content-Type": "application/x-www-form-urlencoded"}
+    response = request(method="POST", headers=headers, path="scripting",
+                       body=urlencode(form))
+    assert response.status == 200  # ok
+    assert response.getheader("Set-Cookie") is None  # no auth token returned
+    assert response.getheader("Content-Type") == 'text/xml; charset=UTF-8'
+
+    body = response.read()
+    xml = etree.fromstring(body)
+
+    # verify unauthenticated call to script ok_anonymous
+    assert xml.xpath("/Response/UserInfo/Roles/Role")[0].text == "anonymous"
+    assert xml.xpath("/Response/script/call")[0].text == "ok_anonymous"
+    assert xml.xpath("/Response/script/stdout")[0].text == "ok_anonymous"
     assert xml.xpath("/Response/script/stderr")[0].text is None
+    assert xml.xpath("/Response/script/@code")[0] == "0"