diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md deleted file mode 120000 index 04c99a55c..000000000 --- a/docs/CHANGELOG.md +++ /dev/null @@ -1 +0,0 @@ -../CHANGELOG.md \ No newline at end of file diff --git a/docs/LICENSE.md b/docs/LICENSE.md deleted file mode 120000 index 7eabdb1c2..000000000 --- a/docs/LICENSE.md +++ /dev/null @@ -1 +0,0 @@ -../LICENSE.md \ No newline at end of file diff --git a/docs/commands.md b/docs/commands.md deleted file mode 100644 index 12616600f..000000000 --- a/docs/commands.md +++ /dev/null @@ -1,6 +0,0 @@ -# Commands - -::: mkdocs-click - :module: ralph.cli - :command: cli - :depth: 1 diff --git a/pyproject.toml b/pyproject.toml index e6c6421e4..8dc427572 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,6 @@ dev = [ "black==23.11.0", "cryptography==41.0.5", "factory-boy==3.3.0", - "hypothesis==6.88.3", "logging-gelf==0.0.31", "mike==2.0.0", "mkdocs==1.5.3", @@ -96,6 +95,7 @@ dev = [ "moto==4.2.8", "mypy==1.7.0", "neoteroi-mkdocs==1.0.4", + "polyfactory==2.12.0", "pyfakefs==5.3.0", "pymdown-extensions==10.4", "pytest==7.4.3", diff --git a/src/ralph/conf.py b/src/ralph/conf.py index eaf979d2f..999bf3f36 100644 --- a/src/ralph/conf.py +++ b/src/ralph/conf.py @@ -4,9 +4,18 @@ import sys from enum import Enum from pathlib import Path -from typing import List, Sequence, Tuple, Union - -from pydantic import AnyHttpUrl, AnyUrl, BaseModel, BaseSettings, Extra, root_validator +from typing import Annotated, List, Sequence, Tuple, Union + +from pydantic import ( + AnyHttpUrl, + AnyUrl, + BaseModel, + BaseSettings, + Extra, + Field, + constr, + root_validator, +) from ralph.exceptions import ConfigurationException @@ -30,6 +39,13 @@ MODEL_PATH_SEPARATOR = "__" +NonEmptyStr = Annotated[str, Field(min_length=1)] +NonEmptyStrictStrPatch = Annotated[str, Field(min_length=1)] +NonEmptyStrictStr = constr( + min_length=1, strict=True +) # Annotated[StrictStr, Field(min_length=1)] + + class BaseSettingsConfig: """Pydantic model for BaseSettings Configuration.""" @@ -122,13 +138,10 @@ class ParserSettings(BaseModel): class XapiForwardingConfigurationSettings(BaseModel): """Pydantic model for xAPI forwarding configuration item.""" - class Config: # noqa: D106 - min_anystr_length = 1 - url: AnyUrl is_active: bool - basic_username: str - basic_password: str + basic_username: NonEmptyStr + basic_password: NonEmptyStr max_retries: int timeout: float diff --git a/src/ralph/models/edx/enrollment/statements.py b/src/ralph/models/edx/enrollment/statements.py index a381eecff..753a88221 100644 --- a/src/ralph/models/edx/enrollment/statements.py +++ b/src/ralph/models/edx/enrollment/statements.py @@ -35,7 +35,6 @@ class EdxCourseEnrollmentActivated(BaseServerModel): __selector__ = selector( event_source="server", event_type="edx.course.enrollment.activated" ) - event: Union[ Json[EnrollmentEventField], EnrollmentEventField, @@ -59,6 +58,7 @@ class EdxCourseEnrollmentDeactivated(BaseServerModel): event_source="server", event_type="edx.course.enrollment.deactivated" ) + event: Union[ Json[EnrollmentEventField], EnrollmentEventField, @@ -83,9 +83,10 @@ class EdxCourseEnrollmentModeChanged(BaseServerModel): event_source="server", event_type="edx.course.enrollment.mode_changed" ) + event: Union[ - Json[EnrollmentEventField], EnrollmentEventField, + Json[EnrollmentEventField], ] event_type: Literal["edx.course.enrollment.mode_changed"] name: Literal["edx.course.enrollment.mode_changed"] diff --git a/src/ralph/models/edx/problem_interaction/fields/events.py b/src/ralph/models/edx/problem_interaction/fields/events.py index 8ba3b0e16..c277eb2fd 100644 --- a/src/ralph/models/edx/problem_interaction/fields/events.py +++ b/src/ralph/models/edx/problem_interaction/fields/events.py @@ -2,9 +2,9 @@ import sys from datetime import datetime -from typing import Dict, List, Optional, Union +from typing import Annotated, Dict, List, Optional, Union -from pydantic import constr +from pydantic import Field from ...base import AbstractBaseEventField, BaseModelWithConfig @@ -62,7 +62,7 @@ class State(BaseModelWithConfig): """ correct_map: Dict[ - constr(regex=r"^[a-f0-9]{32}_[0-9]_[0-9]$"), + Annotated[str, Field(regex=r"^[a-f0-9]{32}_[0-9]_[0-9]$")], CorrectMap, ] done: Optional[bool] @@ -170,23 +170,26 @@ class ProblemCheckEventField(AbstractBaseEventField): """ answers: Dict[ - constr(regex=r"^[a-f0-9]{32}_[0-9]_[0-9]$"), - Union[List[str], str], + Annotated[str, Field(regex=r"^[a-f0-9]{32}_[0-9]_[0-9]$")], + Union[str, List[str]], ] attempts: int correct_map: Dict[ - constr(regex=r"^[a-f0-9]{32}_[0-9]_[0-9]$"), + Annotated[str, Field(regex=r"^[a-f0-9]{32}_[0-9]_[0-9]$")], CorrectMap, ] grade: int max_grade: int - problem_id: constr( - regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" - r"type@problem\+block@[a-f0-9]{32}$" - ) + problem_id: Annotated[ + str, + Field( + regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" + r"type@problem\+block@[a-f0-9]{32}$" + ), + ] state: State submission: Dict[ - constr(regex=r"^[a-f0-9]{32}_[0-9]_[0-9]$"), + Annotated[str, Field(regex=r"^[a-f0-9]{32}_[0-9]_[0-9]$")], SubmissionAnswerField, ] success: Union[Literal["correct"], Literal["incorrect"]] @@ -204,14 +207,17 @@ class ProblemCheckFailEventField(AbstractBaseEventField): """ answers: Dict[ - constr(regex=r"^[a-f0-9]{32}_[0-9]_[0-9]$"), - Union[List[str], str], + Annotated[str, Field(regex=r"^[a-f0-9]{32}_[0-9]_[0-9]$")], + Union[str, List[str]], ] failure: Union[Literal["closed"], Literal["unreset"]] - problem_id: constr( - regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" - r"type@problem\+block@[a-f0-9]{32}$" - ) + problem_id: Annotated[ + str, + Field( + regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" + r"type@problem\+block@[a-f0-9]{32}$" + ), + ] state: State @@ -235,10 +241,13 @@ class ProblemRescoreEventField(AbstractBaseEventField): new_total: int orig_score: int orig_total: int - problem_id: constr( - regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" - r"type@problem\+block@[a-f0-9]{32}$" - ) + problem_id: Annotated[ + str, + Field( + regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" + r"type@problem\+block@[a-f0-9]{32}$" + ), + ] state: State success: Union[Literal["correct"], Literal["incorrect"]] @@ -253,10 +262,13 @@ class ProblemRescoreFailEventField(AbstractBaseEventField): """ failure: Union[Literal["closed"], Literal["unreset"]] - problem_id: constr( - regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" - r"type@problem\+block@[a-f0-9]{32}$" - ) + problem_id: Annotated[ + str, + Field( + regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" + r"type@problem\+block@[a-f0-9]{32}$" + ), + ] state: State @@ -293,10 +305,13 @@ class ResetProblemEventField(AbstractBaseEventField): new_state: State old_state: State - problem_id: constr( - regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" - r"type@problem\+block@[a-f0-9]{32}$" - ) + problem_id: Annotated[ + str, + Field( + regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" + r"type@problem\+block@[a-f0-9]{32}$" + ), + ] class ResetProblemFailEventField(AbstractBaseEventField): @@ -310,10 +325,13 @@ class ResetProblemFailEventField(AbstractBaseEventField): failure: Union[Literal["closed"], Literal["not_done"]] old_state: State - problem_id: constr( - regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" - r"type@problem\+block@[a-f0-9]{32}$" - ) + problem_id: Annotated[ + str, + Field( + regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" + r"type@problem\+block@[a-f0-9]{32}$" + ), + ] class SaveProblemFailEventField(AbstractBaseEventField): @@ -329,10 +347,13 @@ class SaveProblemFailEventField(AbstractBaseEventField): answers: Dict[str, Union[int, str, list, dict]] failure: Union[Literal["closed"], Literal["done"]] - problem_id: constr( - regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" - r"type@problem\+block@[a-f0-9]{32}$" - ) + problem_id: Annotated[ + str, + Field( + regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" + r"type@problem\+block@[a-f0-9]{32}$" + ), + ] state: State @@ -347,10 +368,13 @@ class SaveProblemSuccessEventField(AbstractBaseEventField): """ answers: Dict[str, Union[int, str, list, dict]] - problem_id: constr( - regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" - r"type@problem\+block@[a-f0-9]{32}$" - ) + problem_id: Annotated[ + str, + Field( + regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" + r"type@problem\+block@[a-f0-9]{32}$" + ), + ] state: State @@ -361,7 +385,10 @@ class ShowAnswerEventField(AbstractBaseEventField): problem_id (str): Consists of the ID of the problem being shown. """ - problem_id: constr( - regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" - r"type@problem\+block@[a-f0-9]{32}$" - ) + problem_id: Annotated[ + str, + Field( + regex=r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" + r"type@problem\+block@[a-f0-9]{32}$" + ), + ] diff --git a/src/ralph/models/edx/server.py b/src/ralph/models/edx/server.py index 095a1bdad..dd330892e 100644 --- a/src/ralph/models/edx/server.py +++ b/src/ralph/models/edx/server.py @@ -4,14 +4,14 @@ from pathlib import Path from typing import Union -from pydantic import Json +from pydantic import Json, validator from ralph.models.selector import LazyModelField, selector from .base import AbstractBaseEventField, BaseEdxModel if sys.version_info >= (3, 8): - from typing import Literal + from typing import Any, Literal else: from typing_extensions import Literal @@ -22,6 +22,7 @@ class BaseServerModel(BaseEdxModel): event_source: Literal["server"] + class ServerEventField(AbstractBaseEventField): """Pydantic model for common server `event` field.""" diff --git a/src/ralph/models/edx/video/fields/events.py b/src/ralph/models/edx/video/fields/events.py index 786a57957..d8517c973 100644 --- a/src/ralph/models/edx/video/fields/events.py +++ b/src/ralph/models/edx/video/fields/events.py @@ -2,6 +2,8 @@ import sys +from pydantic import NonNegativeFloat + from ...base import AbstractBaseEventField if sys.version_info >= (3, 8): @@ -61,8 +63,8 @@ class SeekVideoEventField(VideoBaseEventField): within the video, either `onCaptionSeek` or `onSlideSeek` value. """ - new_time: float - old_time: float + new_time: NonNegativeFloat # TODO: Ask Quitterie if this is valid + old_time: NonNegativeFloat type: str diff --git a/src/ralph/models/xapi/base/agents.py b/src/ralph/models/xapi/base/agents.py index 9f6ce53f5..5d2945db9 100644 --- a/src/ralph/models/xapi/base/agents.py +++ b/src/ralph/models/xapi/base/agents.py @@ -4,9 +4,9 @@ from abc import ABC from typing import Optional, Union -from pydantic import StrictStr +from ralph.conf import NonEmptyStrictStr +from ralph.models.xapi.config import BaseModelWithConfig -from ..config import BaseModelWithConfig from .common import IRI from .ifi import ( BaseXapiAccountIFI, @@ -30,7 +30,7 @@ class BaseXapiAgentAccount(BaseModelWithConfig): """ homePage: IRI - name: StrictStr + name: NonEmptyStrictStr class BaseXapiAgentCommonProperties(BaseModelWithConfig, ABC): @@ -38,13 +38,13 @@ class BaseXapiAgentCommonProperties(BaseModelWithConfig, ABC): It defines who performed the action. - Attributes: + Attributes:name: objectType (str): Consists of the value `Agent`. name (str): Consists of the full name of the Agent. """ objectType: Optional[Literal["Agent"]] - name: Optional[StrictStr] + name: Optional[NonEmptyStrictStr] class BaseXapiAgentWithMbox(BaseXapiAgentCommonProperties, BaseXapiMboxIFI): diff --git a/src/ralph/models/xapi/base/common.py b/src/ralph/models/xapi/base/common.py index 14c27d1e7..305ccccc8 100644 --- a/src/ralph/models/xapi/base/common.py +++ b/src/ralph/models/xapi/base/common.py @@ -1,13 +1,15 @@ """Common for xAPI base definitions.""" -from typing import Dict, Generator, Type +from typing import Annotated, Dict, Generator, Type from langcodes import tag_is_valid -from pydantic import StrictStr, validate_email +from pydantic import Field from rfc3987 import parse +from ralph.conf import NonEmptyStr, NonEmptyStrictStr, NonEmptyStrictStrPatch -class IRI(str): + +class IRI(NonEmptyStrictStrPatch): """Pydantic custom data type validating RFC 3987 IRIs.""" @classmethod @@ -20,7 +22,7 @@ def validate(iri: str) -> Type["IRI"]: yield validate -class LanguageTag(str): +class LanguageTag(NonEmptyStr): """Pydantic custom data type validating RFC 5646 Language tags.""" @classmethod @@ -34,19 +36,8 @@ def validate(tag: str) -> Type["LanguageTag"]: yield validate -LanguageMap = Dict[LanguageTag, StrictStr] - - -class MailtoEmail(str): - """Pydantic custom data type validating `mailto:email` format.""" +LanguageMap = Dict[LanguageTag, NonEmptyStrictStr] # TODO: change back to strictstr - @classmethod - def __get_validators__(cls) -> Generator: # noqa: D105 - def validate(mailto: str) -> Type["MailtoEmail"]: - """Check whether the provided value follows the `mailto:email` format.""" - if not mailto.startswith("mailto:"): - raise TypeError("Invalid `mailto:email` value") - valid = validate_email(mailto[7:]) - return cls(f"mailto:{valid[1]}") - yield validate +email_pattern = r"(^mailto:[-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|\"([]!#-[^-~ \t]|(\\[\t -~]))+\")@([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|\[[\t -Z^-~]*])" +MailtoEmail = Annotated[str, Field(regex=email_pattern)] diff --git a/src/ralph/models/xapi/base/contexts.py b/src/ralph/models/xapi/base/contexts.py index febd78754..0fb825a95 100644 --- a/src/ralph/models/xapi/base/contexts.py +++ b/src/ralph/models/xapi/base/contexts.py @@ -3,7 +3,7 @@ from typing import Dict, List, Optional, Union from uuid import UUID -from pydantic import StrictStr +from ralph.conf import NonEmptyStrictStr from ..config import BaseModelWithConfig from .agents import BaseXapiAgent @@ -50,8 +50,8 @@ class BaseXapiContext(BaseModelWithConfig): instructor: Optional[BaseXapiAgent] team: Optional[BaseXapiGroup] contextActivities: Optional[BaseXapiContextContextActivities] - revision: Optional[StrictStr] - platform: Optional[StrictStr] + revision: Optional[NonEmptyStrictStr] + platform: Optional[NonEmptyStrictStr] language: Optional[LanguageTag] statement: Optional[BaseXapiStatementRef] extensions: Optional[Dict[IRI, Union[str, int, bool, list, dict, None]]] diff --git a/src/ralph/models/xapi/base/groups.py b/src/ralph/models/xapi/base/groups.py index 73c320058..2ac3433c5 100644 --- a/src/ralph/models/xapi/base/groups.py +++ b/src/ralph/models/xapi/base/groups.py @@ -4,8 +4,6 @@ from abc import ABC from typing import List, Optional, Union -from pydantic import StrictStr - from ..config import BaseModelWithConfig from .agents import BaseXapiAgent from .ifi import ( @@ -20,6 +18,8 @@ else: from typing_extensions import Literal +from ralph.conf import NonEmptyStrictStr + class BaseXapiGroupCommonProperties(BaseModelWithConfig, ABC): """Pydantic model for core `Group` type property. @@ -32,7 +32,7 @@ class BaseXapiGroupCommonProperties(BaseModelWithConfig, ABC): """ objectType: Literal["Group"] - name: Optional[StrictStr] + name: Optional[NonEmptyStrictStr] class BaseXapiAnonymousGroup(BaseXapiGroupCommonProperties): diff --git a/src/ralph/models/xapi/base/ifi.py b/src/ralph/models/xapi/base/ifi.py index 642e933c7..e7f326710 100644 --- a/src/ralph/models/xapi/base/ifi.py +++ b/src/ralph/models/xapi/base/ifi.py @@ -1,6 +1,8 @@ """Base xAPI `Inverse Functional Identifier` definitions.""" -from pydantic import AnyUrl, StrictStr, constr +from pydantic import AnyUrl, constr + +from ralph.conf import NonEmptyStrictStr from ..config import BaseModelWithConfig from .common import IRI, MailtoEmail @@ -15,7 +17,7 @@ class BaseXapiAccount(BaseModelWithConfig): """ homePage: IRI - name: StrictStr + name: NonEmptyStrictStr class BaseXapiMboxIFI(BaseModelWithConfig): @@ -25,6 +27,8 @@ class BaseXapiMboxIFI(BaseModelWithConfig): mbox (MailtoEmail): Consists of the Agent's email address. """ + # pattern = r'mailto:\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b' + # mbox: Annotated[str, Field(regex=pattern)]# mbox: MailtoEmail diff --git a/src/ralph/models/xapi/base/results.py b/src/ralph/models/xapi/base/results.py index bd3d49ec9..42da00c48 100644 --- a/src/ralph/models/xapi/base/results.py +++ b/src/ralph/models/xapi/base/results.py @@ -4,7 +4,9 @@ from decimal import Decimal from typing import Any, Dict, Optional, Union -from pydantic import StrictBool, StrictStr, conint, root_validator +from pydantic import StrictBool, conint, root_validator + +from ralph.conf import NonEmptyStrictStr from ..config import BaseModelWithConfig from .common import IRI @@ -61,6 +63,6 @@ class BaseXapiResult(BaseModelWithConfig): score: Optional[BaseXapiResultScore] success: Optional[StrictBool] completion: Optional[StrictBool] - response: Optional[StrictStr] + response: Optional[NonEmptyStrictStr] duration: Optional[timedelta] extensions: Optional[Dict[IRI, Union[str, int, bool, list, dict, None]]] diff --git a/src/ralph/models/xapi/base/unnested_objects.py b/src/ralph/models/xapi/base/unnested_objects.py index f1113ab5a..be2d3401b 100644 --- a/src/ralph/models/xapi/base/unnested_objects.py +++ b/src/ralph/models/xapi/base/unnested_objects.py @@ -4,7 +4,9 @@ from typing import Any, Dict, List, Optional, Union from uuid import UUID -from pydantic import AnyUrl, StrictStr, constr, validator +from pydantic import AnyUrl, constr, validator + +from ralph.conf import NonEmptyStrictStr from ..config import BaseModelWithConfig from .common import IRI, LanguageMap @@ -72,7 +74,7 @@ class BaseXapiActivityInteractionDefinition(BaseXapiActivityDefinition): "numeric", "other", ] - correctResponsesPattern: Optional[List[StrictStr]] + correctResponsesPattern: Optional[List[NonEmptyStrictStr]] choices: Optional[List[BaseXapiInteractionComponent]] scale: Optional[List[BaseXapiInteractionComponent]] source: Optional[List[BaseXapiInteractionComponent]] diff --git a/src/ralph/models/xapi/config.py b/src/ralph/models/xapi/config.py index 38927c7b6..a28eb2687 100644 --- a/src/ralph/models/xapi/config.py +++ b/src/ralph/models/xapi/config.py @@ -6,7 +6,7 @@ class BaseModelWithConfig(BaseModel): """Pydantic model for base configuration shared among all models.""" - class Config: # noqa: D106 + class Config: # noqa: D106 # TODO: doing extra = Extra.forbid min_anystr_length = 1 diff --git a/src/ralph/models/xapi/virtual_classroom/results.py b/src/ralph/models/xapi/virtual_classroom/results.py index 0bde87312..d1a438bf7 100644 --- a/src/ralph/models/xapi/virtual_classroom/results.py +++ b/src/ralph/models/xapi/virtual_classroom/results.py @@ -1,6 +1,6 @@ """Virtual classroom xAPI events result fields definitions.""" -from pydantic import StrictStr +from ralph.conf import NonEmptyStrictStr from ..base.results import BaseXapiResult @@ -12,4 +12,4 @@ class VirtualClassroomAnsweredPollResult(BaseXapiResult): response (str): Consists of the response for the given Activity. """ - response: StrictStr # = StrictStr() + response: NonEmptyStrictStr # = StrictStr() diff --git a/src/ralph/models/xapi/virtual_classroom/statements.py b/src/ralph/models/xapi/virtual_classroom/statements.py index 7494b661b..d23e9bb40 100644 --- a/src/ralph/models/xapi/virtual_classroom/statements.py +++ b/src/ralph/models/xapi/virtual_classroom/statements.py @@ -371,7 +371,6 @@ class VirtualClassroomAnsweredPoll(BaseVirtualClassroomStatement): object (dict): See CMIInteractionActivity. context (dict): See VirtualClassroomAnsweredPollContext. result (dict): See AnsweredPollResult. - result (dict): See AnsweredPollResult. timestamp (datetime): Consists of the timestamp of when the event occurred. result (dict): See AnsweredPollResult. timestamp (datetime): Consists of the timestamp of when the event occurred. diff --git a/tests/api/test_forwarding.py b/tests/api/test_forwarding.py index 7f7db3ec2..e65d14ae8 100644 --- a/tests/api/test_forwarding.py +++ b/tests/api/test_forwarding.py @@ -5,23 +5,22 @@ import pytest from httpx import RequestError -from hypothesis import HealthCheck -from hypothesis import settings as hypothesis_settings -from hypothesis import strategies as st from pydantic import ValidationError from ralph.api.forwarding import forward_xapi_statements, get_active_xapi_forwardings from ralph.conf import Settings, XapiForwardingConfigurationSettings -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_instance -@hypothesis_settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -@custom_given(XapiForwardingConfigurationSettings) -def test_api_forwarding_with_valid_configuration(monkeypatch, forwarding_settings): +def test_api_forwarding_with_valid_configuration( + monkeypatch, +): """Test the settings, given a valid forwarding configuration, should not raise an exception. """ + forwarding_settings = mock_instance(XapiForwardingConfigurationSettings) + monkeypatch.delenv("RALPH_XAPI_FORWARDINGS", raising=False) settings = Settings() @@ -33,16 +32,17 @@ def test_api_forwarding_with_valid_configuration(monkeypatch, forwarding_setting assert settings.XAPI_FORWARDINGS[0] == forwarding_settings -@hypothesis_settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) @pytest.mark.parametrize( "missing_key", ["url", "is_active", "basic_username", "basic_password", "max_retries", "timeout"], ) -@custom_given(XapiForwardingConfigurationSettings) -def test_api_forwarding_configuration_with_missing_field(missing_key, forwarding): +def test_api_forwarding_configuration_with_missing_field(missing_key): """Test the forwarding configuration, given a missing field, should raise a validation exception. """ + + forwarding = mock_instance(XapiForwardingConfigurationSettings) + forwarding_dict = json.loads(forwarding.json()) del forwarding_dict[missing_key] with pytest.raises(ValidationError, match=f"{missing_key}\n field required"): @@ -75,18 +75,21 @@ def test_api_forwarding_get_active_xapi_forwardings_with_empty_forwardings( assert caplog.record_tuples[0][2] == expected_log -@hypothesis_settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -@custom_given( - custom_builds(XapiForwardingConfigurationSettings, is_active=st.just(True)), - custom_builds(XapiForwardingConfigurationSettings, is_active=st.just(False)), -) def test_api_forwarding_get_active_xapi_forwardings_with_inactive_forwardings( - monkeypatch, caplog, active_forwarding, inactive_forwarding + monkeypatch, caplog ): """Test that the get_active_xapi_forwardings function, given a forwarding configuration containing inactive forwardings, should log which forwarding configurations are inactive and return a list containing only active forwardings. """ + + active_forwarding = mock_instance( + XapiForwardingConfigurationSettings, is_active=True + ) + inactive_forwarding = mock_instance( + XapiForwardingConfigurationSettings, is_active=False + ) + active_forwarding_json = active_forwarding.json() inactive_forwarding_json = inactive_forwarding.json() @@ -124,24 +127,18 @@ def test_api_forwarding_get_active_xapi_forwardings_with_inactive_forwardings( @pytest.mark.anyio -@hypothesis_settings( - deadline=None, suppress_health_check=(HealthCheck.function_scoped_fixture,) -) @pytest.mark.parametrize("statements", [[{}, {"id": 1}]]) -@custom_given( - custom_builds( - XapiForwardingConfigurationSettings, - max_retries=st.just(1), - is_active=st.just(True), - ) -) async def test_api_forwarding_forward_xapi_statements_with_successful_request( - monkeypatch, caplog, statements, forwarding + monkeypatch, caplog, statements ): """Test the forward_xapi_statements function should log the forwarded statements count if the request was successful. """ + forwarding = mock_instance( + XapiForwardingConfigurationSettings, max_retries=1, is_active=True + ) + class MockSuccessfulResponse: """Dummy Successful Response.""" @@ -172,22 +169,20 @@ async def post_success(*args, **kwargs): @pytest.mark.anyio -@hypothesis_settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) @pytest.mark.parametrize("statements", [[{}, {"id": 1}]]) -@custom_given( - custom_builds( - XapiForwardingConfigurationSettings, - max_retries=st.just(3), - is_active=st.just(True), - ) -) async def test_api_forwarding_forward_xapi_statements_with_unsuccessful_request( - monkeypatch, caplog, statements, forwarding + monkeypatch, caplog, statements ): """Test the forward_xapi_statements function should log the error if the request was successful. """ + forwarding = mock_instance( + XapiForwardingConfigurationSettings, + max_retries=3, + is_active=True, + ) + class MockUnsuccessfulResponse: """Dummy Failing Response.""" diff --git a/tests/conftest.py b/tests/conftest.py index 6b1050e95..5160a8de1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,6 @@ """Module py.test fixtures.""" -from .fixtures import ( - hypothesis_configuration, # noqa: F401 - hypothesis_strategies, # noqa: F401 -) from .fixtures.api import client # noqa: F401 from .fixtures.auth import ( # noqa: F401 basic_auth_credentials, diff --git a/tests/factories.py b/tests/factories.py new file mode 100644 index 000000000..60df509b6 --- /dev/null +++ b/tests/factories.py @@ -0,0 +1,213 @@ +"""Mock model generation for testing.""" + +from decimal import Decimal +from typing import Any, Callable + +from polyfactory.factories.base import BaseFactory +from polyfactory.factories.pydantic_factory import ( + ModelFactory as PolyfactoryModelFactory, +) +from polyfactory.factories.pydantic_factory import ( + T, +) +from polyfactory.fields import Ignore +from pydantic import BaseModel + +from ralph.models.edx.navigational.fields.events import NavigationalEventField +from ralph.models.edx.navigational.statements import UISeqNext, UISeqPrev +from ralph.models.xapi.base.common import IRI, LanguageTag +from ralph.models.xapi.base.contexts import ( + BaseXapiContext, +) +from ralph.models.xapi.base.results import BaseXapiResultScore +from ralph.models.xapi.lms.contexts import ( + LMSContextContextActivities, + LMSProfileActivity, +) +from ralph.models.xapi.video.contexts import ( + VideoContextContextActivities, + VideoProfileActivity, +) +from ralph.models.xapi.virtual_classroom.contexts import ( + VirtualClassroomAnsweredPollContextActivities, + VirtualClassroomPostedPublicMessageContextActivities, + VirtualClassroomProfileActivity, + VirtualClassroomStartedPollContextActivities, +) + + +def prune(d: Any, exceptions: list = []): + """Remove all empty leaves from a dict, except fo those in `exceptions`.""" + # TODO: add test ? + + if isinstance(d, BaseModel): + d = d.dict() + if isinstance(d, dict): + d_dict_not_exceptions = { + k: prune(v) for k, v in d.items() if k not in exceptions + } + d_dict_not_exceptions = {k: v for k, v in d.items() if v} + d_dict_exceptions = {k: v for k, v in d.items() if k in exceptions} + return d_dict_not_exceptions | d_dict_exceptions + elif isinstance(d, list): + d_list = [prune(v) for v in d] + return [v for v in d_list if v] + # if d not in [[], {}, ""]: # TODO: put back ? + # return d + if d: + return d + return False + + +class ModelFactory(PolyfactoryModelFactory[T]): + __allow_none_optionals__ = False + __is_base_factory__ = True + # __random_seed__ = 6 # TODO: remove this + + @classmethod + def get_provider_map(cls) -> dict[Any, Callable[[], Any]]: + provider_map = super().get_provider_map() + provider_map[LanguageTag] = lambda: LanguageTag("en-US") + provider_map[IRI] = lambda: IRI(mock_url()) + return provider_map + + @classmethod + def _get_or_create_factory(cls, model: type): + # print("Cls:", model) + created_factory = super()._get_or_create_factory(model) + created_factory.get_provider_map = cls.get_provider_map + created_factory._get_or_create_factory = cls._get_or_create_factory + return created_factory + + +class BaseXapiResultScoreFactory(ModelFactory[BaseXapiResultScore]): + __set_as_default_factory_for_type__ = True + __model__ = BaseXapiResultScore + + min = Decimal("0.0") + max = Decimal("20.0") + raw = Decimal("11") + + +class BaseXapiContextFactory(ModelFactory[BaseXapiContext]): + __model__ = BaseXapiContext + __set_as_default_factory_for_type__ = True + + revision = Ignore() + platform = Ignore() + + +class LMSContextContextActivitiesFactory(ModelFactory[LMSContextContextActivities]): + __model__ = LMSContextContextActivities + __set_as_default_factory_for_type__ = True + + category = lambda: mock_xapi_instance(LMSProfileActivity) # noqa: E731 + + +class VideoContextContextActivitiesFactory(ModelFactory[VideoContextContextActivities]): + __model__ = VideoContextContextActivities + __set_as_default_factory_for_type__ = True + + category = lambda: mock_xapi_instance(VideoProfileActivity) # noqa: E731 + + +class VirtualClassroomStartedPollContextActivitiesFactory( + ModelFactory[VirtualClassroomStartedPollContextActivities] +): + __model__ = VirtualClassroomStartedPollContextActivities + __set_as_default_factory_for_type__ = True + + category = lambda: mock_xapi_instance(VirtualClassroomProfileActivity) # noqa: E731 + + +class VirtualClassroomAnsweredPollContextActivitiesFactory( + ModelFactory[VirtualClassroomAnsweredPollContextActivities] +): + __model__ = VirtualClassroomAnsweredPollContextActivities + __set_as_default_factory_for_type__ = True + + category = lambda: mock_xapi_instance(VirtualClassroomProfileActivity) # noqa: E731 + + +class VirtualClassroomPostedPublicMessageContextActivitiesFactory( + ModelFactory[VirtualClassroomPostedPublicMessageContextActivities] +): + __model__ = VirtualClassroomPostedPublicMessageContextActivities + __set_as_default_factory_for_type__ = True + + category = lambda: mock_xapi_instance(VirtualClassroomProfileActivity) # noqa: E731 + + +class UISeqPrev(ModelFactory[UISeqPrev]): + __model__ = UISeqPrev + __set_as_default_factory_for_type__ = True + + event = lambda: mock_instance(NavigationalEventField, old=1, new=0) # noqa: E731 + + +class UISeqNext(ModelFactory[UISeqNext]): + __model__ = UISeqNext + __set_as_default_factory_for_type__ = True + + event = lambda: mock_instance(NavigationalEventField, old=0, new=1) # noqa: E731 + + +def mock_xapi_instance(klass, *args, **kwargs): + """Generate a mock instance of a given xAPI model.""" + + # Avoid redifining custom factories + if klass not in BaseFactory._factory_type_mapping: + + class KlassFactory(ModelFactory[klass]): + __model__ = klass + + else: + KlassFactory = BaseFactory._factory_type_mapping[klass] + + kwargs = KlassFactory.process_kwargs(*args, **kwargs) + + kwargs = prune(kwargs, exceptions=["extensions"]) + + return klass(**kwargs) + +def _call_all_callables(value): + if callable(value): + return value() + if isinstance(value, dict): + return {_call_all_callables(k):_call_all_callables(v) for k, v in value.items()} + elif isinstance(value, list): + return [_call_all_callables(v) for v in value] + return value + +def mock_instance(klass, *args, **kwargs): + """Generate a mock instance of a given model.""" + + # Avoid redifining custom factories + if klass not in BaseFactory._factory_type_mapping: + + class KlassFactory(ModelFactory[klass]): + __model__ = klass + + else: + KlassFactory = BaseFactory._factory_type_mapping[klass] + + return KlassFactory.build() + + # kwargs = KlassFactory.process_kwargs(*args, **kwargs) + + # print('OKAY ONE') + # from pprint import pprint + # pprint(kwargs) + # kwargs = _call_all_callables(kwargs) + + # print('OKAY TWO') + # from pprint import pprint + # pprint(kwargs) + + # return klass(**kwargs) + + + +def mock_url(): + """Mock a URL.""" + return ModelFactory.__faker__.url() diff --git a/tests/fixtures/hypothesis_configuration.py b/tests/fixtures/hypothesis_configuration.py deleted file mode 100644 index f7c7844b0..000000000 --- a/tests/fixtures/hypothesis_configuration.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Hypothesis fixture configuration.""" - -import operator - -from hypothesis import provisional, settings -from hypothesis import strategies as st -from pydantic import AnyHttpUrl, AnyUrl, StrictStr - -from ralph.models.xapi.base.common import IRI, LanguageTag, MailtoEmail - -settings.register_profile("development", max_examples=1) -settings.load_profile("development") - -st.register_type_strategy(str, st.text(min_size=1)) -st.register_type_strategy(StrictStr, st.text(min_size=1)) -st.register_type_strategy(AnyUrl, provisional.urls()) -st.register_type_strategy(AnyHttpUrl, provisional.urls()) -st.register_type_strategy(IRI, provisional.urls()) -st.register_type_strategy( - MailtoEmail, st.builds(operator.add, st.just("mailto:"), st.emails()) -) -st.register_type_strategy(LanguageTag, st.just("en-US")) diff --git a/tests/fixtures/hypothesis_strategies.py b/tests/fixtures/hypothesis_strategies.py deleted file mode 100644 index 0d73f53ff..000000000 --- a/tests/fixtures/hypothesis_strategies.py +++ /dev/null @@ -1,127 +0,0 @@ -"""Hypothesis build strategies with special constraints.""" - -import random -from typing import Union - -from hypothesis import given -from hypothesis import strategies as st -from pydantic import BaseModel - -from ralph.models.edx.navigational.fields.events import NavigationalEventField -from ralph.models.edx.navigational.statements import UISeqNext, UISeqPrev -from ralph.models.xapi.base.contexts import BaseXapiContext -from ralph.models.xapi.base.results import BaseXapiResultScore -from ralph.models.xapi.lms.contexts import ( - LMSContextContextActivities, - LMSProfileActivity, -) -from ralph.models.xapi.video.contexts import ( - VideoContextContextActivities, - VideoProfileActivity, -) -from ralph.models.xapi.virtual_classroom.contexts import ( - VirtualClassroomContextContextActivities, - VirtualClassroomProfileActivity, -) - -OVERWRITTEN_STRATEGIES = {} - - -def is_base_model(klass): - """Return True if the given class is a subclass of the pydantic BaseModel.""" - - try: - return issubclass(klass, BaseModel) - except TypeError: - return False - - -def get_strategy_from(annotation): - """Infer a Hypothesis strategy from the given annotation.""" - origin = getattr(annotation, "__origin__", None) - args = getattr(annotation, "__args__", None) - if is_base_model(annotation): - return custom_builds(annotation) - if origin is Union: - return st.one_of( - [get_strategy_from(t) for t in args if not isinstance(t, type(None))] - ) - if origin is list: - return st.lists(get_strategy_from(args[0]), min_size=1) - if origin is dict: - keys = get_strategy_from(args[0]) - values = get_strategy_from(args[1]) - return st.dictionaries(keys, values, min_size=1) - if annotation is None: - return st.none() - return st.from_type(annotation) - - -def custom_builds( - klass: BaseModel, _overwrite_default=True, **kwargs: Union[st.SearchStrategy, bool] -): - """Return a fixed_dictionaries Hypothesis strategy for pydantic models. - - Args: - klass (BaseModel): The pydantic model for which to generate a strategy. - _overwrite_default (bool): By default, fields overwritten by kwargs become - required. If _overwrite_default is set to False, we keep the original field - requirement (either required or optional). - **kwargs (SearchStrategy or bool): If kwargs contain search strategies, they - overwrite the default strategy for the given key. - If kwargs contains booleans, they set whether the given key should be - present (True) or omitted (False) in the generated model. - """ - - for special_class, special_kwargs in OVERWRITTEN_STRATEGIES.items(): - if issubclass(klass, special_class): - kwargs = dict(special_kwargs, **kwargs) - break - optional = {} - required = {} - for name, field in klass.__fields__.items(): - arg = kwargs.get(name, None) - if arg is False: - continue - is_required = field.required or (arg is not None and _overwrite_default) - required_optional = required if is_required or arg is not None else optional - field_strategy = get_strategy_from(field.outer_type_) if arg is None else arg - required_optional[field.alias] = field_strategy - if not required: - # To avoid generating empty values - key, value = random.choice(list(optional.items())) - required[key] = value - del optional[key] - return st.fixed_dictionaries(required, optional=optional).map(klass.parse_obj) - - -def custom_given(*args: Union[st.SearchStrategy, BaseModel], **kwargs): - """Wrap the Hypothesis `given` function. Replace st.builds with custom_builds.""" - strategies = [] - for arg in args: - strategies.append(custom_builds(arg) if is_base_model(arg) else arg) - return given(*strategies, **kwargs) - - -OVERWRITTEN_STRATEGIES = { - UISeqPrev: { - "event": custom_builds(NavigationalEventField, old=st.just(1), new=st.just(0)) - }, - UISeqNext: { - "event": custom_builds(NavigationalEventField, old=st.just(0), new=st.just(1)) - }, - BaseXapiContext: { - "revision": False, - "platform": False, - }, - BaseXapiResultScore: { - "raw": False, - "min": False, - "max": False, - }, - LMSContextContextActivities: {"category": custom_builds(LMSProfileActivity)}, - VideoContextContextActivities: {"category": custom_builds(VideoProfileActivity)}, - VirtualClassroomContextContextActivities: { - "category": custom_builds(VirtualClassroomProfileActivity) - }, -} diff --git a/tests/models/edx/converters/xapi/test_enrollment.py b/tests/models/edx/converters/xapi/test_enrollment.py index 56b57a2b7..f4c370a05 100644 --- a/tests/models/edx/converters/xapi/test_enrollment.py +++ b/tests/models/edx/converters/xapi/test_enrollment.py @@ -4,7 +4,6 @@ from uuid import UUID, uuid5 import pytest -from hypothesis import provisional from ralph.models.converter import convert_dict_event from ralph.models.edx.converters.xapi.enrollment import ( @@ -16,17 +15,18 @@ EdxCourseEnrollmentDeactivated, ) -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance, mock_url -@custom_given(EdxCourseEnrollmentActivated, provisional.urls()) @pytest.mark.parametrize("uuid_namespace", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) def test_models_edx_converters_xapi_enrollment_edx_course_enrollment_activated_to_lms_registered_course( # noqa: E501 - uuid_namespace, event, platform_url + uuid_namespace, # , event, platform_url ): """Test that converting with `EdxCourseEnrollmentActivatedToLMSRegisteredCourse` returns the expected xAPI statement. """ + event = mock_instance(EdxCourseEnrollmentActivated) + platform_url = mock_url() event.event.course_id = "edX/DemoX/Demo_Course" event.context.user_id = "1" @@ -66,15 +66,17 @@ def test_models_edx_converters_xapi_enrollment_edx_course_enrollment_activated_t } -@custom_given(EdxCourseEnrollmentDeactivated, provisional.urls()) +# @custom_given(EdxCourseEnrollmentDeactivated, provisional.urls()) @pytest.mark.parametrize("uuid_namespace", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) def test_models_edx_converters_xapi_enrollment_edx_course_enrollment_deactivated_to_lms_unregistered_course( # noqa: E501 - uuid_namespace, event, platform_url + uuid_namespace, ): """Test that converting with `EdxCourseEnrollmentDeactivatedToLMSUnregisteredCourse` returns the expected xAPI statement. """ + event = mock_instance(EdxCourseEnrollmentDeactivated) + platform_url = mock_url() event.event.course_id = "edX/DemoX/Demo_Course" event.context.user_id = "1" diff --git a/tests/models/edx/converters/xapi/test_navigational.py b/tests/models/edx/converters/xapi/test_navigational.py index 011d1c622..14d87f490 100644 --- a/tests/models/edx/converters/xapi/test_navigational.py +++ b/tests/models/edx/converters/xapi/test_navigational.py @@ -4,23 +4,25 @@ from uuid import UUID, uuid5 import pytest -from hypothesis import provisional from ralph.models.converter import convert_dict_event from ralph.models.edx.converters.xapi.navigational import UIPageCloseToPageTerminated from ralph.models.edx.navigational.statements import UIPageClose -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance, mock_url -@custom_given(UIPageClose, provisional.urls()) @pytest.mark.parametrize("uuid_namespace", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) def test_models_edx_converters_xapi_navigational_ui_page_close_to_page_terminated( - uuid_namespace, event, platform_url + uuid_namespace, ): """Test that converting with UIPageCloseToPageTerminated returns the expected xAPI statement. """ + event = mock_instance(UIPageClose) + platform_url = mock_url() + assert platform_url is not None # TODO: remove this + event.context.user_id = "1" event_str = event.json() event = json.loads(event_str) diff --git a/tests/models/edx/converters/xapi/test_server.py b/tests/models/edx/converters/xapi/test_server.py index 8c0244494..8a1bd215d 100644 --- a/tests/models/edx/converters/xapi/test_server.py +++ b/tests/models/edx/converters/xapi/test_server.py @@ -4,23 +4,25 @@ from uuid import UUID, uuid5 import pytest -from hypothesis import provisional, settings from ralph.models.converter import convert_dict_event, convert_str_event from ralph.models.edx.converters.xapi.server import ServerEventToPageViewed from ralph.models.edx.server import Server -from tests.fixtures.hypothesis_strategies import custom_given +# from tests.fixtures.hypothes is_ strategies import custom_given +from tests.factories import mock_instance, mock_url -@custom_given(Server, provisional.urls()) @pytest.mark.parametrize("uuid_namespace", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) def test_models_edx_converters_xapi_server_server_event_to_page_viewed_constant_uuid( - uuid_namespace, event, platform_url + uuid_namespace, ): """Test that `ServerEventToPageViewed.convert` returns a JSON string with a constant UUID. """ + event = mock_instance(Server) + platform_url = mock_url() + event_str = event.json() event = json.loads(event_str) xapi_event1 = convert_str_event( @@ -32,14 +34,14 @@ def test_models_edx_converters_xapi_server_server_event_to_page_viewed_constant_ assert xapi_event1.id == xapi_event2.id -@custom_given(Server, provisional.urls()) @pytest.mark.parametrize("uuid_namespace", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) -def test_models_edx_converters_xapi_server_server_event_to_page_viewed( - uuid_namespace, event, platform_url -): +def test_models_edx_converters_xapi_server_server_event_to_page_viewed(uuid_namespace): """Test that converting with `ServerEventToPageViewed` returns the expected xAPI statement. """ + event = mock_instance(Server) + platform_url = mock_url() + event.event_type = "/main/blog" event.context.user_id = "1" event_str = event.json() @@ -67,13 +69,13 @@ def test_models_edx_converters_xapi_server_server_event_to_page_viewed( } -@settings(deadline=None) -@custom_given(Server, provisional.urls()) @pytest.mark.parametrize("uuid_namespace", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) def test_models_edx_converters_xapi_server_server_event_to_page_viewed_with_anonymous_user( # noqa: E501 - uuid_namespace, event, platform_url + uuid_namespace, ): """Test that anonymous usernames are replaced with `anonymous`.""" + event = mock_instance(Server) + platform_url = mock_url() event.context.user_id = "" event_str = event.json() diff --git a/tests/models/edx/converters/xapi/test_video.py b/tests/models/edx/converters/xapi/test_video.py index 914c05a46..11e2fd6eb 100644 --- a/tests/models/edx/converters/xapi/test_video.py +++ b/tests/models/edx/converters/xapi/test_video.py @@ -4,7 +4,6 @@ from uuid import UUID, uuid5 import pytest -from hypothesis import provisional from ralph.models.converter import convert_dict_event from ralph.models.edx.converters.xapi.video import ( @@ -22,17 +21,19 @@ UIStopVideo, ) -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance, mock_url -@custom_given(UILoadVideo, provisional.urls()) @pytest.mark.parametrize("uuid_namespace", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) def test_models_edx_converters_xapi_video_ui_load_video_to_video_initialized( - uuid_namespace, event, platform_url + uuid_namespace, ): """Test that converting with `UILoadVideoToVideoInitialized` returns the expected xAPI statement. """ + event = mock_instance(UILoadVideo) + platform_url = mock_url() + event.context.user_id = "1" event.session = "af45a0e650c4a4fdb0bcde75a1e4b694" session_uuid = "af45a0e6-50c4-a4fd-b0bc-de75a1e4b694" @@ -80,14 +81,14 @@ def test_models_edx_converters_xapi_video_ui_load_video_to_video_initialized( } -@custom_given(UIPlayVideo, provisional.urls()) @pytest.mark.parametrize("uuid_namespace", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) -def test_models_edx_converters_xapi_video_ui_play_video_to_video_played( - uuid_namespace, event, platform_url -): +def test_models_edx_converters_xapi_video_ui_play_video_to_video_played(uuid_namespace): """Test that converting with `UIPlayVideoToVideoPlayed` returns the expected xAPI statement. """ + event = mock_instance(UIPlayVideo) + platform_url = mock_url() + event.context.user_id = "1" event.session = "af45a0e650c4a4fdb0bcde75a1e4b694" session_uuid = "af45a0e6-50c4-a4fd-b0bc-de75a1e4b694" @@ -139,14 +140,16 @@ def test_models_edx_converters_xapi_video_ui_play_video_to_video_played( } -@custom_given(UIPauseVideo, provisional.urls()) @pytest.mark.parametrize("uuid_namespace", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) def test_models_edx_converters_xapi_video_ui_pause_video_to_video_paused( - uuid_namespace, event, platform_url + uuid_namespace, ): """Test that converting with `UIPauseVideoToVideoPaused` returns the expected xAPI statement. """ + event = mock_instance(UIPauseVideo) + platform_url = mock_url() + event.context.user_id = "1" event.session = "af45a0e650c4a4fdb0bcde75a1e4b694" session_uuid = "af45a0e6-50c4-a4fd-b0bc-de75a1e4b694" @@ -199,14 +202,16 @@ def test_models_edx_converters_xapi_video_ui_pause_video_to_video_paused( } -@custom_given(UIStopVideo, provisional.urls()) @pytest.mark.parametrize("uuid_namespace", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) def test_models_edx_converters_xapi_video_ui_stop_video_to_video_terminated( - uuid_namespace, event, platform_url + uuid_namespace, ): """Test that converting with `UIStopVideoToVideoTerminated` returns the expected xAPI statement. """ + event = mock_instance(UIStopVideo) + platform_url = mock_url() + event.context.user_id = "1" event.session = "af45a0e650c4a4fdb0bcde75a1e4b694" session_uuid = "af45a0e6-50c4-a4fd-b0bc-de75a1e4b694" @@ -260,14 +265,14 @@ def test_models_edx_converters_xapi_video_ui_stop_video_to_video_terminated( } -@custom_given(UISeekVideo, provisional.urls()) @pytest.mark.parametrize("uuid_namespace", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) -def test_models_edx_converters_xapi_video_ui_seek_video_to_video_seeked( - uuid_namespace, event, platform_url -): +def test_models_edx_converters_xapi_video_ui_seek_video_to_video_seeked(uuid_namespace): """Test that converting with `UISeekVideoToVideoSeeked` returns the expected xAPI statement. """ + event = mock_instance(UISeekVideo) + platform_url = mock_url() + event.context.user_id = "1" event.session = "af45a0e650c4a4fdb0bcde75a1e4b694" session_uuid = "af45a0e6-50c4-a4fd-b0bc-de75a1e4b694" diff --git a/tests/models/edx/navigational/test_events.py b/tests/models/edx/navigational/test_events.py index c40bbf016..6ec8b8fbe 100644 --- a/tests/models/edx/navigational/test_events.py +++ b/tests/models/edx/navigational/test_events.py @@ -8,14 +8,14 @@ from ralph.models.edx.navigational.fields.events import NavigationalEventField -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance -@custom_given(NavigationalEventField) -def test_fields_edx_navigational_events_event_field_with_valid_content(field): +def test_fields_edx_navigational_events_event_field_with_valid_content(): """Test that a valid `NavigationalEventField` does not raise a `ValidationError`. """ + field = mock_instance(NavigationalEventField) assert re.match( ( @@ -53,10 +53,12 @@ def test_fields_edx_navigational_events_event_field_with_valid_content(field): ), ], ) -@custom_given(NavigationalEventField) -def test_fields_edx_navigational_events_event_field_with_invalid_content(id, field): +# @custom_given(NavigationalEventField) +def test_fields_edx_navigational_events_event_field_with_invalid_content(id): """Test that an invalid `NavigationalEventField` raises a `ValidationError`.""" + field = mock_instance(NavigationalEventField) + invalid_field = json.loads(field.json()) invalid_field["id"] = id diff --git a/tests/models/edx/navigational/test_statements.py b/tests/models/edx/navigational/test_statements.py index 7d512c672..675aae887 100644 --- a/tests/models/edx/navigational/test_statements.py +++ b/tests/models/edx/navigational/test_statements.py @@ -4,7 +4,6 @@ import re import pytest -from hypothesis import strategies as st from pydantic.error_wrappers import ValidationError from ralph.models.edx.navigational.fields.events import NavigationalEventField @@ -16,7 +15,7 @@ ) from ralph.models.selector import ModelSelector -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_instance @pytest.mark.parametrize( @@ -28,21 +27,21 @@ UISeqPrev, ], ) -@custom_given(st.data()) -def test_models_edx_navigational_selectors_with_valid_statements(class_, data): +def test_models_edx_navigational_selectors_with_valid_statements(class_): """Test given a valid navigational edX statement the `get_first_model` selector method should return the expected model. """ - statement = json.loads(data.draw(custom_builds(class_)).json()) + statement = json.loads(mock_instance(class_).json()) model = ModelSelector(module="ralph.models.edx").get_first_model(statement) assert model is class_ -@custom_given(NavigationalEventField) -def test_fields_edx_navigational_events_event_field_with_valid_content(field): +def test_fields_edx_navigational_events_event_field_with_valid_content(): """Test that a valid `NavigationalEventField` does not raise a `ValidationError`. """ + field = mock_instance(NavigationalEventField) + assert re.match( ( r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+" @@ -79,9 +78,9 @@ def test_fields_edx_navigational_events_event_field_with_valid_content(field): ), ], ) -@custom_given(NavigationalEventField) -def test_fields_edx_navigational_events_event_field_with_invalid_content(id, field): +def test_fields_edx_navigational_events_event_field_with_invalid_content(id): """Test that an invalid `NavigationalEventField` raises a `ValidationError`.""" + field = mock_instance(NavigationalEventField) invalid_field = json.loads(field.json()) invalid_field["id"] = id @@ -89,34 +88,34 @@ def test_fields_edx_navigational_events_event_field_with_invalid_content(id, fie NavigationalEventField(**invalid_field) -@custom_given(UIPageClose) -def test_models_edx_ui_page_close_with_valid_statement(statement): +def test_models_edx_ui_page_close_with_valid_statement(): """Test that a `page_close` statement has the expected `event`, `event_type` and `name`. """ + statement = mock_instance(UIPageClose) assert statement.event == "{}" assert statement.event_type == "page_close" assert statement.name == "page_close" -@custom_given(UISeqGoto) -def test_models_edx_ui_seq_goto_with_valid_statement(statement): +def test_models_edx_ui_seq_goto_with_valid_statement(): """Test that a `seq_goto` statement has the expected `event_type` and `name`.""" + statement = mock_instance(UISeqGoto) assert statement.event_type == "seq_goto" assert statement.name == "seq_goto" -@custom_given(UISeqNext) -def test_models_edx_ui_seq_next_with_valid_statement(statement): +def test_models_edx_ui_seq_next_with_valid_statement(): """Test that a `seq_next` statement has the expected `event_type` and `name`.""" + statement = mock_instance(UISeqNext) assert statement.event_type == "seq_next" assert statement.name == "seq_next" @pytest.mark.parametrize("old,new", [("0", "10"), ("10", "0")]) -@custom_given(UISeqNext) -def test_models_edx_ui_seq_next_with_invalid_statement(old, new, event): +def test_models_edx_ui_seq_next_with_invalid_statement(old, new): """Test that an invalid `seq_next` event raises a ValidationError.""" + event = mock_instance(UISeqNext) invalid_event = json.loads(event.json()) invalid_event["event"]["old"] = old invalid_event["event"]["new"] = new @@ -128,17 +127,17 @@ def test_models_edx_ui_seq_next_with_invalid_statement(old, new, event): UISeqNext(**invalid_event) -@custom_given(UISeqPrev) -def test_models_edx_ui_seq_prev_with_valid_statement(statement): +def test_models_edx_ui_seq_prev_with_valid_statement(): """Test that a `seq_prev` statement has the expected `event_type` and `name`.""" + statement = mock_instance(UISeqPrev) assert statement.event_type == "seq_prev" assert statement.name == "seq_prev" @pytest.mark.parametrize("old,new", [("0", "10"), ("10", "0")]) -@custom_given(UISeqPrev) -def test_models_edx_ui_seq_prev_with_invalid_statement(old, new, event): +def test_models_edx_ui_seq_prev_with_invalid_statement(old, new): """Test that an invalid `seq_prev` event raises a ValidationError.""" + event = mock_instance(UISeqPrev) invalid_event = json.loads(event.json()) invalid_event["event"]["old"] = old invalid_event["event"]["new"] = new diff --git a/tests/models/edx/open_response_assessment/test_events.py b/tests/models/edx/open_response_assessment/test_events.py index 614f2bc98..61641aa52 100644 --- a/tests/models/edx/open_response_assessment/test_events.py +++ b/tests/models/edx/open_response_assessment/test_events.py @@ -13,38 +13,36 @@ ORAGetSubmissionForStaffGradingEventField, ) -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance -@custom_given(ORAGetPeerSubmissionEventField) -def test_models_edx_ora_get_peer_submission_event_field_with_valid_values(field): +def test_models_edx_ora_get_peer_submission_event_field_with_valid_values(): """Test that a valid `ORAGetPeerSubmissionEventField` does not raise a `ValidationError`. """ + field = mock_instance(ORAGetPeerSubmissionEventField) assert re.match( r"^block-v1:.+\+.+\+.+type@openassessment+block@[a-f0-9]{32}$", field.item_id ) -@custom_given(ORAGetSubmissionForStaffGradingEventField) -def test_models_edx_ora_get_submission_for_staff_grading_event_field_with_valid_values( - field, -): +def test_models_edx_ora_get_submission_for_staff_grading_event_field_with_valid_values(): """Test that a valid `ORAGetSubmissionForStaffGradingEventField` does not raise a `ValidationError`. """ + field = mock_instance(ORAGetSubmissionForStaffGradingEventField) assert re.match( r"^block-v1:.+\+.+\+.+type@openassessment+block@[a-f0-9]{32}$", field.item_id ) -@custom_given(ORAAssessEventField) -def test_models_edx_ora_assess_event_field_with_valid_values(field): +def test_models_edx_ora_assess_event_field_with_valid_values(): """Test that a valid `ORAAssessEventField` does not raise a `ValidationError`. """ + field = mock_instance(ORAAssessEventField) assert field.score_type in {"PE", "SE", "ST"} @@ -53,9 +51,9 @@ def test_models_edx_ora_assess_event_field_with_valid_values(field): "score_type", ["pe", "se", "st", "SA", "PA", "22", "&T"], ) -@custom_given(ORAAssessEventField) -def test_models_edx_ora_assess_event_field_with_invalid_values(score_type, field): +def test_models_edx_ora_assess_event_field_with_invalid_values(score_type): """Test that invalid `ORAAssessEventField` raises a `ValidationError`.""" + field = mock_instance(ORAAssessEventField) invalid_field = json.loads(field.json()) invalid_field["score_type"] = score_type @@ -72,13 +70,13 @@ def test_models_edx_ora_assess_event_field_with_invalid_values(score_type, field "D0d4a647742943e3951b45d9db8a0ea1ff71ae36", ], ) -@custom_given(ORAAssessEventRubricField) def test_models_edx_ora_assess_event_rubric_field_with_invalid_problem_id_value( - content_hash, field + content_hash, ): """Test that an invalid `problem_id` value in `ProblemCheckEventField` raises a `ValidationError`. """ + field = mock_instance(ORAAssessEventRubricField) invalid_field = json.loads(field.json()) invalid_field["content_hash"] = content_hash diff --git a/tests/models/edx/open_response_assessment/test_statements.py b/tests/models/edx/open_response_assessment/test_statements.py index dc03c7b74..ed308f948 100644 --- a/tests/models/edx/open_response_assessment/test_statements.py +++ b/tests/models/edx/open_response_assessment/test_statements.py @@ -3,7 +3,6 @@ import json import pytest -from hypothesis import strategies as st from ralph.models.edx.open_response_assessment.statements import ( ORACreateSubmission, @@ -19,7 +18,7 @@ ) from ralph.models.selector import ModelSelector -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_instance @pytest.mark.parametrize( @@ -37,34 +36,30 @@ ORAStudentTrainingAssessExample, ], ) -@custom_given(st.data()) -def test_models_edx_ora_selectors_with_valid_statements(class_, data): +def test_models_edx_ora_selectors_with_valid_statements(class_): """Test given a valid open response assessment edX statement the `get_first_model` selector method should return the expected model. """ - - statement = json.loads(data.draw(custom_builds(class_)).json()) + statement = json.loads(mock_instance(class_).json()) model = ModelSelector(module="ralph.models.edx").get_first_model(statement) assert model is class_ -@custom_given(ORAGetPeerSubmission) -def test_models_edx_ora_get_peer_submission_with_valid_statement(statement): +def test_models_edx_ora_get_peer_submission_with_valid_statement(): """Test that a `openassessmentblock.get_peer_submission` statement has the expected `event_type` and `page` fields. """ + statement = mock_instance(ORAGetPeerSubmission) assert statement.event_type == "openassessmentblock.get_peer_submission" assert statement.page == "x_module" -@custom_given(ORAGetSubmissionForStaffGrading) -def test_models_edx_ora_get_submission_for_staff_grading_with_valid_statement( - statement, -): +def test_models_edx_ora_get_submission_for_staff_grading_with_valid_statement(): """Test that a `openassessmentblock.get_submission_for_staff_grading` statement has the expected `event_type` and `page` fields. """ + statement = mock_instance(ORAGetSubmissionForStaffGrading) assert ( statement.event_type == "openassessmentblock.get_submission_for_staff_grading" @@ -72,81 +67,81 @@ def test_models_edx_ora_get_submission_for_staff_grading_with_valid_statement( assert statement.page == "x_module" -@custom_given(ORAPeerAssess) -def test_models_edx_ora_peer_assess_with_valid_statement(statement): +def test_models_edx_ora_peer_assess_with_valid_statement(): """Test that a `openassessmentblock.peer_assess` statement has the expected `event_type` and `page` fields. """ + statement = mock_instance(ORAPeerAssess) assert statement.event_type == "openassessmentblock.peer_assess" assert statement.page == "x_module" -@custom_given(ORASelfAssess) -def test_models_edx_ora_self_assess_with_valid_statement(statement): +def test_models_edx_ora_self_assess_with_valid_statement(): """Test that a `openassessmentblock.self_assess` statement has the expected `event_type` and `page` fields. """ + statement = mock_instance(ORASelfAssess) assert statement.event_type == "openassessmentblock.self_assess" assert statement.page == "x_module" -@custom_given(ORAStaffAssess) -def test_models_edx_ora_staff_assess_with_valid_statement(statement): +def test_models_edx_ora_staff_assess_with_valid_statement(): """Test that a `openassessmentblock.staff_assess` statement has the expected `event_type` and `page` fields. """ + statement = mock_instance(ORAStaffAssess) assert statement.event_type == "openassessmentblock.staff_assess" assert statement.page == "x_module" -@custom_given(ORASubmitFeedbackOnAssessments) -def test_models_edx_ora_submit_feedback_on_assessments_with_valid_statement(statement): +def test_models_edx_ora_submit_feedback_on_assessments_with_valid_statement(): """Test that a `openassessmentblock.submit_feedback_on_assessments` statement has the expected `event_type` and `page` fields. """ + statement = mock_instance(ORASubmitFeedbackOnAssessments) assert statement.event_type == "openassessmentblock.submit_feedback_on_assessments" assert statement.page == "x_module" -@custom_given(ORACreateSubmission) -def test_models_edx_ora_create_submission_with_valid_statement(statement): +def test_models_edx_ora_create_submission_with_valid_statement(): """Test that a `openassessmentblock.create_submission` statement has the expected `event_type` and `page` fields. """ + statement = mock_instance(ORACreateSubmission) assert statement.event_type == "openassessmentblock.create_submission" assert statement.page == "x_module" -@custom_given(ORASaveSubmission) -def test_models_edx_ora_save_submission_with_valid_statement(statement): +def test_models_edx_ora_save_submission_with_valid_statement(): """Test that a `openassessmentblock.save_submission` statement has the expected `event_type` and `page` fields. """ + statement = mock_instance(ORASaveSubmission) assert statement.event_type == "openassessmentblock.save_submission" assert statement.page == "x_module" -@custom_given(ORAStudentTrainingAssessExample) -def test_models_edx_ora_student_training_assess_example_with_valid_statement(statement): +def test_models_edx_ora_student_training_assess_example_with_valid_statement(): """Test that a `openassessment.student_training_assess_example` statement has the expected `event_type` and `page` fields. """ + statement = mock_instance(ORAStudentTrainingAssessExample) assert statement.event_type == "openassessment.student_training_assess_example" assert statement.page == "x_module" -@custom_given(ORAUploadFile) -def test_models_edx_ora_upload_file_example_with_valid_statement(statement): +def test_models_edx_ora_upload_file_example_with_valid_statement(): """Test that a `openassessment.upload_file` statement has the expected `event_type` and `page` fields. """ + statement = mock_instance(ORAUploadFile) assert statement.event_type == "openassessment.upload_file" assert statement.name == "openassessment.upload_file" diff --git a/tests/models/edx/peer_instruction/test_events.py b/tests/models/edx/peer_instruction/test_events.py index 8c9cbe0f3..b828a29dd 100644 --- a/tests/models/edx/peer_instruction/test_events.py +++ b/tests/models/edx/peer_instruction/test_events.py @@ -7,22 +7,23 @@ from ralph.models.edx.peer_instruction.fields.events import PeerInstructionEventField -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance -@custom_given(PeerInstructionEventField) -def test_models_edx_peer_instruction_event_field_with_valid_field(field): +def test_models_edx_peer_instruction_event_field_with_valid_field(): """Test that a valid `PeerInstructionEventField` does not raise a `ValidationError`. """ + field = mock_instance(PeerInstructionEventField) assert len(field.rationale) <= 12500 -@custom_given(PeerInstructionEventField) -def test_models_edx_peer_instruction_event_field_with_invalid_rationale(field): +def test_models_edx_peer_instruction_event_field_with_invalid_rationale(): """Test that a valid `PeerInstructionEventField` does not raise a `ValidationError`. """ + field = mock_instance(PeerInstructionEventField) + invalid_field = json.loads(field.json()) invalid_field["rationale"] = "x" * 12501 with pytest.raises( diff --git a/tests/models/edx/peer_instruction/test_statements.py b/tests/models/edx/peer_instruction/test_statements.py index 3c2841573..f4d01ce9c 100644 --- a/tests/models/edx/peer_instruction/test_statements.py +++ b/tests/models/edx/peer_instruction/test_statements.py @@ -3,7 +3,6 @@ import json import pytest -from hypothesis import strategies as st from ralph.models.edx.peer_instruction.statements import ( PeerInstructionAccessed, @@ -12,7 +11,7 @@ ) from ralph.models.selector import ModelSelector -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_instance @pytest.mark.parametrize( @@ -23,44 +22,37 @@ PeerInstructionRevisedSubmitted, ], ) -@custom_given(st.data()) -def test_models_edx_peer_instruction_selectors_with_valid_statements(class_, data): +def test_models_edx_peer_instruction_selectors_with_valid_statements(class_): """Test given a valid peer_instruction edX statement the `get_first_model` selector method should return the expected model. """ - statement = json.loads(data.draw(custom_builds(class_)).json()) + statement = json.loads(mock_instance(class_).json()) model = ModelSelector(module="ralph.models.edx").get_first_model(statement) assert model is class_ -@custom_given(PeerInstructionAccessed) -def test_models_edx_peer_instruction_accessed_with_valid_statement( - statement, -): +def test_models_edx_peer_instruction_accessed_with_valid_statement(): """Test that a `ubc.peer_instruction.accessed` statement has the expected `event_type`. """ + statement = mock_instance(PeerInstructionAccessed) assert statement.event_type == "ubc.peer_instruction.accessed" assert statement.name == "ubc.peer_instruction.accessed" -@custom_given(PeerInstructionOriginalSubmitted) -def test_models_edx_peer_instruction_original_submitted_with_valid_statement( - statement, -): +def test_models_edx_peer_instruction_original_submitted_with_valid_statement(): """Test that a `ubc.peer_instruction.original_submitted` statement has the expected `event_type`. """ + statement = mock_instance(PeerInstructionOriginalSubmitted) assert statement.event_type == "ubc.peer_instruction.original_submitted" assert statement.name == "ubc.peer_instruction.original_submitted" -@custom_given(PeerInstructionRevisedSubmitted) -def test_models_edx_peer_instruction_revised_submitted_with_valid_statement( - statement, -): +def test_models_edx_peer_instruction_revised_submitted_with_valid_statement(): """Test that a `ubc.peer_instruction.revised_submitted` statement has the expected `event_type`. """ + statement = mock_instance(PeerInstructionRevisedSubmitted) assert statement.event_type == "ubc.peer_instruction.revised_submitted" assert statement.name == "ubc.peer_instruction.revised_submitted" diff --git a/tests/models/edx/problem_interaction/test_events.py b/tests/models/edx/problem_interaction/test_events.py index 1eef264fc..e3abd1954 100644 --- a/tests/models/edx/problem_interaction/test_events.py +++ b/tests/models/edx/problem_interaction/test_events.py @@ -19,22 +19,22 @@ SaveProblemSuccessEventField, ) -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance -@custom_given(CorrectMap) -def test_models_edx_correct_map_with_valid_content(subfield): +def test_models_edx_correct_map_with_valid_content(): """Test that a valid `CorrectMap` does not raise a `ValidationError`.""" + subfield = mock_instance(CorrectMap) assert subfield.correctness in ("correct", "incorrect") assert subfield.hintmode in ("on_request", "always", None) @pytest.mark.parametrize("correctness", ["corect", "incorect"]) -@custom_given(CorrectMap) -def test_models_edx_correct_map_with_invalid_correctness_value(correctness, subfield): +def test_models_edx_correct_map_with_invalid_correctness_value(correctness): """Test that an invalid `correctness` value in `CorrectMap` raises a `ValidationError`. """ + subfield = mock_instance(CorrectMap) invalid_subfield = json.loads(subfield.json()) invalid_subfield["correctness"] = correctness @@ -43,11 +43,11 @@ def test_models_edx_correct_map_with_invalid_correctness_value(correctness, subf @pytest.mark.parametrize("hintmode", ["onrequest", "alway"]) -@custom_given(CorrectMap) -def test_models_edx_correct_map_with_invalid_hintmode_value(hintmode, subfield): +def test_models_edx_correct_map_with_invalid_hintmode_value(hintmode): """Test that an invalid `hintmode` value in `CorrectMap` raises a `ValidationError`. """ + subfield = mock_instance(CorrectMap) invalid_subfield = json.loads(subfield.json()) invalid_subfield["hintmode"] = hintmode @@ -55,11 +55,11 @@ def test_models_edx_correct_map_with_invalid_hintmode_value(hintmode, subfield): CorrectMap(**invalid_subfield) -@custom_given(EdxProblemHintFeedbackDisplayedEventField) -def test_models_edx_problem_hint_feedback_displayed_event_field_with_valid_field(field): +def test_models_edx_problem_hint_feedback_displayed_event_field_with_valid_field(): """Test that a valid `EdxProblemHintFeedbackDisplayedEventField` does not raise a `ValidationError`. """ + field = mock_instance(EdxProblemHintFeedbackDisplayedEventField) assert field.question_type in ( "stringresponse", "choiceresponse", @@ -80,13 +80,14 @@ def test_models_edx_problem_hint_feedback_displayed_event_field_with_valid_field "optionrespons", ], ) -@custom_given(EdxProblemHintFeedbackDisplayedEventField) def test_models_edx_problem_hint_feedback_displayed_event_field_with_invalid_question_type_value( # noqa - question_type, field + question_type, ): """Test that an invalid `question_type` value in `EdxProblemHintFeedbackDisplayedEventField` raises a `ValidationError`. """ + field = mock_instance(EdxProblemHintFeedbackDisplayedEventField) + invalid_field = json.loads(field.json()) invalid_field["question_type"] = question_type @@ -95,13 +96,13 @@ def test_models_edx_problem_hint_feedback_displayed_event_field_with_invalid_que @pytest.mark.parametrize("trigger_type", ["jingle", "compund"]) -@custom_given(EdxProblemHintFeedbackDisplayedEventField) def test_models_edx_problem_hint_feedback_displayed_event_field_with_invalid_trigger_type_value( # noqa - trigger_type, field + trigger_type, ): """Test that an invalid `question_type` value in `EdxProblemHintFeedbackDisplayedEventField` raises a `ValidationError`. """ + field = mock_instance(EdxProblemHintFeedbackDisplayedEventField) invalid_field = json.loads(field.json()) invalid_field["trigger_type"] = trigger_type @@ -109,11 +110,11 @@ def test_models_edx_problem_hint_feedback_displayed_event_field_with_invalid_tri EdxProblemHintFeedbackDisplayedEventField(**invalid_field) -@custom_given(ProblemCheckEventField) -def test_models_edx_problem_check_event_field_with_valid_field(field): +def test_models_edx_problem_check_event_field_with_valid_field(): """Test that a valid `ProblemCheckEventField` does not raise a `ValidationError`. """ + field = mock_instance(ProblemCheckEventField) assert re.match( ( r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]" @@ -151,13 +152,11 @@ def test_models_edx_problem_check_event_field_with_valid_field(field): ), ], ) -@custom_given(ProblemCheckEventField) -def test_models_edx_problem_check_event_field_with_invalid_problem_id_value( - problem_id, field -): +def test_models_edx_problem_check_event_field_with_invalid_problem_id_value(problem_id): """Test that an invalid `problem_id` value in `ProblemCheckEventField` raises a `ValidationError`. """ + field = mock_instance(ProblemCheckEventField) invalid_field = json.loads(field.json()) invalid_field["problem_id"] = problem_id @@ -168,13 +167,11 @@ def test_models_edx_problem_check_event_field_with_invalid_problem_id_value( @pytest.mark.parametrize("success", ["corect", "incorect"]) -@custom_given(ProblemCheckEventField) -def test_models_edx_problem_check_event_field_with_invalid_success_value( - success, field -): +def test_models_edx_problem_check_event_field_with_invalid_success_value(success): """Test that an invalid `success` value in `ProblemCheckEventField` raises a `ValidationError`. """ + field = mock_instance(ProblemCheckEventField) invalid_field = json.loads(field.json()) invalid_field["success"] = success @@ -182,11 +179,11 @@ def test_models_edx_problem_check_event_field_with_invalid_success_value( ProblemCheckEventField(**invalid_field) -@custom_given(ProblemCheckFailEventField) -def test_models_edx_problem_check_fail_event_field_with_valid_field(field): +def test_models_edx_problem_check_fail_event_field_with_valid_field(): """Test that a valid `ProblemCheckFailEventField` does not raise a `ValidationError`. """ + field = mock_instance(ProblemCheckFailEventField) assert re.match( ( r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]" @@ -224,13 +221,13 @@ def test_models_edx_problem_check_fail_event_field_with_valid_field(field): ), ], ) -@custom_given(ProblemCheckFailEventField) def test_models_edx_problem_check_fail_event_field_with_invalid_problem_id_value( - problem_id, field + problem_id, ): """Test that an invalid `problem_id` value in `ProblemCheckFailEventField` raises a `ValidationError`. """ + field = mock_instance(ProblemCheckFailEventField) invalid_field = json.loads(field.json()) invalid_field["problem_id"] = problem_id @@ -241,13 +238,11 @@ def test_models_edx_problem_check_fail_event_field_with_invalid_problem_id_value @pytest.mark.parametrize("failure", ["close", "unresit"]) -@custom_given(ProblemCheckFailEventField) -def test_models_edx_problem_check_fail_event_field_with_invalid_failure_value( - failure, field -): +def test_models_edx_problem_check_fail_event_field_with_invalid_failure_value(failure): """Test that an invalid `failure` value in `ProblemCheckFailEventField` raises a `ValidationError`. """ + field = mock_instance(ProblemCheckFailEventField) invalid_field = json.loads(field.json()) invalid_field["failure"] = failure @@ -255,11 +250,11 @@ def test_models_edx_problem_check_fail_event_field_with_invalid_failure_value( ProblemCheckFailEventField(**invalid_field) -@custom_given(ProblemRescoreEventField) -def test_models_edx_problem_rescore_event_field_with_valid_field(field): +def test_models_edx_problem_rescore_event_field_with_valid_field(): """Test that a valid `ProblemRescoreEventField` does not raise a `ValidationError`. """ + field = mock_instance(ProblemRescoreEventField) assert re.match( ( r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]" @@ -297,13 +292,13 @@ def test_models_edx_problem_rescore_event_field_with_valid_field(field): ), ], ) -@custom_given(ProblemRescoreEventField) def test_models_edx_problem_rescore_event_field_with_invalid_problem_id_value( - problem_id, field + problem_id, ): """Test that an invalid `problem_id` value in `ProblemRescoreEventField` raises a `ValidationError`. """ + field = mock_instance(ProblemRescoreEventField) invalid_field = json.loads(field.json()) invalid_field["problem_id"] = problem_id @@ -314,13 +309,11 @@ def test_models_edx_problem_rescore_event_field_with_invalid_problem_id_value( @pytest.mark.parametrize("success", ["corect", "incorect"]) -@custom_given(ProblemRescoreEventField) -def test_models_edx_problem_rescore_event_field_with_invalid_success_value( - success, field -): +def test_models_edx_problem_rescore_event_field_with_invalid_success_value(success): """Test that an invalid `success` value in `ProblemRescoreEventField` raises a `ValidationError`. """ + field = mock_instance(ProblemRescoreEventField) invalid_field = json.loads(field.json()) invalid_field["success"] = success @@ -328,11 +321,11 @@ def test_models_edx_problem_rescore_event_field_with_invalid_success_value( ProblemRescoreEventField(**invalid_field) -@custom_given(ProblemRescoreFailEventField) -def test_models_edx_problem_rescore_fail_event_field_with_valid_field(field): +def test_models_edx_problem_rescore_fail_event_field_with_valid_field(): """Test that a valid `ProblemRescoreFailEventField` does not raise a `ValidationError`. """ + field = mock_instance(ProblemRescoreFailEventField) assert re.match( ( r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]" @@ -370,13 +363,13 @@ def test_models_edx_problem_rescore_fail_event_field_with_valid_field(field): ), ], ) -@custom_given(ProblemRescoreFailEventField) def test_models_edx_problem_rescore_fail_event_field_with_invalid_problem_id_value( - problem_id, field + problem_id, ): """Test that an invalid `problem_id` value in `ProblemRescoreFailEventField` raises a `ValidationError`. """ + field = mock_instance(ProblemRescoreFailEventField) invalid_field = json.loads(field.json()) invalid_field["problem_id"] = problem_id @@ -387,13 +380,13 @@ def test_models_edx_problem_rescore_fail_event_field_with_invalid_problem_id_val @pytest.mark.parametrize("failure", ["close", "unresit"]) -@custom_given(ProblemRescoreFailEventField) def test_models_edx_problem_rescore_fail_event_field_with_invalid_failure_value( - failure, field + failure, ): """Test that an invalid `failure` value in `ProblemRescoreFailEventField` raises a `ValidationError`. """ + field = mock_instance(ProblemRescoreFailEventField) invalid_field = json.loads(field.json()) invalid_field["failure"] = failure @@ -401,11 +394,11 @@ def test_models_edx_problem_rescore_fail_event_field_with_invalid_failure_value( ProblemRescoreFailEventField(**invalid_field) -@custom_given(ResetProblemEventField) -def test_models_edx_reset_problem_event_field_with_valid_field(field): +def test_models_edx_reset_problem_event_field_with_valid_field(): """Test that a valid `ResetProblemEventField` does not raise a `ValidationError`. """ + field = mock_instance(ResetProblemEventField) assert re.match( ( r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]" @@ -442,13 +435,11 @@ def test_models_edx_reset_problem_event_field_with_valid_field(field): ), ], ) -@custom_given(ResetProblemEventField) -def test_models_edx_reset_problem_event_field_with_invalid_problem_id_value( - problem_id, field -): +def test_models_edx_reset_problem_event_field_with_invalid_problem_id_value(problem_id): """Test that an invalid `problem_id` value in `ResetProblemEventField` raises a `ValidationError`. """ + field = mock_instance(ResetProblemEventField) invalid_field = json.loads(field.json()) invalid_field["problem_id"] = problem_id @@ -458,11 +449,11 @@ def test_models_edx_reset_problem_event_field_with_invalid_problem_id_value( ResetProblemEventField(**invalid_field) -@custom_given(ResetProblemFailEventField) -def test_models_edx_reset_problem_fail_event_field_with_valid_field(field): +def test_models_edx_reset_problem_fail_event_field_with_valid_field(): """Test that a valid `ResetProblemFailEventField` does not raise a `ValidationError`. """ + field = mock_instance(ResetProblemFailEventField) assert re.match( ( r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]" @@ -500,13 +491,13 @@ def test_models_edx_reset_problem_fail_event_field_with_valid_field(field): ), ], ) -@custom_given(ResetProblemFailEventField) def test_models_edx_reset_problem_fail_event_field_with_invalid_problem_id_value( - problem_id, field + problem_id, ): """Test that an invalid `problem_id` value in `ResetProblemFailEventField` raises a `ValidationError`. """ + field = mock_instance(ResetProblemFailEventField) invalid_field = json.loads(field.json()) invalid_field["problem_id"] = problem_id @@ -517,13 +508,11 @@ def test_models_edx_reset_problem_fail_event_field_with_invalid_problem_id_value @pytest.mark.parametrize("failure", ["close", "not_close"]) -@custom_given(ResetProblemFailEventField) -def test_models_edx_reset_problem_fail_event_field_with_invalid_failure_value( - failure, field -): +def test_models_edx_reset_problem_fail_event_field_with_invalid_failure_value(failure): """Test that an invalid `failure` value in `ResetProblemFailEventField` raises a `ValidationError`. """ + field = mock_instance(ResetProblemFailEventField) invalid_field = json.loads(field.json()) invalid_field["failure"] = failure @@ -531,11 +520,11 @@ def test_models_edx_reset_problem_fail_event_field_with_invalid_failure_value( ResetProblemFailEventField(**invalid_field) -@custom_given(SaveProblemFailEventField) -def test_models_edx_save_problem_fail_event_field_with_valid_field(field): +def test_models_edx_save_problem_fail_event_field_with_valid_field(): """Test that a valid `SaveProblemFailEventField` does not raise a `ValidationError`. """ + field = mock_instance(SaveProblemFailEventField) assert re.match( ( r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]" @@ -573,13 +562,13 @@ def test_models_edx_save_problem_fail_event_field_with_valid_field(field): ), ], ) -@custom_given(SaveProblemFailEventField) def test_models_edx_save_problem_fail_event_field_with_invalid_problem_id_value( - problem_id, field + problem_id, ): """Test that an invalid `problem_id` value in `SaveProblemFailEventField` raises a `ValidationError`. """ + field = mock_instance(SaveProblemFailEventField) invalid_field = json.loads(field.json()) invalid_field["problem_id"] = problem_id @@ -590,13 +579,11 @@ def test_models_edx_save_problem_fail_event_field_with_invalid_problem_id_value( @pytest.mark.parametrize("failure", ["close", "doned"]) -@custom_given(SaveProblemFailEventField) -def test_models_edx_save_problem_fail_event_field_with_invalid_failure_value( - failure, field -): +def test_models_edx_save_problem_fail_event_field_with_invalid_failure_value(failure): """Test that an invalid `failure` value in `SaveProblemFailEventField` raises a `ValidationError`. """ + field = mock_instance(SaveProblemFailEventField) invalid_field = json.loads(field.json()) invalid_field["failure"] = failure @@ -604,11 +591,11 @@ def test_models_edx_save_problem_fail_event_field_with_invalid_failure_value( SaveProblemFailEventField(**invalid_field) -@custom_given(SaveProblemSuccessEventField) -def test_models_edx_save_problem_success_event_field_with_valid_field(field): +def test_models_edx_save_problem_success_event_field_with_valid_field(): """Test that a valid `SaveProblemFailEventField` does not raise a `ValidationError`. """ + field = mock_instance(SaveProblemSuccessEventField) assert re.match( ( r"^block-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]" @@ -645,13 +632,13 @@ def test_models_edx_save_problem_success_event_field_with_valid_field(field): ), ], ) -@custom_given(SaveProblemSuccessEventField) def test_models_edx_save_problem_success_event_field_with_invalid_problem_id_value( - problem_id, field + problem_id, ): """Test that an invalid `problem_id` value in `SaveProblemSuccessEventField` raises a `ValidationError`. """ + field = mock_instance(SaveProblemSuccessEventField) invalid_field = json.loads(field.json()) invalid_field["problem_id"] = problem_id diff --git a/tests/models/edx/problem_interaction/test_statements.py b/tests/models/edx/problem_interaction/test_statements.py index 0dbb53e19..26b119adc 100644 --- a/tests/models/edx/problem_interaction/test_statements.py +++ b/tests/models/edx/problem_interaction/test_statements.py @@ -3,7 +3,6 @@ import json import pytest -from hypothesis import strategies as st from ralph.models.edx.problem_interaction.statements import ( EdxProblemHintDemandhintDisplayed, @@ -25,7 +24,7 @@ ) from ralph.models.selector import ModelSelector -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_instance @pytest.mark.parametrize( @@ -49,159 +48,154 @@ UIProblemShow, ], ) -@custom_given(st.data()) -def test_models_edx_edx_problem_interaction_selectors_with_valid_statements( - class_, data -): +def test_models_edx_edx_problem_interaction_selectors_with_valid_statements(class_): """Test given a valid problem interaction edX statement the `get_first_model` selector method should return the expected model. """ - statement = json.loads(data.draw(custom_builds(class_)).json()) + statement = json.loads(mock_instance(class_).json()) model = ModelSelector(module="ralph.models.edx").get_first_model(statement) assert model is class_ -@custom_given(EdxProblemHintDemandhintDisplayed) -def test_models_edx_edx_problem_hint_demandhint_displayed_with_valid_statement( - statement, -): +def test_models_edx_edx_problem_hint_demandhint_displayed_with_valid_statement(): """Test that a `edx.problem.hint.demandhint_displayed` statement has the expected `event_type` and `page`. """ + statement = mock_instance(EdxProblemHintDemandhintDisplayed) assert statement.event_type == "edx.problem.hint.demandhint_displayed" assert statement.page == "x_module" -@custom_given(EdxProblemHintFeedbackDisplayed) -def test_models_edx_edx_problem_hint_feedback_displayed_with_valid_statement(statement): +def test_models_edx_edx_problem_hint_feedback_displayed_with_valid_statement(): """Test that a `edx.problem.hint.feedback_displayed` statement has the expected `event_type` and `page`. """ + statement = mock_instance(EdxProblemHintFeedbackDisplayed) assert statement.event_type == "edx.problem.hint.feedback_displayed" assert statement.page == "x_module" -@custom_given(UIProblemCheck) -def test_models_edx_ui_problem_check_with_valid_statement(statement): +def test_models_edx_ui_problem_check_with_valid_statement(): """Test that a `problem_check` browser statement has the expected `event_type` and `name`. """ + statement = mock_instance(UIProblemCheck) assert statement.event_type == "problem_check" assert statement.name == "problem_check" -@custom_given(ProblemCheck) -def test_models_edx_problem_check_with_valid_statement(statement): +def test_models_edx_problem_check_with_valid_statement(): """Test that a `problem_check` server statement has the expected `event_type` and `page`. """ + statement = mock_instance(ProblemCheck) assert statement.event_type == "problem_check" assert statement.page == "x_module" -@custom_given(ProblemCheckFail) -def test_models_edx_problem_check_fail_with_valid_statement(statement): +def test_models_edx_problem_check_fail_with_valid_statement(): """Test that a `problem_check_fail` server statement has the expected `event_type` and `page`. """ + statement = mock_instance(ProblemCheckFail) assert statement.event_type == "problem_check_fail" assert statement.page == "x_module" -@custom_given(UIProblemGraded) -def test_models_edx_ui_problem_graded_with_valid_statement(statement): +def test_models_edx_ui_problem_graded_with_valid_statement(): """Test that a `problem_graded` browser statement has the expected `event_type` and `name`. """ + statement = mock_instance(UIProblemGraded) assert statement.event_type == "problem_graded" assert statement.name == "problem_graded" -@custom_given(ProblemRescore) -def test_models_edx_problem_rescore_with_valid_statement(statement): +def test_models_edx_problem_rescore_with_valid_statement(): """Test that a `problem_rescore` server statement has the expected `event_type` and `page`. """ + statement = mock_instance(ProblemRescore) assert statement.event_type == "problem_rescore" assert statement.page == "x_module" -@custom_given(ProblemRescoreFail) -def test_models_edx_problem_rescore_fail_with_valid_statement(statement): +def test_models_edx_problem_rescore_fail_with_valid_statement(): """Test that a `problem_rescore` server statement has the expected `event_type` and `page`. """ + statement = mock_instance(ProblemRescoreFail) assert statement.event_type == "problem_rescore_fail" assert statement.page == "x_module" -@custom_given(UIProblemReset) -def test_models_edx_ui_problem_reset_with_valid_statement(statement): +def test_models_edx_ui_problem_reset_with_valid_statement(): """Test that a `problem_reset` browser statement has the expected `event_type` and `name`. """ + statement = mock_instance(UIProblemReset) assert statement.event_type == "problem_reset" assert statement.name == "problem_reset" -@custom_given(UIProblemSave) -def test_models_edx_ui_problem_save_with_valid_statement(statement): +def test_models_edx_ui_problem_save_with_valid_statement(): """Test that a `problem_save` browser statement has the expected `event_type` and `name`. """ + statement = mock_instance(UIProblemSave) assert statement.event_type == "problem_save" assert statement.name == "problem_save" -@custom_given(UIProblemShow) -def test_models_edx_ui_problem_show_with_valid_statement(statement): +def test_models_edx_ui_problem_show_with_valid_statement(): """Test that a `problem_show` browser statement has the expected `event_type` and `name`. """ + statement = mock_instance(UIProblemShow) assert statement.event_type == "problem_show" assert statement.name == "problem_show" -@custom_given(ResetProblem) -def test_models_edx_reset_problem_with_valid_statement(statement): +def test_models_edx_reset_problem_with_valid_statement(): """Test that a `reset_problem` server statement has the expected `event_type` and `page`. """ + statement = mock_instance(ResetProblem) assert statement.event_type == "reset_problem" assert statement.page == "x_module" -@custom_given(ResetProblemFail) -def test_models_edx_reset_problem_fail_with_valid_statement(statement): +def test_models_edx_reset_problem_fail_with_valid_statement(): """Test that a `reset_problem_fail` server statement has the expected `event_type` and `page`. """ + statement = mock_instance(ResetProblemFail) assert statement.event_type == "reset_problem_fail" assert statement.page == "x_module" -@custom_given(SaveProblemFail) -def test_models_edx_save_problem_fail_with_valid_statement(statement): +def test_models_edx_save_problem_fail_with_valid_statement(): """Test that a `save_problem_fail` server statement has the expected `event_type` and `page`. """ + statement = mock_instance(SaveProblemFail) assert statement.event_type == "save_problem_fail" assert statement.page == "x_module" -@custom_given(SaveProblemSuccess) -def test_models_edx_save_problem_success_with_valid_statement(statement): +def test_models_edx_save_problem_success_with_valid_statement(): """Test that a `save_problem_success` server statement has the expected `event_type` and `page`. """ + statement = mock_instance(SaveProblemSuccess) assert statement.event_type == "save_problem_success" assert statement.page == "x_module" -@custom_given(ShowAnswer) -def test_models_edx_show_answer_with_valid_statement(statement): +def test_models_edx_show_answer_with_valid_statement(): """Test that a `showanswer` server statement has the expected `event_type` and `page`. """ + statement = mock_instance(ShowAnswer) assert statement.event_type == "showanswer" assert statement.page == "x_module" diff --git a/tests/models/edx/test_base.py b/tests/models/edx/test_base.py index 8f3a65de8..108284f81 100644 --- a/tests/models/edx/test_base.py +++ b/tests/models/edx/test_base.py @@ -8,12 +8,14 @@ from ralph.models.edx.base import BaseEdxModel -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance -@custom_given(BaseEdxModel) -def test_models_edx_base_edx_model_with_valid_statement(statement): +# @custom_given(BaseEdxModel) +def test_models_edx_base_edx_model_with_valid_statement(): """Test that a valid base `Edx` statement does not raise a `ValidationError`.""" + statement = mock_instance(BaseEdxModel) + assert len(statement.username) == 0 or (len(statement.username) in range(2, 31, 1)) assert ( re.match(r"^course-v1:.+\+.+\+.+$", statement.context.course_id) @@ -42,9 +44,10 @@ def test_models_edx_base_edx_model_with_valid_statement(statement): ), ], ) -@custom_given(BaseEdxModel) -def test_models_edx_base_edx_model_with_invalid_statement(course_id, error, statement): +# @custom_given(BaseEdxModel) +def test_models_edx_base_edx_model_with_invalid_statement(course_id, error): """Test that an invalid base `Edx` statement raises a `ValidationError`.""" + statement = mock_instance(BaseEdxModel) invalid_statement = json.loads(statement.json()) invalid_statement["context"]["course_id"] = course_id diff --git a/tests/models/edx/test_browser.py b/tests/models/edx/test_browser.py index 06b2a7bed..856b5d98b 100644 --- a/tests/models/edx/test_browser.py +++ b/tests/models/edx/test_browser.py @@ -8,12 +8,13 @@ from ralph.models.edx.browser import BaseBrowserModel -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance -@custom_given(BaseBrowserModel) -def test_models_edx_base_browser_model_with_valid_statement(statement): +# @custom_given(BaseBrowserModel) +def test_models_edx_base_browser_model_with_valid_statement(): """Test that a valid base browser statement does not raise a `ValidationError`.""" + statement = mock_instance(BaseBrowserModel) assert re.match(r"^[a-f0-9]{32}$", statement.session) or statement.session == "" @@ -28,11 +29,10 @@ def test_models_edx_base_browser_model_with_valid_statement(statement): ("abcdef0123456789_abcdef012345678", "string does not match regex"), ], ) -@custom_given(BaseBrowserModel) -def test_models_edx_base_browser_model_with_invalid_statement( - session, error, statement -): +# @custom_given(BaseBrowserModel) +def test_models_edx_base_browser_model_with_invalid_statement(session, error): """Test that an invalid base browser statement raises a `ValidationError`.""" + statement = mock_instance(BaseBrowserModel) invalid_statement = json.loads(statement.json()) invalid_statement["session"] = session diff --git a/tests/models/edx/test_enrollment.py b/tests/models/edx/test_enrollment.py index 53c1af26c..3cac1973a 100644 --- a/tests/models/edx/test_enrollment.py +++ b/tests/models/edx/test_enrollment.py @@ -3,7 +3,6 @@ import json import pytest -from hypothesis import strategies as st from ralph.models.edx.enrollment.statements import ( EdxCourseEnrollmentActivated, @@ -14,7 +13,7 @@ ) from ralph.models.selector import ModelSelector -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_instance @pytest.mark.parametrize( @@ -27,66 +26,68 @@ UIEdxCourseEnrollmentUpgradeClicked, ], ) -@custom_given(st.data()) -def test_models_edx_edx_course_enrollment_selectors_with_valid_statements(class_, data): +def test_models_edx_edx_course_enrollment_selectors_with_valid_statements(class_): """Test given a valid course enrollment edX statement the `get_first_model` selector method should return the expected model. """ - statement = json.loads(data.draw(custom_builds(class_)).json()) + statement = json.loads(mock_instance(class_).json()) model = ModelSelector(module="ralph.models.edx").get_first_model(statement) assert model is class_ -@custom_given(EdxCourseEnrollmentActivated) +# @custom_given(EdxCourseEnrollmentActivated) def test_models_edx_edx_course_enrollment_activated_with_valid_statement( - statement, + # statement, ): """Test that a `edx.course.enrollment.activated` statement has the expected `event_type` and `name`. """ + statement = mock_instance(EdxCourseEnrollmentActivated) assert statement.event_type == "edx.course.enrollment.activated" assert statement.name == "edx.course.enrollment.activated" -@custom_given(EdxCourseEnrollmentDeactivated) +# @custom_given(EdxCourseEnrollmentDeactivated) def test_models_edx_edx_course_enrollment_deactivated_with_valid_statement( - statement, + # statement, ): """Test that a `edx.course.enrollment.deactivated` statement has the expected `event_type` and `name`. """ + statement = mock_instance(EdxCourseEnrollmentDeactivated) assert statement.event_type == "edx.course.enrollment.deactivated" assert statement.name == "edx.course.enrollment.deactivated" -@custom_given(EdxCourseEnrollmentModeChanged) def test_models_edx_edx_course_enrollment_mode_changed_with_valid_statement( - statement, + # statement, ): """Test that a `edx.course.enrollment.mode_changed` statement has the expected `event_type` and `name`. """ + statement = mock_instance(EdxCourseEnrollmentModeChanged) assert statement.event_type == "edx.course.enrollment.mode_changed" assert statement.name == "edx.course.enrollment.mode_changed" -@custom_given(UIEdxCourseEnrollmentUpgradeClicked) def test_models_edx_ui_edx_course_enrollment_upgrade_clicked_with_valid_statement( - statement, + # statement, ): """Test that a `edx.course.enrollment.upgrade_clicked` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UIEdxCourseEnrollmentUpgradeClicked) assert statement.event_type == "edx.course.enrollment.upgrade_clicked" assert statement.name == "edx.course.enrollment.upgrade_clicked" -@custom_given(EdxCourseEnrollmentUpgradeSucceeded) +# @custom_given(EdxCourseEnrollmentUpgradeSucceeded) def test_models_edx_edx_course_enrollment_upgrade_succeeded_with_valid_statement( - statement, + # statement, ): """Test that a `edx.course.enrollment.upgrade.succeeded` statement has the expected `event_type` and `name`. """ + statement = mock_instance(EdxCourseEnrollmentUpgradeSucceeded) assert statement.event_type == "edx.course.enrollment.upgrade.succeeded" assert statement.name == "edx.course.enrollment.upgrade.succeeded" diff --git a/tests/models/edx/test_server.py b/tests/models/edx/test_server.py index ef8dbded4..ffee331f3 100644 --- a/tests/models/edx/test_server.py +++ b/tests/models/edx/test_server.py @@ -8,14 +8,15 @@ from ralph.models.edx.server import Server from ralph.models.selector import ModelSelector -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance -@custom_given(Server) -def test_model_selector_server_get_model_with_valid_event(event): +def test_model_selector_server_get_model_with_valid_event(): """Test given a server statement, the get_model method should return the corresponding model. """ + event = mock_instance(Server) + event = json.loads(event.json()) assert ModelSelector(module="ralph.models.edx").get_first_model(event) is Server diff --git a/tests/models/edx/textbook_interaction/test_events.py b/tests/models/edx/textbook_interaction/test_events.py index 024fff7ac..e1a03914f 100644 --- a/tests/models/edx/textbook_interaction/test_events.py +++ b/tests/models/edx/textbook_interaction/test_events.py @@ -11,14 +11,14 @@ TextbookPdfChapterNavigatedEventField, ) -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance -@custom_given(TextbookInteractionBaseEventField) -def test_fields_edx_textbook_interaction_base_event_field_with_valid_content(field): +def test_fields_edx_textbook_interaction_base_event_field_with_valid_content(): """Test that a valid `TextbookInteractionBaseEventField` does not raise a `ValidationError`. """ + field = mock_instance(TextbookInteractionBaseEventField) assert re.match( r"^\/asset-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+type@asset\+block.+$", @@ -61,13 +61,11 @@ def test_fields_edx_textbook_interaction_base_event_field_with_valid_content(fie ), ), ) -@custom_given(TextbookInteractionBaseEventField) -def test_fields_edx_textbook_interaction_base_event_field_with_invalid_content( - chapter, field -): +def test_fields_edx_textbook_interaction_base_event_field_with_invalid_content(chapter): """Test that an invalid `TextbookInteractionBaseEventField` raises a `ValidationError`. """ + field = mock_instance(TextbookInteractionBaseEventField) invalid_field = json.loads(field.json()) invalid_field["chapter"] = chapter @@ -76,13 +74,11 @@ def test_fields_edx_textbook_interaction_base_event_field_with_invalid_content( TextbookInteractionBaseEventField(**invalid_field) -@custom_given(TextbookPdfChapterNavigatedEventField) -def test_fields_edx_textbook_pdf_chapter_navigated_event_field_with_valid_content( - field, -): +def test_fields_edx_textbook_pdf_chapter_navigated_event_field_with_valid_content(): """Test that a valid `TextbookPdfChapterNavigatedEventField` does not raise a `ValidationError`. """ + field = mock_instance(TextbookPdfChapterNavigatedEventField) assert re.match( (r"^\/asset-v1:[^\/+]+(\/|\+)[^\/+]+(\/|\+)[^\/?]+type@asset\+block.+$"), @@ -121,13 +117,13 @@ def test_fields_edx_textbook_pdf_chapter_navigated_event_field_with_valid_conten ), ), ) -@custom_given(TextbookPdfChapterNavigatedEventField) def test_fields_edx_textbook_pdf_chapter_navigated_event_field_with_invalid_content( - chapter, field + chapter, ): """Test that an invalid `TextbookPdfChapterNavigatedEventField` raises a `ValidationError`. """ + field = mock_instance(TextbookPdfChapterNavigatedEventField) invalid_field = json.loads(field.json()) invalid_field["chapter"] = chapter diff --git a/tests/models/edx/textbook_interaction/test_statements.py b/tests/models/edx/textbook_interaction/test_statements.py index 1218d74ce..6b522cba9 100644 --- a/tests/models/edx/textbook_interaction/test_statements.py +++ b/tests/models/edx/textbook_interaction/test_statements.py @@ -3,7 +3,6 @@ import json import pytest -from hypothesis import strategies as st from ralph.models.edx.textbook_interaction.statements import ( UIBook, @@ -23,7 +22,7 @@ ) from ralph.models.selector import ModelSelector -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_instance @pytest.mark.parametrize( @@ -45,153 +44,134 @@ UITextbookPdfZoomMenuChanged, ], ) -@custom_given(st.data()) -def test_models_edx_ui_textbook_interaction_selectors_with_valid_statements( - class_, data -): +def test_models_edx_ui_textbook_interaction_selectors_with_valid_statements(class_): """Test given a valid textbook interaction edX statement the `get_first_model` selector method should return the expected model. """ - statement = json.loads(data.draw(custom_builds(class_)).json()) + statement = json.loads(mock_instance(class_).json()) model = ModelSelector(module="ralph.models.edx").get_first_model(statement) assert model is class_ -@custom_given(UIBook) -def test_models_edx_ui_book_with_valid_statement(statement): +def test_models_edx_ui_book_with_valid_statement(): """Test that a `book` statement has the expected `event_type` and `name`.""" + statement = mock_instance(UIBook) assert statement.event_type == "book" assert statement.name == "book" -@custom_given(UITextbookPdfThumbnailsToggled) -def test_models_edx_ui_textbook_pdf_thumbnails_toggled_with_valid_statement(statement): +def test_models_edx_ui_textbook_pdf_thumbnails_toggled_with_valid_statement(): """Test that a `textbook.pdf.thumbnails.toggled` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfThumbnailsToggled) assert statement.event_type == "textbook.pdf.thumbnails.toggled" assert statement.name == "textbook.pdf.thumbnails.toggled" -@custom_given(UITextbookPdfThumbnailNavigated) -def test_models_edx_ui_textbook_pdf_thumbnail_navigated_with_valid_statement( - statement, -): +def test_models_edx_ui_textbook_pdf_thumbnail_navigated_with_valid_statement(): """Test that a `textbook.pdf.thumbnail.navigated` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfThumbnailNavigated) assert statement.event_type == "textbook.pdf.thumbnail.navigated" assert statement.name == "textbook.pdf.thumbnail.navigated" -@custom_given(UITextbookPdfOutlineToggled) -def test_models_edx_ui_textbook_pdf_outline_toggled_with_valid_statement( - statement, -): +def test_models_edx_ui_textbook_pdf_outline_toggled_with_valid_statement(): """Test that a `textbook.pdf.outline.toggled` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfOutlineToggled) assert statement.event_type == "textbook.pdf.outline.toggled" assert statement.name == "textbook.pdf.outline.toggled" -@custom_given(UITextbookPdfChapterNavigated) -def test_models_edx_ui_textbook_pdf_chapter_navigated_with_valid_statement( - statement, -): +def test_models_edx_ui_textbook_pdf_chapter_navigated_with_valid_statement(): """Test that a `textbook.pdf.chapter.navigated` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfChapterNavigated) assert statement.event_type == "textbook.pdf.chapter.navigated" assert statement.name == "textbook.pdf.chapter.navigated" -@custom_given(UITextbookPdfPageNavigated) -def test_models_edx_ui_textbook_pdf_page_navigated_with_valid_statement( - statement, -): +def test_models_edx_ui_textbook_pdf_page_navigated_with_valid_statement(): """Test that a `textbook.pdf.page.navigated` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfPageNavigated) assert statement.event_type == "textbook.pdf.page.navigated" assert statement.name == "textbook.pdf.page.navigated" -@custom_given(UITextbookPdfZoomButtonsChanged) -def test_models_edx_ui_textbook_pdf_zoom_buttons_changed_with_valid_statement( - statement, -): +def test_models_edx_ui_textbook_pdf_zoom_buttons_changed_with_valid_statement(): """Test that a `textbook.pdf.zoom.buttons.changed` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfZoomButtonsChanged) assert statement.event_type == "textbook.pdf.zoom.buttons.changed" assert statement.name == "textbook.pdf.zoom.buttons.changed" -@custom_given(UITextbookPdfZoomMenuChanged) -def test_models_edx_ui_textbook_pdf_zoom_menu_changed_with_valid_statement(statement): +def test_models_edx_ui_textbook_pdf_zoom_menu_changed_with_valid_statement(): """Test that a `textbook.pdf.zoom.menu.changed` has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfZoomMenuChanged) assert statement.event_type == "textbook.pdf.zoom.menu.changed" assert statement.name == "textbook.pdf.zoom.menu.changed" -@custom_given(UITextbookPdfDisplayScaled) -def test_models_edx_ui_textbook_pdf_display_scaled_with_valid_statement(statement): +def test_models_edx_ui_textbook_pdf_display_scaled_with_valid_statement(): """Test that a `textbook.pdf.display.scaled` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfDisplayScaled) assert statement.event_type == "textbook.pdf.display.scaled" assert statement.name == "textbook.pdf.display.scaled" -@custom_given(UITextbookPdfPageScrolled) -def test_models_edx_ui_textbook_pdf_page_scrolled_with_valid_statement(statement): +def test_models_edx_ui_textbook_pdf_page_scrolled_with_valid_statement(): """Test that a `textbook.pdf.page.scrolled` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfPageScrolled) assert statement.event_type == "textbook.pdf.page.scrolled" assert statement.name == "textbook.pdf.page.scrolled" -@custom_given(UITextbookPdfSearchExecuted) -def test_models_edx_ui_textbook_pdf_search_executed_with_valid_statement(statement): +def test_models_edx_ui_textbook_pdf_search_executed_with_valid_statement(): """Test that a `textbook.pdf.search.executed` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfSearchExecuted) assert statement.event_type == "textbook.pdf.search.executed" assert statement.name == "textbook.pdf.search.executed" -@custom_given(UITextbookPdfSearchNavigatedNext) -def test_models_edx_ui_textbook_pdf_search_navigated_next_with_valid_statement( - statement, -): +def test_models_edx_ui_textbook_pdf_search_navigated_next_with_valid_statement(): """Test that a `textbook.pdf.search.navigatednext` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfSearchNavigatedNext) assert statement.event_type == "textbook.pdf.search.navigatednext" assert statement.name == "textbook.pdf.search.navigatednext" -@custom_given(UITextbookPdfSearchHighlightToggled) -def test_models_edx_ui_textbook_pdf_search_highlight_toggled_with_valid_statement( - statement, -): +def test_models_edx_ui_textbook_pdf_search_highlight_toggled_with_valid_statement(): """Test that a `textbook.pdf.search.highlight.toggled` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfSearchHighlightToggled) assert statement.event_type == "textbook.pdf.search.highlight.toggled" assert statement.name == "textbook.pdf.search.highlight.toggled" -@custom_given(UITextbookPdfSearchCaseSensitivityToggled) -def test_models_edx_ui_textbook_pdf_search_case_sensitivity_toggled_with_valid_statement( # noqa - statement, -): +def test_models_edx_ui_textbook_pdf_search_case_sensitivity_toggled_with_valid_statement(): # noqa """Test that a `textbook.pdf.searchcasesensitivity.toggled` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UITextbookPdfSearchCaseSensitivityToggled) assert statement.event_type == "textbook.pdf.searchcasesensitivity.toggled" assert statement.name == "textbook.pdf.searchcasesensitivity.toggled" diff --git a/tests/models/edx/video/test_events.py b/tests/models/edx/video/test_events.py index 166589e72..b7b0bc830 100644 --- a/tests/models/edx/video/test_events.py +++ b/tests/models/edx/video/test_events.py @@ -7,14 +7,14 @@ from ralph.models.edx.video.fields.events import SpeedChangeVideoEventField -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance -@custom_given(SpeedChangeVideoEventField) -def test_models_edx_speed_change_video_event_field_with_valid_field(field): +def test_models_edx_speed_change_video_event_field_with_valid_field(): """Test that a valid `SpeedChangeVideoEventField` does not raise a `ValidationError`. """ + field = mock_instance(SpeedChangeVideoEventField) assert field.old_speed in ["0.75", "1.0", "1.25", "1.50", "2.0"] assert field.new_speed in ["0.75", "1.0", "1.25", "1.50", "2.0"] @@ -23,13 +23,13 @@ def test_models_edx_speed_change_video_event_field_with_valid_field(field): "old_speed", ["0,75", "1", "-1.0", "1.30"], ) -@custom_given(SpeedChangeVideoEventField) def test_models_edx_speed_change_video_event_field_with_invalid_old_speed_value( - old_speed, field + old_speed, ): """Test that an invalid `old_speed` value in `SpeedChangeVideoEventField` raises a `ValidationError`. """ + field = mock_instance(SpeedChangeVideoEventField) invalid_field = json.loads(field.json()) invalid_field["old_speed"] = old_speed @@ -41,13 +41,13 @@ def test_models_edx_speed_change_video_event_field_with_invalid_old_speed_value( "new_speed", ["0,75", "1", "-1.0", "1.30"], ) -@custom_given(SpeedChangeVideoEventField) def test_models_edx_speed_change_video_event_field_with_invalid_new_speed_value( - new_speed, field + new_speed, ): """Test that an invalid `new_speed` value in `SpeedChangeVideoEventField` raises a `ValidationError`. """ + field = mock_instance(SpeedChangeVideoEventField) invalid_field = json.loads(field.json()) invalid_field["new_speed"] = new_speed diff --git a/tests/models/edx/video/test_statements.py b/tests/models/edx/video/test_statements.py index e2dec9269..4dc3694b1 100644 --- a/tests/models/edx/video/test_statements.py +++ b/tests/models/edx/video/test_statements.py @@ -3,7 +3,6 @@ import json import pytest -from hypothesis import strategies as st from ralph.models.edx.video.statements import ( UIHideTranscript, @@ -19,7 +18,7 @@ ) from ralph.models.selector import ModelSelector -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_instance @pytest.mark.parametrize( @@ -37,98 +36,77 @@ UIVideoShowCCMenu, ], ) -@custom_given(st.data()) -def test_models_edx_video_selectors_with_valid_statements(class_, data): +def test_models_edx_video_selectors_with_valid_statements(class_): """Test given a valid video edX statement the `get_first_model` selector method should return the expected model. """ - statement = json.loads(data.draw(custom_builds(class_)).json()) + statement = json.loads(mock_instance(class_).json()) model = ModelSelector(module="ralph.models.edx").get_first_model(statement) assert model is class_ -@custom_given(UIPlayVideo) -def test_models_edx_ui_play_video_with_valid_statement( - statement, -): +def test_models_edx_ui_play_video_with_valid_statement(): + statement = mock_instance(UIPlayVideo) """Test that a `play_video` statement has the expected `event_type`.""" assert statement.event_type == "play_video" -@custom_given(UIPauseVideo) -def test_models_edx_ui_pause_video_with_valid_statement( - statement, -): +def test_models_edx_ui_pause_video_with_valid_statement(): + statement = mock_instance(UIPauseVideo) """Test that a `pause_video` statement has the expected `event_type`.""" assert statement.event_type == "pause_video" -@custom_given(UILoadVideo) -def test_models_edx_ui_load_video_with_valid_statement( - statement, -): +def test_models_edx_ui_load_video_with_valid_statement(): + statement = mock_instance(UILoadVideo) """Test that a `load_video` statement has the expected `event_type` and `name`.""" assert statement.event_type == "load_video" assert statement.name in {"load_video", "edx.video.loaded"} -@custom_given(UISeekVideo) -def test_models_edx_ui_seek_video_with_valid_statement( - statement, -): +def test_models_edx_ui_seek_video_with_valid_statement(): + statement = mock_instance(UISeekVideo) """Test that a `seek_video` statement has the expected `event_type`.""" assert statement.event_type == "seek_video" -@custom_given(UIStopVideo) -def test_models_edx_ui_stop_video_with_valid_statement( - statement, -): +def test_models_edx_ui_stop_video_with_valid_statement(): + statement = mock_instance(UIStopVideo) """Test that a `stop_video` statement has the expected `event_type`.""" assert statement.event_type == "stop_video" -@custom_given(UIHideTranscript) -def test_models_edx_ui_hide_transcript_with_valid_statement( - statement, -): +def test_models_edx_ui_hide_transcript_with_valid_statement(): """Test that a `hide_transcript` statement has the expected `event_type` and `name`. """ + statement = mock_instance(UIHideTranscript) assert statement.event_type == "hide_transcript" assert statement.name in {"hide_transcript", "edx.video.transcript.hidden"} -@custom_given(UIShowTranscript) -def test_models_edx_ui_show_transcript_with_valid_statement( - statement, -): +def test_models_edx_ui_show_transcript_with_valid_statement(): """Test that a `show_transcript` statement has the expected `event_type` and `name. """ + statement = mock_instance(UIShowTranscript) assert statement.event_type == "show_transcript" assert statement.name in {"show_transcript", "edx.video.transcript.shown"} -@custom_given(UISpeedChangeVideo) -def test_models_edx_ui_speed_change_video_with_valid_statement( - statement, -): +def test_models_edx_ui_speed_change_video_with_valid_statement(): """Test that a `speed_change_video` statement has the expected `event_type`.""" + statement = mock_instance(UISpeedChangeVideo) assert statement.event_type == "speed_change_video" -@custom_given(UIVideoHideCCMenu) -def test_models_edx_ui_vide_hide_cc_menu_with_valid_statement( - statement, -): +def test_models_edx_ui_vide_hide_cc_menu_with_valid_statement(): """Test that a `video_hide_cc_menu` statement has the expected `event_type`.""" + statement = mock_instance(UIVideoHideCCMenu) assert statement.event_type == "video_hide_cc_menu" -@custom_given(UIVideoShowCCMenu) -def test_models_edx_ui_video_show_cc_menu_with_valid_statement( - statement, -): +def test_models_edx_ui_video_show_cc_menu_with_valid_statement(): """Test that a `video_show_cc_menu` statement has the expected `event_type`.""" + statement = mock_instance(UIVideoShowCCMenu) assert statement.event_type == "video_show_cc_menu" diff --git a/tests/models/test_converter.py b/tests/models/test_converter.py index 2fcad9e82..829ac431f 100644 --- a/tests/models/test_converter.py +++ b/tests/models/test_converter.py @@ -5,7 +5,6 @@ from typing import Any, Optional import pytest -from hypothesis import HealthCheck, settings from pydantic import BaseModel from pydantic.error_wrappers import ValidationError @@ -24,7 +23,7 @@ from ralph.models.edx.converters.xapi.base import BaseConversionSet from ralph.models.edx.navigational.statements import UIPageClose -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance @pytest.mark.parametrize( @@ -328,16 +327,16 @@ def test_converter_convert_with_an_event_missing_a_conversion_set_raises_an_exce list(result) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) @pytest.mark.parametrize("valid_uuid", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) @pytest.mark.parametrize("invalid_platform_url", ["", "not an URL"]) -@custom_given(UIPageClose) def test_converter_convert_with_invalid_arguments_raises_an_exception( - valid_uuid, invalid_platform_url, caplog, event + valid_uuid, invalid_platform_url, caplog ): """Test given invalid arguments causing the conversion to fail at the validation step, the convert method should raise a ValidationError. """ + event = mock_instance(UIPageClose) + event_str = event.json() result = Converter( platform_url=invalid_platform_url, uuid_namespace=valid_uuid @@ -350,11 +349,13 @@ def test_converter_convert_with_invalid_arguments_raises_an_exception( @pytest.mark.parametrize("ignore_errors", [True, False]) @pytest.mark.parametrize("fail_on_unknown", [True, False]) @pytest.mark.parametrize("valid_uuid", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) -@custom_given(UIPageClose) def test_converter_convert_with_valid_events( - ignore_errors, fail_on_unknown, valid_uuid, event + ignore_errors, fail_on_unknown, valid_uuid ): """Test given a valid event the convert method should yield it.""" + + event = mock_instance(UIPageClose) + event_str = event.json() result = Converter( platform_url="https://fun-mooc.fr", uuid_namespace=valid_uuid @@ -365,13 +366,13 @@ def test_converter_convert_with_valid_events( ) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -@custom_given(UIPageClose) @pytest.mark.parametrize("valid_uuid", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) -def test_converter_convert_counter(valid_uuid, caplog, event): +def test_converter_convert_counter(valid_uuid, caplog): """Test given multiple events the convert method should log the total and invalid events. """ + event = mock_instance(UIPageClose) + valid_event = event.json() invalid_event_1 = 1 invalid_event_2 = "" diff --git a/tests/models/test_validator.py b/tests/models/test_validator.py index 61a6c8fb0..42a130b05 100644 --- a/tests/models/test_validator.py +++ b/tests/models/test_validator.py @@ -5,7 +5,6 @@ import logging import pytest -from hypothesis import HealthCheck, settings from pydantic import ValidationError, create_model from ralph.exceptions import BadFormatException, UnknownEventException @@ -14,7 +13,7 @@ from ralph.models.selector import ModelSelector from ralph.models.validator import Validator -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_instance def test_models_validator_validate_with_no_events(caplog): @@ -142,11 +141,11 @@ def test_models_validator_validate_with_invalid_page_close_event_raises_an_excep @pytest.mark.parametrize("ignore_errors", [True, False]) @pytest.mark.parametrize("fail_on_unknown", [True, False]) -@custom_given(UIPageClose) -def test_models_validator_validate_with_valid_events( - ignore_errors, fail_on_unknown, event -): +# @custom_given(UIPageClose) +def test_models_validator_validate_with_valid_events(ignore_errors, fail_on_unknown): """Test given a valid event the validate method should yield it.""" + event = mock_instance(UIPageClose) + event_str = event.json() event_dict = json.loads(event_str) validator = Validator(ModelSelector(module="ralph.models.edx")) @@ -154,12 +153,13 @@ def test_models_validator_validate_with_valid_events( assert json.loads(next(result)) == event_dict -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -@custom_given(UIPageClose) -def test_models_validator_validate_counter(caplog, event): +# @custom_given(UIPageClose) +def test_models_validator_validate_counter(caplog): """Test given multiple events the validate method should log the total and invalid events. """ + event = mock_instance(UIPageClose) + valid_event = event.json() invalid_event_1 = 1 invalid_event_2 = "" @@ -176,11 +176,13 @@ def test_models_validator_validate_counter(caplog, event): ) in caplog.record_tuples -@custom_given(Server) -def test_models_validator_validate_typing_cleanup(event): +# @custom_given(Server) +def test_models_validator_validate_typing_cleanup(): """Test given a valid event with wrong field types, the validate method should fix them. """ + event = mock_instance(Server) + valid_event_str = event.json() valid_event = json.loads(valid_event_str) valid_event["host"] = "1" diff --git a/tests/models/xapi/base/test_agents.py b/tests/models/xapi/base/test_agents.py index 0a7b48dae..a4acbc579 100644 --- a/tests/models/xapi/base/test_agents.py +++ b/tests/models/xapi/base/test_agents.py @@ -8,16 +8,14 @@ from ralph.models.xapi.base.agents import BaseXapiAgentWithMboxSha1Sum -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_xapi_instance -@custom_given(BaseXapiAgentWithMboxSha1Sum) -def test_models_xapi_base_agent_with_mbox_sha1_sum_ifi_with_valid_field( - field, -): +def test_models_xapi_base_agent_with_mbox_sha1_sum_ifi_with_valid_field(): """Test a valid BaseXapiAgentWithMboxSha1Sum has the expected `mbox_sha1sum` regex. """ + field = mock_xapi_instance(BaseXapiAgentWithMboxSha1Sum) assert re.match(r"^[0-9a-f]{40}$", field.mbox_sha1sum) @@ -30,14 +28,13 @@ def test_models_xapi_base_agent_with_mbox_sha1_sum_ifi_with_valid_field( "1baccdd9abcdfd4ae9b24dedfa939c7deffa3db3a7", ], ) -@custom_given(BaseXapiAgentWithMboxSha1Sum) -def test_models_xapi_base_agent_with_mbox_sha1_sum_ifi_with_invalid_field( - mbox_sha1sum, field -): +def test_models_xapi_base_agent_with_mbox_sha1_sum_ifi_with_invalid_field(mbox_sha1sum): """Test an invalid `mbox_sha1sum` property in BaseXapiAgentWithMboxSha1Sum raises a `ValidationError`. """ + field = mock_xapi_instance(BaseXapiAgentWithMboxSha1Sum) + invalid_field = json.loads(field.json()) invalid_field["mbox_sha1sum"] = mbox_sha1sum diff --git a/tests/models/xapi/base/test_groups.py b/tests/models/xapi/base/test_groups.py index c3ad162c1..9bb41bbff 100644 --- a/tests/models/xapi/base/test_groups.py +++ b/tests/models/xapi/base/test_groups.py @@ -2,15 +2,13 @@ from ralph.models.xapi.base.groups import BaseXapiGroupCommonProperties -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_xapi_instance -@custom_given(BaseXapiGroupCommonProperties) -def test_models_xapi_base_groups_group_common_properties_with_valid_field( - field, -): +def test_models_xapi_base_groups_group_common_properties_with_valid_field(): """Test a valid BaseXapiGroupCommonProperties has the expected `objectType` value. """ + field = mock_xapi_instance(BaseXapiGroupCommonProperties) assert field.objectType == "Group" diff --git a/tests/models/xapi/base/test_objects.py b/tests/models/xapi/base/test_objects.py index 8129c48a5..1cd225b3b 100644 --- a/tests/models/xapi/base/test_objects.py +++ b/tests/models/xapi/base/test_objects.py @@ -2,11 +2,11 @@ from ralph.models.xapi.base.objects import BaseXapiSubStatement -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_xapi_instance -@custom_given(BaseXapiSubStatement) -def test_models_xapi_object_base_sub_statement_type_with_valid_field(field): +def test_models_xapi_object_base_sub_statement_type_with_valid_field(): """Test a valid BaseXapiSubStatement has the expected `objectType` value.""" + field = mock_xapi_instance(BaseXapiSubStatement) assert field.objectType == "SubStatement" diff --git a/tests/models/xapi/base/test_results.py b/tests/models/xapi/base/test_results.py index 61164f411..5e796704b 100644 --- a/tests/models/xapi/base/test_results.py +++ b/tests/models/xapi/base/test_results.py @@ -7,7 +7,7 @@ from ralph.models.xapi.base.results import BaseXapiResultScore -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_xapi_instance @pytest.mark.parametrize( @@ -18,13 +18,13 @@ (12, 5, 10, "raw cannot be greater than max"), ], ) -@custom_given(BaseXapiResultScore) def test_models_xapi_base_result_score_with_invalid_raw_min_max_relation( - raw_value, min_value, max_value, error_msg, field + raw_value, min_value, max_value, error_msg ): """Test invalids `raw`,`min`,`max` relation in BaseXapiResultScore raises ValidationError. """ + field = mock_xapi_instance(BaseXapiResultScore) invalid_field = json.loads(field.json()) invalid_field["raw"] = raw_value diff --git a/tests/models/xapi/base/test_statements.py b/tests/models/xapi/base/test_statements.py index a88fca426..d1d98e57a 100644 --- a/tests/models/xapi/base/test_statements.py +++ b/tests/models/xapi/base/test_statements.py @@ -3,8 +3,6 @@ import json import pytest -from hypothesis import settings -from hypothesis import strategies as st from pydantic import ValidationError from ralph.models.selector import ModelSelector @@ -25,16 +23,15 @@ ) from ralph.utils import set_dict_value_from_path -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import ModelFactory, mock_xapi_instance @pytest.mark.parametrize( "path", - ["id", "stored", "verb__display", "context__contextActivities__parent"], + ["id", "stored", "verb__display", "result__score__raw"], ) @pytest.mark.parametrize("value", [None, "", {}]) -@custom_given(BaseXapiStatement) -def test_models_xapi_base_statement_with_invalid_null_values(path, value, statement): +def test_models_xapi_base_statement_with_invalid_null_values(path, value): """Test that the statement does not accept any null values. XAPI-00001 @@ -42,8 +39,11 @@ def test_models_xapi_base_statement_with_invalid_null_values(path, value, statem value is set to "null", an empty object, or has no value, except in an "extensions" property. """ + statement = mock_xapi_instance(BaseXapiStatement) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, path.split("__"), value) + with pytest.raises(ValidationError, match="invalid empty value"): BaseXapiStatement(**statement) @@ -57,8 +57,7 @@ def test_models_xapi_base_statement_with_invalid_null_values(path, value, statem ], ) @pytest.mark.parametrize("value", [None, "", {}]) -@custom_given(custom_builds(BaseXapiStatement, object=custom_builds(BaseXapiActivity))) -def test_models_xapi_base_statement_with_valid_null_values(path, value, statement): +def test_models_xapi_base_statement_with_valid_null_values(path, value): """Test that the statement does accept valid null values in extensions fields. XAPI-00001 @@ -66,7 +65,13 @@ def test_models_xapi_base_statement_with_valid_null_values(path, value, statemen value is set to "null", an empty object, or has no value, except in an "extensions" property. """ + + statement = mock_xapi_instance( + BaseXapiStatement, object=mock_xapi_instance(BaseXapiActivity) + ) + statement = statement.dict(exclude_none=True) + set_dict_value_from_path(statement, path.split("__"), value) try: BaseXapiStatement(**statement) @@ -75,21 +80,21 @@ def test_models_xapi_base_statement_with_valid_null_values(path, value, statemen @pytest.mark.parametrize("path", ["object__definition__correctResponsesPattern"]) -@custom_given( - custom_builds( - BaseXapiStatement, - object=custom_builds( - BaseXapiActivity, - definition=custom_builds(BaseXapiActivityInteractionDefinition), - ), - ) -) -def test_models_xapi_base_statement_with_valid_empty_array(path, statement): +def test_models_xapi_base_statement_with_valid_empty_array(path): """Test that the statement does accept a valid empty array. Where the Correct Responses Pattern contains an empty array, the meaning of this is that there is no correct answer. """ + + statement = mock_xapi_instance( + BaseXapiStatement, + object=mock_xapi_instance( + BaseXapiActivity, + definition=mock_xapi_instance(BaseXapiActivityInteractionDefinition), + ), + ) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, path.split("__"), []) try: @@ -102,8 +107,7 @@ def test_models_xapi_base_statement_with_valid_empty_array(path, statement): "field", ["actor", "verb", "object"], ) -@custom_given(BaseXapiStatement) -def test_models_xapi_base_statement_must_use_actor_verb_and_object(field, statement): +def test_models_xapi_base_statement_must_use_actor_verb_and_object(field): """Test that the statement raises an exception if required fields are missing. XAPI-00003 @@ -116,7 +120,11 @@ def test_models_xapi_base_statement_must_use_actor_verb_and_object(field, statem An LRS rejects with error code 400 Bad Request a Statement which does not contain an "object" property. """ + + statement = mock_xapi_instance(BaseXapiStatement) + statement = statement.dict(exclude_none=True) + del statement["context"] # Necessary as context leads to another validation error del statement[field] with pytest.raises(ValidationError, match="field required"): BaseXapiStatement(**statement) @@ -134,19 +142,22 @@ def test_models_xapi_base_statement_must_use_actor_verb_and_object(field, statem ("object__id", ["foo"]), # Should be an IRI ], ) -@custom_given( - custom_builds(BaseXapiStatement, actor=custom_builds(BaseXapiAgentWithAccount)) -) -def test_models_xapi_base_statement_with_invalid_data_types(path, value, statement): +def test_models_xapi_base_statement_with_invalid_data_types(path, value): """Test that the statement does not accept values with wrong types. XAPI-00006 An LRS rejects with error code 400 Bad Request a Statement which uses the wrong data type. """ + statement = mock_xapi_instance( + BaseXapiStatement, actor=mock_xapi_instance(BaseXapiAgentWithAccount) + ) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, path.split("__"), value) + err = "(type expected|not a valid dict|expected string )" + with pytest.raises(ValidationError, match=err): BaseXapiStatement(**statement) @@ -160,10 +171,7 @@ def test_models_xapi_base_statement_with_invalid_data_types(path, value, stateme ("object__id", ["This is not an IRI"]), # Should be an IRI ], ) -@custom_given( - custom_builds(BaseXapiStatement, actor=custom_builds(BaseXapiAgentWithAccount)) -) -def test_models_xapi_base_statement_with_invalid_data_format(path, value, statement): +def test_models_xapi_base_statement_with_invalid_data_format(path, value): """Test that the statement does not accept values having a wrong format. XAPI-00007 @@ -172,24 +180,29 @@ def test_models_xapi_base_statement_with_invalid_data_format(path, value, statem particular format (such as mailto IRI, UUID, or IRI) is required. (Empty strings are covered by XAPI-00001) """ + statement = mock_xapi_instance( + BaseXapiStatement, actor=mock_xapi_instance(BaseXapiAgentWithAccount) + ) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, path.split("__"), value) - err = "(Invalid `mailto:email`|Invalid RFC 5646 Language tag|not a valid uuid)" + err = "(string does not match regex|Invalid RFC 5646 Language tag|not a valid uuid)" with pytest.raises(ValidationError, match=err): BaseXapiStatement(**statement) @pytest.mark.parametrize("path,value", [("actor__objecttype", "Agent")]) -@custom_given( - custom_builds(BaseXapiStatement, actor=custom_builds(BaseXapiAgentWithAccount)) -) -def test_models_xapi_base_statement_with_invalid_letter_cases(path, value, statement): +def test_models_xapi_base_statement_with_invalid_letter_cases(path, value): """Test that the statement does not accept keys having invalid letter cases. XAPI-00008 An LRS rejects with error code 400 Bad Request a Statement where the case of a key does not match the case specified in this specification. """ + statement = mock_xapi_instance( + BaseXapiStatement, actor=mock_xapi_instance(BaseXapiAgentWithAccount) + ) + statement = statement.dict(exclude_none=True) if statement["actor"].get("objectType", None): del statement["actor"]["objectType"] @@ -199,14 +212,15 @@ def test_models_xapi_base_statement_with_invalid_letter_cases(path, value, state BaseXapiStatement(**statement) -@custom_given(BaseXapiStatement) -def test_models_xapi_base_statement_should_not_accept_additional_properties(statement): +def test_models_xapi_base_statement_should_not_accept_additional_properties(): """Test that the statement does not accept additional properties. XAPI-00010 An LRS rejects with error code 400 Bad Request a Statement where a key or value is not allowed by this specification. """ + statement = mock_xapi_instance(BaseXapiStatement) + invalid_statement = statement.dict(exclude_none=True) invalid_statement["NEW_INVALID_FIELD"] = "some value" with pytest.raises(ValidationError, match="extra fields not permitted"): @@ -214,14 +228,15 @@ def test_models_xapi_base_statement_should_not_accept_additional_properties(stat @pytest.mark.parametrize("path,value", [("object__id", "w3id.org/xapi/video")]) -@custom_given(BaseXapiStatement) -def test_models_xapi_base_statement_with_iri_without_scheme(path, value, statement): +def test_models_xapi_base_statement_with_iri_without_scheme(path, value): """Test that the statement does not accept IRIs without a scheme. XAPI-00011 An LRS rejects with error code 400 Bad Request a Statement containing IRL or IRI values without a scheme. """ + statement = mock_xapi_instance(BaseXapiStatement) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, path.split("__"), value) with pytest.raises(ValidationError, match="is not a valid 'IRI'"): @@ -236,14 +251,17 @@ def test_models_xapi_base_statement_with_iri_without_scheme(path, value, stateme "context__extensions__w3id.org/xapi/video", ], ) -@custom_given(custom_builds(BaseXapiStatement, object=custom_builds(BaseXapiActivity))) -def test_models_xapi_base_statement_with_invalid_extensions(path, statement): +def test_models_xapi_base_statement_with_invalid_extensions(path): """Test that the statement does not accept extensions keys with invalid IRIs. XAPI-00118 An Extension "key" is an IRI. The LRS rejects with 400 a statement which has an extension key which is not a valid IRI, if an extension object is present. """ + statement = mock_xapi_instance( + BaseXapiStatement, object=mock_xapi_instance(BaseXapiActivity) + ) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, path.split("__"), "") with pytest.raises(ValidationError, match="is not a valid 'IRI'"): @@ -251,28 +269,30 @@ def test_models_xapi_base_statement_with_invalid_extensions(path, statement): @pytest.mark.parametrize("path,value", [("actor__mbox", "mailto:example@mail.com")]) -@custom_given( - custom_builds(BaseXapiStatement, actor=custom_builds(BaseXapiAgentWithAccount)) -) -def test_models_xapi_base_statement_with_two_agent_types(path, value, statement): +def test_models_xapi_base_statement_with_two_agent_types(path, value): """Test that the statement does not accept multiple agent types. An Agent MUST NOT include more than one Inverse Functional Identifier. """ + statement = mock_xapi_instance( + BaseXapiStatement, actor=mock_xapi_instance(BaseXapiAgentWithAccount) + ) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, path.split("__"), value) with pytest.raises(ValidationError, match="extra fields not permitted"): BaseXapiStatement(**statement) -@custom_given( - custom_builds(BaseXapiStatement, actor=custom_builds(BaseXapiAnonymousGroup)) -) -def test_models_xapi_base_statement_missing_member_property(statement): +def test_models_xapi_base_statement_missing_member_property(): """Test that the statement does not accept group agents with missing members. An Anonymous Group MUST include a "member" property listing constituent Agents. """ + statement = mock_xapi_instance( + BaseXapiStatement, actor=mock_xapi_instance(BaseXapiAnonymousGroup) + ) + statement = statement.dict(exclude_none=True) del statement["actor"]["member"] with pytest.raises(ValidationError, match="member\n field required"): @@ -280,7 +300,7 @@ def test_models_xapi_base_statement_missing_member_property(statement): @pytest.mark.parametrize( - "value", + "klass", [ BaseXapiAnonymousGroup, BaseXapiIdentifiedGroupWithMbox, @@ -289,54 +309,46 @@ def test_models_xapi_base_statement_missing_member_property(statement): BaseXapiIdentifiedGroupWithAccount, ], ) -@custom_given( - st.one_of( - custom_builds(BaseXapiStatement, actor=custom_builds(BaseXapiAnonymousGroup)), - custom_builds( - BaseXapiStatement, - actor=custom_builds(BaseXapiIdentifiedGroupWithMbox), - ), - custom_builds( - BaseXapiStatement, - actor=custom_builds(BaseXapiIdentifiedGroupWithMboxSha1Sum), - ), - custom_builds( - BaseXapiStatement, - actor=custom_builds(BaseXapiIdentifiedGroupWithOpenId), - ), - custom_builds( - BaseXapiStatement, - actor=custom_builds(BaseXapiIdentifiedGroupWithAccount), - ), - ), - st.data(), -) -def test_models_xapi_base_statement_with_invalid_group_objects(value, statement, data): +def test_models_xapi_base_statement_with_invalid_group_objects(klass): """Test that the statement does not accept group agents with group members. An Anonymous Group MUST NOT contain Group Objects in the "member" identifiers. An Identified Group MUST NOT contain Group Objects in the "member" property. """ + + actor_class = ModelFactory.__random__.choice( + [ + BaseXapiAnonymousGroup, + BaseXapiIdentifiedGroupWithMbox, + BaseXapiIdentifiedGroupWithMboxSha1Sum, + BaseXapiIdentifiedGroupWithOpenId, + BaseXapiIdentifiedGroupWithAccount, + ] + ) + statement = mock_xapi_instance( + BaseXapiStatement, actor=mock_xapi_instance(actor_class) + ) + kwargs = {"exclude_none": True} statement = statement.dict(**kwargs) - statement["actor"]["member"] = [data.draw(custom_builds(value)).dict(**kwargs)] + statement["actor"]["member"] = [ + mock_xapi_instance(klass).dict(**kwargs) + ] # TODO: check that nothing was lost err = "actor -> member -> 0 -> objectType\n unexpected value; permitted: 'Agent'" with pytest.raises(ValidationError, match=err): BaseXapiStatement(**statement) @pytest.mark.parametrize("path,value", [("actor__mbox", "mailto:example@mail.com")]) -@custom_given( - custom_builds( - BaseXapiStatement, - actor=custom_builds(BaseXapiIdentifiedGroupWithAccount), - ) -) -def test_models_xapi_base_statement_with_two_group_identifiers(path, value, statement): +def test_models_xapi_base_statement_with_two_group_identifiers(path, value): """Test that the statement does not accept multiple group identifiers. An Identified Group MUST include exactly one Inverse Functional Identifier. """ + statement = mock_xapi_instance( + BaseXapiStatement, actor=mock_xapi_instance(BaseXapiIdentifiedGroupWithAccount) + ) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, path.split("__"), value) with pytest.raises(ValidationError, match="extra fields not permitted"): @@ -352,15 +364,16 @@ def test_models_xapi_base_statement_with_two_group_identifiers(path, value, stat ("object__authority", {"mbox": "mailto:example@mail.com"}), ], ) -@custom_given( - custom_builds(BaseXapiStatement, object=custom_builds(BaseXapiSubStatement)) -) -def test_models_xapi_base_statement_with_sub_statement_ref(path, value, statement): +def test_models_xapi_base_statement_with_sub_statement_ref(path, value): """Test that the sub-statement does not accept invalid properties. A SubStatement MUST NOT have the "id", "stored", "version" or "authority" properties. """ + statement = mock_xapi_instance( + BaseXapiStatement, object=mock_xapi_instance(BaseXapiSubStatement) + ) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, path.split("__"), value) with pytest.raises(ValidationError, match="extra fields not permitted"): @@ -375,21 +388,20 @@ def test_models_xapi_base_statement_with_sub_statement_ref(path, value, statemen [{"id": "invalid_duplicate"}, {"id": "invalid_duplicate"}], ], ) -@custom_given( - custom_builds( - BaseXapiStatement, - object=custom_builds( - BaseXapiActivity, - definition=custom_builds(BaseXapiActivityInteractionDefinition), - ), - ) -) -def test_models_xapi_base_statement_with_invalid_interaction_object(value, statement): +def test_models_xapi_base_statement_with_invalid_interaction_object(value): """Test that the statement does not accept invalid interaction fields. An interaction component's id value SHOULD NOT have whitespace. Within an array of interaction components, all id values MUST be distinct. """ + statement = mock_xapi_instance( + BaseXapiStatement, + object=mock_xapi_instance( + BaseXapiActivity, + definition=mock_xapi_instance(BaseXapiActivityInteractionDefinition), + ), + ) + statement = statement.dict(exclude_none=True) path = "object.definition.scale".split(".") set_dict_value_from_path(statement, path, value) @@ -405,18 +417,23 @@ def test_models_xapi_base_statement_with_invalid_interaction_object(value, state ("context__platform", "FUN MOOC"), ], ) -@custom_given( - st.one_of( - custom_builds(BaseXapiStatement, object=custom_builds(BaseXapiSubStatement)), - custom_builds(BaseXapiStatement, object=custom_builds(BaseXapiStatementRef)), - ), -) -def test_models_xapi_base_statement_with_invalid_context_value(path, value, statement): +def test_models_xapi_base_statement_with_invalid_context_value(path, value): """Test that the statement does not accept an invalid revision/platform value. The "revision" property MUST only be used if the Statement's Object is an Activity. The "platform" property MUST only be used if the Statement's Object is an Activity. """ + + object_class = ModelFactory.__random__.choice( + [ + BaseXapiSubStatement, + BaseXapiStatementRef, + ] + ) + statement = mock_xapi_instance( + BaseXapiStatement, object=mock_xapi_instance(object_class) + ) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, path.split("__"), value) err = "properties can only be used if the Statement's Object is an Activity" @@ -425,13 +442,14 @@ def test_models_xapi_base_statement_with_invalid_context_value(path, value, stat @pytest.mark.parametrize("path", ["context.contextActivities.not_parent"]) -@custom_given(BaseXapiStatement) -def test_models_xapi_base_statement_with_invalid_context_activities(path, statement): +def test_models_xapi_base_statement_with_invalid_context_activities(path): """Test that the statement does not accept invalid context activity properties. Every key in the contextActivities Object MUST be one of parent, grouping, category, or other. """ + statement = mock_xapi_instance(BaseXapiStatement) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, path.split("."), {"id": "http://w3id.org/xapi"}) with pytest.raises(ValidationError, match="extra fields not permitted"): @@ -446,13 +464,14 @@ def test_models_xapi_base_statement_with_invalid_context_activities(path, statem [{"id": "http://w3id.org/xapi"}, {"id": "http://w3id.org/xapi/video"}], ], ) -@custom_given(BaseXapiStatement) -def test_models_xapi_base_statement_with_valid_context_activities(value, statement): +def test_models_xapi_base_statement_with_valid_context_activities(value): """Test that the statement does accept valid context activities fields. Every value in the contextActivities Object MUST be either a single Activity Object or an array of Activity Objects. """ + statement = mock_xapi_instance(BaseXapiStatement) + statement = statement.dict(exclude_none=True) path = ["context", "contextActivities"] for activity in ["parent", "grouping", "category", "other"]: @@ -464,26 +483,28 @@ def test_models_xapi_base_statement_with_valid_context_activities(value, stateme @pytest.mark.parametrize("value", ["0.0.0", "1.1.0", "1", "2", "1.10.1", "1.0.1.1"]) -@custom_given(BaseXapiStatement) -def test_models_xapi_base_statement_with_invalid_version(value, statement): +def test_models_xapi_base_statement_with_invalid_version(value): """Test that the statement does not accept an invalid version field. An LRS MUST reject all Statements with a version specified that does not start with 1.0. """ + statement = mock_xapi_instance(BaseXapiStatement) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, ["version"], value) with pytest.raises(ValidationError, match="version\n string does not match regex"): BaseXapiStatement(**statement) -@custom_given(BaseXapiStatement) -def test_models_xapi_base_statement_with_valid_version(statement): +def test_models_xapi_base_statement_with_valid_version(): """Test that the statement does accept a valid version field. Statements returned by an LRS MUST retain the version they are accepted with. If they lack a version, the version MUST be set to 1.0.0. """ + statement = mock_xapi_instance(BaseXapiStatement) + statement = statement.dict(exclude_none=True) set_dict_value_from_path(statement, ["version"], "1.0.3") assert "1.0.3" == BaseXapiStatement(**statement).dict()["version"] @@ -491,19 +512,21 @@ def test_models_xapi_base_statement_with_valid_version(statement): assert "1.0.0" == BaseXapiStatement(**statement).dict()["version"] -@settings(deadline=None) @pytest.mark.parametrize( "model", list(ModelSelector("ralph.models.xapi").model_rules), ) -@custom_given(st.data()) def test_models_xapi_base_statement_should_consider_valid_all_defined_xapi_models( - model, data + model, ): """Test that all defined xAPI models in the ModelSelector make valid statements.""" + # All specific xAPI models should inherit BaseXapiStatement assert issubclass(model, BaseXapiStatement) - statement = data.draw(custom_builds(model)).json(exclude_none=True, by_alias=True) + statement = mock_xapi_instance(model) + statement = statement.json( + exclude_none=True, by_alias=True + ) # TODO: check that we are not losing info by mocking random model try: BaseXapiStatement(**json.loads(statement)) except ValidationError as err: diff --git a/tests/models/xapi/base/test_unnested_objects.py b/tests/models/xapi/base/test_unnested_objects.py index a5bc5a24f..164fa41b0 100644 --- a/tests/models/xapi/base/test_unnested_objects.py +++ b/tests/models/xapi/base/test_unnested_objects.py @@ -12,22 +12,18 @@ BaseXapiStatementRef, ) -from tests.fixtures.hypothesis_strategies import custom_given +from tests.factories import mock_xapi_instance -@custom_given(BaseXapiStatementRef) -def test_models_xapi_base_object_statement_ref_type_with_valid_field(field): +def test_models_xapi_base_object_statement_ref_type_with_valid_field(): """Test a valid BaseXapiStatementRef has the expected `objectType` value.""" - + field = mock_xapi_instance(BaseXapiStatementRef) assert field.objectType == "StatementRef" -@custom_given(BaseXapiInteractionComponent) -def test_models_xapi_base_object_interaction_component_with_valid_field( - field, -): +def test_models_xapi_base_object_interaction_component_with_valid_field(): """Test a valid BaseXapiInteractionComponent has the expected `id` regex.""" - + field = mock_xapi_instance(BaseXapiInteractionComponent) assert re.match(r"^[^\s]+$", field.id) @@ -35,13 +31,11 @@ def test_models_xapi_base_object_interaction_component_with_valid_field( "id_value", [" test_id", "\ntest"], ) -@custom_given(BaseXapiInteractionComponent) -def test_models_xapi_base_object_interaction_component_with_invalid_field( - id_value, field -): +def test_models_xapi_base_object_interaction_component_with_invalid_field(id_value): """Test an invalid `id` property in BaseXapiInteractionComponent raises a `ValidationError`. """ + field = mock_xapi_instance(BaseXapiInteractionComponent) invalid_property = json.loads(field.json()) invalid_property["id"] = id_value @@ -50,13 +44,11 @@ def test_models_xapi_base_object_interaction_component_with_invalid_field( BaseXapiInteractionComponent(**invalid_property) -@custom_given(BaseXapiActivityInteractionDefinition) -def test_models_xapi_base_object_activity_type_interaction_definition_with_valid_field( - field, -): +def test_models_xapi_base_object_activity_type_interaction_definition_with_valid_field(): """Test a valid BaseXapiActivityInteractionDefinition has the expected `objectType` value. """ + field = mock_xapi_instance(BaseXapiActivityInteractionDefinition) assert field.interactionType in ( "true-false", diff --git a/tests/models/xapi/concepts/test_activity_types.py b/tests/models/xapi/concepts/test_activity_types.py index e0d535084..305f19881 100644 --- a/tests/models/xapi/concepts/test_activity_types.py +++ b/tests/models/xapi/concepts/test_activity_types.py @@ -2,8 +2,6 @@ import json import pytest -from hypothesis import settings -from hypothesis import strategies as st from ralph.models.xapi.concepts.activity_types.acrossx_profile import ( MessageActivity, @@ -26,10 +24,9 @@ VirtualClassroomActivity, ) -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_xapi_instance -@settings(deadline=None) @pytest.mark.parametrize( "class_, definition_type", [ @@ -50,12 +47,9 @@ (DocumentActivity, "http://id.tincanapi.com/activitytype/document"), ], ) -@custom_given(st.data()) -def test_models_xapi_concept_activity_types_with_valid_field( - class_, definition_type, data -): +def test_models_xapi_concept_activity_types_with_valid_field(class_, definition_type): """Test that a valid xAPI activity has the expected the `definition`.`type` value. """ - field = json.loads(data.draw(custom_builds(class_)).json()) + field = json.loads(mock_xapi_instance(class_).json()) assert field["definition"]["type"] == definition_type diff --git a/tests/models/xapi/concepts/test_verbs.py b/tests/models/xapi/concepts/test_verbs.py index a69a4a097..3d3fbf844 100644 --- a/tests/models/xapi/concepts/test_verbs.py +++ b/tests/models/xapi/concepts/test_verbs.py @@ -2,8 +2,6 @@ import json import pytest -from hypothesis import settings -from hypothesis import strategies as st from ralph.models.xapi.concepts.verbs.acrossx_profile import PostedVerb from ralph.models.xapi.concepts.verbs.activity_streams_vocabulary import ( @@ -42,10 +40,9 @@ UnsharedScreenVerb, ) -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_xapi_instance -@settings(deadline=None) @pytest.mark.parametrize( "class_, verb_id", [ @@ -89,8 +86,7 @@ ), ], ) -@custom_given(st.data()) -def test_models_xapi_concept_verbs_with_valid_field(class_, verb_id, data): +def test_models_xapi_concept_verbs_with_valid_field(class_, verb_id): """Test that a valid xAPI verb has the expected the `id` value.""" - field = json.loads(data.draw(custom_builds(class_)).json()) + field = json.loads(mock_xapi_instance(class_).json()) assert field["id"] == verb_id diff --git a/tests/models/xapi/test_lms.py b/tests/models/xapi/test_lms.py index 82e9efe87..e03aed070 100644 --- a/tests/models/xapi/test_lms.py +++ b/tests/models/xapi/test_lms.py @@ -3,8 +3,6 @@ import json import pytest -from hypothesis import settings -from hypothesis import strategies as st from pydantic import ValidationError from ralph.models.selector import ModelSelector @@ -24,10 +22,9 @@ LMSUploadedVideo, ) -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_xapi_instance -@settings(deadline=None) @pytest.mark.parametrize( "class_", [ @@ -45,21 +42,20 @@ LMSUploadedAudio, ], ) -@custom_given(st.data()) -def test_models_xapi_lms_selectors_with_valid_statements(class_, data): +def test_models_xapi_lms_selectors_with_valid_statements(class_): """Test given a valid LMS xAPI statement the `get_first_model` selector method should return the expected model. """ - statement = json.loads(data.draw(custom_builds(class_)).json()) + statement = json.loads(mock_xapi_instance(class_).json()) model = ModelSelector(module="ralph.models.xapi").get_first_model(statement) assert model is class_ -@custom_given(LMSRegisteredCourse) -def test_models_xapi_lms_registered_course_with_valid_statement(statement): +def test_models_xapi_lms_registered_course_with_valid_statement(): """Test that a valid registered to a course statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(LMSRegisteredCourse) assert statement.verb.id == "http://adlnet.gov/expapi/verbs/registered" assert ( @@ -67,11 +63,11 @@ def test_models_xapi_lms_registered_course_with_valid_statement(statement): ) -@custom_given(LMSUnregisteredCourse) -def test_models_xapi_lms_unregistered_course_with_valid_statement(statement): +def test_models_xapi_lms_unregistered_course_with_valid_statement(): """Test that a valid unregistered to a course statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(LMSUnregisteredCourse) assert statement.verb.id == "http://id.tincanapi.com/verb/unregistered" assert ( @@ -79,11 +75,11 @@ def test_models_xapi_lms_unregistered_course_with_valid_statement(statement): ) -@custom_given(LMSAccessedPage) -def test_models_xapi_lms_accessed_page_with_valid_statement(statement): +def test_models_xapi_lms_accessed_page_with_valid_statement(): """Test that a valid accessed a page statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(LMSAccessedPage) assert statement.verb.id == "https://w3id.org/xapi/netc/verbs/accessed" assert ( @@ -92,41 +88,41 @@ def test_models_xapi_lms_accessed_page_with_valid_statement(statement): ) -@custom_given(LMSAccessedFile) -def test_models_xapi_lms_accessed_file_with_valid_statement(statement): +def test_models_xapi_lms_accessed_file_with_valid_statement(): """Test that a valid accessed a file statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(LMSAccessedFile) assert statement.verb.id == "https://w3id.org/xapi/netc/verbs/accessed" assert statement.object.definition.type == "http://activitystrea.ms/file" -@custom_given(LMSUploadedFile) -def test_models_xapi_lms_uploaded_file_with_valid_statement(statement): +def test_models_xapi_lms_uploaded_file_with_valid_statement(): """Test that a valid uploaded a file statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(LMSUploadedFile) assert statement.verb.id == "https://w3id.org/xapi/netc/verbs/uploaded" assert statement.object.definition.type == "http://activitystrea.ms/file" -@custom_given(LMSDownloadedFile) -def test_models_xapi_lms_downloaded_file_with_valid_statement(statement): +def test_models_xapi_lms_downloaded_file_with_valid_statement(): """Test that a valid downloaded a file statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(LMSDownloadedFile) assert statement.verb.id == "http://id.tincanapi.com/verb/downloaded" assert statement.object.definition.type == "http://activitystrea.ms/file" -@custom_given(LMSDownloadedVideo) -def test_models_xapi_lms_downloaded_video_with_valid_statement(statement): +def test_models_xapi_lms_downloaded_video_with_valid_statement(): """Test that a valid downloaded a video statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(LMSDownloadedVideo) assert statement.verb.id == "http://id.tincanapi.com/verb/downloaded" assert ( @@ -135,11 +131,11 @@ def test_models_xapi_lms_downloaded_video_with_valid_statement(statement): ) -@custom_given(LMSUploadedVideo) -def test_models_xapi_lms_uploaded_video_with_valid_statement(statement): +def test_models_xapi_lms_uploaded_video_with_valid_statement(): """Test that a valid uploaded a video statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(LMSUploadedVideo) assert statement.verb.id == "https://w3id.org/xapi/netc/verbs/uploaded" assert ( @@ -148,11 +144,11 @@ def test_models_xapi_lms_uploaded_video_with_valid_statement(statement): ) -@custom_given(LMSDownloadedDocument) -def test_models_xapi_lms_downloaded_document_with_valid_statement(statement): +def test_models_xapi_lms_downloaded_document_with_valid_statement(): """Test that a valid downloaded a document statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(LMSDownloadedDocument) assert statement.verb.id == "http://id.tincanapi.com/verb/downloaded" assert ( @@ -161,11 +157,11 @@ def test_models_xapi_lms_downloaded_document_with_valid_statement(statement): ) -@custom_given(LMSUploadedDocument) -def test_models_xapi_lms_uploaded_document_with_valid_statement(statement): +def test_models_xapi_lms_uploaded_document_with_valid_statement(): """Test that a valid uploaded a document statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(LMSUploadedDocument) assert statement.verb.id == "https://w3id.org/xapi/netc/verbs/uploaded" assert ( @@ -174,11 +170,11 @@ def test_models_xapi_lms_uploaded_document_with_valid_statement(statement): ) -@custom_given(LMSDownloadedAudio) -def test_models_xapi_lms_downloaded_audio_with_valid_statement(statement): +def test_models_xapi_lms_downloaded_audio_with_valid_statement(): """Test that a valid downloaded an audio statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(LMSDownloadedAudio) assert statement.verb.id == "http://id.tincanapi.com/verb/downloaded" assert ( @@ -187,11 +183,11 @@ def test_models_xapi_lms_downloaded_audio_with_valid_statement(statement): ) -@custom_given(LMSUploadedAudio) -def test_models_xapi_lms_uploaded_audio_with_valid_statement(statement): +def test_models_xapi_lms_uploaded_audio_with_valid_statement(): """Test that a valid uploaded an audio statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(LMSUploadedAudio) assert statement.verb.id == "https://w3id.org/xapi/netc/verbs/uploaded" assert ( @@ -200,7 +196,6 @@ def test_models_xapi_lms_uploaded_audio_with_valid_statement(statement): ) -@settings(deadline=None) @pytest.mark.parametrize( "category", [ @@ -213,13 +208,11 @@ def test_models_xapi_lms_uploaded_audio_with_valid_statement(statement): [{"id": "https://foo.bar"}, {"id": "https://w3id.org/xapi/lms"}], ], ) -@custom_given(LMSContextContextActivities) -def test_models_xapi_lms_context_context_activities_with_valid_category( - category, context_activities -): +def test_models_xapi_lms_context_context_activities_with_valid_category(category): """Test that a valid `LMSContextContextActivities` should not raise a `ValidationError`. """ + context_activities = mock_xapi_instance(LMSContextContextActivities) activities = json.loads(context_activities.json(exclude_none=True, by_alias=True)) activities["category"] = category try: @@ -230,7 +223,6 @@ def test_models_xapi_lms_context_context_activities_with_valid_category( ) -@settings(deadline=None) @pytest.mark.parametrize( "category", [ @@ -243,13 +235,11 @@ def test_models_xapi_lms_context_context_activities_with_valid_category( [{"id": "https://foo.bar"}, {"id": "https://w3id.org/xapi/not-lms"}], ], ) -@custom_given(LMSContextContextActivities) -def test_models_xapi_lms_context_context_activities_with_invalid_category( - category, context_activities -): +def test_models_xapi_lms_context_context_activities_with_invalid_category(category): """Test that an invalid `LMSContextContextActivities` should raise a `ValidationError`. """ + context_activities = mock_xapi_instance(LMSContextContextActivities) activities = json.loads(context_activities.json(exclude_none=True, by_alias=True)) activities["category"] = category msg = ( diff --git a/tests/models/xapi/test_navigation.py b/tests/models/xapi/test_navigation.py index 2a0293a81..3244c1430 100644 --- a/tests/models/xapi/test_navigation.py +++ b/tests/models/xapi/test_navigation.py @@ -3,40 +3,36 @@ import json import pytest -from hypothesis import settings -from hypothesis import strategies as st from ralph.models.selector import ModelSelector from ralph.models.xapi.navigation.statements import PageTerminated, PageViewed -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_xapi_instance -@settings(deadline=None) @pytest.mark.parametrize("class_", [PageTerminated, PageViewed]) -@custom_given(st.data()) -def test_models_xapi_navigation_selectors_with_valid_statements(class_, data): +def test_models_xapi_navigation_selectors_with_valid_statements(class_): """Test given a valid navigation xAPI statement the `get_first_model` selector method should return the expected model. """ - statement = json.loads(data.draw(custom_builds(class_)).json()) + statement = json.loads(mock_xapi_instance(class_).json()) model = ModelSelector(module="ralph.models.xapi").get_first_model(statement) assert model is class_ -@custom_given(PageTerminated) -def test_models_xapi_navigation_page_terminated_with_valid_statement(statement): +def test_models_xapi_navigation_page_terminated_with_valid_statement(): """Test that a valid page_terminated statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(PageTerminated) assert statement.verb.id == "http://adlnet.gov/expapi/verbs/terminated" assert statement.object.definition.type == "http://activitystrea.ms/schema/1.0/page" -@custom_given(PageViewed) -def test_models_xapi_page_viewed_with_valid_statement(statement): +def test_models_xapi_page_viewed_with_valid_statement(): """Test that a valid page_viewed statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(PageViewed) assert statement.verb.id == "http://id.tincanapi.com/verb/viewed" assert statement.object.definition.type == "http://activitystrea.ms/schema/1.0/page" diff --git a/tests/models/xapi/test_video.py b/tests/models/xapi/test_video.py index 52d1b974f..5a1f2f074 100644 --- a/tests/models/xapi/test_video.py +++ b/tests/models/xapi/test_video.py @@ -3,8 +3,6 @@ import json import pytest -from hypothesis import settings -from hypothesis import strategies as st from pydantic import ValidationError from ralph.models.selector import ModelSelector @@ -22,10 +20,9 @@ VideoVolumeChangeInteraction, ) -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_xapi_instance -@settings(deadline=None) @pytest.mark.parametrize( "class_", [ @@ -37,17 +34,15 @@ VideoTerminated, ], ) -@custom_given(st.data()) -def test_models_xapi_video_selectors_with_valid_statements(class_, data): +def test_models_xapi_video_selectors_with_valid_statements(class_): """Test given a valid video xAPI statement the `get_first_model` selector method should return the expected model. """ - statement = json.loads(data.draw(custom_builds(class_)).json()) + statement = json.loads(mock_xapi_instance(class_).json()) model = ModelSelector(module="ralph.models.xapi").get_first_model(statement) assert model is class_ -@settings(deadline=None) @pytest.mark.parametrize( "class_", [ @@ -56,14 +51,13 @@ def test_models_xapi_video_selectors_with_valid_statements(class_, data): VideoScreenChangeInteraction, ], ) -@custom_given(st.data()) -def test_models_xapi_video_interaction_validator_with_valid_statements(class_, data): +def test_models_xapi_video_interaction_validator_with_valid_statements(class_): """Test given a valid video interaction xAPI statement the `get_first_valid_model` validator method should return the expected model. """ statement = json.loads( - data.draw(custom_builds(class_)).json(exclude_none=True, by_alias=True) + mock_xapi_instance(class_).json(exclude_none=True, by_alias=True) ) model = Validator(ModelSelector(module="ralph.models.xapi")).get_first_valid_model( @@ -73,11 +67,11 @@ def test_models_xapi_video_interaction_validator_with_valid_statements(class_, d assert isinstance(model, class_) -@custom_given(VideoInitialized) -def test_models_xapi_video_initialized_with_valid_statement(statement): +def test_models_xapi_video_initialized_with_valid_statement(): """Test that a valid video initialized statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VideoInitialized) assert statement.verb.id == "http://adlnet.gov/expapi/verbs/initialized" assert ( @@ -86,11 +80,11 @@ def test_models_xapi_video_initialized_with_valid_statement(statement): ) -@custom_given(VideoPlayed) -def test_models_xapi_video_played_with_valid_statement(statement): +def test_models_xapi_video_played_with_valid_statement(): """Test that a valid video played statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VideoPlayed) assert statement.verb.id == "https://w3id.org/xapi/video/verbs/played" assert ( @@ -99,11 +93,11 @@ def test_models_xapi_video_played_with_valid_statement(statement): ) -@custom_given(VideoPaused) -def test_models_xapi_video_paused_with_valid_statement(statement): +def test_models_xapi_video_paused_with_valid_statement(): """Test that a video paused statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VideoPaused) assert statement.verb.id == "https://w3id.org/xapi/video/verbs/paused" assert ( @@ -112,11 +106,11 @@ def test_models_xapi_video_paused_with_valid_statement(statement): ) -@custom_given(VideoSeeked) -def test_models_xapi_video_seeked_with_valid_statement(statement): +def test_models_xapi_video_seeked_with_valid_statement(): """Test that a video seeked statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VideoSeeked) assert statement.verb.id == "https://w3id.org/xapi/video/verbs/seeked" assert ( @@ -125,11 +119,11 @@ def test_models_xapi_video_seeked_with_valid_statement(statement): ) -@custom_given(VideoCompleted) -def test_models_xapi_video_completed_with_valid_statement(statement): +def test_models_xapi_video_completed_with_valid_statement(): """Test that a video completed statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VideoCompleted) assert statement.verb.id == "http://adlnet.gov/expapi/verbs/completed" assert ( @@ -138,11 +132,11 @@ def test_models_xapi_video_completed_with_valid_statement(statement): ) -@custom_given(VideoTerminated) -def test_models_xapi_video_terminated_with_valid_statement(statement): +def test_models_xapi_video_terminated_with_valid_statement(): """Test that a video terminated statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VideoTerminated) assert statement.verb.id == "http://adlnet.gov/expapi/verbs/terminated" assert ( @@ -151,11 +145,11 @@ def test_models_xapi_video_terminated_with_valid_statement(statement): ) -@custom_given(VideoEnableClosedCaptioning) -def test_models_xapi_video_enable_closed_captioning_with_valid_statement(statement): +def test_models_xapi_video_enable_closed_captioning_with_valid_statement(): """Test that a video enable closed captioning statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VideoEnableClosedCaptioning) assert statement.verb.id == "http://adlnet.gov/expapi/verbs/interacted" assert ( @@ -164,11 +158,11 @@ def test_models_xapi_video_enable_closed_captioning_with_valid_statement(stateme ) -@custom_given(VideoVolumeChangeInteraction) -def test_models_xapi_video_volume_change_interaction_with_valid_statement(statement): +def test_models_xapi_video_volume_change_interaction_with_valid_statement(): """Test that a video volume change interaction statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VideoVolumeChangeInteraction) assert statement.verb.id == "http://adlnet.gov/expapi/verbs/interacted" assert ( @@ -177,11 +171,11 @@ def test_models_xapi_video_volume_change_interaction_with_valid_statement(statem ) -@custom_given(VideoScreenChangeInteraction) -def test_models_xapi_video_screen_change_interaction_with_valid_statement(statement): +def test_models_xapi_video_screen_change_interaction_with_valid_statement(): """Test that a video screen change interaction statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VideoScreenChangeInteraction) assert statement.verb.id == "http://adlnet.gov/expapi/verbs/interacted" assert ( @@ -190,7 +184,6 @@ def test_models_xapi_video_screen_change_interaction_with_valid_statement(statem ) -@settings(deadline=None) @pytest.mark.parametrize( "category", [ @@ -203,13 +196,11 @@ def test_models_xapi_video_screen_change_interaction_with_valid_statement(statem [{"id": "https://foo.bar"}, {"id": "https://w3id.org/xapi/video"}], ], ) -@custom_given(VideoContextContextActivities) -def test_models_xapi_video_context_activities_with_valid_category( - category, context_activities -): +def test_models_xapi_video_context_activities_with_valid_category(category): """Test that a valid `VideoContextContextActivities` should not raise a `ValidationError`. """ + context_activities = mock_xapi_instance(VideoContextContextActivities) activities = json.loads(context_activities.json(exclude_none=True, by_alias=True)) activities["category"] = category try: @@ -220,7 +211,6 @@ def test_models_xapi_video_context_activities_with_valid_category( ) -@settings(deadline=None) @pytest.mark.parametrize( "category", [ @@ -233,13 +223,11 @@ def test_models_xapi_video_context_activities_with_valid_category( [{"id": "https://foo.bar"}, {"id": "https://w3id.org/xapi/not-video"}], ], ) -@custom_given(VideoContextContextActivities) -def test_models_xapi_video_context_activities_with_invalid_category( - category, context_activities -): +def test_models_xapi_video_context_activities_with_invalid_category(category): """Test that an invalid `VideoContextContextActivities` should raise a `ValidationError`. """ + context_activities = mock_xapi_instance(VideoContextContextActivities) activities = json.loads(context_activities.json(exclude_none=True, by_alias=True)) activities["category"] = category msg = ( diff --git a/tests/models/xapi/test_virtual_classroom.py b/tests/models/xapi/test_virtual_classroom.py index b3eeadeb4..adf27bde2 100644 --- a/tests/models/xapi/test_virtual_classroom.py +++ b/tests/models/xapi/test_virtual_classroom.py @@ -3,8 +3,6 @@ import json import pytest -from hypothesis import settings -from hypothesis import strategies as st from pydantic import ValidationError from ralph.models.selector import ModelSelector @@ -29,10 +27,9 @@ VirtualClassroomUnsharedScreen, ) -from tests.fixtures.hypothesis_strategies import custom_builds, custom_given +from tests.factories import mock_xapi_instance -@settings(deadline=None) @pytest.mark.parametrize( "class_", [ @@ -53,21 +50,20 @@ VirtualClassroomStoppedCamera, ], ) -@custom_given(st.data()) -def test_models_xapi_virtual_classroom_selectors_with_valid_statements(class_, data): +def test_models_xapi_virtual_classroom_selectors_with_valid_statements(class_): """Test given a valid virtual classroom xAPI statement the `get_first_model` selector method should return the expected model. """ - statement = json.loads(data.draw(custom_builds(class_)).json()) + statement = json.loads(mock_xapi_instance(class_).json()) model = ModelSelector(module="ralph.models.xapi").get_first_model(statement) assert model is class_ -@custom_given(VirtualClassroomInitialized) -def test_models_xapi_virtual_classroom_initialized_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_initialized_with_valid_statement(): """Test that a valid virtual classroom initialized statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomInitialized) assert statement.verb.id == "http://adlnet.gov/expapi/verbs/initialized" assert ( statement.object.definition.type @@ -75,11 +71,11 @@ def test_models_xapi_virtual_classroom_initialized_with_valid_statement(statemen ) -@custom_given(VirtualClassroomJoined) -def test_models_xapi_virtual_classroom_joined_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_joined_with_valid_statement(): """Test that a virtual classroom joined statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomJoined) assert statement.verb.id == "http://activitystrea.ms/join" assert ( statement.object.definition.type @@ -87,11 +83,11 @@ def test_models_xapi_virtual_classroom_joined_with_valid_statement(statement): ) -@custom_given(VirtualClassroomLeft) -def test_models_xapi_virtual_classroom_left_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_left_with_valid_statement(): """Test that a virtual classroom left statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomLeft) assert statement.verb.id == "http://activitystrea.ms/leave" assert ( statement.object.definition.type @@ -99,11 +95,11 @@ def test_models_xapi_virtual_classroom_left_with_valid_statement(statement): ) -@custom_given(VirtualClassroomTerminated) -def test_models_xapi_virtual_classroom_terminated_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_terminated_with_valid_statement(): """Test that a virtual classroom terminated statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomTerminated) assert statement.verb.id == "http://adlnet.gov/expapi/verbs/terminated" assert ( statement.object.definition.type @@ -111,11 +107,11 @@ def test_models_xapi_virtual_classroom_terminated_with_valid_statement(statement ) -@custom_given(VirtualClassroomMuted) -def test_models_xapi_virtual_classroom_muted_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_muted_with_valid_statement(): """Test that a virtual classroom muted statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomMuted) assert statement.verb.id == "https://w3id.org/xapi/virtual-classroom/verbs/muted" assert ( statement.object.definition.type @@ -123,11 +119,11 @@ def test_models_xapi_virtual_classroom_muted_with_valid_statement(statement): ) -@custom_given(VirtualClassroomUnmuted) -def test_models_xapi_virtual_classroom_unmuted_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_unmuted_with_valid_statement(): """Test that a virtual classroom unmuted statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomUnmuted) assert statement.verb.id == "https://w3id.org/xapi/virtual-classroom/verbs/unmuted" assert ( statement.object.definition.type @@ -135,11 +131,11 @@ def test_models_xapi_virtual_classroom_unmuted_with_valid_statement(statement): ) -@custom_given(VirtualClassroomSharedScreen) -def test_models_xapi_virtual_classroom_shared_screen_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_shared_screen_with_valid_statement(): """Test that a virtual classroom shared screen statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomSharedScreen) assert ( statement.verb.id == "https://w3id.org/xapi/virtual-classroom/verbs/shared-screen" @@ -150,11 +146,11 @@ def test_models_xapi_virtual_classroom_shared_screen_with_valid_statement(statem ) -@custom_given(VirtualClassroomUnsharedScreen) -def test_models_xapi_virtual_classroom_unshared_screen_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_unshared_screen_with_valid_statement(): """Test that a virtual classroom unshared screen statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomUnsharedScreen) assert ( statement.verb.id == "https://w3id.org/xapi/virtual-classroom/verbs/unshared-screen" @@ -165,11 +161,11 @@ def test_models_xapi_virtual_classroom_unshared_screen_with_valid_statement(stat ) -@custom_given(VirtualClassroomStartedCamera) -def test_models_xapi_virtual_classroom_started_camera_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_started_camera_with_valid_statement(): """Test that a virtual classroom started camera statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomStartedCamera) assert ( statement.verb.id == "https://w3id.org/xapi/virtual-classroom/verbs/started-camera" @@ -180,11 +176,11 @@ def test_models_xapi_virtual_classroom_started_camera_with_valid_statement(state ) -@custom_given(VirtualClassroomStoppedCamera) -def test_models_xapi_virtual_classroom_stopped_camera_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_stopped_camera_with_valid_statement(): """Test that a virtual classroom stopped camera statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomStoppedCamera) assert ( statement.verb.id == "https://w3id.org/xapi/virtual-classroom/verbs/stopped-camera" @@ -195,11 +191,11 @@ def test_models_xapi_virtual_classroom_stopped_camera_with_valid_statement(state ) -@custom_given(VirtualClassroomRaisedHand) -def test_models_xapi_virtual_classroom_raised_hand_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_raised_hand_with_valid_statement(): """Test that a virtual classroom raised hand statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomRaisedHand) assert ( statement.verb.id == "https://w3id.org/xapi/virtual-classroom/verbs/raised-hand" ) @@ -209,11 +205,11 @@ def test_models_xapi_virtual_classroom_raised_hand_with_valid_statement(statemen ) -@custom_given(VirtualClassroomLoweredHand) -def test_models_xapi_virtual_classroom_lowered_hand_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_lowered_hand_with_valid_statement(): """Test that a virtual classroom lowered hand statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomLoweredHand) assert ( statement.verb.id == "https://w3id.org/xapi/virtual-classroom/verbs/lowered-hand" @@ -224,11 +220,11 @@ def test_models_xapi_virtual_classroom_lowered_hand_with_valid_statement(stateme ) -@custom_given(VirtualClassroomStartedPoll) -def test_models_xapi_virtual_classroom_started_poll_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_started_poll_with_valid_statement(): """Test that a virtual classroom started poll statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomStartedPoll) assert statement.verb.id == "http://adlnet.gov/expapi/verbs/asked" assert ( statement.object.definition.type @@ -236,11 +232,11 @@ def test_models_xapi_virtual_classroom_started_poll_with_valid_statement(stateme ) -@custom_given(VirtualClassroomAnsweredPoll) -def test_models_xapi_virtual_classroom_answered_poll_with_valid_statement(statement): +def test_models_xapi_virtual_classroom_answered_poll_with_valid_statement(): """Test that a virtual classroom answered poll statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ + statement = mock_xapi_instance(VirtualClassroomAnsweredPoll) assert statement.verb.id == "http://adlnet.gov/expapi/verbs/answered" assert ( statement.object.definition.type @@ -248,10 +244,8 @@ def test_models_xapi_virtual_classroom_answered_poll_with_valid_statement(statem ) -@custom_given(VirtualClassroomPostedPublicMessage) -def test_models_xapi_virtual_classroom_posted_public_message_with_valid_statement( - statement, -): +def test_models_xapi_virtual_classroom_posted_public_message_with_valid_statement(): + statement = mock_xapi_instance(VirtualClassroomPostedPublicMessage) """Test that a virtual classroom posted public message statement has the expected `verb`.`id` and `object`.`definition`.`type` property values. """ @@ -262,7 +256,6 @@ def test_models_xapi_virtual_classroom_posted_public_message_with_valid_statemen ) -@settings(deadline=None) @pytest.mark.parametrize( "category", [ @@ -275,13 +268,11 @@ def test_models_xapi_virtual_classroom_posted_public_message_with_valid_statemen [{"id": "https://foo.bar"}, {"id": "https://w3id.org/xapi/virtual-classroom"}], ], ) -@custom_given(VirtualClassroomContextContextActivities) -def test_models_xapi_virtual_classroom_context_activities_with_valid_category( - category, context_activities -): +def test_models_xapi_virtual_classroom_context_activities_with_valid_category(category): """Test that a valid `VirtualClassroomContextContextActivities` should not raise a `ValidationError`. """ + context_activities = mock_xapi_instance(VirtualClassroomContextContextActivities) activities = json.loads(context_activities.json(exclude_none=True, by_alias=True)) activities["category"] = category try: @@ -293,7 +284,6 @@ def test_models_xapi_virtual_classroom_context_activities_with_valid_category( ) -@settings(deadline=None) @pytest.mark.parametrize( "category", [ @@ -309,13 +299,13 @@ def test_models_xapi_virtual_classroom_context_activities_with_valid_category( ], ], ) -@custom_given(VirtualClassroomContextContextActivities) def test_models_xapi_virtual_classroom_context_activities_with_invalid_category( - category, context_activities + category, ): """Test that an invalid `VirtualClassroomContextContextActivities` should raise a `ValidationError`. """ + context_activities = mock_xapi_instance(VirtualClassroomContextContextActivities) activities = json.loads(context_activities.json(exclude_none=True, by_alias=True)) activities["category"] = category msg = ( diff --git a/tests/test_cli.py b/tests/test_cli.py index a859adea8..38a78c663 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -9,7 +9,6 @@ from click.exceptions import BadParameter from click.testing import CliRunner from elasticsearch.helpers import bulk, scan -from hypothesis import settings as hypothesis_settings from pydantic import ValidationError from ralph import cli as cli_module @@ -27,13 +26,13 @@ from ralph.models.edx.navigational.statements import UIPageClose from ralph.models.xapi.navigation.statements import PageTerminated +from tests.factories import mock_instance from tests.fixtures.backends import ( ES_TEST_HOSTS, ES_TEST_INDEX, WS_TEST_HOST, WS_TEST_PORT, ) -from tests.fixtures.hypothesis_strategies import custom_given test_logger = logging.getLogger("ralph") @@ -471,20 +470,21 @@ def test_cli_extract_command_with_es_parser(): assert "\n".join([json.dumps({"id": idx}) for idx in range(10)]) in result.output -@custom_given(UIPageClose) -def test_cli_validate_command_with_edx_format(event): +def test_cli_validate_command_with_edx_format(): """Test ralph validate command using the edx format.""" + event = mock_instance(UIPageClose) + event_str = event.json() runner = CliRunner() result = runner.invoke(cli, ["validate", "-f", "edx"], input=event_str) assert event_str in result.output -@hypothesis_settings(deadline=None) -@custom_given(UIPageClose) @pytest.mark.parametrize("valid_uuid", ["ee241f8b-174f-5bdb-bae9-c09de5fe017f"]) -def test_cli_convert_command_from_edx_to_xapi_format(valid_uuid, event): +def test_cli_convert_command_from_edx_to_xapi_format(valid_uuid): """Test ralph convert command from edx to xapi format.""" + event = mock_instance(UIPageClose) + event_str = event.json() runner = CliRunner() command = f"-v ERROR convert -f edx -t xapi -u {valid_uuid} -p https://fun-mooc.fr"