Skip to content

Commit

Permalink
✅(api) add tests on video new downloads endpoint
Browse files Browse the repository at this point in the history
Current tests are following the exact same structure as
the `views` ones. Please note that these tests are a basis
and should be extended with more advanced one as soon as the
API complexity and usage increase.
  • Loading branch information
lebaudantoine committed Jul 26, 2023
1 parent 2281d23 commit 31070c5
Show file tree
Hide file tree
Showing 2 changed files with 248 additions and 2 deletions.
188 changes: 187 additions & 1 deletion src/api/plugins/video/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
from httpx import AsyncClient
from pytest_httpx import HTTPXMock
from ralph.models.xapi.concepts.constants.video import RESULT_EXTENSION_TIME
from ralph.models.xapi.concepts.verbs.tincan_vocabulary import DownloadedVerb
from ralph.models.xapi.concepts.verbs.video import PlayedVerb
from warren.backends import lrs_client
from warren.models import DailyCounts, Response
from warren_video.factories import VideoPlayedFactory
from warren_video.factories import VideoDownloadedFactory, VideoPlayedFactory


@pytest.mark.anyio
Expand Down Expand Up @@ -197,3 +198,188 @@ async def test_unique_views_backend_query(
}

assert video_views == expected_video_views


@pytest.mark.anyio
@pytest.mark.parametrize(
"video_uuid", ["foo", "foo/bar", "/foo/bar", "foo%2Fbar", "%2Ffoo%2Fbar"]
)
async def test_downloads_invalid_video_uuid(http_client: AsyncClient, video_uuid: str):
"""Test the video downloads endpoint with an invalid `video_uuid` path."""
date_query_params = {
"since": "2023-01-01",
"until": "2023-01-31",
}

response = await http_client.get(
f"/api/v1/video/{video_uuid}/downloads", params=date_query_params
)

assert response.status_code == 422
assert "is not a valid 'IRI'." in response.json().get("detail")[0].get("msg")


@pytest.mark.anyio
async def test_downloads_valid_video_uuid_path_but_no_matching_video(
http_client: AsyncClient, httpx_mock: HTTPXMock
):
"""Test the video downloads endpoint with a valid `video_uuid` but no results."""
lrs_client.base_url = "http://fake-lrs.com"

# Mock the call to the LRS so that it would return no statements, as it
# would do with no matching video
httpx_mock.add_response(
url=re.compile(r"^http://fake-lrs\.com/xAPI/statements\?.*$"),
method="GET",
json={"statements": []},
status_code=200,
)

response = await http_client.get(
url="/api/v1/video/uuid://fake-uuid/downloads",
params={
"since": "2023-01-01",
"until": "2023-01-01",
},
)

assert response.status_code == 200
assert DailyCounts.parse_obj(response.json()) == DailyCounts(
total_count=0,
count_by_date=[],
)


@pytest.mark.anyio
async def test_downloads_backend_query(http_client: AsyncClient, httpx_mock: HTTPXMock):
"""Test the video downloads endpoint backend query results."""
# Define 3 video downloads fixtures
video_uuid = "uuid://ba4252ce-d042-43b0-92e8-f033f45612ee"
video_download_timestamps = [
"2020-01-01T00:00:00.000+00:00",
"2020-01-01T00:00:30.000+00:00",
"2020-01-02T00:00:00.000+00:00",
]

# Build video statements from fixtures
video_statements = [
VideoDownloadedFactory.build(
[
{"object": {"id": video_uuid, "objectType": "Activity"}},
{"verb": {"id": DownloadedVerb().id}},
{"timestamp": download_timestamp},
]
)
for download_timestamp in video_download_timestamps
]

# Convert each video statement to a JSON object
video_statements_json = [
json.loads(statement.json()) for statement in video_statements
]

# Mock the LRS call so that it returns the fixture statements
lrs_client.base_url = "http://fake-lrs.com"
httpx_mock.add_response(
url=re.compile(r"^http://fake-lrs\.com/xAPI/statements\?.*$"),
method="GET",
json={"statements": video_statements_json},
status_code=200,
)

# Perform the call to warren backend. When fetching the LRS statements, it will
# get the above mocked statements
response = await http_client.get(
url=f"/api/v1/video/{video_uuid}/downloads",
params={
"since": "2020-01-01",
"until": "2020-01-03",
},
)

assert response.status_code == 200

# Parse the response to obtain video downloads count_by_date
video_downloads = (Response[DailyCounts]).parse_obj(response.json()).content

# Counting all downloads is expected
expected_video_downloads = {
"total_count": 3,
"count_by_date": [
{"date": "2020-01-01", "count": 2},
{"date": "2020-01-02", "count": 1},
],
}

assert video_downloads == expected_video_downloads


@pytest.mark.anyio
async def test_unique_downloads_backend_query(
http_client: AsyncClient, httpx_mock: HTTPXMock
):
"""Test the video downloads endpoint, with parameter unique=True."""
# Define 3 video views fixtures
video_uuid = "uuid://ba4252ce-d042-43b0-92e8-f033f45612ee"
video_download_timestamps = [
"2020-01-01T00:00:00.000+00:00",
"2020-01-01T00:00:30.000+00:00",
"2020-01-02T00:00:00.000+00:00",
]

# Build video statements from fixtures
video_statements = [
VideoDownloadedFactory.build(
[
{
"actor": {
"objectType": "Agent",
"account": {"name": "John", "homePage": "http://fun-mooc.fr"},
}
},
{"object": {"id": video_uuid, "objectType": "Activity"}},
{"verb": {"id": DownloadedVerb().id}},
{"timestamp": download_timestamp},
]
)
for download_timestamp in video_download_timestamps
]

# Convert each video statement to a JSON object
video_statements_json = [
json.loads(statement.json()) for statement in video_statements
]

# Mock the LRS call so that it returns the fixture statements
lrs_client.base_url = "http://fake-lrs.com"
httpx_mock.add_response(
url=re.compile(r"^http://fake-lrs\.com/xAPI/statements\?.*$"),
method="GET",
json={"statements": video_statements_json},
status_code=200,
)

# Perform the call to warren backend. When fetching the LRS statements, it will
# get the above mocked statements
response = await http_client.get(
url=f"/api/v1/video/{video_uuid}/downloads?unique=true",
params={
"since": "2020-01-01",
"until": "2020-01-03",
},
)

assert response.status_code == 200

# Parse the response to obtain video downloads count_by_date
video_downloads = (Response[DailyCounts]).parse_obj(response.json()).content

# Counting only the first download is expected
expected_video_downloads = {
"total_count": 1,
"count_by_date": [
{"date": "2020-01-01", "count": 1},
],
}

assert video_downloads == expected_video_downloads
62 changes: 61 additions & 1 deletion src/api/plugins/video/warren_video/factories.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Factories for video xAPI events."""
from ralph.models.xapi.concepts.constants.video import RESULT_EXTENSION_TIME
from ralph.models.xapi.video.statements import VideoPlayed
from ralph.models.xapi.video.statements import VideoDownloaded, VideoPlayed
from warren.factories.base import BaseXapiStatementFactory


Expand Down Expand Up @@ -61,3 +61,63 @@ class VideoPlayedFactory(BaseXapiStatementFactory):
"timestamp": "2021-12-01T08:17:47.150905+00:00",
}
model: VideoPlayed = VideoPlayed


class VideoDownloadedFactory(BaseXapiStatementFactory):
"""VideoDownloaded xAPI statement factory."""

template: dict = {
"verb": {
"id": "http://id.tincanapi.com/verb/downloaded",
"display": {"en-US": "downloaded"},
},
"context": {
"extensions": {
"https://w3id.org/xapi/video/extensions/session-id": (
"6e952c73-71e3-5c8c-837b-39baea0e73b9"
),
"https://w3id.org/xapi/video/extensions/quality": 720,
"https://w3id.org/xapi/video/extensions/length": 508,
},
"contextActivities": {
"category": [
{
"id": "https://w3id.org/xapi/video",
"definition": {
"id": "uuid://C678149d-956a-483d-8975-c1506de1e1a9",
"definition": {
"type": "http://adlnet.gov/expapi/activities/profile"
},
},
}
],
"parent": [
{
"id": "course-v1:FUN-MOOC+00001+session01",
"objectType": "Activity",
"definition": {
"type": "http://adlnet.gov/expapi/activities/course"
},
}
],
},
},
"id": "2140967b-563b-464b-90c0-2e114bd8e133",
"actor": {
"objectType": "Agent",
"account": {
"name": "d5b3733b-ccd9-4ab1-bb29-22e3c2f2e592",
"homePage": "http://lms.example.org",
},
},
"object": {
"definition": {
"type": "https://w3id.org/xapi/video/activity-type/video",
"name": {"en-US": "Learning analytics 101"},
},
"id": "uuid://dd38149d-956a-483d-8975-c1506de1e1a9",
"objectType": "Activity",
},
"timestamp": "2021-12-01T08:17:47.150905+00:00",
}
model: VideoPlayed = VideoDownloaded

0 comments on commit 31070c5

Please sign in to comment.