coulomb-social/tests/test_spaces.py
tegwick be32432d39 Add Space metadata model and provisional Forgejo content ADR
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.
2026-08-12 01:30:05 +02:00

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