Skip to content

Commit 128f19a

Browse files
authored
Merge pull request #16 from eadwinCode/app_factory_test
AppFactory and TestClientFactory Test
2 parents ddc9134 + e33f0a3 commit 128f19a

File tree

10 files changed

+337
-136
lines changed

10 files changed

+337
-136
lines changed

ellar/core/factory.py

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from ellar.core import Config
1414
from ellar.core.main import App
1515
from ellar.core.modules import ModuleBase
16-
from ellar.core.modules.ref import ModuleTemplateRef, create_module_ref_factor
16+
from ellar.core.modules.ref import create_module_ref_factor
1717
from ellar.di import EllarInjector, ProviderConfig
1818
from ellar.reflect import reflect
1919

@@ -25,7 +25,7 @@
2525
class AppFactory:
2626
@classmethod
2727
def _read_all_module(
28-
cls, module: t.Type[ModuleBase]
28+
cls, module: t.Type[t.Union[ModuleBase, t.Any]]
2929
) -> t.Dict[t.Type, t.Type[ModuleBase]]:
3030
modules = reflect.get_metadata(MODULE_METADATA.MODULES, module) or []
3131
module_dependency = OrderedDict()
@@ -36,11 +36,14 @@ def _read_all_module(
3636

3737
@classmethod
3838
def _build_modules(
39-
cls, app_module: t.Type[ModuleBase], config: Config, injector: EllarInjector
39+
cls,
40+
app_module: t.Type[t.Union[ModuleBase, t.Any]],
41+
config: Config,
42+
injector: EllarInjector,
4043
) -> None:
4144
assert reflect.get_metadata(
4245
MODULE_WATERMARK, app_module
43-
), "Only ApplicationModule is allowed"
46+
), "Only Module is allowed"
4447

4548
module_dependency = [app_module] + list(
4649
cls._read_all_module(app_module).values()
@@ -56,19 +59,10 @@ def _build_modules(
5659
)
5760
injector.add_module(module_ref)
5861

59-
@classmethod
60-
def _run_module_application_ready(
61-
cls,
62-
modules: t.Dict[t.Type[ModuleBase], ModuleTemplateRef],
63-
app: App,
64-
) -> None:
65-
for _, module_ref in modules.items():
66-
module_ref.run_application_ready(app)
67-
6862
@classmethod
6963
def _create_app(
7064
cls,
71-
module: t.Type[ModuleBase],
65+
module: t.Type[t.Union[ModuleBase, t.Any]],
7266
global_guards: t.List[
7367
t.Union[t.Type["GuardCanActivate"], "GuardCanActivate"]
7468
] = None,
@@ -95,9 +89,6 @@ def _create_app(
9589
global_guards=global_guards,
9690
)
9791

98-
cls._run_module_application_ready(
99-
modules=injector.get_templating_modules(), app=app
100-
)
10192
return app
10293

10394
@classmethod
@@ -108,7 +99,7 @@ def create_app(
10899
t.Union["ModuleRouter", "ModuleMount", Mount, Host]
109100
] = tuple(),
110101
providers: t.Sequence[t.Union[t.Type, "ProviderConfig"]] = tuple(),
111-
modules: t.Sequence[t.Type] = (),
102+
modules: t.Sequence[t.Type[t.Union[ModuleBase, t.Any]]] = (),
112103
template_folder: t.Optional[str] = None,
113104
base_directory: t.Optional[str] = None,
114105
static_folder: str = "static",
@@ -131,7 +122,7 @@ def create_app(
131122
app_factory_module = type(f"Module{uuid4().hex[:6]}", (ModuleBase,), {})
132123
module(app_factory_module)
133124
return cls._create_app(
134-
t.cast(t.Type[ModuleBase], app_factory_module),
125+
module=app_factory_module,
135126
config_module=config_module,
136127
global_guards=global_guards,
137128
)

ellar/core/main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,12 @@ def enable_versioning(
219219
**init_kwargs,
220220
)
221221

222+
def _run_module_application_ready(
223+
self,
224+
) -> None:
225+
for _, module_ref in self.injector.get_templating_modules().items():
226+
module_ref.run_application_ready(self)
227+
222228
def _finalize_app_initialization(self) -> None:
223229
self.injector.container.register_instance(self)
224230
self.injector.container.register_instance(Reflector())
@@ -228,6 +234,7 @@ def _finalize_app_initialization(self) -> None:
228234
IExecutionContext,
229235
ModuleProvider(ExecutionContext, scope={}, receive=None, send=None),
230236
)
237+
self._run_module_application_ready()
231238

232239
def add_exception_handler(
233240
self,

ellar/core/testclient.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@
22

33
from starlette.testclient import TestClient as TestClient
44

5-
from ellar.constants import MODULE_METADATA, MODULE_WATERMARK
65
from ellar.core import ModuleBase
76
from ellar.core.factory import AppFactory
87
from ellar.core.main import App
98
from ellar.core.routing import ModuleRouter
109
from ellar.di import ProviderConfig
11-
from ellar.reflect import reflect
1210

1311

1412
class _TestingModule:
@@ -39,7 +37,7 @@ def create_test_module(
3937
cls,
4038
controllers: t.Sequence[t.Union[t.Any]] = tuple(),
4139
routers: t.Sequence[ModuleRouter] = tuple(),
42-
services: t.Sequence[ProviderConfig] = tuple(),
40+
providers: t.Sequence[ProviderConfig] = tuple(),
4341
template_folder: t.Optional[str] = None,
4442
base_directory: t.Optional[str] = None,
4543
static_folder: str = "static",
@@ -48,7 +46,7 @@ def create_test_module(
4846
app = AppFactory.create_app(
4947
controllers=controllers,
5048
routers=routers,
51-
providers=services,
49+
providers=providers,
5250
template_folder=template_folder,
5351
base_directory=base_directory,
5452
static_folder=static_folder,
@@ -60,22 +58,10 @@ def create_test_module(
6058
def create_test_module_from_module(
6159
cls,
6260
module: t.Union[t.Type, t.Type[ModuleBase]],
63-
mock_services: t.Sequence[ProviderConfig] = tuple(),
61+
mock_providers: t.Sequence[ProviderConfig] = tuple(),
6462
config_module: str = None,
6563
) -> _TestingModule:
66-
app_module_watermark = reflect.get_metadata(MODULE_WATERMARK, module)
67-
if mock_services:
68-
reflect.define_metadata(
69-
MODULE_METADATA.PROVIDERS, list(mock_services), module, default_value=[]
70-
)
71-
72-
if app_module_watermark:
73-
return _TestingModule(
74-
app=AppFactory.create_from_app_module(
75-
module, config_module=config_module
76-
)
77-
)
7864
app = AppFactory.create_app(
79-
modules=(module,), providers=mock_services, config_module=config_module
65+
modules=(module,), providers=mock_providers, config_module=config_module
8066
)
8167
return _TestingModule(app=app)

tests/notstarted/test_application.py

Whitespace-only changes.

tests/notstarted/test_application_factory.py

Whitespace-only changes.

tests/notstarted/test_testclient_factory.py

Whitespace-only changes.

tests/test_application/sample.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import os
2+
3+
from starlette.exceptions import HTTPException
4+
from starlette.responses import JSONResponse, PlainTextResponse
5+
from starlette.routing import Host, Mount, Route, Router
6+
7+
from ellar.common import Controller, Module, ModuleRouter, exception_handler, get
8+
from ellar.core.guard import APIKeyQuery
9+
10+
11+
def create_tmp_template_and_static_dir(tmpdir):
12+
template_folder = os.path.join(tmpdir, "templates")
13+
os.mkdir(template_folder)
14+
static_folder = os.path.join(tmpdir, "statics")
15+
os.mkdir(static_folder)
16+
17+
path = os.path.join(static_folder, "example.txt")
18+
with open(path, "w") as file:
19+
file.write("<file content>")
20+
21+
path = os.path.join(template_folder, "example.html")
22+
with open(path, "w") as file:
23+
file.write("<html>Hello World<html/>")
24+
return template_folder, static_folder
25+
26+
27+
class AppAPIKey(APIKeyQuery):
28+
async def authenticate(self, connection, key):
29+
return key
30+
31+
32+
def custom_sub_domain(request):
33+
return PlainTextResponse("Subdomain: " + request.path_params["subdomain"])
34+
35+
36+
def all_users_page(request):
37+
return PlainTextResponse("Hello, everyone!")
38+
39+
40+
def user_page(request):
41+
username = request.path_params["username"]
42+
return PlainTextResponse(f"Hello, {username}!")
43+
44+
45+
sub_domain = Router(
46+
routes=[
47+
Route("/", custom_sub_domain),
48+
]
49+
)
50+
51+
users = Router(
52+
routes=[
53+
Route("/", endpoint=all_users_page),
54+
Route("/{username}", endpoint=user_page),
55+
]
56+
)
57+
58+
router = ModuleRouter()
59+
60+
61+
@router.get("/func")
62+
@router.head("/func")
63+
def func_homepage(request):
64+
return PlainTextResponse("Hello, world!")
65+
66+
67+
@router.get("/async")
68+
async def async_homepage(request):
69+
return PlainTextResponse("Hello, world!")
70+
71+
72+
@router.ws_route("/ws")
73+
async def websocket_endpoint(session):
74+
await session.accept()
75+
await session.send_text("Hello, world!")
76+
await session.close()
77+
78+
79+
@Controller
80+
class ClassBaseController:
81+
@get("/class")
82+
def class_function(self):
83+
return PlainTextResponse("Hello, world!")
84+
85+
@get("/500")
86+
def runtime_error(self, request):
87+
raise RuntimeError()
88+
89+
90+
@Module(
91+
routers=[
92+
Host("{subdomain}.example.org", app=sub_domain),
93+
Mount("/users", app=users),
94+
router,
95+
],
96+
controllers=[ClassBaseController],
97+
)
98+
class ApplicationModule:
99+
@exception_handler(405)
100+
async def method_not_allow_exception(self, request, exec):
101+
return JSONResponse({"detail": "Custom message"}, status_code=405)
102+
103+
@exception_handler(500)
104+
async def error_500(self, request, exec):
105+
return JSONResponse({"detail": "Server Error"}, status_code=500)
106+
107+
@exception_handler(HTTPException)
108+
async def http_exception(self, request, exc):
109+
return JSONResponse({"detail": exc.detail}, status_code=exc.status_code)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from starlette.routing import Host, Mount
2+
3+
from ellar.common import Module
4+
from ellar.core import AppFactory, Config, ModuleBase, TestClient
5+
from ellar.di import EllarInjector
6+
7+
from .sample import (
8+
AppAPIKey,
9+
ApplicationModule,
10+
ClassBaseController,
11+
create_tmp_template_and_static_dir,
12+
router,
13+
sub_domain,
14+
users,
15+
)
16+
17+
18+
@Module(modules=[ApplicationModule])
19+
class AModule:
20+
pass
21+
22+
23+
@Module(modules=(AModule,))
24+
class BModule:
25+
pass
26+
27+
28+
def test_factory__read_all_module():
29+
modules_dict = AppFactory._read_all_module(module=BModule)
30+
assert len(modules_dict) == 2
31+
assert list(modules_dict.values())[0] is AModule
32+
assert list(modules_dict.values())[1] is ApplicationModule
33+
34+
modules_dict = AppFactory._read_all_module(module=AModule)
35+
assert len(modules_dict) == 1
36+
assert list(modules_dict.values())[0] is ApplicationModule
37+
38+
modules_dict = AppFactory._read_all_module(module=ApplicationModule)
39+
assert len(modules_dict) == 0
40+
41+
42+
def test_factory__build_modules():
43+
config = Config()
44+
injector = EllarInjector()
45+
assert len(injector.get_modules()) == 0
46+
47+
AppFactory._build_modules(app_module=BModule, config=config, injector=injector)
48+
module_refs = injector.get_modules()
49+
assert len(module_refs) == 3
50+
51+
modules = [AModule, ApplicationModule, BModule]
52+
for k, v in module_refs.items():
53+
assert k in modules
54+
55+
56+
def test_factory_create_from_app_module():
57+
app = AppFactory.create_from_app_module(
58+
module=BModule,
59+
global_guards=[AppAPIKey],
60+
config_module="tests.test_application.settings",
61+
)
62+
assert app.get_guards() == [AppAPIKey]
63+
assert app.config.config_module == "tests.test_application.settings"
64+
65+
66+
def test_factory_create_app_dynamically_creates_module():
67+
app = AppFactory.create_app()
68+
first_module_ref = next(app.get_module_loaders())
69+
module_instance = first_module_ref.get_module_instance()
70+
assert isinstance(module_instance, ModuleBase)
71+
assert "ellar.core.factory.Module" in str(module_instance.__class__)
72+
73+
74+
def test_factory_create_app_works(tmpdir):
75+
create_tmp_template_and_static_dir(tmpdir)
76+
77+
@Module()
78+
class CModule:
79+
pass
80+
81+
app = AppFactory.create_app(
82+
modules=(CModule,),
83+
global_guards=[AppAPIKey],
84+
config_module="tests.test_application.settings",
85+
controllers=(ClassBaseController,),
86+
routers=[
87+
Host("{subdomain}.example.org", app=sub_domain),
88+
Mount("/users", app=users),
89+
router,
90+
],
91+
template_folder="templates",
92+
static_folder="statics",
93+
base_directory=tmpdir,
94+
)
95+
96+
client = TestClient(app)
97+
res = client.get("/static/example.txt")
98+
assert res.status_code == 200
99+
assert res.text == "<file content>"
100+
101+
template = app.jinja_environment.get_template("example.html")
102+
result = template.render()
103+
assert result == "<html>Hello World<html/>"

0 commit comments

Comments
 (0)