Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • infrablue/python-bigbluebutton2
  • dss/python-bigbluebutton2
  • 3mulmulMexbe/python-bigbluebutton2
  • 7ilraWbrunse/python-bigbluebutton2
  • 1carceinwo/python-bigbluebutton2
5 results
Show changes
Commits on Source (28)
...@@ -66,3 +66,5 @@ docs/_build/ ...@@ -66,3 +66,5 @@ docs/_build/
.coverage .coverage
.mypy_cache/ .mypy_cache/
.pytest_cache/ .pytest_cache/
.vscode/
...@@ -43,6 +43,6 @@ license. See the LICENSE file for details. ...@@ -43,6 +43,6 @@ license. See the LICENSE file for details.
.. _BigBlueButton: https://bigbluebutton.org/ .. _BigBlueButton: https://bigbluebutton.org/
.. _XML-RPC API: https://docs.bigbluebutton.org/dev/api.html .. _XML-RPC API: https://docs.bigbluebutton.org/dev/api.html
.. _Django: https://www.djangoproject.com/ .. _Django: https://www.djangoproject.com/
.. _InfraBlue: https://edugit.org/nik/infrablue .. _InfraBlue: https://edugit.org/infrablue/infrablue
.. _AlekSIS: https://aleksis.org/ .. _AlekSIS: https://aleksis.org/
.. _full documentation: https://infrablue.edugit.io/python-bigbluebutton2/docs/html/ .. _full documentation: https://infrablue.edugit.io/python-bigbluebutton2/docs/html/
...@@ -175,7 +175,7 @@ class BigBlueButton: ...@@ -175,7 +175,7 @@ class BigBlueButton:
return meeting return meeting
def get_meetings(self) -> Dict[str, "Meeting"]: def get_meetings(self, filter_meta: Optional[Dict[str, str]] = None) -> Dict[str, "Meeting"]:
"""Get all meetings known on the BigBlueButton server. """Get all meetings known on the BigBlueButton server.
This method calls the getMeetings API call on the BigBlueButton server This method calls the getMeetings API call on the BigBlueButton server
...@@ -186,6 +186,9 @@ class BigBlueButton: ...@@ -186,6 +186,9 @@ class BigBlueButton:
As every server is part of a server group, consumers should always call As every server is part of a server group, consumers should always call
:func:`BigBlueButtonGroup.create_meeting` instead. :func:`BigBlueButtonGroup.create_meeting` instead.
The results are returned after filtering on the `filter_meta` argument;
see :meth:`filter_meetings` for details.
:return: A dictionary mapping meeting IDs to Meeting objects :return: A dictionary mapping meeting IDs to Meeting objects
""" """
logger.info(f"Updating meetings on server {self.name}") logger.info(f"Updating meetings on server {self.name}")
...@@ -221,7 +224,17 @@ class BigBlueButton: ...@@ -221,7 +224,17 @@ class BigBlueButton:
if meeting_id not in found_meeting_ids: if meeting_id not in found_meeting_ids:
del self.meetings[meeting_id] del self.meetings[meeting_id]
return self.meetings return BigBlueButton.filter_meetings(self.meetings, filter_meta)
@staticmethod
def filter_meetings(
meetings: Dict[str, "Meeting"], filter_meta: Optional[Dict[str, str]] = None
) -> Dict[str, "Meeting"]:
"""Filter the known meetings on meta-data.
See :meth:`Meeting.matches_meta` for the semantics of the arguments.
"""
return dict(filter(lambda i: i[1].matches_meta(filter_meta), meetings.items()))
def ssh_command( def ssh_command(
self, command: Sequence[str], input_: Optional[str] = None self, command: Sequence[str], input_: Optional[str] = None
...@@ -398,7 +411,7 @@ class BigBlueButtonGroup: ...@@ -398,7 +411,7 @@ class BigBlueButtonGroup:
return res return res
def get_meetings(self) -> Dict[str, "Meeting"]: def get_meetings(self, filter_meta: Optional[Dict[str, str]] = None) -> Dict[str, "Meeting"]:
"""Update meeting information on all server objects. """Update meeting information on all server objects.
It is aggregated into the meetings attribute. It is aggregated into the meetings attribute.
...@@ -407,7 +420,7 @@ class BigBlueButtonGroup: ...@@ -407,7 +420,7 @@ class BigBlueButtonGroup:
the meeting property should be used instead. the meeting property should be used instead.
""" """
self._foreach("get_meetings") self._foreach("get_meetings")
return self.meetings return BigBlueButton.filter_meetings(self.meetings, filter_meta)
def ssh_command( def ssh_command(
self, command: Sequence[str], input_: Optional[str] = None self, command: Sequence[str], input_: Optional[str] = None
...@@ -490,7 +503,9 @@ class BigBlueButtonGroup: ...@@ -490,7 +503,9 @@ class BigBlueButtonGroup:
api = random.choice(apis_won) # noqa: S311, not cryptographic api = random.choice(apis_won) # noqa: S311, not cryptographic
return api return api
def create_meeting(self, do_create: bool = True, *args, **kwargs) -> Meeting: def create_meeting(
self, do_create: bool = True, filter_meta: Optional[Dict[str, str]] = None, *args, **kwargs
) -> Meeting:
"""Create a new meeting on one of the backend servers. """Create a new meeting on one of the backend servers.
For the arguments, see the documentation on :class:`~bigbluebutton.api.meeting.Meeting`. For the arguments, see the documentation on :class:`~bigbluebutton.api.meeting.Meeting`.
...@@ -508,6 +523,15 @@ class BigBlueButtonGroup: ...@@ -508,6 +523,15 @@ class BigBlueButtonGroup:
""" """
if "meeting_id" in kwargs: if "meeting_id" in kwargs:
meeting = self._find_meeting(kwargs["meeting_id"]) meeting = self._find_meeting(kwargs["meeting_id"])
if meeting and not self._find_meeting(kwargs["meeting_id"], filter_meta):
logger.info(
f"Found meeting with id {meeting.meeting_id} on server {api.name}, but meta-data did not match"
)
logger.warn(
f"Stripping meeting id {meeting.meeting_id} from create request due to collision"
)
meeting = None
del kwargs["meeting_id"]
else: else:
meeting = None meeting = None
...@@ -520,20 +544,31 @@ class BigBlueButtonGroup: ...@@ -520,20 +544,31 @@ class BigBlueButtonGroup:
return api.create_meeting(do_create, *args, **kwargs) return api.create_meeting(do_create, *args, **kwargs)
def _find_meeting(self, meeting_id: str) -> Optional[Meeting]: def _find_meeting(
if meeting_id in self.meetings or meeting_id in self.get_meetings(): self, meeting_id: str, filter_meta: Optional[Dict[str, str]] = None
) -> Optional[Meeting]:
if (
meeting_id in self.meetings or meeting_id in self.get_meetings()
) and meeting_id in BigBlueButton.filter_meetings(self.meetings, filter_meta):
return self.meetings[meeting_id] return self.meetings[meeting_id]
return None return None
def is_meeting_running(self, meeting_id: str) -> bool: def is_meeting_running(
self, meeting_id: str, filter_meta: Optional[Dict[str, str]] = None
) -> bool:
"""Determine whether a meeting with a given ID is running on any backend server.""" """Determine whether a meeting with a given ID is running on any backend server."""
meeting = self._find_meeting(meeting_id) meeting = self._find_meeting(meeting_id, filter_meta)
if meeting: if meeting:
return meeting.is_meeting_running() return meeting.is_meeting_running()
else: else:
return False return False
def end_meeting(self, meeting_id: str, password: Optional[str] = None) -> None: def end_meeting(
self,
meeting_id: str,
password: Optional[str] = None,
filter_meta: Optional[Dict[str, str]] = None,
) -> None:
"""End a meeting determined from the passed ID. """End a meeting determined from the passed ID.
If the meeting is not found, this method silently does nothing. If the meeting is not found, this method silently does nothing.
...@@ -542,7 +577,7 @@ class BigBlueButtonGroup: ...@@ -542,7 +577,7 @@ class BigBlueButtonGroup:
the moderarotr password of the meeting before the end request is even the moderarotr password of the meeting before the end request is even
sent to the backend. sent to the backend.
""" """
meeting = self._find_meeting(meeting_id) meeting = self._find_meeting(meeting_id, filter_meta)
if meeting: if meeting:
# Verify password before even sending the call, if provided # Verify password before even sending the call, if provided
# Entirely useless because anyone who can call end can also call getMeetingInfo, # Entirely useless because anyone who can call end can also call getMeetingInfo,
...@@ -556,7 +591,7 @@ class BigBlueButtonGroup: ...@@ -556,7 +591,7 @@ class BigBlueButtonGroup:
method: str, method: str,
attrs: Optional[dict] = None, attrs: Optional[dict] = None,
content: Optional[str] = None, content: Optional[str] = None,
filter_meta: Optional[dict] = None, filter_meta: Optional[Dict[str, str]] = None,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Handle an API call from a method, a dict of parameters, and a text body. """Handle an API call from a method, a dict of parameters, and a text body.
...@@ -573,11 +608,14 @@ class BigBlueButtonGroup: ...@@ -573,11 +608,14 @@ class BigBlueButtonGroup:
meeting: Optional["Meeting"] meeting: Optional["Meeting"]
if method == "create": if method == "create":
kwargs = Meeting.get_kwargs_from_url_args(attrs) kwargs = Meeting.get_kwargs_from_url_args(attrs)
# Merge filter_meta into created meeting, so later responses can be filtered on it
kwargs.setdefault("meta", {}).update(filter_meta or {})
meeting = self.create_meeting(**kwargs) meeting = self.create_meeting(**kwargs)
return meeting.to_dict() return meeting.to_dict()
elif method == "join": elif method == "join":
meeting = self._find_meeting(attrs["meetingID"]) meeting = self._find_meeting(attrs["meetingID"], filter_meta)
if meeting: if meeting:
kwargs = Attendee.get_kwargs_from_url_args(attrs, meeting) kwargs = Attendee.get_kwargs_from_url_args(attrs, meeting)
attendee = Attendee(meeting=meeting, **kwargs) attendee = Attendee(meeting=meeting, **kwargs)
...@@ -596,20 +634,20 @@ class BigBlueButtonGroup: ...@@ -596,20 +634,20 @@ class BigBlueButtonGroup:
else: else:
raise KeyError("Meeting not found.") raise KeyError("Meeting not found.")
elif method == "isMeetingRunning": elif method == "isMeetingRunning":
running = self.is_meeting_running(attrs["meetingID"]) running = self.is_meeting_running(attrs["meetingID"], filter_meta)
return {"running": "true" if running else "false"} return {"running": "true" if running else "false"}
elif method == "end": elif method == "end":
self.end_meeting(attrs["meetingID"], attrs["password"]) self.end_meeting(attrs["meetingID"], attrs["password"], filter_meta)
return {} return {}
elif method == "getMeetingInfo": elif method == "getMeetingInfo":
meeting = self._find_meeting(attrs["meetingID"]) meeting = self._find_meeting(attrs["meetingID"], filter_meta)
if meeting: if meeting:
meeting.get_meeting_info() meeting.get_meeting_info()
return meeting.to_dict() return meeting.to_dict()
else: else:
raise KeyError("Meeting not found.") raise KeyError("Meeting not found.")
elif method == "getMeetings": elif method == "getMeetings":
meetings = self.get_meetings() meetings = self.get_meetings(filter_meta)
return { return {
"meetings": [ "meetings": [
{"meeting": meeting.to_dict()} for meeting_id, meeting in meetings.items() {"meeting": meeting.to_dict()} for meeting_id, meeting in meetings.items()
......
...@@ -93,6 +93,15 @@ class Meeting: ...@@ -93,6 +93,15 @@ class Meeting:
self.attendees = {} self.attendees = {}
self.api.meetings[self.meeting_id] = self self.api.meetings[self.meeting_id] = self
def matches_meta(self, filter_meta: Optional[Dict[str, str]] = None) -> bool:
"""Verify if meeting meta-data matches a set of keys and values.
If the `fitler_meta` argument is not None, all attributes in it must be present
in the meta-data of the meeting.
"""
filter_meta = filter_meta or {}
return set(filter_meta.items()).issubset(set(self.meta.items()))
def create(self) -> None: def create(self) -> None:
"""Create the meeting on the linked BigBlueButton server.""" """Create the meeting on the linked BigBlueButton server."""
# Set origin metadata from API defaults if not defined # Set origin metadata from API defaults if not defined
......
...@@ -33,38 +33,6 @@ class Migration(migrations.Migration): ...@@ -33,38 +33,6 @@ class Migration(migrations.Migration):
("salt", models.CharField(max_length=60, verbose_name="API shared secret")), ("salt", models.CharField(max_length=60, verbose_name="API shared secret")),
], ],
), ),
migrations.CreateModel(
name="Meeting",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("name", models.CharField(max_length=60, verbose_name="Meeting name")),
("welcome_message", models.TextField(blank=True, verbose_name="Welcome message")),
(
"moderator_message",
models.TextField(blank=True, verbose_name="Welcome message for moderators"),
),
(
"max_participants",
models.PositiveSmallIntegerField(
default=0, verbose_name="Maximum number of participants"
),
),
("guid", models.UUIDField(default=uuid.uuid1, editable=False)),
(
"api",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="meetings",
to="django.BigBlueButton",
),
),
],
),
migrations.CreateModel( migrations.CreateModel(
name="BigBlueButtonGroup", name="BigBlueButtonGroup",
fields=[ fields=[
...@@ -95,9 +63,41 @@ class Migration(migrations.Migration): ...@@ -95,9 +63,41 @@ class Migration(migrations.Migration):
field=models.ForeignKey( field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
related_name="apis", related_name="apis",
to="django.BigBlueButtonGroup", to="bigbluebutton.BigBlueButtonGroup",
), ),
), ),
migrations.CreateModel(
name="Meeting",
fields=[
(
"id",
models.AutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
("name", models.CharField(max_length=60, verbose_name="Meeting name")),
("welcome_message", models.TextField(blank=True, verbose_name="Welcome message")),
(
"moderator_message",
models.TextField(blank=True, verbose_name="Welcome message for moderators"),
),
(
"max_participants",
models.PositiveSmallIntegerField(
default=0, verbose_name="Maximum number of participants"
),
),
("guid", models.UUIDField(default=uuid.uuid1, editable=False)),
(
"api_group",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="meetings",
to="bigbluebutton.BigBlueButtonGroup",
),
),
],
),
migrations.CreateModel( migrations.CreateModel(
name="APIToken", name="APIToken",
fields=[ fields=[
...@@ -129,7 +129,8 @@ class Migration(migrations.Migration): ...@@ -129,7 +129,8 @@ class Migration(migrations.Migration):
( (
"server_group", "server_group",
models.ForeignKey( models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="django.BigBlueButtonGroup" on_delete=django.db.models.deletion.CASCADE,
to="bigbluebutton.BigBlueButtonGroup"
), ),
), ),
( (
......
...@@ -163,7 +163,7 @@ class Meeting(models.Model): ...@@ -163,7 +163,7 @@ class Meeting(models.Model):
verbose_name=_("Maximum duration (in minutes)"), null=True verbose_name=_("Maximum duration (in minutes)"), null=True
) )
api = models.ForeignKey("BigBlueButton", on_delete=models.CASCADE, related_name="meetings") api_group = models.ForeignKey("BigBlueButtonGroup", on_delete=models.CASCADE, related_name="meetings")
guid = models.UUIDField(default=uuid.uuid1, editable=False) guid = models.UUIDField(default=uuid.uuid1, editable=False)
...@@ -203,7 +203,7 @@ class Meeting(models.Model): ...@@ -203,7 +203,7 @@ class Meeting(models.Model):
then cached. then cached.
""" """
if self._meeting is None: if self._meeting is None:
self._meeting = self.api.api.create_meeting( self._meeting = self.api_group.api_group.create_meeting(
do_create=False, do_create=False,
meeting_id=self.meeting_id, meeting_id=self.meeting_id,
meeting_name=self.name, meeting_name=self.name,
......
...@@ -39,7 +39,7 @@ def get_display_name(user: Union["AnonymousUser", "User"]) -> str: ...@@ -39,7 +39,7 @@ def get_display_name(user: Union["AnonymousUser", "User"]) -> str:
""" """
if user.is_anonymous: if user.is_anonymous:
return _("Anonymous User") return user.username or _("Anonymous User")
else: else:
return user.get_full_name().strip() or user.username return user.get_full_name().strip() or user.username
......
...@@ -295,18 +295,15 @@ description = "flake8 plugin that integrates isort ." ...@@ -295,18 +295,15 @@ description = "flake8 plugin that integrates isort ."
name = "flake8-isort" name = "flake8-isort"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "3.0.0" version = "4.0.0"
[package.dependencies] [package.dependencies]
flake8 = ">=3.2.1" flake8 = ">=3.2.1,<4"
testfixtures = "*" isort = ">=4.3.5,<6"
testfixtures = ">=6.8.0,<7"
[package.dependencies.isort]
extras = ["pyproject"]
version = ">=4.3.5"
[package.extras] [package.extras]
test = ["pytest"] test = ["pytest (>=4.0.2,<6)", "toml"]
[[package]] [[package]]
category = "dev" category = "dev"
...@@ -1100,7 +1097,7 @@ django = ["dicttoxml", "django"] ...@@ -1100,7 +1097,7 @@ django = ["dicttoxml", "django"]
sysstat = ["sadf"] sysstat = ["sadf"]
[metadata] [metadata]
content-hash = "beca6d8e515ae0c467be57cc036201401148a8b4b85c84bbb09f5f39d3fbade0" content-hash = "0cd260802e26220166a68d23603d4bb8516d965e9d86fd968a72150ab24862ad"
python-versions = "^3.6" python-versions = "^3.6"
[metadata.files] [metadata.files]
...@@ -1231,8 +1228,8 @@ flake8-docstrings = [ ...@@ -1231,8 +1228,8 @@ flake8-docstrings = [
{file = "flake8_docstrings-1.5.0-py2.py3-none-any.whl", hash = "sha256:a256ba91bc52307bef1de59e2a009c3cf61c3d0952dbe035d6ff7208940c2edc"}, {file = "flake8_docstrings-1.5.0-py2.py3-none-any.whl", hash = "sha256:a256ba91bc52307bef1de59e2a009c3cf61c3d0952dbe035d6ff7208940c2edc"},
] ]
flake8-isort = [ flake8-isort = [
{file = "flake8-isort-3.0.0.tar.gz", hash = "sha256:3ce227b5c5342b6d63937d3863e8de8783ae21863cb035cf992cdb0ba5990aa3"}, {file = "flake8-isort-4.0.0.tar.gz", hash = "sha256:2b91300f4f1926b396c2c90185844eb1a3d5ec39ea6138832d119da0a208f4d9"},
{file = "flake8_isort-3.0.0-py2.py3-none-any.whl", hash = "sha256:f5322a85cea89998e0df954162fd35a1f1e5b5eb4fc0c79b5975aa2799106baa"}, {file = "flake8_isort-4.0.0-py2.py3-none-any.whl", hash = "sha256:729cd6ef9ba3659512dee337687c05d79c78e1215fdf921ed67e5fe46cce2f3c"},
] ]
flake8-mypy = [ flake8-mypy = [
{file = "flake8-mypy-17.8.0.tar.gz", hash = "sha256:47120db63aff631ee1f84bac6fe8e64731dc66da3efc1c51f85e15ade4a3ba18"}, {file = "flake8-mypy-17.8.0.tar.gz", hash = "sha256:47120db63aff631ee1f84bac6fe8e64731dc66da3efc1c51f85e15ade4a3ba18"},
......
...@@ -53,7 +53,7 @@ flake8 = "^3.8.2" ...@@ -53,7 +53,7 @@ flake8 = "^3.8.2"
flake8-black = "^0.2.0" flake8-black = "^0.2.0"
flake8-mypy = "^17.8.0" flake8-mypy = "^17.8.0"
flake8-bandit = "^2.1.2" flake8-bandit = "^2.1.2"
flake8-isort = "^3.0.0" flake8-isort = "^4.0.0"
flake8-builtins = "^1.5.3" flake8-builtins = "^1.5.3"
flake8-docstrings = "^1.5.0" flake8-docstrings = "^1.5.0"
flake8-rst-docstrings = "^0.0.13" flake8-rst-docstrings = "^0.0.13"
......
from unittest import TestCase
from unittest.mock import MagicMock
from bigbluebutton.api.bigbluebutton import BigBlueButton, BigBlueButtonGroup
from bigbluebutton.api.meeting import Meeting
class TestBase(TestCase):
def setUp(self):
# Create a dummy BigBlueButton group with one dummy server
self.group = BigBlueButtonGroup("Dummy")
self.api = self.group.new(
"Dummy", "https://example.com/bigbluebutton/api/", "eeK1iw5se1gie0hohhai9sheiquaiMee"
)
# Set up a test meeting for later reference
self.meeting = Meeting(
self.api,
meeting_id="MeetingFromSetup",
meeting_name="Meeting from setUp",
attendee_pw="ap",
moderator_pw="mp",
create_time=12345678,
)
# Mock the _request method to not ever send off requests
BigBlueButton._request = MagicMock()
from unittest import TestCase
from unittest.mock import MagicMock
import xmltodict import xmltodict
from bigbluebutton.api.attendee import Role from bigbluebutton.api.attendee import Role
from bigbluebutton.api.bigbluebutton import BigBlueButton, BigBlueButtonGroup
from bigbluebutton.api.meeting import Meeting from ._test_base import TestBase
XML_EMPTY_MEETINGS = """ XML_EMPTY_MEETINGS = """
<response> <response>
...@@ -13,27 +10,7 @@ XML_EMPTY_MEETINGS = """ ...@@ -13,27 +10,7 @@ XML_EMPTY_MEETINGS = """
""" """
class TestHandleFromData(TestCase): class TestHandleFromData(TestBase):
def setUp(self):
# Create a dummy BigBlueButton group with one dummy server
self.group = BigBlueButtonGroup("Dummy")
self.api = self.group.new(
"Dummy", "https://example.com/bigbluebutton/api/", "eeK1iw5se1gie0hohhai9sheiquaiMee"
)
# Set up a test meeting for later reference
self.meeting = Meeting(
self.api,
meeting_id="MeetingFromSetup",
meeting_name="Meeting from setUp",
attendee_pw="ap",
moderator_pw="mp",
create_time=12345678,
)
# Mock the _request method to not ever send off requests
BigBlueButton._request = MagicMock()
def test_invalid_method(self): def test_invalid_method(self):
method = "invalidMethod" method = "invalidMethod"
...@@ -503,3 +480,107 @@ class TestHandleFromData(TestCase): ...@@ -503,3 +480,107 @@ class TestHandleFromData(TestCase):
self.api._request.assert_called_once_with(method) self.api._request.assert_called_once_with(method)
self.assertEqual(res["meetings"][0]["meeting"]["meetingName"], self.meeting.meeting_name) self.assertEqual(res["meetings"][0]["meeting"]["meetingName"], self.meeting.meeting_name)
self.assertEqual(res["meetings"][0]["meeting"]["meetingID"], self.meeting.meeting_id) self.assertEqual(res["meetings"][0]["meeting"]["meetingID"], self.meeting.meeting_id)
class TestSingleAPI(TestBase):
def test_get_meetings_filter(self):
fake_xml = """
<response>
<returncode>SUCCESS</returncode>
<meetings>
<meeting>
<meetingName>Test meeting 1</meetingName>
<meetingID>testmeeting1</meetingID>
<attendees/>
<metadata>
<filter-key1>a</filter-key1>
<filter-key2>b</filter-key2>
</metadata>
</meeting>
<meeting>
<meetingName>Test meeting 2</meetingName>
<meetingID>testmeeting2</meetingID>
<attendees/>
<metadata>
<filter-key1>c</filter-key1>
<filter-key2>d</filter-key2>
</metadata>
</meeting>
</meetings>
</response>
"""
self.api._request.side_effect = [
xmltodict.parse(fake_xml)["response"],
xmltodict.parse(fake_xml)["response"],
xmltodict.parse(fake_xml)["response"],
xmltodict.parse(fake_xml)["response"],
]
# Test for only meeting 1
res = self.api.get_meetings({"filter-key1": "a"})
self.assertEqual(len(res), 1)
self.assertEqual(res["testmeeting1"].meeting_name, "Test meeting 1")
# Test for only meeting 2
res = self.api.get_meetings({"filter-key1": "c"})
self.assertEqual(len(res), 1)
self.assertEqual(res["testmeeting2"].meeting_name, "Test meeting 2")
# Test for one wrong key
res = self.api.get_meetings({"filter-key1": "e"})
self.assertEqual(len(res), 0)
# Test for one correct and one wrong key
res = self.api.get_meetings({"filter-key1": "a", "filter-key2": "d"})
self.assertEqual(len(res), 0)
class TestAPIGroup(TestBase):
def test_get_meetings_filter(self):
fake_xml = """
<response>
<returncode>SUCCESS</returncode>
<meetings>
<meeting>
<meetingName>Test meeting 1</meetingName>
<meetingID>testmeeting1</meetingID>
<attendees/>
<metadata>
<filter-key1>a</filter-key1>
<filter-key2>b</filter-key2>
</metadata>
</meeting>
<meeting>
<meetingName>Test meeting 2</meetingName>
<meetingID>testmeeting2</meetingID>
<attendees/>
<metadata>
<filter-key1>c</filter-key1>
<filter-key2>d</filter-key2>
</metadata>
</meeting>
</meetings>
</response>
"""
self.api._request.side_effect = [
xmltodict.parse(fake_xml)["response"],
xmltodict.parse(fake_xml)["response"],
xmltodict.parse(fake_xml)["response"],
xmltodict.parse(fake_xml)["response"],
]
# Test for only meeting 1
res = self.group.get_meetings({"filter-key1": "a"})
self.assertEqual(len(res), 1)
self.assertEqual(res["testmeeting1"].meeting_name, "Test meeting 1")
# Test for only meeting 2
res = self.group.get_meetings({"filter-key1": "c"})
self.assertEqual(len(res), 1)
self.assertEqual(res["testmeeting2"].meeting_name, "Test meeting 2")
# Test for one wrong key
res = self.group.get_meetings({"filter-key1": "e"})
self.assertEqual(len(res), 0)
# Test for one correct and one wrong key
res = self.group.get_meetings({"filter-key1": "a", "filter-key2": "d"})
self.assertEqual(len(res), 0)