Skip to content

Commit d3c9562

Browse files
committed
✅(backend) add tests for document import feature
Added comprehensive tests covering DocSpec converter service, converter orchestration, and document creation with file uploads. Tests validate DOCX and Markdown conversion workflows, error handling, service availability, and edge cases including empty files and Unicode filenames. Signed-off-by: Stephan Meijer <me@stephanmeijer.com>
1 parent eeeaa0c commit d3c9562

File tree

9 files changed

+610
-36
lines changed

9 files changed

+610
-36
lines changed

src/backend/core/api/serializers.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
from django.utils.text import slugify
1212
from django.utils.translation import gettext_lazy as _
1313

14-
from core.services import mime_types
1514
import magic
1615
from rest_framework import serializers
1716

1817
from core import choices, enums, models, utils, validators
18+
from core.services import mime_types
1919
from core.services.ai_services import AI_ACTIONS
2020
from core.services.converter_services import (
2121
ConversionError,
@@ -465,9 +465,7 @@ def create(self, validated_data):
465465

466466
try:
467467
document_content = Converter().convert(
468-
validated_data["content"],
469-
mime_types.MARKDOWN,
470-
mime_types.YJS
468+
validated_data["content"], mime_types.MARKDOWN, mime_types.YJS
471469
)
472470
except ConversionError as err:
473471
raise serializers.ValidationError(

src/backend/core/api/viewsets.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,19 @@
3737
from rest_framework.permissions import AllowAny
3838

3939
from core import authentication, choices, enums, models
40+
from core.services import mime_types
4041
from core.services.ai_services import AIService
4142
from core.services.collaboration_services import CollaborationService
4243
from core.services.converter_services import (
4344
ConversionError,
45+
Converter,
46+
)
47+
from core.services.converter_services import (
4448
ServiceUnavailableError as YProviderServiceUnavailableError,
49+
)
50+
from core.services.converter_services import (
4551
ValidationError as YProviderValidationError,
46-
Converter,
4752
)
48-
from core.services import mime_types
4953
from core.tasks.mail import send_ask_for_access_mail
5054
from core.utils import extract_attachments, filter_descendants
5155

@@ -515,7 +519,7 @@ def perform_create(self, serializer):
515519
converted_content = converter.convert(
516520
file_content,
517521
content_type=uploaded_file.content_type,
518-
accept=mime_types.YJS
522+
accept=mime_types.YJS,
519523
)
520524
serializer.validated_data["content"] = converted_content
521525
serializer.validated_data["title"] = uploaded_file.name

src/backend/core/services/converter_services.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
"""Y-Provider API services."""
22

3+
import typing
34
from base64 import b64encode
45

56
from django.conf import settings
67

78
import requests
8-
import typing
99

1010
from core.services import mime_types
1111

12+
1213
class ConversionError(Exception):
1314
"""Base exception for conversion-related errors."""
1415

@@ -22,28 +23,33 @@ class ServiceUnavailableError(ConversionError):
2223

2324

2425
class ConverterProtocol(typing.Protocol):
25-
def convert(self, text, content_type, accept): ...
26+
"""Protocol for converter classes."""
27+
28+
def convert(self, text, content_type, accept):
29+
"""Convert content from one format to another."""
2630

2731

2832
class Converter:
33+
"""Orchestrates conversion between different formats using specialized converters."""
34+
2935
docspec: ConverterProtocol
3036
ydoc: ConverterProtocol
3137

3238
def __init__(self):
3339
self.docspec = DocSpecConverter()
3440
self.ydoc = YdocConverter()
3541

36-
def convert(self, input, content_type, accept):
42+
def convert(self, data, content_type, accept):
3743
"""Convert input into other formats using external microservices."""
38-
44+
3945
if content_type == mime_types.DOCX and accept == mime_types.YJS:
4046
return self.convert(
41-
self.docspec.convert(input, mime_types.DOCX, mime_types.BLOCKNOTE),
47+
self.docspec.convert(data, mime_types.DOCX, mime_types.BLOCKNOTE),
4248
mime_types.BLOCKNOTE,
43-
mime_types.YJS
49+
mime_types.YJS,
4450
)
45-
46-
return self.ydoc.convert(input, content_type, accept)
51+
52+
return self.ydoc.convert(data, content_type, accept)
4753

4854

4955
class DocSpecConverter:
@@ -61,15 +67,17 @@ def _request(self, url, data, content_type):
6167
)
6268
response.raise_for_status()
6369
return response
64-
70+
6571
def convert(self, data, content_type, accept):
6672
"""Convert a Document to BlockNote."""
6773
if not data:
6874
raise ValidationError("Input data cannot be empty")
69-
75+
7076
if content_type != mime_types.DOCX or accept != mime_types.BLOCKNOTE:
71-
raise ValidationError(f"Conversion from {content_type} to {accept} is not supported.")
72-
77+
raise ValidationError(
78+
f"Conversion from {content_type} to {accept} is not supported."
79+
)
80+
7381
try:
7482
return self._request(settings.DOCSPEC_API_URL, data, content_type).content
7583
except requests.RequestException as err:
@@ -103,9 +111,7 @@ def _request(self, url, data, content_type, accept):
103111
response.raise_for_status()
104112
return response
105113

106-
def convert(
107-
self, text, content_type=mime_types.MARKDOWN, accept=mime_types.YJS
108-
):
114+
def convert(self, text, content_type=mime_types.MARKDOWN, accept=mime_types.YJS):
109115
"""Convert a Markdown text into our internal format using an external microservice."""
110116

111117
if not text:

src/backend/core/services/mime_types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""MIME type constants for document conversion."""
2+
13
BLOCKNOTE = "application/vnd.blocknote+json"
24
YJS = "application/vnd.yjs.doc"
35
MARKDOWN = "text/markdown"

0 commit comments

Comments
 (0)