Skip to content
Open
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
12 changes: 12 additions & 0 deletions packages/django-cf/tests/in_worker/worker/src/_django_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# pyright: reportMissingImports=false

import functools

R2_LOCATION = "in-worker-media"


@functools.cache
def django_wsgi_app():
from django.core.wsgi import get_wsgi_application

return get_wsgi_application()
87 changes: 87 additions & 0 deletions packages/django-cf/tests/in_worker/worker/src/_do_orm_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# pyright: reportMissingImports=false

from django.db import models
from django.http import JsonResponse
from django.urls import path

DO_ALIAS = "do"
DO_TABLE = "_django_cf_do_orm_records"
CREATE_DO_TABLE_SQL = (
f"CREATE TABLE {DO_TABLE} ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"value TEXT NOT NULL, "
"weight INTEGER NOT NULL)"
)
DROP_DO_TABLE_SQL = f"DROP TABLE IF EXISTS {DO_TABLE}"


class DoOrmRecord(models.Model):
value = models.CharField(max_length=64)
weight = models.IntegerField()

class Meta:
app_label = "django_cf_in_worker"
db_table = DO_TABLE
managed = False


def _records():
return DoOrmRecord.objects.using(DO_ALIAS)


def do_orm_crud_view(request):
del request
created = _records().create(value="alpha", weight=3)
_records().create(value="bravo", weight=1)
_records().create(value="charlie", weight=2)

loaded = _records().get(pk=created.pk)
loaded.value = "alpha-updated"
loaded.save(using=DO_ALIAS, update_fields=["value"])

deleted, _ = _records().filter(value="bravo").delete()

return JsonResponse(
{
"created_id": created.pk,
"updated_value": _records().get(pk=created.pk).value,
"deleted": deleted,
"remaining": list(
_records().order_by("value").values_list("value", flat=True)
),
}
)


def do_orm_query_view(request):
del request
for value, weight in (("one", 1), ("two", 2), ("three", 3)):
_records().create(value=value, weight=weight)

ascending = list(_records().order_by("weight").values_list("value", flat=True))
descending = list(_records().order_by("-weight").values_list("value", flat=True))
excluded = list(
_records().exclude(weight=2).order_by("weight").values_list("value", flat=True)
)
matching = _records().filter(value="two").exists()
missing = _records().filter(value="four").exists()
deleted, _ = _records().all().delete()

return JsonResponse(
{
"count": len(ascending),
"matching": matching,
"missing": missing,
"ascending": ascending,
"descending": descending,
"excluded": excluded,
"deleted": deleted,
"remaining": _records().count(),
}
)


urlpatterns = [
path("do/orm/crud/", do_orm_crud_view),
path("do/orm/query/", do_orm_query_view),
]
85 changes: 85 additions & 0 deletions packages/django-cf/tests/in_worker/worker/src/_r2_document_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# pyright: reportMissingImports=false

from uuid import uuid4

from django.core.files.base import ContentFile
from django.db import connections, models
from django.http import JsonResponse

D1_ALIAS = "d1"
R2_TABLE = "_django_cf_r2_documents"
R2_UPLOAD_TO = "r2-documents"
R2_CONTENT = b"r2-document-content"
CREATE_R2_TABLE_SQL = (
f"CREATE TABLE {R2_TABLE} "
"(id INTEGER PRIMARY KEY AUTOINCREMENT, attachment TEXT NOT NULL)"
)
DROP_R2_TABLE_SQL = f"DROP TABLE IF EXISTS {R2_TABLE}"


class R2Document(models.Model):
attachment = models.FileField(upload_to=R2_UPLOAD_TO, max_length=200)

class Meta:
app_label = "django_cf_in_worker"
db_table = R2_TABLE
managed = False


def drop_r2_table():
connections[D1_ALIAS].run_query(DROP_R2_TABLE_SQL)


def create_r2_table():
drop_r2_table()
connections[D1_ALIAS].run_query(CREATE_R2_TABLE_SQL)


def document_lifecycle_payload():
documents = R2Document.objects.using(D1_ALIAS)
document = R2Document()
document.attachment.save(
f"payload-{uuid4().hex}.bin", ContentFile(R2_CONTENT), save=False
)
name = document.attachment.name
storage = document.attachment.storage

try:
document.save(using=D1_ALIAS)
loaded = documents.get(pk=document.pk)
attachment = loaded.attachment
attachment.open("rb")
try:
content = attachment.read()
finally:
attachment.close()

payload = {
"id": loaded.pk,
"name": name,
"content": content.decode(),
"size": attachment.size,
"url": attachment.url,
"exists_before_delete": storage.exists(name),
}

attachment.delete(save=False)
payload["exists_after_delete"] = storage.exists(name)
loaded.delete(using=D1_ALIAS)
payload["rows_after_delete"] = documents.count()
return payload
finally:
if storage.exists(name):
storage.delete(name)
if document.pk is not None:
documents.filter(pk=document.pk).delete()


def sync_document_view(request):
del request
return JsonResponse(document_lifecycle_payload())


async def async_document_view(request):
del request
return JsonResponse(document_lifecycle_payload())
25 changes: 25 additions & 0 deletions packages/django-cf/tests/in_worker/worker/src/_wsgi_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# pyright: reportMissingImports=false

import json as _json

from _django_app import django_wsgi_app
from workers import Request

from django_cf import handle_wsgi

BASE_URL = "http://testserver"


async def fetch(path, *, env=None):
request = Request(f"{BASE_URL}{path}")
return await handle_wsgi(request, django_wsgi_app(), {} if env is None else env)


async def read_json(response):
text = await response.text()
return _json.loads(text) if text else None


async def get_json(path, **kwargs):
response = await fetch(path, **kwargs)
return response, await read_json(response)
59 changes: 59 additions & 0 deletions packages/django-cf/tests/in_worker/worker/src/test_asgi_do.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Durable Objects WSGI ORM tests executed inside workerd.

``stub.fetch`` reaches ``DjangoCFDurableObject.fetch``, which hands the request
to ``django_cf.handle_wsgi``, so these are WSGI requests served by
``TestDurableObject`` with the synchronous ORM running against the Durable
Object storage the object configures in its constructor.
"""

# pyright: reportMissingImports=false

import pytest
from _wsgi_client import read_json

DO_BASE_URL = "http://django-cf-do"


async def get_do_stub(env):
namespace = env.DO_STORAGE
object_id = namespace.idFromName("django-cf-backend-tests")
return namespace.get(object_id)


async def _run_do_request(env, path_name):
stub = await get_do_stub(env)
await stub.create_orm_table()
try:
response = await stub.fetch(f"{DO_BASE_URL}{path_name}")
return response, await read_json(response)
finally:
await stub.drop_orm_table()


@pytest.mark.asyncio
async def test_do_orm_wsgi_crud_lifecycle_through_django_url(env):
response, payload = await _run_do_request(env, "/do/orm/crud/")

assert response.status == 200
assert payload is not None
assert isinstance(payload["created_id"], int)
assert payload["created_id"] > 0
assert payload["updated_value"] == "alpha-updated"
assert payload["deleted"] == 1
assert payload["remaining"] == ["alpha-updated", "charlie"]


@pytest.mark.asyncio
async def test_do_orm_wsgi_filter_order_and_delete_through_django_url(env):
response, payload = await _run_do_request(env, "/do/orm/query/")

assert response.status == 200
assert payload is not None
assert payload["count"] == 3
assert payload["matching"] is True
assert payload["missing"] is False
assert payload["ascending"] == ["one", "two", "three"]
assert payload["descending"] == ["three", "two", "one"]
assert payload["excluded"] == ["one", "three"]
assert payload["deleted"] == 3
assert payload["remaining"] == 0
66 changes: 31 additions & 35 deletions packages/django-cf/tests/in_worker/worker/src/test_asgi_r2.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,48 @@
"""ASGI R2 tests executed inside workerd."""
"""ASGI R2 tests executed inside workerd.

# pyright: reportMissingImports=false
The view goes through the configured ``STORAGES["default"]`` backend and a model
``FileField`` instead of instantiating ``R2Storage`` directly. ``test_wsgi_r2``
drives the same lifecycle through the WSGI adapter.
"""

from uuid import uuid4
# pyright: reportMissingImports=false

import pytest
from _asgi_client import fetch
from django.core.files.base import ContentFile
from _asgi_client import get_json
from _django_app import R2_LOCATION
from _r2_document_app import (
R2_CONTENT,
R2_UPLOAD_TO,
async_document_view,
create_r2_table,
drop_r2_table,
)
from django.core.handlers.asgi import ASGIHandler
from django.http import HttpResponse
from django.test import override_settings
from django.urls import path

from django_cf.storage import R2Storage
urlpatterns = [path("asgi/r2/document/", async_document_view)]


async def r2_view(request):
del request
storage = R2Storage(binding="BUCKET", location=f"asgi-r2-{uuid4().hex}")
content = b"asgi-r2-content"
saved_name = None

async def _run_r2_request(path_name):
create_r2_table()
try:
saved_name = storage.save("payload.bin", ContentFile(content))
r2_file = storage.open(saved_name, "rb")
try:
loaded = r2_file.read()
finally:
r2_file.close()
with override_settings(ROOT_URLCONF=__name__):
return await get_json(ASGIHandler(), path_name)
finally:
if saved_name is not None:
storage.delete(saved_name)

return HttpResponse(loaded, content_type="application/octet-stream")


urlpatterns = [path("asgi/r2/", r2_view)]


async def _fetch_r2_response(path_name):
with override_settings(ROOT_URLCONF=__name__):
app = ASGIHandler()
return await fetch(app, path_name)
drop_r2_table()


@pytest.mark.asyncio
async def test_asgi_r2_save_and_read():
response = await _fetch_r2_response("/asgi/r2/")
async def test_asgi_r2_filefield_save_read_and_delete():
response, payload = await _run_r2_request("/asgi/r2/document/")

assert response.status == 200
assert await response.text() == "asgi-r2-content"
assert payload is not None
assert payload["name"].startswith(f"{R2_UPLOAD_TO}/payload-")
assert payload["content"] == R2_CONTENT.decode()
assert payload["size"] == len(R2_CONTENT)
assert payload["url"] == f"/media/{R2_LOCATION}/{payload['name']}"
assert payload["exists_before_delete"] is True
assert payload["exists_after_delete"] is False
assert payload["rows_after_delete"] == 0
Loading
Loading