Skip to content

Commit

Permalink
✨(api) introduce Moodle indexers
Browse files Browse the repository at this point in the history
Added tools to index from Moodle:
* available courses
* available modules from a given course

These indexers don't handle precisely course and module lifecycle,
a deleted course or module from Moodle won't be archived in
the XI.
  • Loading branch information
lebaudantoine committed Dec 28, 2023
1 parent 5cecf42 commit 1948079
Show file tree
Hide file tree
Showing 9 changed files with 994 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/api/core/warren/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ class Settings(BaseSettings):
EXECUTION_ENVIRONMENT: str = "development"
SENTRY_API_TRACES_SAMPLE_RATE: float = 1.0

# Experience Index
MOODLE_BASE_URL: str = "https://moodle.preprod-fun.apps.openfun.fr"
MOODLE_WS_TOKEN: str = "87e89ec22a9c96965677d9e86288b98d"

@property
def DATABASE_URL(self) -> str:
"""Get the database URL as required by SQLAlchemy."""
Expand Down
1 change: 1 addition & 0 deletions src/api/core/warren/tests/xi/indexers/moodle/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Experience Index Moodle tests package."""
238 changes: 238 additions & 0 deletions src/api/core/warren/tests/xi/indexers/moodle/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
"""Tests for Moodle client."""

import re

import pytest
from httpx import HTTPError
from pydantic import ValidationError
from pytest_httpx import HTTPXMock

from warren.xi.indexers.moodle.client import Moodle
from warren.xi.indexers.moodle.models import Course, Module, Section


@pytest.mark.anyio
async def test_get(httpx_mock: HTTPXMock):
"""Test '_get' method from 'Moodle' client in multiple scenario."""
client = Moodle()

# Assert '_get' raises exception when encountering HTTP errors
httpx_mock.add_response(
method="POST", url=re.compile(r".*webservice.*"), status_code=404
)
with pytest.raises(HTTPError):
await client._get(wsfunction="foo.")

# Assert '_get' returns a dict response
httpx_mock.add_response(
method="POST", url=re.compile(r".*webservice.*"), json=[{"foo": "John Doe"}]
)
assert await client._get(wsfunction="foo.") == [{"foo": "John Doe"}]


@pytest.mark.anyio
async def test_get_courses(httpx_mock: HTTPXMock):
"""Test 'get_courses' method from 'Moodle' client in multiple scenario."""
client = Moodle()

# Fetch an empty list of courses
httpx_mock.add_response(
method="POST",
url=re.compile(r".*webservice.*"),
json=[],
)
assert await client.get_courses() == []

# Fetch a list of wrongly formatted courses
httpx_mock.add_response(
method="POST",
url=re.compile(r".*webservice.*"),
json=[
{
"foo.": "John Doe",
},
{
"foo.": "Martin Thomas",
},
],
)
with pytest.raises(ValidationError):
await client.get_courses()

# Fetch a valid course
# todo - extract this in the factory
httpx_mock.add_response(
method="POST",
url=re.compile(r".*webservice.*"),
json=[
{
"id": 30,
"shortname": "Lorem",
"categoryid": 12,
"categorysortorder": 0,
"fullname": "Lorem ipsum dolor sit amet",
"displayname": "Lorem Ipsum",
"idnumber": "",
"summary": "",
"summaryformat": 1,
"format": "topics",
"showgrades": 1,
"newsitems": 5,
"startdate": 1703203200,
"enddate": 1734739200,
"numsections": 5,
"maxbytes": 0,
"showreports": 0,
"visible": 1,
"hiddensections": 1,
"groupmode": 1,
"groupmodeforce": 0,
"defaultgroupingid": 0,
"timecreated": 1703147509,
"timemodified": 1703153561,
"enablecompletion": 1,
"completionnotify": 0,
"lang": "fr",
"forcetheme": "",
}
],
)
assert await client.get_courses() == [
Course(
id=30,
lang="fr",
displayname="Lorem Ipsum",
summary="",
timecreated=1703147509,
timemodified=1703153561,
)
]


@pytest.mark.anyio
async def test_get_course_contents(httpx_mock: HTTPXMock):
"""Test 'get_course_contents' method from 'Moodle' client in multiple scenario."""
client = Moodle()

# Fetch an empty list of sections
httpx_mock.add_response(
method="POST",
url=re.compile(r".*webservice.*"),
json=[],
)
assert await client.get_course_contents(course_id=1) == []

# Fetch a list of wrongly formatted sections
httpx_mock.add_response(
method="POST",
url=re.compile(r".*webservice.*"),
json=[
{
"foo.": "John Doe",
},
{
"foo.": "Martin Thomas",
},
],
)
with pytest.raises(ValidationError):
await client.get_course_contents(course_id=1)

# Fetch a valid section
# todo - extract this in the factory
httpx_mock.add_response(
method="POST",
url=re.compile(r".*webservice.*"),
json=[
{
"id": 163,
"name": "Lorem Ipsum",
"visible": 1,
"summary": "",
"summaryformat": 1,
"section": 0,
"hiddenbynumsections": 0,
"uservisible": True,
"modules": [
{
"id": 194,
"url": None,
"name": "Annonces",
"instance": 47,
"contextid": 325,
"visible": 1,
"uservisible": True,
"visibleoncoursepage": 1,
"modicon": "https://moodle.preprod-fun.apps.openfun.fr:443/theme/image.php/boost/forum/1703697967/monologo?filtericon=1",
"modname": "forum",
"modplural": "Forums",
"availability": None,
"indent": 0,
"onclick": "",
"afterlink": None,
"customdata": '""',
"noviewlink": False,
"completion": 0,
"downloadcontent": 1,
"dates": [],
},
{
"id": 195,
"url": "https://moodle.preprod-fun.apps.openfun.fr:443/mod/workshop/view.php?id=127",
"name": "Activité Atelier",
"instance": 3,
"contextid": 226,
"description": "",
"visible": 1,
"uservisible": True,
"visibleoncoursepage": 1,
"modicon": "https://moodle.preprod-fun.apps.openfun.fr:443/theme/image.php/boost/workshop/1703698702/monologo?filtericon=1",
"modname": "workshop",
"modplural": "Ateliers",
"availability": None,
"indent": 0,
"onclick": "",
"afterlink": None,
"customdata": '""',
"noviewlink": False,
"completion": 1,
"completiondata": {
"state": 0,
"timecompleted": 0,
"overrideby": None,
"valueused": False,
"hascompletion": True,
"isautomatic": False,
"istrackeduser": True,
"uservisible": True,
"details": [],
},
"downloadcontent": 1,
"dates": [],
},
],
}
],
)
assert await client.get_course_contents(course_id=1) == [
Section(
id=163,
name="Lorem Ipsum",
modules=[
Module(
id=194,
url=None,
name="Annonces",
modname="forum",
description=None,
),
Module(
id=195,
url="https://moodle.preprod-fun.apps.openfun.fr:443/mod/workshop/view.php?id=127",
name="Activité Atelier",
modname="workshop",
description="",
),
],
)
]
Loading

0 comments on commit 1948079

Please sign in to comment.