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