Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds tests for rooms #181

Merged
merged 9 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 1 addition & 33 deletions tests/test_loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,12 @@

import asyncio
from datetime import datetime
from typing import Any

import pytest
from jupyter_server import _tz as tz

from jupyter_collaboration.loaders import FileLoader, FileLoaderMapping


class FakeFileIDManager:
def __init__(self, mapping: dict[str, str]):
self.mapping = mapping

def get_path(self, id: str) -> str:
return self.mapping[id]


class FakeContentsManager:
def __init__(self, model: dict):
self.model = {
"name": "",
"path": "",
"last_modified": datetime(1970, 1, 1, 0, 0, tzinfo=tz.UTC),
"created": datetime(1970, 1, 1, 0, 0, tzinfo=tz.UTC),
"content": None,
"format": None,
"mimetype": None,
"size": 0,
"writable": False,
}
self.model.update(model)

def get(
self, path: str, content: bool = True, format: str | None = None, type: str | None = None
) -> dict:
return self.model

def save_content(self, model: dict[str, Any], path: str) -> dict:
return self.model
from .utils import FakeContentsManager, FakeFileIDManager


@pytest.mark.asyncio
Expand Down
152 changes: 152 additions & 0 deletions tests/test_rooms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

from __future__ import annotations

import asyncio
from datetime import datetime

import pytest
from ypy_websocket.yutils import write_var_uint

from jupyter_collaboration.loaders import FileLoader
from jupyter_collaboration.rooms import DocumentRoom
from jupyter_collaboration.utils import RoomMessages

from .utils import FakeContentsManager, FakeEventLogger, FakeFileIDManager


@pytest.mark.asyncio
async def test_should_initialize_document_room_without_store():
id = "test-id"
content = "test"
paths = {id: "test.txt"}
cm = FakeContentsManager({"content": content})
loader = FileLoader(
id,
FakeFileIDManager(paths),
cm,
poll_interval=0.1,
)
Comment on lines +21 to +30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if this should be a fixture or a factory to avoid too many copy paste.

Fully optional - happy to merge as is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at it again. Even though it seems like code duplication, it is not. Each test uses a different argument, and we need to create and parameterize multiple fixtures.

Let's merge the PR, and I'll work on creating a proper set of helpers and fixtures for testing.


room = DocumentRoom("test-room", "text", "file", loader, FakeEventLogger(), None, None)

await room.initialize()
assert room._document.source == content


@pytest.mark.asyncio
async def test_should_initialize_document_room_from_store():
"""
We need to create test files with Y updates to simulate
a store.
"""
pass


@pytest.mark.asyncio
async def test_defined_save_delay_should_save_content_after_document_change():
id = "test-id"
content = "test"
paths = {id: "test.txt"}
cm = FakeContentsManager({"content": content})
loader = FileLoader(
id,
FakeFileIDManager(paths),
cm,
poll_interval=0.1,
)

room = DocumentRoom("test-room", "text", "file", loader, FakeEventLogger(), None, None, 0.01)

await room.initialize()
room._document.source = "Test 2"

# Wait for a bit more than the poll_interval
await asyncio.sleep(0.15)
hbcarlos marked this conversation as resolved.
Show resolved Hide resolved

assert "save" in cm.actions


@pytest.mark.asyncio
async def test_undefined_save_delay_should_not_save_content_after_document_change():
id = "test-id"
content = "test"
paths = {id: "test.txt"}
cm = FakeContentsManager({"content": content})
loader = FileLoader(
id,
FakeFileIDManager(paths),
cm,
poll_interval=0.1,
)

room = DocumentRoom("test-room", "text", "file", loader, FakeEventLogger(), None, None, None)

await room.initialize()
room._document.source = "Test 2"

# Wait for a bit more than the poll_interval
await asyncio.sleep(0.15)
hbcarlos marked this conversation as resolved.
Show resolved Hide resolved

assert "save" not in cm.actions


@pytest.mark.asyncio
async def test_should_reload_content_from_disk():
id = "test-id"
content = "test"
paths = {id: "test.txt"}
last_modified = datetime.now()
cm = FakeContentsManager({"last_modified": last_modified, "content": "whatever"})
loader = FileLoader(
id,
FakeFileIDManager(paths),
cm,
poll_interval=0.1,
)

room = DocumentRoom("test-room", "text", "file", loader, FakeEventLogger(), None, None, None)

await room.initialize()

# Make sure the time increases
cm.model["last_modified"] = datetime.fromtimestamp(last_modified.timestamp() + 1)
cm.model["content"] = content

await loader.notify()

msg_id = next(iter(room._messages)).encode("utf8")
await room.handle_msg(bytes([RoomMessages.RELOAD]) + write_var_uint(len(msg_id)) + msg_id)

assert room._document.source == content


@pytest.mark.asyncio
async def test_should_not_reload_content_from_disk():
id = "test-id"
content = "test"
paths = {id: "test.txt"}
last_modified = datetime.now()
cm = FakeContentsManager({"last_modified": datetime.now(), "content": content})
loader = FileLoader(
id,
FakeFileIDManager(paths),
cm,
poll_interval=0.1,
)

room = DocumentRoom("test-room", "text", "file", loader, FakeEventLogger(), None, None, None)

await room.initialize()

# Make sure the time increases
cm.model["last_modified"] = datetime.fromtimestamp(last_modified.timestamp() + 1)
cm.model["content"] = "whatever"

await loader.notify()

msg_id = list(room._messages.keys())[0].encode("utf8")
await room.handle_msg(bytes([RoomMessages.OVERWRITE]) + write_var_uint(len(msg_id)) + msg_id)

assert room._document.source == content
54 changes: 54 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

from __future__ import annotations

from datetime import datetime
from typing import Any

from jupyter_server import _tz as tz


class FakeFileIDManager:
def __init__(self, mapping: dict):
self.mapping = mapping

def get_path(self, id: str) -> str:
return self.mapping[id]


class FakeContentsManager:
def __init__(self, model: dict):
self.model = {
"name": "",
"path": "",
"last_modified": datetime(1970, 1, 1, 0, 0, tzinfo=tz.UTC),
"created": datetime(1970, 1, 1, 0, 0, tzinfo=tz.UTC),
"content": None,
"format": None,
"mimetype": None,
"size": 0,
"writable": False,
}
self.model.update(model)

self.actions: list[str] = []

def get(
self, path: str, content: bool = True, format: str | None = None, type: str | None = None
) -> dict:
self.actions.append("get")
return self.model

def save(self, model: dict[str, Any], path: str) -> dict:
self.actions.append("save")
return self.model

def save_content(self, model: dict[str, Any], path: str) -> dict:
self.actions.append("save_content")
return self.model


class FakeEventLogger:
def emit(self, schema_id: str, data: dict) -> None:
print(data)