diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f437abb0578ca69685857826a8b34343c0a0b89c..04e832402292530aae089e76a52bb83d87392591 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,9 +1,9 @@
 #
 # This file is a part of the CaosDB Project.
 #
-# Copyright (C) 2019-2021 Indiscale GmbH <info@indiscale.com>
+# Copyright (C) 2019-2022 Indiscale GmbH <info@indiscale.com>
 # Copyright (C) 2019 Henrik tom Wörden <h.tomwoerden@indiscale.com>
-# Copyright (C) 2020 Daniel Hornung <d.hornung@indiscale.com>
+# Copyright (C) 2020-2022 Daniel Hornung <d.hornung@indiscale.com>
 # Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
 #
 # This program is free software: you can redistribute it and/or modify
@@ -23,7 +23,6 @@
 variables:
   CPPLIB_REGISTRY_IMAGE: $CI_REGISTRY/caosdb/src/caosdb-cpplib/testenv:$CI_COMMIT_REF_NAME
 
-  CPPINTTEST_PIPELINE: https://gitlab.indiscale.com/api/v4/projects/111/trigger/pipeline
   CPPINTTEST_BRANCHES: https://gitlab.indiscale.com/api/v4/projects/111/repository/branches
   GIT_SUBMODULE_STRATEGY: normal
 
@@ -32,6 +31,9 @@ variables:
   TRIGGERED_BY_REF: $CI_COMMIT_REF_NAME
   TRIGGERED_BY_HASH: $CI_COMMIT_SHORT_SHA
 
+  # For storing cross-job environment variables
+  DOTENV: custom.env
+
 
 image: $CPPLIB_REGISTRY_IMAGE
 
@@ -92,12 +94,11 @@ test:
     - cmake --build . -j --target cxxcaosdbcli
     # - cmake --build . -j --target ccaosdbcli  # Disabled until it compiles again.
 
-# trigger the integration tests
-trigger_inttest:
+trigger_prepare:
+  # Calculate the branch name
   tags: [ docker ]
   stage: deploy
   script:
-
     ## Determine the cppinttest branch...
     # ... use an f-branch if posible...
     - F_BRANCH=dev
@@ -122,18 +123,36 @@ trigger_inttest:
     # ... and fall-back to dev
     - CPPINT_REF=${CPPINT_REF:-dev}
 
-    - echo "Triggering caosdb-cppinttest@${CPPINT_REF} (F_BRANCH=$F_BRANCH)"
-    - curl -w "%{stderr}HTTPCODE=%{http_code}" -X POST
-      -F token=$CI_JOB_TOKEN
-      -F "variables[TRIGGERED_BY_REPO]=$TRIGGERED_BY_REPO"
-      -F "variables[TRIGGERED_BY_REF]=$TRIGGERED_BY_REF"
-      -F "variables[TRIGGERED_BY_HASH]=$TRIGGERED_BY_HASH"
-      -F "variables[CPPLIB_REGISTRY_IMAGE]=$CPPLIB_REGISTRY_IMAGE"
-      -F "variables[F_BRANCH]=${F_BRANCH}"
-      -F ref=${CPPINT_REF} $CPPINTTEST_PIPELINE 2>HTTPCODE
-
-    # fail if the request failed
-    - grep -c "HTTPCODE=2" HTTPCODE
+    # Write to dotenv
+    - echo "CPPINT_REF=${CPPINT_REF}" >> "$DOTENV"
+    - echo "F_BRANCH=${F_BRANCH}" >> "$DOTENV"
+    - cat "$DOTENV"
+  artifacts:
+    # this special artifact will be loaded in the next job and make the environment variables available
+    reports:
+      dotenv: "$DOTENV"
+    expire_in: 1 day
+
+
+# trigger the integration tests
+trigger_inttest:
+  stage: deploy
+  needs: [ trigger_prepare ]
+  inherit:
+    variables:
+      # List the variables that shall be inherited, which also means they will override any equally
+      # named varibles in child pipelines.
+      - TRIGGERED_BY_REPO
+      - TRIGGERED_BY_REF
+      - TRIGGERED_BY_HASH
+      - CPPLIB_REGISTRY_IMAGE
+  variables:
+    # dotenv variables must be set again here.
+    F_BRANCH: $F_BRANCH
+  trigger:
+    project: caosdb/src/caosdb-cppinttest
+    branch: $CPPINT_REF
+    strategy: depend
 
 # Build the sphinx documentation and make it ready for deployment by Gitlab Pages
 # Special job for serving a static website. See https://docs.gitlab.com/ee/ci/yaml/README.html#pages
diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md
index adbf07c79763f4af236ac90eff74e44669ec7f02..be4e73f31904decf166a523c229b58a0ca47af10 100644
--- a/doc/CHANGELOG.md
+++ b/doc/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added
 
+* Simple `User` class and methods for user creation/retrieval/deletion.
+
 ### Changed
 
 * Transaction::ExecuteAsynchronously is actually asynchronous now.
diff --git a/include/caosdb/acm/user.h b/include/caosdb/acm/user.h
index 2310bd2195186cf1237d59593b26369d01f2baa7..9c1ae91ec9451c7d7a33b4b209646676fa2d7eb0 100644
--- a/include/caosdb/acm/user.h
+++ b/include/caosdb/acm/user.h
@@ -40,30 +40,96 @@ class Connection;
 
 namespace caosdb::acm {
 
+/**
+ * The UserImpl class is the delegate of the User class. It hides the
+ * implementation details from the clients.
+ */
 class UserImpl;
 
+/**
+ * The User class is a delegator. The actual data is stored in a wrapped
+ * UserImpl object.
+ */
 class User {
 public:
+  /**
+   * Default constructor.
+   *
+   * Initialize a user without any name, realm, password...
+   */
   User();
+  /**
+   * Constructor. Initialize a user in the given realm with the given name.
+   */
   explicit User(std::string realm, std::string name);
+  /**
+   * Constructor. Initialize a user with the given name.
+   */
   explicit User(std::string name);
-  explicit User(std::unique_ptr<UserImpl> wrapped);
+  /**
+   * Copy constructor.
+   */
   User(const User &user);
+  /**
+   * Move constructor.
+   *
+   * The moved-from user is empty, but still usable.
+   */
   User(User &&user) noexcept;
+  /**
+   * Copy assignment.
+   */
   auto operator=(const User &user) -> User &;
+  /**
+   * Move assignment.
+   *
+   * The moved-from user is empty, but still usable.
+   */
   auto operator=(User &&user) noexcept -> User &;
+  /**
+   * Dtor.
+   */
   ~User();
+
+  /**
+   * Return a string representation of this user.
+   */
   auto ToString() const -> std::string;
+  /**
+   * Return the name of this user or the empty string.
+   */
   [[nodiscard]] auto GetName() const -> const std::string &;
+  /**
+   * Set the name of this user.
+   */
   auto SetName(const std::string &name) -> void;
+  /**
+   * Return the realm of this user or the empty.
+   */
   [[nodiscard]] auto GetRealm() const -> const std::string &;
+  /**
+   * Set the realm of this user.
+   */
   auto SetRealm(const std::string &realm) -> void;
+  /**
+   * Return the password of this user or the empty string.
+   */
   [[nodiscard]] auto GetPassword() const -> const std::string &;
+  /**
+   * Set the password of this user.
+   */
   auto SetPassword(const std::string &password) -> void;
 
   friend class caosdb::connection::Connection;
 
 private:
+  /**
+   * Constructor. Create a user object from the given UserImpl delegate.
+   */
+  explicit User(std::unique_ptr<UserImpl> wrapped);
+  /**
+   * The UserImpl delegate.
+   */
   std::unique_ptr<UserImpl> wrapped;
 };
 
diff --git a/src/caosdb/unary_rpc_handler.cpp b/src/caosdb/unary_rpc_handler.cpp
index d6f970663a6a8f92a5afe4ae402e73b886a3fe0e..06afc127cecea22b1b805d20a68eab177841e981 100644
--- a/src/caosdb/unary_rpc_handler.cpp
+++ b/src/caosdb/unary_rpc_handler.cpp
@@ -100,8 +100,8 @@ bool UnaryRpcHandler::OnNext(bool ok) {
 
 void UnaryRpcHandler::Cancel() {
   state_ = CallState::CallComplete;
-  call_context.TryCancel();
   transaction_status = TransactionStatus::CANCELLED();
+  call_context.TryCancel();
 }
 
 void UnaryRpcHandler::handleCallCompleteState() {
diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp
index 125b0626c367e7312b69aa9ff997ccec66803a7b..e710b6c775615968b567b6efa64ba76e56717c69 100644
--- a/test/test_transaction.cpp
+++ b/test/test_transaction.cpp
@@ -264,8 +264,8 @@ TEST(test_transaction, test_multiple_execute) {
   EXPECT_EQ(transaction->GetStatus().GetCode(), StatusCode::GO_ON);
 
   EXPECT_EQ(transaction->ExecuteAsynchronously(), StatusCode::EXECUTING);
-  // EXPECT_EQ(transaction->ExecuteAsynchronously(), StatusCode::TRANSACTION_STATUS_ERROR);
-  // EXPECT_EQ(transaction->ExecuteAsynchronously(), StatusCode::TRANSACTION_STATUS_ERROR);
+  EXPECT_EQ(transaction->ExecuteAsynchronously(), StatusCode::TRANSACTION_STATUS_ERROR);
+  EXPECT_EQ(transaction->ExecuteAsynchronously(), StatusCode::TRANSACTION_STATUS_ERROR);
   transaction->Cancel();
 }