Introduce tenant-scoped Space/SpaceMembership with list and detail views. Document markdown-in-Forgejo as content SoR (ADR-0002). Run migrations on container start so app.coulomb.social picks up the new tables.
111 lines
3.6 KiB
Python
111 lines
3.6 KiB
Python
import pytest
|
|
from django.urls import reverse
|
|
|
|
from coulomb_social.apps.members.models import Member, User
|
|
from coulomb_social.apps.spaces.models import Space, SpaceMembership
|
|
from coulomb_social.apps.spaces.services import spaces_for_member
|
|
|
|
|
|
def _login(client, settings, *, subject: str, tenant: str, name: str):
|
|
settings.DEBUG = True
|
|
settings.OIDC_ENABLED = False
|
|
settings.DEFAULT_TENANT_ID = tenant
|
|
return client.post(
|
|
reverse("identity:dev_login"),
|
|
{
|
|
"subject": subject,
|
|
"issuer": "https://local.dev/issuer",
|
|
"name": name,
|
|
"tenant": tenant,
|
|
},
|
|
)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_spaces_list_empty_for_tenant(client, settings):
|
|
_login(client, settings, subject="u1", tenant="tenant:a", name="User One")
|
|
r = client.get(reverse("core:app_home"))
|
|
assert r.status_code == 200
|
|
assert b"No spaces yet" in r.content
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_spaces_list_shows_same_tenant_only(client, settings):
|
|
_login(client, settings, subject="u1", tenant="tenant:a", name="User One")
|
|
member = Member.objects.get(subject="u1")
|
|
Space.objects.create(
|
|
tenant_id="tenant:a",
|
|
slug="alpha",
|
|
title="Alpha Space",
|
|
created_by=member,
|
|
)
|
|
Space.objects.create(
|
|
tenant_id="tenant:b",
|
|
slug="beta",
|
|
title="Beta Space",
|
|
)
|
|
|
|
r = client.get(reverse("core:app_home"))
|
|
assert r.status_code == 200
|
|
assert b"Alpha Space" in r.content
|
|
assert b"Beta Space" not in r.content
|
|
assert b"No spaces yet" not in r.content
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_space_detail_tenant_isolation(client, settings):
|
|
_login(client, settings, subject="u1", tenant="tenant:a", name="User One")
|
|
Space.objects.create(tenant_id="tenant:b", slug="secret", title="Secret")
|
|
r = client.get(reverse("spaces:detail", kwargs={"slug": "secret"}))
|
|
assert r.status_code == 404
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_space_detail_ok(client, settings):
|
|
_login(client, settings, subject="u1", tenant="tenant:a", name="User One")
|
|
member = Member.objects.get(subject="u1")
|
|
Space.objects.create(
|
|
tenant_id="tenant:a",
|
|
slug="lab",
|
|
title="Lab",
|
|
description="Research lab",
|
|
created_by=member,
|
|
forgejo_owner="coulomb",
|
|
forgejo_repo="space-lab",
|
|
)
|
|
r = client.get(reverse("spaces:detail", kwargs={"slug": "lab"}))
|
|
assert r.status_code == 200
|
|
assert b"Lab" in r.content
|
|
assert b"coulomb/space-lab" in r.content
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_membership_narrows_visibility(client, settings):
|
|
_login(client, settings, subject="u1", tenant="tenant:a", name="User One")
|
|
member = Member.objects.get(subject="u1")
|
|
other_user = User.objects.create_user(username="other")
|
|
other = Member.objects.create(
|
|
tenant_id="tenant:a",
|
|
user=other_user,
|
|
user_engine_user_id="usr_other",
|
|
issuer="https://local.dev/issuer",
|
|
subject="other-sub",
|
|
display_name="Other",
|
|
)
|
|
open_space = Space.objects.create(
|
|
tenant_id="tenant:a", slug="open", title="Open", created_by=other
|
|
)
|
|
closed = Space.objects.create(
|
|
tenant_id="tenant:a", slug="closed", title="Closed", created_by=other
|
|
)
|
|
# Any membership in tenant switches mode to membership-scoped
|
|
SpaceMembership.objects.create(
|
|
space=closed, member=other, role=SpaceMembership.Role.OWNER
|
|
)
|
|
SpaceMembership.objects.create(
|
|
space=open_space, member=member, role=SpaceMembership.Role.MEMBER
|
|
)
|
|
|
|
visible = list(spaces_for_member(member).values_list("slug", flat=True))
|
|
assert "open" in visible
|
|
assert "closed" not in visible
|