Skip to content

Commit 3744a76

Browse files
Enhance error handling and logging in writers; refactor tests to use pytest
1 parent caa9bc2 commit 3744a76

File tree

5 files changed

+412
-313
lines changed

5 files changed

+412
-313
lines changed

src/writer_eventbridge.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def init(logger, CONFIG):
1818
def write(topicName, message):
1919
if not EVENT_BUS_ARN:
2020
_logger.debug("No EventBus Arn - skipping")
21-
return True
21+
return True, None
2222

2323
try:
2424
_logger.debug(f"Sending to eventBridge {topicName}")
@@ -33,10 +33,12 @@ def write(topicName, message):
3333
]
3434
)
3535
if response["FailedEntryCount"] > 0:
36-
_logger.error(str(response))
37-
return False
36+
msg = str(response)
37+
_logger.error(msg)
38+
return False, msg
3839
except Exception as e:
39-
_logger.error(f"The EventBridge writer failed with unknown error: {str(e)}")
40-
return False
40+
err_msg = f"The EventBridge writer failed with unknown error: {str(e)}"
41+
_logger.error(err_msg)
42+
return False, err_msg
4143

42-
return True
44+
return True, None

src/writer_postgres.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,10 @@ def write(topicName, message):
190190
try:
191191
if not POSTGRES["database"]:
192192
_logger.debug("No Postgres - skipping")
193-
return True
193+
return True, None
194194
if psycopg2 is None:
195195
_logger.debug("psycopg2 not available - skipping actual Postgres write")
196-
return True
196+
return True, None
197197

198198
with psycopg2.connect(
199199
database=POSTGRES["database"],
@@ -210,12 +210,14 @@ def write(topicName, message):
210210
elif topicName == "public.cps.za.test":
211211
postgres_test_write(cursor, "public_cps_za_test", message)
212212
else:
213-
_logger.error(f"unknown topic for postgres {topicName}")
214-
return False
213+
msg = f"unknown topic for postgres {topicName}"
214+
_logger.error(msg)
215+
return False, msg
215216

216217
connection.commit()
217-
except Exception as e:
218-
_logger.error(f"The Postgres writer with failed unknown error: {str(e)}")
219-
return False
218+
except Exception as e: # pragma: no cover - defensive (still tested though)
219+
err_msg = f"The Postgres writer with failed unknown error: {str(e)}"
220+
_logger.error(err_msg)
221+
return False, err_msg
220222

221-
return True
223+
return True, None

tests/test_conf_validation.py

Lines changed: 38 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
import json
3-
import unittest
43
from glob import glob
4+
import pytest
55

66
CONF_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "conf")
77

@@ -17,50 +17,41 @@ def load_json(path):
1717
with open(path, "r") as f:
1818
return json.load(f)
1919

20-
class TestConfigFiles(unittest.TestCase):
21-
def test_config_files_have_required_keys(self):
22-
# Pick up any config*.json (excluding access and topics)
23-
config_files = [
24-
f for f in glob(os.path.join(CONF_DIR, "config*.json"))
25-
if os.path.basename(f) not in {"access.json"}
26-
]
27-
self.assertTrue(config_files, "No config files found matching pattern config*.json")
28-
for path in config_files:
29-
with self.subTest(config=path):
30-
data = load_json(path)
31-
missing = REQUIRED_CONFIG_KEYS - data.keys()
32-
self.assertFalse(missing, f"Config {path} missing keys: {missing}")
33-
34-
def test_access_json_structure(self):
35-
path = os.path.join(CONF_DIR, "access.json")
20+
@pytest.fixture(scope="module")
21+
def config_files():
22+
files = [
23+
f for f in glob(os.path.join(CONF_DIR, "config*.json"))
24+
if os.path.basename(f) not in {"access.json"}
25+
]
26+
assert files, "No config files found matching pattern config*.json"
27+
return files
28+
29+
@pytest.mark.parametrize("key", sorted(REQUIRED_CONFIG_KEYS))
30+
def test_config_files_have_required_keys(config_files, key):
31+
for path in config_files:
3632
data = load_json(path)
37-
self.assertIsInstance(data, dict, "access.json must contain an object mapping topic -> list[user]")
38-
for topic, users in data.items():
39-
with self.subTest(topic=topic):
40-
self.assertIsInstance(topic, str)
41-
self.assertIsInstance(users, list, f"Topic {topic} value must be a list of users")
42-
self.assertTrue(all(isinstance(u, str) for u in users), f"All users for topic {topic} must be strings")
43-
44-
def test_topic_json_schemas_basic(self):
45-
topic_files = glob(os.path.join(CONF_DIR, "topic_*.json"))
46-
self.assertTrue(topic_files, "No topic_*.json files found")
47-
for path in topic_files:
48-
with self.subTest(topic_file=path):
49-
schema = load_json(path)
50-
# Basic required structure
51-
self.assertEqual(schema.get("type"), "object", "Schema root 'type' must be 'object'")
52-
props = schema.get("properties")
53-
self.assertIsInstance(props, dict, "Schema must define 'properties' object")
54-
required = schema.get("required")
55-
self.assertIsInstance(required, list, "Schema must define 'required' list")
56-
# Each required field is present in properties
57-
missing_props = [r for r in required if r not in props]
58-
self.assertFalse(missing_props, f"Required fields missing in properties: {missing_props}")
59-
# Ensure property entries themselves have at least a type
60-
for name, definition in props.items():
61-
self.assertIsInstance(definition, dict, f"Property {name} definition must be an object")
62-
self.assertIn("type", definition, f"Property {name} must specify a 'type'")
63-
64-
if __name__ == "__main__": # pragma: no cover
65-
unittest.main()
66-
33+
assert key in data, f"Config {path} missing key: {key}"
34+
35+
def test_access_json_structure():
36+
path = os.path.join(CONF_DIR, "access.json")
37+
data = load_json(path)
38+
assert isinstance(data, dict), "access.json must contain an object mapping topic -> list[user]"
39+
for topic, users in data.items():
40+
assert isinstance(topic, str)
41+
assert isinstance(users, list), f"Topic {topic} value must be a list of users"
42+
assert all(isinstance(u, str) for u in users), f"All users for topic {topic} must be strings"
43+
44+
@pytest.mark.parametrize("topic_file", glob(os.path.join(CONF_DIR, "topic_*.json")))
45+
def test_topic_json_schemas_basic(topic_file):
46+
assert topic_file, "No topic_*.json files found"
47+
schema = load_json(topic_file)
48+
assert schema.get("type") == "object", "Schema root 'type' must be 'object'"
49+
props = schema.get("properties")
50+
assert isinstance(props, dict), "Schema must define 'properties' object"
51+
required = schema.get("required")
52+
assert isinstance(required, list), "Schema must define 'required' list"
53+
missing_props = [r for r in required if r not in props]
54+
assert not missing_props, f"Required fields missing in properties: {missing_props}"
55+
for name, definition in props.items():
56+
assert isinstance(definition, dict), f"Property {name} definition must be an object"
57+
assert "type" in definition, f"Property {name} must specify a 'type'"

0 commit comments

Comments
 (0)