From 7bb9c11cc045acce4f463f0abc25823d009d9554 Mon Sep 17 00:00:00 2001
From: jedi <git@m.j3d1.de>
Date: Thu, 5 Dec 2024 01:31:06 +0100
Subject: [PATCH] stash

---
 .forgejo/workflows/test.yml                | 11 +++-
 core/.coveragerc                           |  2 +-
 core/authentication/api_v2.py              |  4 +-
 core/authentication/tests/v2/test_users.py | 11 ++++
 core/core/globals.py                       |  8 +--
 core/helper.py                             | 30 -----------
 core/integration_tests/__init__.py         |  0
 core/integration_tests/main.py             | 61 ++++++++++++++++++++++
 core/integration_tests/test_bar.py         |  1 +
 core/integration_tests/test_foo.sh         |  5 ++
 core/server.py                             |  4 +-
 11 files changed, 93 insertions(+), 44 deletions(-)
 delete mode 100644 core/helper.py
 create mode 100644 core/integration_tests/__init__.py
 create mode 100644 core/integration_tests/main.py
 create mode 100644 core/integration_tests/test_bar.py
 create mode 100644 core/integration_tests/test_foo.sh

diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml
index 480e590..14c7591 100644
--- a/.forgejo/workflows/test.yml
+++ b/.forgejo/workflows/test.yml
@@ -1,3 +1,4 @@
+name: Test
 on:
   pull_request:
   push:
@@ -16,6 +17,12 @@ jobs:
       - name: Install dependencies
         working-directory: core
         run: pip3 install -r requirements.dev.txt
-      - name: Run django tests
+      - name: Run django tests with coverage
         working-directory: core
-        run: python3 manage.py test
+        run: coverage run manage.py test
+      - name: Run integration tests with coverage
+        working-directory: core
+        run: python3 integration_tests/main.py
+      - name: Evaluate coverage
+        working-directory: core
+        run: coverage report
diff --git a/core/.coveragerc b/core/.coveragerc
index 14c1fba..d06fc0e 100644
--- a/core/.coveragerc
+++ b/core/.coveragerc
@@ -8,7 +8,7 @@ skip_covered = True
 omit =
     */tests/*
     */migrations/*
+    integration_tests/*
     core/asgi.py
-    core/wsgi.py
     core/settings.py
     manage.py
\ No newline at end of file
diff --git a/core/authentication/api_v2.py b/core/authentication/api_v2.py
index 2547b6d..b7ecfe3 100644
--- a/core/authentication/api_v2.py
+++ b/core/authentication/api_v2.py
@@ -54,9 +54,9 @@ def registerUser(request):
             errors['password'] = 'Password is required'
         if not email:
             errors['email'] = 'Email is required'
-        if ExtendedUser.objects.filter(email=email).exists():
+        if email and ExtendedUser.objects.filter(email=email).exists():
             errors['email'] = 'Email already exists'
-        if ExtendedUser.objects.filter(username=username).exists():
+        if username and ExtendedUser.objects.filter(username=username).exists():
             errors['username'] = 'Username already exists'
         if errors:
             return Response({'errors': errors}, status=400)
diff --git a/core/authentication/tests/v2/test_users.py b/core/authentication/tests/v2/test_users.py
index 125be9b..02096ea 100644
--- a/core/authentication/tests/v2/test_users.py
+++ b/core/authentication/tests/v2/test_users.py
@@ -72,6 +72,17 @@ class UserApiTest(TestCase):
         self.assertEqual(ExtendedUser.objects.get(username='testuser2').email, 'test2')
         self.assertTrue(ExtendedUser.objects.get(username='testuser2').check_password('test'))
 
+    def test_register_user_fail(self):
+        anonymous = Client()
+        response = anonymous.post('/api/2/register/', {'username': 'testuser2', 'password': 'test', 'email': 'test2'},
+                                  content_type='application/json')
+        self.assertEqual(response.status_code, 201)
+        self.assertEqual(response.json()['username'], 'testuser2')
+        self.assertEqual(response.json()['email'], 'test2')
+        self.assertEqual(len(ExtendedUser.objects.all()), 3)
+        self.assertEqual(ExtendedUser.objects.get(username='testuser2').email, 'test2')
+        self.assertTrue(ExtendedUser.objects.get(username='testuser2').check_password('test'))
+
     def test_register_user_duplicate(self):
         anonymous = Client()
         response = anonymous.post('/api/2/register/', {'username': 'testuser', 'password': 'test', 'email': 'test2'},
diff --git a/core/core/globals.py b/core/core/globals.py
index d84a7a0..eae7884 100644
--- a/core/core/globals.py
+++ b/core/core/globals.py
@@ -2,12 +2,7 @@ import asyncio
 import logging
 import signal
 
-loop = asyncio.get_event_loop()
-
-
-def create_task(coro):
-    global loop
-    loop.create_task(coro)
+loop = None
 
 
 async def shutdown(sig, loop):
@@ -24,6 +19,7 @@ async def shutdown(sig, loop):
 
 def init_loop():
     global loop
+    loop = asyncio.get_event_loop()
     loop.add_signal_handler(signal.SIGTERM, lambda: asyncio.create_task(shutdown(signal.SIGTERM, loop)))
     loop.add_signal_handler(signal.SIGINT, lambda: asyncio.create_task(shutdown(signal.SIGINT, loop)))
     return loop
diff --git a/core/helper.py b/core/helper.py
deleted file mode 100644
index ae3c53b..0000000
--- a/core/helper.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import asyncio
-import logging
-import signal
-
-loop = None
-
-
-def create_task(coro):
-    global loop
-    loop.create_task(coro)
-
-
-async def shutdown(sig, loop):
-    log = logging.getLogger()
-    log.info(f"Received exit signal {sig.name}...")
-    tasks = [t for t in asyncio.all_tasks() if t is not
-             asyncio.current_task()]
-    [task.cancel() for task in tasks]
-    log.info(f"Cancelling {len(tasks)} outstanding tasks")
-    await asyncio.wait_for(loop.shutdown_asyncgens(), timeout=10)
-    loop.stop()
-    log.info("Shutdown complete.")
-
-
-def init_loop():
-    global loop
-    loop = asyncio.get_event_loop()
-    loop.add_signal_handler(signal.SIGTERM, lambda: asyncio.create_task(shutdown(signal.SIGTERM, loop)))
-    loop.add_signal_handler(signal.SIGINT, lambda: asyncio.create_task(shutdown(signal.SIGINT, loop)))
-    return loop
diff --git a/core/integration_tests/__init__.py b/core/integration_tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/core/integration_tests/main.py b/core/integration_tests/main.py
new file mode 100644
index 0000000..52ed1d1
--- /dev/null
+++ b/core/integration_tests/main.py
@@ -0,0 +1,61 @@
+import time
+import os
+import signal
+import re
+import sys
+import subprocess
+
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+
+def run():
+    while True:
+        newpid = os.fork()
+        if newpid == 0:
+            import coverage
+            cov = coverage.Coverage()
+            cov.load()
+            cov.start()
+            signal.signal(signal.SIGINT, signal.default_int_handler)
+            try:
+                from server import main
+                main()
+            except KeyboardInterrupt:
+                pass
+            cov.stop()
+            cov.save()
+            os._exit(0)
+        else:
+            return newpid
+
+
+if __name__ == '__main__':
+    pid = run()
+    time.sleep(2)
+    script_dir = os.path.dirname(os.path.abspath(__file__))
+    pattern = re.compile(r'^test_.*\.(sh|py)$')
+    dotpy = re.compile(r'^.*\.py$')
+    dotsh = re.compile(r'^.*\.sh$')
+    matching_files = [
+        filename for filename in os.listdir(script_dir)
+        if os.path.isfile(os.path.join(script_dir, filename)) and pattern.match(filename)
+    ]
+    total = 0
+    failed = 0
+    for file in matching_files:
+        file_path = os.path.join(script_dir, file)
+        if dotpy.match(file):
+            result = subprocess.run(['python3', file_path], capture_output=True, text=True)
+        elif dotsh.match(file):
+            result = subprocess.run(['bash', file_path], capture_output=True, text=True)
+        else:
+            result = subprocess.run(['bash', '-c', file_path], capture_output=True, text=True)
+        print('{} returned {}'.format(file, result.returncode))
+        if result.returncode != 0:
+            print(result.stderr, result.stdout)
+            failed += 1
+        total += 1
+    time.sleep(1)
+    os.kill(pid, signal.SIGINT)
+    print(f'{total - failed} out of {total} tests succeeded, {failed} failed')
+    os._exit(0 if failed == 0 else 1)
diff --git a/core/integration_tests/test_bar.py b/core/integration_tests/test_bar.py
new file mode 100644
index 0000000..5f7ce86
--- /dev/null
+++ b/core/integration_tests/test_bar.py
@@ -0,0 +1 @@
+#!/usr/bin/env python3
\ No newline at end of file
diff --git a/core/integration_tests/test_foo.sh b/core/integration_tests/test_foo.sh
new file mode 100644
index 0000000..65c2eaf
--- /dev/null
+++ b/core/integration_tests/test_foo.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+set -e
+
+swaks --to user@localhost --socket lmtp.sock --protocol LMTP
diff --git a/core/server.py b/core/server.py
index d08b595..59dcb3f 100644
--- a/core/server.py
+++ b/core/server.py
@@ -9,7 +9,7 @@ import uvicorn
 
 django.setup()
 
-from helper import init_loop
+from core.globals import init_loop
 from mail.protocol import LMTPHandler
 from mail.socket import UnixSocketLMTPController
 
@@ -51,7 +51,6 @@ async def lmtp(loop):
 
     async with server:
         await server.serve_forever()
-    log.info("LMTP done")
 
 
 def main():
@@ -82,7 +81,6 @@ def main():
         os.remove("web.sock")
     except Exception as e:
         log.error(e)
-        log.error(e)
     logging.info("Server stopped")
 
     logging.shutdown()