2# +==== BEGIN CatFeeder =================+
5# ...............)..(.')
7# ...............\(__)|
8# Inspired by Joan Stark
9# source https://www.asciiart.eu/
13# FILE: testing_endpoints.py
14# CREATION DATE: 30-11-2025
15# LAST Modified: 21:56:40 23-01-2026
17# This is the backend server in charge of making the actual website work.
19# COPYRIGHT: (c) Cat Feeder
20# PURPOSE: Files to test the submodules of the server.
22# +==== END CatFeeder =================+
25from typing
import Optional, Dict, Union, TYPE_CHECKING
26from datetime
import datetime
27from display_tty
import Disp, initialise_logger
28from fastapi
import Request, Response
29from ...http_codes
import HCI, HttpDataTypes
30from ...core
import RuntimeControl, RuntimeManager, RI
31from ...favicon
import favicon_constants
as FAV_CONST
34 from ...sql
import SQL
35 from ...bucket
import Bucket
36 from ...crons
import BackgroundTasks
37 from ...image_reducer
import ImageReducer
38 from ...server_header
import ServerHeaders
39 from ...boilerplates
import BoilerplateIncoming, BoilerplateResponses, BoilerplateNonHTTP
45 disp: Disp = initialise_logger(__qualname__,
False)
47 def __init__(self, success: int = 0, error: int = 84, debug: bool =
False) ->
None:
51 success (int, optional): _description_. Defaults to 0.
52 error (int, optional): _description_. Defaults to 84.
53 debug (bool, optional): _description_. Defaults to False.
56 self.
disp.update_disp_debug(debug)
57 self.
disp.log_debug(
"Initialising...")
65 "BoilerplateIncoming")
67 "BoilerplateResponses")
88 self.
disp.log_debug(
"Initialised")
91 """Get the token of the user if they are administrator, otherwise, return the correct http response.
94 title (str): The title of the endpoint calling this function.
95 request (Request): The incoming request parameters
98 Union[Response, str]: The response if an error occurred, the token otherwise.
114 """Get a list of all database table names.
117 request (Request): The incoming HTTP request.
120 Response: JSON list of table names or error response.
123 if isinstance(token, Response):
130 return HCI.service_unavailable(
"Database connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
131 self.
disp.log_debug(
"Gathering tables")
133 self.
disp.log_debug(f
"Gathered tables: {data}")
134 if isinstance(data, int):
139 """Get column names for a specific table.
142 table_name (str): Name of the table to inspect.
145 request (Request): The incoming HTTP request.
148 Response: JSON list of column names or error response.
151 if isinstance(token, Response):
156 return HCI.service_unavailable(
"Database connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
157 table_name = request.query_params.get(
"table_name")
159 return HCI.bad_request(
"Missing required parameter: table_name", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
160 self.
disp.log_debug(f
"Gathering columns from table: {table_name}")
162 self.
disp.log_debug(f
"Gathered columns: {data}")
163 if isinstance(data, int):
164 return HCI.not_found(f
"Table '{table_name}' not found or has no columns", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
165 return HCI.success({
"table": table_name,
"columns": data}, content_type=HttpDataTypes.JSON, headers=self.
server_headers_initialised.for_json())
168 """Get the full schema description of a table.
171 table_name (str): Name of the table to describe.
174 request (Request): The incoming HTTP request.
177 Response: JSON with table schema information or error response.
180 if isinstance(token, Response):
185 return HCI.service_unavailable(
"Database connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
186 table_name = request.query_params.get(
"table_name")
188 return HCI.bad_request(
"Missing required parameter: table_name", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
189 self.
disp.log_debug(f
"Describing table: {table_name}")
191 self.
disp.log_debug(f
"Table description: {data}")
192 if isinstance(data, int):
193 return HCI.not_found(f
"Failed to describe table '{table_name}'", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
194 return HCI.success({
"table": table_name,
"schema": data}, content_type=HttpDataTypes.JSON, headers=self.
server_headers_initialised.for_json())
197 """Get the database version information.
200 request (Request): The incoming HTTP request.
203 Response: JSON with database version or error response.
206 if isinstance(token, Response):
211 return HCI.service_unavailable(
"Database connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
212 self.
disp.log_debug(
"Gathering database version")
214 self.
disp.log_debug(f
"Database version: {data}")
216 return HCI.not_found(
"Failed to retrieve database version", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
220 """Test if the SQL database connection is active.
223 request (Request): The incoming HTTP request.
226 Response: JSON with connection status.
229 if isinstance(token, Response):
234 return HCI.service_unavailable(
"Database connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
235 self.
disp.log_debug(
"Testing database connection")
237 result: Dict[str, Union[str, bool]] = {
"connected": is_connected}
238 node_id: str =
"message"
240 result[node_id] =
"Connection is active"
242 result[node_id] =
"Connection failed"
243 self.
disp.log_debug(f
"Connection status: {is_connected}")
247 """Get the number of rows in a table.
250 table_name (str): Name of the table to count rows.
253 request (Request): The incoming HTTP request.
256 Response: JSON with row count or error response.
259 if isinstance(token, Response):
264 return HCI.service_unavailable(
"Database connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
265 table_name = request.query_params.get(
"table_name")
267 return HCI.bad_request(
"Missing required parameter: table_name", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
268 self.
disp.log_debug(f
"Getting size of table: {table_name}")
270 self.
disp.log_debug(f
"Table size: {row_count}")
272 return HCI.not_found(f
"Failed to get size of table '{table_name}'", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
273 return HCI.success({
"table": table_name,
"row_count": row_count}, content_type=HttpDataTypes.JSON, headers=self.
server_headers_initialised.for_json())
276 """Get all database triggers.
279 request (Request): The incoming HTTP request.
282 Response: JSON with triggers dictionary or error response.
285 if isinstance(token, Response):
290 return HCI.service_unavailable(
"Database connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
291 self.
disp.log_debug(
"Gathering triggers")
293 self.
disp.log_debug(f
"Gathered triggers: {data}")
294 if isinstance(data, int):
295 return HCI.not_found(
"Failed to retrieve triggers", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
299 """Get list of all trigger names.
302 request (Request): The incoming HTTP request.
305 Response: JSON list of trigger names or error response.
308 if isinstance(token, Response):
313 return HCI.service_unavailable(
"Database connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
314 self.
disp.log_debug(
"Gathering trigger names")
316 self.
disp.log_debug(f
"Gathered trigger names: {data}")
317 if isinstance(data, int):
318 return HCI.not_found(
"Failed to retrieve trigger names", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
322 """Get current datetime in the project's format.
325 request (Request): The incoming HTTP request.
328 Response: JSON with current datetime string.
331 if isinstance(token, Response):
336 return HCI.service_unavailable(
"Database connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
337 self.
disp.log_debug(
"Getting current datetime")
339 self.
disp.log_debug(f
"Current datetime: {datetime_str}")
340 return HCI.success({
"datetime": datetime_str}, content_type=HttpDataTypes.JSON, headers=self.
server_headers_initialised.for_json())
343 """Get current date in the project's format.
346 request (Request): The incoming HTTP request.
349 Response: JSON with current date string.
352 if isinstance(token, Response):
357 return HCI.service_unavailable(
"Database connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
358 self.
disp.log_debug(
"Getting current date")
360 self.
disp.log_debug(f
"Current date: {date_str}")
364 """Convert a datetime object to project's string format.
367 datetime (str): ISO format datetime string to convert.
368 date_only (bool, optional): If true, return only date portion. Defaults to false.
369 sql_mode (bool, optional): If true, include millisecond precision. Defaults to false.
372 request (Request): The incoming HTTP request.
375 Response: JSON with formatted datetime string or error response.
378 if isinstance(token, Response):
383 return HCI.service_unavailable(
"Database connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
384 datetime_str = request.query_params.get(
"datetime")
386 return HCI.bad_request(
"Missing required parameter: datetime (ISO format)", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
387 date_only = request.query_params.get(
388 "date_only",
"false").lower() ==
"true"
389 sql_mode = request.query_params.get(
390 "sql_mode",
"false").lower() ==
"true"
391 self.
disp.log_debug(f
"Converting datetime: {datetime_str}")
393 dt_obj = datetime.fromisoformat(datetime_str)
395 dt_obj, date_only, sql_mode)
396 self.
disp.log_debug(f
"Formatted datetime: {formatted}")
397 return HCI.success({
"original": datetime_str,
"formatted": formatted,
"date_only": date_only,
"sql_mode": sql_mode}, content_type=HttpDataTypes.JSON, headers=self.
server_headers_initialised.for_json())
398 except ValueError
as e:
399 return HCI.bad_request(f
"Invalid datetime format: {str(e)}", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
402 """Convert a project-formatted string to datetime object.
405 datetime_str (str): Project-formatted datetime string to parse.
406 date_only (bool, optional): If true, parse as date only. Defaults to false.
409 request (Request): The incoming HTTP request.
412 Response: JSON with ISO format datetime or error response.
415 if isinstance(token, Response):
420 return HCI.service_unavailable(
"Database connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
421 datetime_str = request.query_params.get(
"datetime_str")
423 return HCI.bad_request(
"Missing required parameter: datetime_str", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
424 date_only = request.query_params.get(
425 "date_only",
"false").lower() ==
"true"
426 self.
disp.log_debug(f
"Parsing datetime string: {datetime_str}")
429 datetime_str, date_only)
430 iso_format = dt_obj.isoformat()
431 self.
disp.log_debug(f
"Parsed to ISO: {iso_format}")
432 return HCI.success({
"original": datetime_str,
"iso_format": iso_format,
"date_only": date_only}, content_type=HttpDataTypes.JSON, headers=self.
server_headers_initialised.for_json())
433 except ValueError
as e:
434 return HCI.bad_request(f
"Invalid datetime string: {str(e)}", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
439 """Get a list of all S3 bucket names.
442 request (Request): The incoming HTTP request.
445 Response: JSON list of bucket names or error response.
448 if isinstance(token, Response):
455 return HCI.service_unavailable(
"S3 bucket connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
456 self.
disp.log_debug(
"Gathering buckets")
458 self.
disp.log_debug(f
"Gathered buckets: {data}")
459 if isinstance(data, int):
460 return HCI.not_found(
"Failed to retrieve bucket names", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
464 """Test if the S3 bucket connection is active.
467 request (Request): The incoming HTTP request.
470 Response: JSON with connection status.
473 if isinstance(token, Response):
480 return HCI.service_unavailable(
"S3 bucket connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
481 self.
disp.log_debug(
"Testing bucket connection")
483 result: Dict[str, Union[str, bool]] = {
"connected": is_connected}
484 node_id: str =
"message"
486 result[node_id] =
"Connection is active"
488 result[node_id] =
"Connection failed"
489 self.
disp.log_debug(f
"Connection status: {is_connected}")
493 """Get all files in a specific bucket.
496 bucket_name (str): Name of the bucket to list files from.
499 request (Request): The incoming HTTP request.
502 Response: JSON list of file names or error response.
505 if isinstance(token, Response):
512 return HCI.service_unavailable(
"S3 bucket connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
513 bucket_name = request.query_params.get(
"bucket_name")
515 return HCI.bad_request(
"Missing required parameter: bucket_name", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
516 self.
disp.log_debug(f
"Gathering files from bucket: {bucket_name}")
518 self.
disp.log_debug(f
"Gathered files: {data}")
519 if isinstance(data, int):
520 return HCI.not_found(f
"Failed to retrieve files from bucket '{bucket_name}'", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
521 return HCI.success({
"bucket": bucket_name,
"files": data}, content_type=HttpDataTypes.JSON, headers=self.
server_headers_initialised.for_json())
524 """Get information about a specific file in a bucket.
527 bucket_name (str): Name of the bucket.
528 file_name (str): Name of the file to get info about.
531 request (Request): The incoming HTTP request.
534 Response: JSON with file metadata or error response.
537 if isinstance(token, Response):
544 return HCI.service_unavailable(
"S3 bucket connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
545 bucket_name = request.query_params.get(
"bucket_name")
546 file_name = request.query_params.get(
"file_name")
547 if not bucket_name
or not file_name:
548 return HCI.bad_request(
"Missing required parameters: bucket_name and file_name", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
550 f
"Getting info for file '{file_name}' in bucket '{bucket_name}'"
553 self.
disp.log_debug(f
"File info: {data}")
554 if isinstance(data, int):
555 return HCI.not_found(f
"File '{file_name}' not found in bucket '{bucket_name}'", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
559 """Create a new test bucket.
562 bucket_name (str): Name of the bucket to create.
565 request (Request): The incoming HTTP request.
568 Response: Success or error response.
571 if isinstance(token, Response):
578 return HCI.service_unavailable(
"S3 bucket connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
579 bucket_name = request.query_params.get(
"bucket_name")
581 return HCI.bad_request(
"Missing required parameter: bucket_name", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
582 self.
disp.log_debug(f
"Creating bucket: {bucket_name}")
585 return HCI.internal_server_error(f
"Failed to create bucket '{bucket_name}'", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
586 self.
disp.log_debug(f
"Bucket '{bucket_name}' created successfully")
587 return HCI.created({
"message": f
"Bucket '{bucket_name}' created successfully"}, content_type=HttpDataTypes.JSON, headers=self.
server_headers_initialised.for_json())
590 """Delete a test bucket.
593 bucket_name (str): Name of the bucket to delete.
596 request (Request): The incoming HTTP request.
599 Response: Success or error response.
602 if isinstance(token, Response):
609 return HCI.service_unavailable(
"S3 bucket connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
610 bucket_name = request.query_params.get(
"bucket_name")
612 return HCI.bad_request(
"Missing required parameter: bucket_name", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
613 self.
disp.log_debug(f
"Deleting bucket: {bucket_name}")
616 return HCI.internal_server_error(f
"Failed to delete bucket '{bucket_name}'", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
617 self.
disp.log_debug(f
"Bucket '{bucket_name}' deleted successfully")
618 return HCI.success({
"message": f
"Bucket '{bucket_name}' deleted successfully"}, content_type=HttpDataTypes.JSON, headers=self.
server_headers_initialised.for_json())
621 """Upload a file to a bucket as a byte stream (from request body).
624 bucket_name (str): Name of the bucket to upload to.
625 file_name (str): Name to save the file as in the bucket.
627 Body: Raw file content (bytes).
630 request (Request): The incoming HTTP request.
633 Response: Success or error response.
636 if isinstance(token, Response):
643 return HCI.service_unavailable(
"S3 bucket connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
644 bucket_name = request.query_params.get(
"bucket_name")
645 file_name = request.query_params.get(
"file_name")
646 if not bucket_name
or not file_name:
647 return HCI.bad_request(
"Missing required parameters: bucket_name and file_name", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
649 file_data = await request.body()
651 return HCI.bad_request(
"Request body is empty", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
653 f
"Uploading file stream '{file_name}' to bucket '{bucket_name}' ({len(file_data)} bytes)")
655 bucket_name, file_data, file_name)
657 return HCI.internal_server_error(f
"Failed to upload file '{file_name}' to bucket '{bucket_name}'", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
658 self.
disp.log_debug(f
"File '{file_name}' uploaded successfully")
659 return HCI.created({
"message": f
"File '{file_name}' uploaded successfully to bucket '{bucket_name}'"}, content_type=HttpDataTypes.JSON, headers=self.
server_headers_initialised.for_json())
660 except Exception
as e:
661 self.
disp.log_error(f
"Error uploading file stream: {str(e)}")
662 return HCI.internal_server_error(f
"Error uploading file: {str(e)}", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
665 """Download a file from a bucket as a byte stream.
668 bucket_name (str): Name of the bucket.
669 file_name (str): Name of the file to download.
672 request (Request): The incoming HTTP request.
675 Response: File content as bytes or error response.
678 if isinstance(token, Response):
685 return HCI.service_unavailable(
"S3 bucket connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
686 bucket_name = request.query_params.get(
"bucket_name")
687 file_name = request.query_params.get(
"file_name")
688 if not bucket_name
or not file_name:
689 return HCI.bad_request(
"Missing required parameters: bucket_name and file_name", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
691 f
"Downloading file stream '{file_name}' from bucket '{bucket_name}'")
693 bucket_name, file_name)
694 if isinstance(file_data, int):
695 return HCI.not_found(f
"File '{file_name}' not found in bucket '{bucket_name}'", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
697 f
"File '{file_name}' downloaded successfully ({len(file_data)} bytes)")
703 "ImageReducer not available, returning as octet-stream"
708 f
"Detected file format: {detected_format}"
710 file_format = FAV_CONST.reducer_type_to_data_type(
714 f
"Detected file format: {file_format}, returning accordingly")
718 """Delete a file from a bucket.
721 bucket_name (str): Name of the bucket.
722 file_name (str): Name of the file to delete.
725 request (Request): The incoming HTTP request.
728 Response: Success or error response.
731 if isinstance(token, Response):
738 return HCI.service_unavailable(
"S3 bucket connection not available", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
739 bucket_name = request.query_params.get(
"bucket_name")
740 file_name = request.query_params.get(
"file_name")
741 if not bucket_name
or not file_name:
742 return HCI.bad_request(
"Missing required parameters: bucket_name and file_name", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
744 f
"Deleting file '{file_name}' from bucket '{bucket_name}'")
747 return HCI.internal_server_error(f
"Failed to delete file '{file_name}' from bucket '{bucket_name}'", content_type=HttpDataTypes.TEXT, headers=self.
server_headers_initialised.for_text())
748 self.
disp.log_debug(f
"File '{file_name}' deleted successfully")
749 return HCI.success({
"message": f
"File '{file_name}' deleted successfully from bucket '{bucket_name}'"}, content_type=HttpDataTypes.JSON, headers=self.
server_headers_initialised.for_json())
Response get_current_date(self, Request request)
Response upload_test_file_stream(self, Request request)
Response download_test_file_stream(self, Request request)
Response convert_string_to_datetime(self, Request request)
Response get_bucket_files(self, Request request)
Response get_table_columns(self, Request request)
Response test_bucket_connection(self, Request request)
Optional[] bucket_connection
Response describe_table(self, Request request)
RuntimeManager boilerplate_non_http_initialised
RuntimeManager image_reducer
RuntimeManager boilerplate_incoming_initialised
None __init__(self, int success=0, int error=84, bool debug=False)
Response get_table_size(self, Request request)
Response get_triggers(self, Request request)
RuntimeManager server_headers_initialised
Response get_tables(self, Request request)
Response get_trigger_names(self, Request request)
Response create_test_bucket(self, Request request)
RuntimeManager background_tasks_initialised
Union[Response, str] _get_admin_token(self, str title, Request request)
Response get_bucket_file_info(self, Request request)
Response test_sql_connection(self, Request request)
RuntimeManager runtime_manager
Optional[] sql_connection
Response delete_test_file(self, Request request)
Response get_buckets(self, Request request)
RuntimeManager boilerplate_responses_initialised
Response get_current_datetime(self, Request request)
RuntimeManager runtime_controls_initialised
Response delete_test_bucket(self, Request request)
Response get_database_version(self, Request request)
Response convert_datetime_to_string(self, Request request)