Skip to content

Commit a683db0

Browse files
committed
Added test for file decorator, FileResponseModel and StreamingResponseModel
1 parent e1becb2 commit a683db0

File tree

6 files changed

+353
-1
lines changed

6 files changed

+353
-1
lines changed

tests/private/test.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.div {background: red}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import pytest
2+
3+
from ellar.common import Controller, file, get
4+
from ellar.constants import CONTROLLER_OPERATION_HANDLER_KEY, RESPONSE_OVERRIDE_KEY
5+
from ellar.core.connection import Request
6+
from ellar.core.response.model import (
7+
FileResponseModel,
8+
ResponseModelField,
9+
StreamingResponseModel,
10+
)
11+
from ellar.reflect import reflect
12+
13+
14+
def test_file_decorator_works():
15+
@file(media_type="text/javascript")
16+
def endpoint_file(request: Request):
17+
pass
18+
19+
response_override = reflect.get_metadata(RESPONSE_OVERRIDE_KEY, endpoint_file)
20+
assert isinstance(response_override, dict)
21+
file_response: FileResponseModel = response_override[200]
22+
assert isinstance(file_response, FileResponseModel)
23+
assert file_response.media_type == "text/javascript"
24+
assert isinstance(file_response.get_init_kwargs_schema(), ResponseModelField)
25+
26+
27+
def test_file_streaming_decorator_works():
28+
@file(media_type="text/javascript", streaming=True)
29+
def endpoint_file(request: Request):
30+
pass
31+
32+
response_override = reflect.get_metadata(RESPONSE_OVERRIDE_KEY, endpoint_file)
33+
assert isinstance(response_override, dict)
34+
file_response: StreamingResponseModel = response_override[200]
35+
assert isinstance(file_response, StreamingResponseModel)
36+
assert file_response.media_type == "text/javascript"
37+
38+
39+
def test_render_decorator_raise_exception_for_invalid_template_name():
40+
with pytest.raises(AssertionError, match="File decorator must invoked eg. @file()"):
41+
42+
@file
43+
def endpoint_file(request: Request):
44+
pass
45+
46+
47+
def test_file_decorator_wont_work_after_route_action_definition():
48+
@file()
49+
class Whatever:
50+
pass
51+
52+
response_override = reflect.get_metadata(RESPONSE_OVERRIDE_KEY, Whatever)
53+
assert response_override is None
54+
55+
56+
def test_file_decorator_uses_endpoint_name_as_template_name():
57+
@Controller
58+
class AFileController:
59+
@get("/endpoint_file")
60+
@file(media_type="text/javascript")
61+
def endpoint_file(self, request: Request):
62+
pass
63+
64+
a_controller_operations = reflect.get_metadata(
65+
CONTROLLER_OPERATION_HANDLER_KEY, AFileController
66+
)
67+
assert len(a_controller_operations) == 1
68+
endpoint_render_operation = a_controller_operations[0]
69+
response_override = reflect.get_metadata(
70+
RESPONSE_OVERRIDE_KEY, endpoint_render_operation.endpoint
71+
)
72+
73+
file_response: FileResponseModel = response_override[200]
74+
assert isinstance(file_response, FileResponseModel)
75+
assert file_response.media_type == "text/javascript"
76+
assert isinstance(file_response.get_init_kwargs_schema(), ResponseModelField)
77+
78+
79+
def test_file_stream_decorator_uses_endpoint_name_as_template_name():
80+
@Controller
81+
class AStreamFileController:
82+
@get("/endpoint_file")
83+
@file(media_type="text/javascript", streaming=True)
84+
def endpoint_file(self, request: Request):
85+
pass
86+
87+
a_controller_operations = reflect.get_metadata(
88+
CONTROLLER_OPERATION_HANDLER_KEY, AStreamFileController
89+
)
90+
assert len(a_controller_operations) == 1
91+
endpoint_render_operation = a_controller_operations[0]
92+
response_override = reflect.get_metadata(
93+
RESPONSE_OVERRIDE_KEY, endpoint_render_operation.endpoint
94+
)
95+
96+
file_response: StreamingResponseModel = response_override[200]
97+
assert isinstance(file_response, StreamingResponseModel)
98+
assert file_response.media_type == "text/javascript"

tests/test_common/test_decorators/test_html.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ def endpoint_render(request: Request):
2727
assert html_response.template_name == "index"
2828

2929

30+
def test_render_decorator_wont_work_after_route_action_definition():
31+
@render("index")
32+
class Whatever:
33+
pass
34+
35+
response_override = reflect.get_metadata(RESPONSE_OVERRIDE_KEY, Whatever)
36+
assert response_override is None
37+
38+
3039
def test_render_decorator_raise_exception_for_invalid_template_name():
3140
with pytest.raises(
3241
AssertionError, match="Render Operation must invoked eg. @render()"
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from pathlib import Path
2+
3+
import pytest
4+
5+
from ellar.common import Controller, file, get
6+
from ellar.core import TestClientFactory
7+
from ellar.core.response.model import FileResponseModel
8+
from ellar.core.routing import ModuleRouter
9+
from ellar.openapi import OpenAPIDocumentBuilder
10+
from ellar.serializer import serialize_object
11+
12+
BASEDIR = Path(__file__).resolve().parent.parent
13+
FILE_RESPONSE_SCHEMA = {
14+
"200": {
15+
"description": "Successful Response",
16+
"content": {"text/css": {"schema": {"type": "string"}}},
17+
}
18+
}
19+
20+
21+
mr = ModuleRouter("/mr")
22+
23+
24+
@mr.get("/index-manual", response=FileResponseModel(media_type="text/css"))
25+
def file_template():
26+
return {"path": f"{BASEDIR}/private/test.css", "filename": "file-test-css.css"}
27+
28+
29+
@mr.get("/index-decorator")
30+
@file(media_type="text/css")
31+
def render_template():
32+
return {"path": f"{BASEDIR}/private/test.css", "filename": "file-test-css.css"}
33+
34+
35+
@Controller
36+
class EllarController:
37+
@get(
38+
"/index-manual",
39+
response={200: FileResponseModel(media_type="text/css")},
40+
)
41+
def index2(self):
42+
"""Looks for ellar/index since use_mvc=True"""
43+
return {"path": f"{BASEDIR}/private/test.css", "filename": "file-test-css.css"}
44+
45+
@get("/index-decorator")
46+
@file(media_type="text/css")
47+
def index(self):
48+
"""detest its mvc and Looks for ellar/index"""
49+
return {"path": f"{BASEDIR}/private/test.css", "filename": "file-test-css.css"}
50+
51+
@get("/index-invalid")
52+
@file(media_type="text/css")
53+
def index3(self):
54+
"""detest its mvc and Looks for ellar/index"""
55+
return {
56+
"path": f"{BASEDIR}/private/test.css",
57+
"filename": "file-test-css.css",
58+
"content_disposition_type": "whatever",
59+
}
60+
61+
62+
test_module = TestClientFactory.create_test_module(
63+
controllers=(EllarController,),
64+
routers=(mr,),
65+
)
66+
document = serialize_object(OpenAPIDocumentBuilder().build_document(test_module.app))
67+
68+
69+
@pytest.mark.parametrize(
70+
"path",
71+
[
72+
"/ellar/index-manual",
73+
"/ellar/index-decorator",
74+
"/mr/index-manual",
75+
"/mr/index-decorator",
76+
],
77+
)
78+
def test_file_response_for_module_router_and_controller(path):
79+
client = test_module.get_client()
80+
response = client.get(path)
81+
response.raise_for_status()
82+
assert (
83+
response.headers["content-disposition"]
84+
== 'attachment; filename="file-test-css.css"'
85+
)
86+
assert response.headers["content-length"] == "22"
87+
assert response.headers["etag"]
88+
assert response.text == ".div {background: red}"
89+
90+
91+
@pytest.mark.parametrize(
92+
"path",
93+
[
94+
"/ellar/index-manual",
95+
"/ellar/index-decorator",
96+
"/mr/index-manual",
97+
"/mr/index-decorator",
98+
],
99+
)
100+
def test_response_schema(path):
101+
path_response = document["paths"][path]["get"]["responses"]
102+
assert path_response == FILE_RESPONSE_SCHEMA
103+
104+
105+
def test_invalid_parameter_returned():
106+
client = test_module.get_client()
107+
response = client.get("/ellar/index-invalid")
108+
assert response.status_code == 422
109+
110+
assert response.json() == {
111+
"detail": [
112+
{
113+
"loc": ["response_model", "content_disposition_type"],
114+
"msg": "value is not a valid enumeration member; permitted: 'inline', 'attachment'",
115+
"type": "type_error.enum",
116+
"ctx": {"enum_values": ["inline", "attachment"]},
117+
}
118+
]
119+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import asyncio
2+
from pathlib import Path
3+
4+
import pytest
5+
6+
from ellar.common import Controller, file, get
7+
from ellar.core import TestClientFactory
8+
from ellar.core.response.model import (
9+
StreamingResponseModel,
10+
StreamingResponseModelInvalidContent,
11+
)
12+
from ellar.core.routing import ModuleRouter
13+
from ellar.openapi import OpenAPIDocumentBuilder
14+
from ellar.serializer import serialize_object
15+
16+
BASEDIR = Path(__file__).resolve().parent.parent
17+
FILE_RESPONSE_SCHEMA = {
18+
"200": {
19+
"description": "Successful Response",
20+
"content": {"text/html": {"schema": {"type": "string"}}},
21+
}
22+
}
23+
24+
25+
async def slow_numbers(minimum: int, maximum: int):
26+
yield ("<html><body><ul>")
27+
for number in range(minimum, maximum + 1):
28+
yield "<li>%d</li>" % number
29+
await asyncio.sleep(0.01)
30+
yield ("</ul></body></html>")
31+
32+
33+
streaming_mr = ModuleRouter("/mr")
34+
35+
36+
@streaming_mr.get(
37+
"/index-manual", response=StreamingResponseModel(media_type="text/html")
38+
)
39+
def file_template():
40+
return slow_numbers(1, 4)
41+
42+
43+
@streaming_mr.get("/index-decorator")
44+
@file(media_type="text/html", streaming=True)
45+
def render_template():
46+
return slow_numbers(1, 4)
47+
48+
49+
@Controller
50+
class EllarController:
51+
@get(
52+
"/index-manual",
53+
response={200: StreamingResponseModel(media_type="text/html")},
54+
)
55+
def index2(self):
56+
"""Looks for ellar/index since use_mvc=True"""
57+
return slow_numbers(1, 4)
58+
59+
@get("/index-decorator")
60+
@file(media_type="text/html", streaming=True)
61+
def index(self):
62+
"""detest its mvc and Looks for ellar/index"""
63+
return slow_numbers(1, 4)
64+
65+
@get("/index-invalid")
66+
@file(media_type="text/html", streaming=True)
67+
def index3(self):
68+
"""detest its mvc and Looks for ellar/index"""
69+
return {
70+
"path": f"{BASEDIR}/private/test.css",
71+
"filename": "file-test-css.css",
72+
"content_disposition_type": "whatever",
73+
}
74+
75+
76+
test_module = TestClientFactory.create_test_module(
77+
controllers=(EllarController,),
78+
routers=(streaming_mr,),
79+
)
80+
document = serialize_object(OpenAPIDocumentBuilder().build_document(test_module.app))
81+
82+
83+
@pytest.mark.parametrize(
84+
"path",
85+
[
86+
"/ellar/index-manual",
87+
"/ellar/index-decorator",
88+
"/mr/index-manual",
89+
"/mr/index-decorator",
90+
],
91+
)
92+
def test_file_stream_response_for_module_router_and_controller(path):
93+
client = test_module.get_client()
94+
response = client.get(path)
95+
response.raise_for_status()
96+
assert response.status_code == 200
97+
assert (
98+
response.text
99+
== "<html><body><ul><li>1</li><li>2</li><li>3</li><li>4</li></ul></body></html>"
100+
)
101+
102+
103+
@pytest.mark.parametrize(
104+
"path",
105+
[
106+
"/ellar/index-manual",
107+
"/ellar/index-decorator",
108+
"/mr/index-manual",
109+
"/mr/index-decorator",
110+
],
111+
)
112+
def test_response_schema(path):
113+
path_response = document["paths"][path]["get"]["responses"]
114+
assert path_response == FILE_RESPONSE_SCHEMA
115+
116+
117+
def test_invalid_parameter_returned():
118+
client = test_module.get_client()
119+
with pytest.raises(
120+
StreamingResponseModelInvalidContent,
121+
match="Content must typing.AsyncIterable OR typing.Iterable",
122+
):
123+
client.get("/ellar/index-invalid")

tests/test_response/test_route_response_model.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class JsonApiResponse(JSONResponse):
2020

2121
class JsonApiResponseModel(ResponseModel):
2222
response_type = JsonApiResponse
23+
model_field_or_schema = List[Union[NoteSchemaDC, BlogObjectDTO]]
24+
default_description = "Successful JsonAPI Response"
2325

2426

2527
@pytest.mark.parametrize(
@@ -63,7 +65,7 @@ class JsonApiResponseModel(ResponseModel):
6365
{200: JsonApiResponseModel()},
6466
JsonApiResponseModel,
6567
200,
66-
"Successful Response",
68+
"Successful JsonAPI Response",
6769
"application/vnd.api+json",
6870
),
6971
(

0 commit comments

Comments
 (0)