1+ import asyncio
12import os
3+ from contextlib import suppress
24from unittest import skipIf
35
46import pytest
57from channels .db import database_sync_to_async
8+ from channels .layers import get_channel_layer
69from channels .routing import ProtocolTypeRouter
710from django .conf import settings
811from django .contrib .auth import get_user_model
912from django .contrib .auth .models import Permission
1013from django .utils .module_loading import import_string
11- from django_loci .tests . base . test_channels import BaseTestChannels
14+ from django_loci .tests import TestChannelsMixin
1215from swapper import load_model
1316
17+ from openwisp_controller .geo .channels .consumers import (
18+ CommonLocationBroadcast ,
19+ LocationBroadcast ,
20+ )
21+
1422from .utils import TestGeoMixin
1523
1624Device = load_model ("config" , "Device" )
2129
2230
2331@skipIf (os .environ .get ("SAMPLE_APP" , False ), "Running tests on SAMPLE_APP" )
24- class TestChannels (TestGeoMixin , BaseTestChannels ):
32+ class TestChannels (TestGeoMixin , TestChannelsMixin ):
33+ location_consumer = LocationBroadcast
34+ common_location_consumer = CommonLocationBroadcast
2535 application = import_string (getattr (settings , "ASGI_APPLICATION" ))
2636 object_model = Device
2737 location_model = Location
@@ -37,35 +47,32 @@ async def test_consumer_staff_but_no_change_permission(self):
3747 location = await database_sync_to_async (self ._create_location )(is_mobile = True )
3848 await database_sync_to_async (self ._create_object_location )(location = location )
3949 pk = location .pk
40- request_vars = await self ._get_specific_location_request_dict (user = user , pk = pk )
50+ request_vars = await self ._get_specific_location_request_dict (pk = pk , user = user )
4151 communicator = self ._get_specific_location_communicator (request_vars , user )
4252 connected , _ = await communicator .connect ()
4353 assert not connected
4454 await communicator .disconnect ()
4555 # add permission to change location and repeat
46- perm = await database_sync_to_async (
47- (
48- await database_sync_to_async (Permission .objects .filter )(
49- name = "Can change location"
50- )
51- ).first
52- )()
56+ perm = await Permission .objects .filter (
57+ codename = f"change_{ self .location_model ._meta .model_name } " ,
58+ content_type__app_label = self .location_model ._meta .app_label ,
59+ ).afirst ()
5360 await database_sync_to_async (user .user_permissions .add )(perm )
5461 user = await database_sync_to_async (User .objects .get )(pk = user .pk )
55- request_vars = await self ._get_specific_location_request_dict (user = user , pk = pk )
62+ request_vars = await self ._get_specific_location_request_dict (pk = pk , user = user )
5663 communicator = self ._get_specific_location_communicator (request_vars , user )
5764 connected , _ = await communicator .connect ()
5865 assert not connected
5966 await communicator .disconnect ()
6067 # add user to organization
6168 await database_sync_to_async (OrganizationUser .objects .create )(
62- organization = location .organization , user = user , is_admin = True
69+ organization = location .organization ,
70+ user = user ,
71+ is_admin = True ,
6372 )
6473 await database_sync_to_async (location .organization .save )()
6574 user = await database_sync_to_async (User .objects .get )(pk = user .pk )
66- request_vars = await self ._ge_get_specific_location_request_dictt_request_dict (
67- user = user , pk = pk
68- )
75+ request_vars = await self ._get_specific_location_request_dict (pk = pk , user = user )
6976 communicator = self ._get_specific_location_communicator (request_vars , user )
7077 connected , _ = await communicator .connect ()
7178 assert connected
@@ -80,37 +87,82 @@ async def test_common_location_consumer_staff_but_no_change_permission(self):
8087 location = await database_sync_to_async (self ._create_location )(is_mobile = True )
8188 await database_sync_to_async (self ._create_object_location )(location = location )
8289 pk = location .pk
83- request_vars = await self ._get_common_location_request_dict (user = user , pk = pk )
90+ request_vars = await self ._get_common_location_request_dict (pk = pk , user = user )
8491 communicator = self ._get_common_location_communicator (request_vars , user )
8592 connected , _ = await communicator .connect ()
8693 assert not connected
8794 await communicator .disconnect ()
88- # add permission to change location and repeat
89- perm = await database_sync_to_async (
90- (
91- await database_sync_to_async (Permission .objects .filter )(
92- name = "Can change location"
93- )
94- ).first
95- )()
95+ # After granting change permission, the user can connect to the common
96+ # location endpoint, but must receive updates only for locations
97+ # belonging to their organization.
98+ perm = await Permission .objects .filter (
99+ codename = f"change_{ self .location_model ._meta .model_name } " ,
100+ content_type__app_label = self .location_model ._meta .app_label ,
101+ ).afirst ()
96102 await database_sync_to_async (user .user_permissions .add )(perm )
97103 user = await database_sync_to_async (User .objects .get )(pk = user .pk )
98- request_vars = await self ._get_common_location_request_dict (user = user , pk = pk )
104+ request_vars = await self ._get_common_location_request_dict (pk = pk , user = user )
99105 communicator = self ._get_common_location_communicator (request_vars , user )
100106 connected , _ = await communicator .connect ()
101- assert not connected
107+ assert connected
102108 await communicator .disconnect ()
103- # add user to organization
109+
110+ @pytest .mark .asyncio
111+ @pytest .mark .django_db (transaction = True )
112+ async def test_common_location_org_isolation (self ):
113+ org1 = await database_sync_to_async (self ._create_organization )(name = "test1" )
114+ org2 = await database_sync_to_async (self ._create_organization )(name = "test2" )
115+ location1 = await database_sync_to_async (self ._create_location )(
116+ is_mobile = True , organization = org1
117+ )
118+ location2 = await database_sync_to_async (self ._create_location )(
119+ is_mobile = True , organization = org2
120+ )
121+ user1 = await database_sync_to_async (User .objects .create_user )(
122+ username = "user1" ,
password = "password" ,
email = "[email protected] " ,
is_staff = True 123+ )
124+ user2 = await database_sync_to_async (User .objects .create_user )(
125+ username = "user2" ,
password = "password" ,
email = "[email protected] " ,
is_staff = True 126+ )
127+ perm = await Permission .objects .filter (
128+ codename = f"change_{ self .location_model ._meta .model_name } " ,
129+ content_type__app_label = self .location_model ._meta .app_label ,
130+ ).afirst ()
131+ await database_sync_to_async (user1 .user_permissions .add )(perm )
132+ await database_sync_to_async (user2 .user_permissions .add )(perm )
104133 await database_sync_to_async (OrganizationUser .objects .create )(
105- organization = location . organization , user = user , is_admin = True
134+ organization = org1 , user = user1 , is_admin = True
106135 )
107- await database_sync_to_async (location .organization .save )()
108- user = await database_sync_to_async (User .objects .get )(pk = user .pk )
109- request_vars = await self ._get_common_location_request_dict (user = user , pk = pk )
110- communicator = self ._get_common_location_communicator (request_vars , user )
111- connected , _ = await communicator .connect ()
136+ await database_sync_to_async (OrganizationUser .objects .create )(
137+ organization = org2 , user = user2 , is_admin = True
138+ )
139+ user1 = await database_sync_to_async (User .objects .get )(pk = user1 .pk )
140+ user2 = await database_sync_to_async (User .objects .get )(pk = user2 .pk )
141+ channel_layer = get_channel_layer ()
142+ communicator1 = self ._get_common_location_communicator (
143+ await self ._get_common_location_request_dict (pk = location1 .pk , user = user1 ),
144+ user1 ,
145+ )
146+ communicator2 = self ._get_common_location_communicator (
147+ await self ._get_common_location_request_dict (pk = location2 .pk , user = user2 ),
148+ user2 ,
149+ )
150+ connected , _ = await communicator1 .connect ()
112151 assert connected
113- await communicator .disconnect ()
152+ connected , _ = await communicator2 .connect ()
153+ assert connected
154+ await channel_layer .group_send (
155+ f"loci.mobile-location.organization.{ org1 .pk } " ,
156+ {"type" : "send.message" , "message" : {"id" : str (location1 .pk )}},
157+ )
158+ response = await communicator1 .receive_json_from (timeout = 2 )
159+ assert response ["id" ] == str (location1 .pk )
160+ with pytest .raises (asyncio .TimeoutError ):
161+ await communicator2 .receive_json_from (timeout = 2 )
162+ # The task is been cancelled if not completed in the given timeout
163+ await communicator1 .disconnect ()
164+ with suppress (asyncio .CancelledError ):
165+ await communicator2 .disconnect ()
114166
115167 def test_asgi_application_router (self ):
116168 assert isinstance (self .application , ProtocolTypeRouter )
0 commit comments