Skip to content

Commit de60d68

Browse files
committed
Added separate set of tests for healthcheck
1 parent 6144181 commit de60d68

File tree

10 files changed

+161
-28
lines changed

10 files changed

+161
-28
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ COPY tests/*.py ./app/tests/
2525
COPY tools/*.py ./app/tools/
2626

2727
# Request-response-schemas repo/branch to use for validation
28-
ENV SCHEMAS_URL = https://raw.githubusercontent.com/lambda-feedback/request-response-schemas/579-adding-preview-command
28+
ENV SCHEMAS_URL=https://raw.githubusercontent.com/lambda-feedback/request-response-schemas/579-adding-preview-command

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ requests
33
evaluation-function-utils
44
typing_extensions
55
black
6-
autopep8
6+
autopep8
7+
pytest
8+
python-dotenv

tests/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +0,0 @@
1-
from .commands import TestCommandsModule # noqa
2-
from .docs import TestDocsModule # noqa
3-
from .parse import TestParseModule # noqa
4-
from .requests import TestRequestValidation # noqa
5-
from .responses import TestResponseValidation # noqa

tests/commands.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ def evaluation_function(
1717

1818

1919
class TestCommandsModule(unittest.TestCase):
20+
def __init__(self, methodName: str = "runTest") -> None:
21+
super().__init__(methodName)
22+
2023
def setUp(self) -> None:
2124
commands.evaluation_function = evaluation_function
2225
return super().setUp()
@@ -344,6 +347,35 @@ def test_valid_preview_command(self):
344347

345348
self.assertEqual(result["preview"], "hello")
346349

350+
def test_invalid_preview_args_raises_parse_error(self):
351+
event = {"headers": "any", "other": "params"}
352+
353+
with self.assertRaises(parse.ParseError) as e:
354+
commands.preview(event)
355+
356+
self.assertEqual(
357+
e.exception.message, "No data supplied in request body."
358+
)
359+
360+
def test_invalid_preview_schema_raises_validation_error(self):
361+
event = {"body": {"response": "hello", "answer": "hello"}}
362+
363+
with self.assertRaises(validate.ValidationError) as e:
364+
commands.preview(event)
365+
366+
self.assertEqual(
367+
e.exception.message,
368+
"Failed to validate body against the preview schema.",
369+
)
370+
371+
def test_healthcheck(self):
372+
response = commands.healthcheck()
373+
374+
self.assertIn("result", response)
375+
result = response["result"] # type: ignore
376+
377+
self.assertIn("tests_passed", result)
378+
347379

348380
if __name__ == "__main__":
349381
unittest.main()

tests/docs.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ def test_handling_missing_doc(self):
3232
"tests/non-existent-doc.md missing from evaluation function files",
3333
)
3434

35-
"""Can't test available dev docs without the layer above."""
35+
"""
36+
Can't test available dev docs without the layer above.
37+
38+
Instead, we can only test that missing docs are correctly handled.
39+
However, we test for available docs in the healthcheck smoke tests.
40+
"""
3641

3742
def test_handling_missing_user_docs(self):
3843
result = docs.user()

tests/handling.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,6 @@ def test_healthcheck(self):
114114
self.assertEqual(response.get("command"), "healthcheck")
115115
self.assertIn("result", response)
116116

117-
result = response.get("result")
118-
119-
self.assertTrue(result["tests_passed"]) # type: ignore
120-
121117
def test_invalid_command(self):
122118
event = {
123119
"random": "metadata",

tools/commands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from .validate import ReqBodyValidators
1515

1616
try:
17-
from ..evaluate import evaluation_function # type: ignore
17+
from ..evaluation import evaluation_function # type: ignore
1818

1919
except ImportError:
2020
evaluation_function: Optional[EvaluationFunctionType] = None

tools/healthcheck.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,7 @@
77

88
from typing_extensions import NotRequired
99

10-
from ..tests.commands import TestCommandsModule
11-
from ..tests.docs import TestDocsModule
12-
from ..tests.parse import TestParseModule
13-
from ..tests.requests import TestRequestValidation
14-
from ..tests.responses import TestResponseValidation
10+
from .smoke_tests import SmokeTests
1511

1612
try:
1713
from ..evaluation_tests import TestEvaluationFunction # type: ignore
@@ -130,17 +126,13 @@ def healthcheck() -> HealthcheckJsonTestResult:
130126
"""
131127
# Redirect stderr stream to null to prevent logging unittest results
132128
no_stream = open(os.devnull, "w")
133-
sys.stderr = no_stream
129+
# sys.stderr = no_stream
134130

135131
# Create a test loader and test runner instance
136132
loader = unittest.TestLoader()
137133

138134
cases = (
139-
TestRequestValidation,
140-
TestResponseValidation,
141-
TestCommandsModule,
142-
TestParseModule,
143-
TestDocsModule,
135+
SmokeTests,
144136
TestEvaluationFunction,
145137
TestPreviewFunction,
146138
)

tools/smoke_tests.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import inspect
2+
import unittest
3+
4+
import jsonschema
5+
6+
from . import docs, validate
7+
8+
9+
class SmokeTests(unittest.TestCase):
10+
"""Tests to check that all functions, files and tests are defined
11+
in the evaluation function by the user.
12+
13+
Base Evaluation Function tests are no longer applied in the healthcheck,
14+
instead we only check that the necessary functions, classes and files
15+
are defined (here) and the user-defined tests pass.
16+
17+
Base Evaluation Function tests are only applied when pushing to Docker.
18+
19+
This test case also checks that all schemas are working appropriately.
20+
"""
21+
22+
def test_import_evaluation_function(self):
23+
from .. import evaluation # type: ignore
24+
25+
self.assertTrue(inspect.ismodule(evaluation))
26+
self.assertIsNotNone(evaluation.evaluation_function)
27+
28+
@unittest.skip("Ignored until all functions are updated with preview.")
29+
def test_import_preview_function(self):
30+
from .. import preview # type: ignore
31+
32+
self.assertTrue(inspect.ismodule(preview))
33+
self.assertIsNotNone(preview.preview_function)
34+
35+
def test_import_evaluation_tests(self):
36+
from .. import evaluation_tests # type: ignore
37+
38+
self.assertTrue(inspect.ismodule(evaluation_tests))
39+
self.assertIsNotNone(evaluation_tests.TestEvaluationFunction)
40+
41+
@unittest.skip("Ignored until all functions are updated with preview.")
42+
def test_import_preview_tests(self):
43+
from .. import preview_tests # type: ignore
44+
45+
self.assertTrue(inspect.ismodule(preview_tests))
46+
self.assertIsNotNone(preview_tests.TestPreviewFunction)
47+
48+
def test_load_dev_docs(self):
49+
result = docs.dev()
50+
51+
self.assertEqual(result["statusCode"], 200)
52+
self.assertDictEqual(
53+
result["headers"], {"Content-Type": "application/octet-stream"}
54+
)
55+
self.assertTrue(result["isBase64Encoded"])
56+
57+
def test_load_user_docs(self):
58+
result = docs.user()
59+
60+
self.assertEqual(result["statusCode"], 200)
61+
self.assertDictEqual(
62+
result["headers"], {"Content-Type": "application/octet-stream"}
63+
)
64+
self.assertTrue(result["isBase64Encoded"])
65+
66+
def test_load_eval_req_schema(self):
67+
schema = validate.load_validator_from_url(
68+
validate.ReqBodyValidators.EVALUATION
69+
)
70+
71+
self.assertIsInstance(schema, jsonschema.Draft7Validator)
72+
73+
def test_load_preview_req_schema(self):
74+
schema = validate.load_validator_from_url(
75+
validate.ReqBodyValidators.PREVIEW
76+
)
77+
78+
self.assertIsInstance(schema, jsonschema.Draft7Validator)
79+
80+
def test_load_eval_res_schema(self):
81+
schema = validate.load_validator_from_url(
82+
validate.ResBodyValidators.EVALUATION
83+
)
84+
85+
self.assertIsInstance(schema, jsonschema.Draft7Validator)
86+
87+
def test_load_preview_res_schema(self):
88+
schema = validate.load_validator_from_url(
89+
validate.ResBodyValidators.PREVIEW
90+
)
91+
92+
self.assertIsInstance(schema, jsonschema.Draft7Validator)
93+
94+
def test_load_health_res_schema(self):
95+
schema = validate.load_validator_from_url(
96+
validate.ResBodyValidators.HEALTHCHECK
97+
)
98+
99+
self.assertIsInstance(schema, jsonschema.Draft7Validator)

tools/validate.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
import os
44
from typing import Dict, List, TypedDict, Union
55

6+
import dotenv
67
import jsonschema
78
import jsonschema.exceptions
89
import requests
910

11+
dotenv.load_dotenv()
12+
1013

1114
class SchemaErrorThrown(TypedDict):
1215
"""Detail object returned in the error response for schema exceptions."""
@@ -52,7 +55,9 @@ class ResBodyValidators(enum.Enum):
5255

5356

5457
@functools.lru_cache
55-
def load_validator_from_url(validator_enum: BodyValidators):
58+
def load_validator_from_url(
59+
validator_enum: BodyValidators,
60+
) -> jsonschema.Draft7Validator:
5661
"""Loads a json schema for body validations.
5762
5863
Args:
@@ -67,12 +72,19 @@ def load_validator_from_url(validator_enum: BodyValidators):
6772
schemas_url = os.environ.get("SCHEMAS_URL")
6873

6974
if schemas_url is None:
70-
raise ValueError("Schema URL is not defined in base layer.")
75+
raise RuntimeError("Schema URL is not defined in base layer.")
7176

7277
schema_url = os.path.join(schemas_url, validator_enum.value)
7378

74-
schema = requests.get(schema_url).json()
75-
return jsonschema.Draft7Validator(schema)
79+
response = requests.get(schema_url)
80+
81+
if response.status_code != 200:
82+
raise RuntimeError(
83+
f"Failed to get {validator_enum.name.lower()} "
84+
f"schema (status code {response.status_code})."
85+
)
86+
87+
return jsonschema.Draft7Validator(response.json())
7688

7789

7890
def body(body: Union[Dict, TypedDict], validator_enum: BodyValidators) -> None:

0 commit comments

Comments
 (0)