From 8ae8877e3e2588db76285e7e3ddfb8c7b9948a96 Mon Sep 17 00:00:00 2001
From: Christian Heller <c.heller@plomlompom.de>
Date: Wed, 27 Mar 2024 01:34:22 +0100
Subject: [PATCH] Use different exceptions throwing different HTTP codes for
 different cases.

---
 plomtask/days.py       |  6 +++---
 plomtask/db.py         |  2 +-
 plomtask/exceptions.py | 19 +++++++++++++++++++
 plomtask/http.py       | 12 ++++++------
 plomtask/misc.py       |  8 --------
 plomtask/processes.py  |  4 ++--
 run.py                 |  2 +-
 tests/days.py          | 16 ++++++++--------
 tests/processes.py     | 16 ++++++++--------
 9 files changed, 48 insertions(+), 37 deletions(-)
 create mode 100644 plomtask/exceptions.py
 delete mode 100644 plomtask/misc.py

diff --git a/plomtask/days.py b/plomtask/days.py
index 3b81a7f..afdea33 100644
--- a/plomtask/days.py
+++ b/plomtask/days.py
@@ -2,7 +2,7 @@
 from __future__ import annotations
 from datetime import datetime, timedelta
 from sqlite3 import Row
-from plomtask.misc import HandledException
+from plomtask.exceptions import BadFormatException, NotFoundException
 from plomtask.db import DatabaseConnection
 
 DATE_FORMAT = '%Y-%m-%d'
@@ -16,7 +16,7 @@ def valid_date(date_str: str) -> str:
         dt = datetime.strptime(date_str, DATE_FORMAT)
     except (ValueError, TypeError) as e:
         msg = f'Given date of wrong format: {date_str}'
-        raise HandledException(msg) from e
+        raise BadFormatException(msg) from e
     return dt.strftime(DATE_FORMAT)
 
 
@@ -76,7 +76,7 @@ class Day:
         for row in db_conn.exec('SELECT * FROM days WHERE date = ?', (date,)):
             return cls.from_table_row(row)
         if not create:
-            raise HandledException(f'Day not found for date: {date}')
+            raise NotFoundException(f'Day not found for date: {date}')
         return cls(date)
 
     @property
diff --git a/plomtask/db.py b/plomtask/db.py
index e0a5d4f..929a733 100644
--- a/plomtask/db.py
+++ b/plomtask/db.py
@@ -3,7 +3,7 @@ from os.path import isfile
 from difflib import Differ
 from sqlite3 import connect as sql_connect, Cursor
 from typing import Any
-from plomtask.misc import HandledException
+from plomtask.exceptions import HandledException
 
 PATH_DB_SCHEMA = 'scripts/init.sql'
 
diff --git a/plomtask/exceptions.py b/plomtask/exceptions.py
new file mode 100644
index 0000000..379ae9f
--- /dev/null
+++ b/plomtask/exceptions.py
@@ -0,0 +1,19 @@
+"""
+Whatever fits nowhere else, and/or is too small/trivial
+to merit its own module at this point.
+"""
+
+
+class HandledException(Exception):
+    """To identify Exceptions based on expected (if faulty) user behavior."""
+    http_code = 400
+
+
+class BadFormatException(HandledException):
+    """To identify Exceptions on malformed inputs."""
+    http_code = 401
+
+
+class NotFoundException(HandledException):
+    """To identify Exceptions on unsuccessful queries."""
+    http_code = 404
diff --git a/plomtask/http.py b/plomtask/http.py
index ddea087..af1a60c 100644
--- a/plomtask/http.py
+++ b/plomtask/http.py
@@ -6,7 +6,7 @@ from urllib.parse import urlparse, parse_qs
 from os.path import split as path_split
 from jinja2 import Environment as JinjaEnv, FileSystemLoader as JinjaFSLoader
 from plomtask.days import Day, todays_date
-from plomtask.misc import HandledException
+from plomtask.exceptions import HandledException, BadFormatException
 from plomtask.db import DatabaseConnection, DatabaseFile
 from plomtask.processes import Process
 
@@ -43,7 +43,7 @@ class TaskHandler(BaseHTTPRequestHandler):
                 try:
                     id__ = int(id_) if id_ else None
                 except ValueError as e:
-                    raise HandledException(f'Bad ?id= value: {id_}') from e
+                    raise BadFormatException(f'Bad ?id= value: {id_}') from e
                 html = self.do_GET_process(conn, id__)
             elif 'processes' == site:
                 html = self.do_GET_processes(conn)
@@ -53,7 +53,7 @@ class TaskHandler(BaseHTTPRequestHandler):
             conn.close()
             self._send_html(html)
         except HandledException as error:
-            self._send_msg(error)
+            self._send_msg(error, code=error.http_code)
 
     def do_GET_calendar(self, conn: DatabaseConnection,
                         start: str, end: str) -> str:
@@ -92,13 +92,13 @@ class TaskHandler(BaseHTTPRequestHandler):
                 try:
                     id__ = int(id_) if id_ else None
                 except ValueError as e:
-                    raise HandledException(f'Bad ?id= value: {id_}') from e
+                    raise BadFormatException(f'Bad ?id= value: {id_}') from e
                 self.do_POST_process(conn, id__, postvars)
             conn.commit()
             conn.close()
             self._redirect('/')
         except HandledException as error:
-            self._send_msg(error)
+            self._send_msg(error, code=error.http_code)
 
     def do_POST_day(self, conn: DatabaseConnection,
                     date: str, postvars: dict[str, list[str]]) -> None:
@@ -117,7 +117,7 @@ class TaskHandler(BaseHTTPRequestHandler):
         try:
             process.effort.set(float(effort))
         except ValueError as e:
-            raise HandledException(f'Bad effort value: {effort}') from e
+            raise BadFormatException(f'Bad effort value: {effort}') from e
         process.save(conn)
 
     def _init_handling(self) -> \
diff --git a/plomtask/misc.py b/plomtask/misc.py
deleted file mode 100644
index 1b780e2..0000000
--- a/plomtask/misc.py
+++ /dev/null
@@ -1,8 +0,0 @@
-"""
-Whatever fits nowhere else, and/or is too small/trivial
-to merit its own module at this point.
-"""
-
-
-class HandledException(Exception):
-    """To identify Exceptions based on expected (if faulty) user behavior."""
diff --git a/plomtask/processes.py b/plomtask/processes.py
index 4867227..8b5ff42 100644
--- a/plomtask/processes.py
+++ b/plomtask/processes.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 from sqlite3 import Row
 from datetime import datetime
 from plomtask.db import DatabaseConnection
-from plomtask.misc import HandledException
+from plomtask.exceptions import NotFoundException
 
 
 class Process:
@@ -46,7 +46,7 @@ class Process:
             break
         if not process:
             if not create:
-                raise HandledException(f'Process not found of id: {id_}')
+                raise NotFoundException(f'Process not found of id: {id_}')
             process = Process(id_)
         if process:
             for row in db_conn.exec('SELECT * FROM process_titles '
diff --git a/run.py b/run.py
index 31d11ce..e1bbe5d 100755
--- a/run.py
+++ b/run.py
@@ -2,7 +2,7 @@
 """Call this to start the application."""
 from sys import exit as sys_exit
 from os import environ
-from plomtask.misc import HandledException
+from plomtask.exceptions import HandledException
 from plomtask.http import TaskHandler, TaskServer
 from plomtask.db import DatabaseFile
 
diff --git a/tests/days.py b/tests/days.py
index f3ed082..61a27ed 100644
--- a/tests/days.py
+++ b/tests/days.py
@@ -3,7 +3,7 @@ from unittest import TestCase
 from datetime import datetime
 from tests.utils import TestCaseWithDB, TestCaseWithServer
 from plomtask.days import Day, todays_date
-from plomtask.misc import HandledException
+from plomtask.exceptions import BadFormatException, NotFoundException
 
 
 class TestsSansDB(TestCase):
@@ -11,11 +11,11 @@ class TestsSansDB(TestCase):
 
     def test_Day_dates(self) -> None:
         """Test Day's date format."""
-        with self.assertRaises(HandledException):
+        with self.assertRaises(BadFormatException):
             Day('foo')
-        with self.assertRaises(HandledException):
+        with self.assertRaises(BadFormatException):
             Day('2024-02-30')
-        with self.assertRaises(HandledException):
+        with self.assertRaises(BadFormatException):
             Day('2024-02-01 23:00:00')
         self.assertEqual(datetime(2024, 1, 1), Day('2024-01-01').datetime)
 
@@ -37,12 +37,12 @@ class TestsWithDB(TestCaseWithDB):
 
     def test_Day_by_date(self) -> None:
         """Test Day.by_date()."""
-        with self.assertRaises(HandledException):
+        with self.assertRaises(NotFoundException):
             Day.by_date(self.db_conn, '2024-01-01')
         Day('2024-01-01').save(self.db_conn)
         self.assertEqual(Day('2024-01-01'),
                          Day.by_date(self.db_conn, '2024-01-01'))
-        with self.assertRaises(HandledException):
+        with self.assertRaises(NotFoundException):
             Day.by_date(self.db_conn, '2024-01-02')
         self.assertEqual(Day('2024-01-02'),
                          Day.by_date(self.db_conn, '2024-01-02', create=True))
@@ -97,7 +97,7 @@ class TestsWithServer(TestCaseWithServer):
         self.conn.request('GET', '/day?date=3000-01-01')
         self.assertEqual(self.conn.getresponse().status, 200)
         self.conn.request('GET', '/day?date=FOO')
-        self.assertEqual(self.conn.getresponse().status, 400)
+        self.assertEqual(self.conn.getresponse().status, 401)
         self.conn.request('GET', '/calendar')
         self.assertEqual(self.conn.getresponse().status, 200)
         self.conn.request('GET', '/calendar?start=&end=')
@@ -107,4 +107,4 @@ class TestsWithServer(TestCaseWithServer):
         self.conn.request('GET', '/calendar?start=2024-01-01&end=2025-01-01')
         self.assertEqual(self.conn.getresponse().status, 200)
         self.conn.request('GET', '/calendar?start=foo')
-        self.assertEqual(self.conn.getresponse().status, 400)
+        self.assertEqual(self.conn.getresponse().status, 401)
diff --git a/tests/processes.py b/tests/processes.py
index 17af14e..399eb9d 100644
--- a/tests/processes.py
+++ b/tests/processes.py
@@ -3,7 +3,7 @@ from unittest import TestCase
 from urllib.parse import urlencode
 from tests.utils import TestCaseWithDB, TestCaseWithServer
 from plomtask.processes import Process
-from plomtask.misc import HandledException
+from plomtask.exceptions import NotFoundException
 
 
 class TestsSansDB(TestCase):
@@ -42,11 +42,11 @@ class TestsWithDB(TestCaseWithDB):
 
     def test_Process_by_id(self) -> None:
         """Test Process.by_id()."""
-        with self.assertRaises(HandledException):
+        with self.assertRaises(NotFoundException):
             Process.by_id(self.db_conn, None, create=False)
-        with self.assertRaises(HandledException):
+        with self.assertRaises(NotFoundException):
             Process.by_id(self.db_conn, 0, create=False)
-        with self.assertRaises(HandledException):
+        with self.assertRaises(NotFoundException):
             Process.by_id(self.db_conn, 1, create=False)
         self.assertNotEqual(Process(1).id_,
                             Process.by_id(self.db_conn, None, create=True).id_)
@@ -81,11 +81,11 @@ class TestsWithServer(TestCaseWithServer):
                               body=encoded_form_data, headers=headers)
             self.assertEqual(self.conn.getresponse().status, expect)
         form_data = {'title': 'foo', 'description': 'foo', 'effort': 1.0}
-        post_data_to_expect(form_data, '/process?id=FOO', 400)
+        post_data_to_expect(form_data, '/process?id=FOO', 401)
         form_data['effort'] = 'foo'
-        post_data_to_expect(form_data, '/process?id=', 400)
+        post_data_to_expect(form_data, '/process?id=', 401)
         form_data['effort'] = None
-        post_data_to_expect(form_data, '/process?id=', 400)
+        post_data_to_expect(form_data, '/process?id=', 401)
         form_data = {'title': None, 'description': 1, 'effort': 1.0}
         post_data_to_expect(form_data, '/process?id=', 302)
         retrieved = Process.by_id(self.db_conn, 1)
@@ -102,6 +102,6 @@ class TestsWithServer(TestCaseWithServer):
         self.conn.request('GET', '/process?id=0')
         self.assertEqual(self.conn.getresponse().status, 200)
         self.conn.request('GET', '/process?id=FOO')
-        self.assertEqual(self.conn.getresponse().status, 400)
+        self.assertEqual(self.conn.getresponse().status, 401)
         self.conn.request('GET', '/processes')
         self.assertEqual(self.conn.getresponse().status, 200)
-- 
2.30.2