Skip to content

Commit 3bf8765

Browse files
committed
Human rework of the PR task
1 parent ae76d69 commit 3bf8765

File tree

4 files changed

+117
-119
lines changed

4 files changed

+117
-119
lines changed

conf/api.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,14 @@ paths:
6666
status:
6767
type: string
6868
example: degraded
69-
details:
69+
failures:
7070
type: object
7171
additionalProperties:
7272
type: string
7373
example:
74-
kafka: not_initialized
74+
eventbridge: client not initialized
75+
kafka: producer not initialized
76+
postgres: host not configured
7577

7678
/topics:
7779
get:

src/event_gate_lambda.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
handler_topic = HandlerTopic(CONF_DIR, ACCESS, handler_token).load_topic_schemas()
8888

8989
# Initialize health handler
90-
handler_health = HandlerHealth(logger, config)
90+
handler_health = HandlerHealth()
9191

9292

9393
def get_api() -> Dict[str, Any]:

src/handlers/handler_health.py

Lines changed: 37 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -19,83 +19,74 @@
1919
"""
2020
import json
2121
import logging
22+
import os
2223
from datetime import datetime, timezone
2324
from typing import Dict, Any
2425

2526
from src.writers import writer_eventbridge, writer_kafka, writer_postgres
2627

28+
logger = logging.getLogger(__name__)
29+
log_level = os.environ.get("LOG_LEVEL", "INFO")
30+
logger.setLevel(log_level)
31+
2732

2833
class HandlerHealth:
2934
"""
3035
HandlerHealth manages service health checks and dependency status monitoring.
3136
"""
3237

33-
def __init__(self, logger_instance: logging.Logger, config: Dict[str, Any]):
34-
"""
35-
Initialize the health handler.
36-
37-
Args:
38-
logger_instance: Shared application logger.
39-
config: Configuration dictionary.
40-
"""
41-
self.logger = logger_instance
42-
self.config = config
43-
self.start_time = datetime.now(timezone.utc)
38+
def __init__(self):
39+
self.start_time: datetime = datetime.now(timezone.utc)
4440

4541
def get_health(self) -> Dict[str, Any]:
4642
"""
4743
Check service health and return status.
4844
49-
Performs lightweight dependency checks by verifying that writer STATE
50-
dictionaries are properly initialized with required keys.
51-
5245
Returns:
5346
Dict[str, Any]: API Gateway response with health status.
5447
- 200: All dependencies healthy
5548
- 503: One or more dependencies not initialized
5649
"""
57-
self.logger.debug("Handling GET Health")
58-
59-
details: Dict[str, str] = {}
60-
all_healthy = True
61-
62-
# Check Kafka writer STATE
63-
kafka_state = writer_kafka.STATE
64-
if not all(key in kafka_state for key in ["logger", "producer"]):
65-
details["kafka"] = "not_initialized"
66-
all_healthy = False
67-
self.logger.debug("Kafka writer not properly initialized")
68-
69-
# Check EventBridge writer STATE
70-
eventbridge_state = writer_eventbridge.STATE
71-
if not all(key in eventbridge_state for key in ["logger", "client", "event_bus_arn"]):
72-
details["eventbridge"] = "not_initialized"
73-
all_healthy = False
74-
self.logger.debug("EventBridge writer not properly initialized")
75-
76-
# Check PostgreSQL writer - it uses global logger variable and POSTGRES dict
77-
# Just verify the module is accessible (init is always called in event_gate_lambda)
78-
try:
79-
_ = writer_postgres.logger
80-
except AttributeError:
81-
details["postgres"] = "not_initialized"
82-
all_healthy = False
83-
self.logger.debug("PostgreSQL writer not accessible")
50+
logger.debug("Handling GET Health")
51+
52+
failures: Dict[str, str] = {}
53+
54+
# Check Kafka writer
55+
if writer_kafka.STATE.get("producer") is None:
56+
failures["kafka"] = "producer not initialized"
57+
58+
# Check EventBridge writer
59+
eventbus_arn = writer_eventbridge.STATE.get("event_bus_arn")
60+
eventbridge_client = writer_eventbridge.STATE.get("client")
61+
if eventbus_arn:
62+
if eventbridge_client is None:
63+
failures["eventbridge"] = "client not initialized"
64+
65+
# Check PostgreSQL writer
66+
postgres_config = writer_postgres.POSTGRES
67+
if postgres_config.get("database"):
68+
if not postgres_config.get("host"):
69+
failures["postgres"] = "host not configured"
70+
elif not postgres_config.get("user"):
71+
failures["postgres"] = "user not configured"
72+
elif not postgres_config.get("password"):
73+
failures["postgres"] = "password not configured"
74+
elif not postgres_config.get("port"):
75+
failures["postgres"] = "port not configured"
8476

85-
# Calculate uptime
8677
uptime_seconds = int((datetime.now(timezone.utc) - self.start_time).total_seconds())
8778

88-
if all_healthy:
89-
self.logger.debug("Health check passed - all dependencies healthy")
79+
if not failures:
80+
logger.debug("Health check passed")
9081
return {
9182
"statusCode": 200,
9283
"headers": {"Content-Type": "application/json"},
9384
"body": json.dumps({"status": "ok", "uptime_seconds": uptime_seconds}),
9485
}
9586

96-
self.logger.debug("Health check degraded - some dependencies not initialized: %s", details)
87+
logger.debug("Health check degraded: %s", failures)
9788
return {
9889
"statusCode": 503,
9990
"headers": {"Content-Type": "application/json"},
100-
"body": json.dumps({"status": "degraded", "details": details}),
91+
"body": json.dumps({"status": "degraded", "failures": failures}),
10192
}

tests/handlers/test_handler_health.py

Lines changed: 75 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -16,126 +16,131 @@
1616

1717
import json
1818
from unittest.mock import MagicMock, patch
19-
import logging
2019

2120
from src.handlers.handler_health import HandlerHealth
2221

22+
### get_health()
2323

24-
## get_health() - healthy state
25-
def test_get_health_all_dependencies_healthy():
26-
"""Health check returns 200 when all writer STATEs are properly initialized."""
27-
logger = logging.getLogger("test")
28-
config = {}
29-
handler = HandlerHealth(logger, config)
24+
## Minimal healthy state (just kafka)
25+
def test_get_health_minimal_kafka_healthy():
26+
"""Health check returns 200 when Kafka is initialized and optional writers are disabled."""
27+
handler = HandlerHealth()
3028

31-
# Mock all writers as healthy
3229
with (
33-
patch("src.handlers.handler_health.writer_kafka.STATE", {"logger": logger, "producer": MagicMock()}),
34-
patch(
35-
"src.handlers.handler_health.writer_eventbridge.STATE",
36-
{
37-
"logger": logger,
38-
"client": MagicMock(),
39-
"event_bus_arn": "arn:aws:events:us-east-1:123456789012:event-bus/my-bus",
40-
},
41-
),
42-
patch("src.handlers.handler_health.writer_postgres.logger", logger),
30+
patch("src.handlers.handler_health.writer_kafka.STATE", {"producer": MagicMock()}),
31+
patch("src.handlers.handler_health.writer_eventbridge.STATE", {"client": None, "event_bus_arn": ""}),
32+
patch("src.handlers.handler_health.writer_postgres.POSTGRES", {"database": ""}),
33+
):
34+
response = handler.get_health()
35+
36+
assert response["statusCode"] == 200
37+
body = json.loads(response["body"])
38+
assert body["status"] == "ok"
39+
assert "uptime_seconds" in body
40+
41+
42+
## Healthy state with all writers enabled
43+
def test_get_health_all_writers_enabled_and_healthy():
44+
"""Health check returns 200 when all writers are enabled and properly configured."""
45+
handler = HandlerHealth()
46+
postgres_config = {"database": "db", "host": "localhost", "user": "user", "password": "pass", "port": "5432"}
47+
48+
with (
49+
patch("src.handlers.handler_health.writer_kafka.STATE", {"producer": MagicMock()}),
50+
patch("src.handlers.handler_health.writer_eventbridge.STATE", {"client": MagicMock(), "event_bus_arn": "arn"}),
51+
patch("src.handlers.handler_health.writer_postgres.POSTGRES", postgres_config),
4352
):
4453
response = handler.get_health()
4554

4655
assert response["statusCode"] == 200
4756
body = json.loads(response["body"])
4857
assert body["status"] == "ok"
4958
assert "uptime_seconds" in body
50-
assert isinstance(body["uptime_seconds"], int)
51-
assert body["uptime_seconds"] >= 0
5259

5360

54-
## get_health() - degraded state - kafka
61+
## Degraded state with all writers enabled
5562
def test_get_health_kafka_not_initialized():
5663
"""Health check returns 503 when Kafka writer is not initialized."""
57-
logger = logging.getLogger("test")
58-
config = {}
59-
handler = HandlerHealth(logger, config)
64+
handler = HandlerHealth()
65+
postgres_config = {"database": "db", "host": "", "user": "", "password": "", "port": ""}
6066

61-
# Mock Kafka as not initialized (missing producer key)
6267
with (
63-
patch("src.handlers.handler_health.writer_kafka.STATE", {"logger": logger}),
68+
patch("src.handlers.handler_health.writer_kafka.STATE", {"producer": None}),
6469
patch(
6570
"src.handlers.handler_health.writer_eventbridge.STATE",
66-
{"logger": logger, "client": MagicMock(), "event_bus_arn": "arn"},
71+
{"client": None, "event_bus_arn": "arn:aws:events:us-east-1:123:event-bus/bus"}
6772
),
68-
patch("src.handlers.handler_health.writer_postgres.logger", logger),
73+
patch("src.handlers.handler_health.writer_postgres.POSTGRES", postgres_config),
6974
):
7075
response = handler.get_health()
7176

7277
assert response["statusCode"] == 503
7378
body = json.loads(response["body"])
7479
assert body["status"] == "degraded"
75-
assert "details" in body
76-
assert "kafka" in body["details"]
77-
assert body["details"]["kafka"] == "not_initialized"
80+
assert "kafka" in body["failures"]
81+
assert "eventbridge" in body["failures"]
82+
assert "postgres" in body["failures"]
7883

7984

80-
## get_health() - degraded state - eventbridge
81-
def test_get_health_eventbridge_not_initialized():
82-
"""Health check returns 503 when EventBridge writer is not initialized."""
83-
logger = logging.getLogger("test")
84-
config = {}
85-
handler = HandlerHealth(logger, config)
85+
## Healthy when eventbridge is disabled
86+
def test_get_health_eventbridge_disabled():
87+
"""Health check returns 200 when EventBridge is disabled (empty event_bus_arn)."""
88+
handler = HandlerHealth()
89+
postgres_config = {"database": "db", "host": "localhost", "user": "user", "password": "pass", "port": "5432"}
8690

87-
# Mock EventBridge as not initialized (missing client key)
8891
with (
89-
patch("src.handlers.handler_health.writer_kafka.STATE", {"logger": logger, "producer": MagicMock()}),
90-
patch("src.handlers.handler_health.writer_eventbridge.STATE", {"logger": logger}),
91-
patch("src.handlers.handler_health.writer_postgres.logger", logger),
92+
patch("src.handlers.handler_health.writer_kafka.STATE", {"producer": MagicMock()}),
93+
patch("src.handlers.handler_health.writer_eventbridge.STATE", {"client": None, "event_bus_arn": ""}),
94+
patch("src.handlers.handler_health.writer_postgres.POSTGRES", postgres_config),
9295
):
9396
response = handler.get_health()
9497

95-
assert response["statusCode"] == 503
96-
body = json.loads(response["body"])
97-
assert body["status"] == "degraded"
98-
assert "eventbridge" in body["details"]
99-
assert body["details"]["eventbridge"] == "not_initialized"
98+
assert response["statusCode"] == 200
99+
100+
101+
## Healthy when postgres is disabled
102+
def test_get_health_postgres_disabled():
103+
"""Health check returns 200 when PostgreSQL is disabled (empty database)."""
104+
handler = HandlerHealth()
105+
106+
with (
107+
patch("src.handlers.handler_health.writer_kafka.STATE", {"producer": MagicMock()}),
108+
patch("src.handlers.handler_health.writer_eventbridge.STATE", {"client": MagicMock(), "event_bus_arn": "arn"}),
109+
patch("src.handlers.handler_health.writer_postgres.POSTGRES", {"database": ""}),
110+
):
111+
response = handler.get_health()
112+
113+
assert response["statusCode"] == 200
100114

101115

102-
## get_health() - degraded state - multiple failures
103-
def test_get_health_multiple_dependencies_not_initialized():
104-
"""Health check returns 503 when multiple writers are not initialized."""
105-
logger = logging.getLogger("test")
106-
config = {}
107-
handler = HandlerHealth(logger, config)
116+
## Degraded state - postgres host not configured
117+
def test_get_health_postgres_host_not_configured():
118+
"""Health check returns 503 when PostgreSQL host is not configured."""
119+
handler = HandlerHealth()
120+
postgres_config = {"database": "db", "host": "", "user": "user", "password": "pass", "port": "5432"}
108121

109-
# Mock multiple writers as not initialized
110122
with (
111-
patch("src.handlers.handler_health.writer_kafka.STATE", {}),
112-
patch("src.handlers.handler_health.writer_eventbridge.STATE", {}),
113-
patch("src.handlers.handler_health.writer_postgres", spec=[]), # spec=[] makes logger not exist
123+
patch("src.handlers.handler_health.writer_kafka.STATE", {"producer": MagicMock()}),
124+
patch("src.handlers.handler_health.writer_eventbridge.STATE", {"client": MagicMock(), "event_bus_arn": "arn"}),
125+
patch("src.handlers.handler_health.writer_postgres.POSTGRES", postgres_config),
114126
):
115127
response = handler.get_health()
116128

117129
assert response["statusCode"] == 503
118130
body = json.loads(response["body"])
119-
assert body["status"] == "degraded"
120-
assert len(body["details"]) >= 2 # At least kafka and eventbridge
121-
assert "kafka" in body["details"]
122-
assert "eventbridge" in body["details"]
131+
assert body["failures"]["postgres"] == "host not configured"
123132

124133

125-
## get_health() - uptime calculation
134+
## Uptime calculation
126135
def test_get_health_uptime_is_positive():
127136
"""Verify uptime_seconds is calculated and is a positive integer."""
128-
logger = logging.getLogger("test")
129-
config = {}
130-
handler = HandlerHealth(logger, config)
137+
handler = HandlerHealth()
138+
postgres_config = {"database": "db", "host": "localhost", "user": "user", "password": "pass", "port": "5432"}
131139

132140
with (
133-
patch("src.handlers.handler_health.writer_kafka.STATE", {"logger": logger, "producer": MagicMock()}),
134-
patch(
135-
"src.handlers.handler_health.writer_eventbridge.STATE",
136-
{"logger": logger, "client": MagicMock(), "event_bus_arn": "arn"},
137-
),
138-
patch("src.handlers.handler_health.writer_postgres.logger", logger),
141+
patch("src.handlers.handler_health.writer_kafka.STATE", {"producer": MagicMock()}),
142+
patch("src.handlers.handler_health.writer_eventbridge.STATE", {"client": MagicMock(), "event_bus_arn": "arn"}),
143+
patch("src.handlers.handler_health.writer_postgres.POSTGRES", postgres_config),
139144
):
140145
response = handler.get_health()
141146

0 commit comments

Comments
 (0)