Add basic test

This commit is contained in:
phil 2024-12-14 17:34:48 +01:00
parent f2b924e9e9
commit c2a4aaac51
4 changed files with 128 additions and 22 deletions

View file

@ -60,4 +60,6 @@ dev-dependencies = [
"asyncpg-stubs>=0.29.1", "asyncpg-stubs>=0.29.1",
"types-python-jose>=3.3.4.20240106", "types-python-jose>=3.3.4.20240106",
"types-passlib>=1.7.7.20240311", "types-passlib>=1.7.7.20240311",
"pytest>=8.3.4",
"httpx>=0.28.1",
] ]

View file

@ -9,15 +9,18 @@ from gisaf.baskets import Basket, standard_baskets
from gisaf.redis_tools import store from gisaf.redis_tools import store
from gisaf.registry import registry from gisaf.registry import registry
logger = logging.getLogger('Gisaf admin manager') logger = logging.getLogger("Gisaf admin manager")
class AdminManager: class AdminManager:
""" """
Application wide manager of the admin (baskets). Application wide manager of the admin (baskets).
One instance only, handled by Gisaf's process. One instance only, handled by Gisaf's process.
""" """
store: Store store: Store
baskets: dict[str, Basket] baskets: dict[str, Basket]
async def setup_admin(self): async def setup_admin(self):
""" """
Create the default baskets, scan and create baskets Create the default baskets, scan and create baskets
@ -28,12 +31,9 @@ class AdminManager:
# self.store = app['store'] # self.store = app['store']
## Standard baskets ## Standard baskets
self.baskets = { self.baskets = {basket.name: basket for basket in standard_baskets}
basket.name: basket
for basket in standard_baskets
}
for entry_point in entry_points().select(group='gisaf_extras.baskets'): for entry_point in entry_points().select(group="gisaf_extras.baskets"):
try: try:
basket_class = entry_point.load() basket_class = entry_point.load()
except ModuleNotFoundError as err: except ModuleNotFoundError as err:
@ -46,24 +46,32 @@ class AdminManager:
else: else:
name = basket_class.name name = basket_class.name
if name in self.baskets: if name in self.baskets:
logger.warn(f'Skip basket {name} in {entry_point.module}: name already defined') logger.warning(
f"Skip basket {name} in {entry_point.module}: name already defined"
)
continue continue
## Instanciate ## Instanciate
basket = basket_class() basket = basket_class()
basket._custom_module = entry_point.name # type: ignore basket._custom_module = entry_point.name # type: ignore
## Check base_dir, eventually create it ## Check base_dir, eventually create it
if not basket.base_dir.exists(): if not basket.base_dir.exists():
try: try:
basket.base_dir.mkdir() basket.base_dir.mkdir()
except Exception as err: except Exception as err:
logger.warn(f'Skip basket {name} in {entry_point.module}: ' logger.warning(
f'cannot create directory for basket {name} at {basket.base_dir}') f"Skip basket {name} in {entry_point.module}: "
f"cannot create directory for basket {name} at {basket.base_dir}"
)
continue continue
else: else:
logger.info(f'Created directory for basket {name} at {basket.base_dir}') logger.info(
f"Created directory for basket {name} at {basket.base_dir}"
)
## Add to register ## Add to register
self.baskets[name] = basket self.baskets[name] = basket
logger.info(f'Added Basket {entry_point.name} from {entry_point.module}') logger.info(
f"Added Basket {entry_point.name} from {entry_point.module}"
)
## Give a reference to the application to the baskets ## Give a reference to the application to the baskets
# for basket in self.baskets.values(): # for basket in self.baskets.values():
@ -72,16 +80,17 @@ class AdminManager:
## Subscribe to admin redis channels ## Subscribe to admin redis channels
self.pub_categories = store.redis.pubsub() self.pub_categories = store.redis.pubsub()
self.pub_scheduler = store.redis.pubsub() self.pub_scheduler = store.redis.pubsub()
await self.pub_categories.psubscribe('admin:categories:update') await self.pub_categories.psubscribe("admin:categories:update")
task1 = create_task(self._listen_to_redis_categories()) task1 = create_task(self._listen_to_redis_categories())
await self.pub_scheduler.psubscribe('admin:scheduler:json') await self.pub_scheduler.psubscribe("admin:scheduler:json")
task2 = create_task(self._listen_to_redis_scheduler()) task2 = create_task(self._listen_to_redis_scheduler())
# app['admin'] = self # app['admin'] = self
async def baskets_for_role(self, user: User) -> dict[str, Basket]: async def baskets_for_role(self, user: User) -> dict[str, Basket]:
return { return {
name: basket for name, basket in self.baskets.items() name: basket
for name, basket in self.baskets.items()
if await basket.allowed_for(user) if await basket.allowed_for(user)
} }
@ -90,9 +99,9 @@ class AdminManager:
Subscribe the redis sub channel for category updates ("admin:categories:update") Subscribe the redis sub channel for category updates ("admin:categories:update")
""" """
async for msg in self.pub_categories.listen(): async for msg in self.pub_categories.listen():
if msg['type'] == 'pmessage': if msg["type"] == "pmessage":
## XXX: Why the name isn't retrieved? ## XXX: Why the name isn't retrieved?
#client = await self.app['store'].pub.client_getname() # client = await self.app['store'].pub.client_getname()
client = store.uuid client = store.uuid
## !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ## !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@ -100,8 +109,8 @@ class AdminManager:
## !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ## !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
## Skip for the process which sent this message actually updated its registry ## Skip for the process which sent this message actually updated its registry
#breakpoint() # breakpoint()
if client != msg['data'].decode(): if client != msg["data"].decode():
await registry.make_registry() await registry.make_registry()
async def _listen_to_redis_scheduler(self): async def _listen_to_redis_scheduler(self):
@ -109,8 +118,11 @@ class AdminManager:
Subscribe the redis sub channel for scheduler jobs ("admin:scheduler:json") Subscribe the redis sub channel for scheduler jobs ("admin:scheduler:json")
""" """
async for msg in self.pub_scheduler.listen(): async for msg in self.pub_scheduler.listen():
if msg['type'] == 'pmessage': if msg["type"] == "pmessage":
await live_server._send_to_ws_clients(msg['channel'].decode(), msg['data'].decode()) await live_server._send_to_ws_clients(
msg["channel"].decode(), msg["data"].decode()
)
manager = AdminManager() manager = AdminManager()

27
tests/basic.py Normal file
View file

@ -0,0 +1,27 @@
from fastapi.testclient import TestClient
from gisaf.application import app
client = TestClient(app)
def test_bootstrap():
with TestClient(app) as client:
response = client.get("/api/bootstrap")
assert response.status_code == 200
json = response.json()
assert "version" in json
assert "title" in json
assert "windowTitle" in json
assert "map" in json
assert "geo" in json
assert "measures" in json
assert json["user"] is None
assert "bearing" in json["map"]
assert "lat" in json["map"]
assert "lng" in json["map"]
assert "pitch" in json["map"]
assert "zoom" in json["map"]
assert "style" in json["map"]
assert "status" in json["map"]
assert isinstance(json["map"]["status"], list)

65
uv.lock generated
View file

@ -652,9 +652,11 @@ mqtt = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "asyncpg-stubs" }, { name = "asyncpg-stubs" },
{ name = "httpx" },
{ name = "ipdb" }, { name = "ipdb" },
{ name = "pandas-stubs" }, { name = "pandas-stubs" },
{ name = "pretty-errors" }, { name = "pretty-errors" },
{ name = "pytest" },
{ name = "types-passlib" }, { name = "types-passlib" },
{ name = "types-psycopg2" }, { name = "types-psycopg2" },
{ name = "types-python-jose" }, { name = "types-python-jose" },
@ -699,9 +701,11 @@ requires-dist = [
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "asyncpg-stubs", specifier = ">=0.29.1" }, { name = "asyncpg-stubs", specifier = ">=0.29.1" },
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "ipdb", specifier = ">=0.13.13" }, { name = "ipdb", specifier = ">=0.13.13" },
{ name = "pandas-stubs", specifier = ">=2.1.4.231218" }, { name = "pandas-stubs", specifier = ">=2.1.4.231218" },
{ name = "pretty-errors", specifier = ">=1.2.25" }, { name = "pretty-errors", specifier = ">=1.2.25" },
{ name = "pytest", specifier = ">=8.3.4" },
{ name = "types-passlib", specifier = ">=1.7.7.20240311" }, { name = "types-passlib", specifier = ">=1.7.7.20240311" },
{ name = "types-psycopg2", specifier = ">=2.9.21.20" }, { name = "types-psycopg2", specifier = ">=2.9.21.20" },
{ name = "types-python-jose", specifier = ">=3.3.4.20240106" }, { name = "types-python-jose", specifier = ">=3.3.4.20240106" },
@ -759,6 +763,34 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
] ]
[[package]]
name = "httpcore"
version = "1.0.7"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.10" version = "3.10"
@ -768,6 +800,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
] ]
[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
]
[[package]] [[package]]
name = "ipdb" name = "ipdb"
version = "0.13.13" version = "0.13.13"
@ -1215,6 +1256,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/ae/580600f441f6fc05218bd6c9d5794f4aef072a7d9093b291f1c50a9db8bc/plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089", size = 19054220 }, { url = "https://files.pythonhosted.org/packages/e5/ae/580600f441f6fc05218bd6c9d5794f4aef072a7d9093b291f1c50a9db8bc/plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089", size = 19054220 },
] ]
[[package]]
name = "pluggy"
version = "1.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
]
[[package]] [[package]]
name = "pretty-errors" name = "pretty-errors"
version = "1.2.25" version = "1.2.25"
@ -1517,6 +1567,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/98/2f/68116db5b36b895c0450e3072b8cb6c2fac0359279b182ea97014d3c8ac0/pyshp-2.3.1-py2.py3-none-any.whl", hash = "sha256:67024c0ccdc352ba5db777c4e968483782dfa78f8e200672a90d2d30fd8b7b49", size = 46537 }, { url = "https://files.pythonhosted.org/packages/98/2f/68116db5b36b895c0450e3072b8cb6c2fac0359279b182ea97014d3c8ac0/pyshp-2.3.1-py2.py3-none-any.whl", hash = "sha256:67024c0ccdc352ba5db777c4e968483782dfa78f8e200672a90d2d30fd8b7b49", size = 46537 },
] ]
[[package]]
name = "pytest"
version = "8.3.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 },
]
[[package]] [[package]]
name = "python-dateutil" name = "python-dateutil"
version = "2.9.0.post0" version = "2.9.0.post0"