diff --git a/src/tests/Feature/Controller/NGINXTest.php b/src/tests/Feature/Controller/NGINXTest.php
--- a/src/tests/Feature/Controller/NGINXTest.php
+++ b/src/tests/Feature/Controller/NGINXTest.php
@@ -314,6 +314,32 @@
      */
     public function testCyrusSaslHook(): void
     {
-        $this->markTestIncomplete();
+        $pass = \App\Utils::generatePassphrase();
+
+        // Pass
+        $response = $this->postWithBody("api/webhooks/cyrus-sasl", "john kolab.org $pass");
+        $response->assertStatus(200);
+
+        // Pass without realm
+        $response = $this->postWithBody("api/webhooks/cyrus-sasl", "john@kolab.org  $pass");
+        $response->assertStatus(200);
+
+        // Invalid password
+        $response = $this->postWithBody("api/webhooks/cyrus-sasl", "john kolab.org fail");
+        $response->assertStatus(403);
+
+        $this->getTestUser('cyrus-admin');
+
+        // service-account Pass
+        $response = $this->postWithBody("api/webhooks/cyrus-sasl", "cyrus-admin  $pass");
+        $response->assertStatus(200);
+
+        // service-account fail
+        $response = $this->postWithBody("api/webhooks/cyrus-sasl", "cyrus-admin  fail");
+        $response->assertStatus(403);
+
+        // unknown user fail
+        $response = $this->postWithBody("api/webhooks/cyrus-sasl", "missing@kolab.org  $pass");
+        $response->assertStatus(403);
     }
 }
diff --git a/src/tests/TestCase.php b/src/tests/TestCase.php
--- a/src/tests/TestCase.php
+++ b/src/tests/TestCase.php
@@ -105,4 +105,15 @@
         \config(['app.url' => str_replace('//', '//services.', \config('app.url'))]);
         url()->forceRootUrl(config('app.url'));
     }
+
+
+    /**
+     * The test equivalent of Http::withBody, which is not available for tests.
+     *
+     * Required to test request handlers that use Request::getContent
+     */
+    protected function postWithBody($url, $content)
+    {
+        return $this->call('POST', $url, [], [], [], [], $content);
+    }
 }