import random from datetime import timedelta from unittest import mock from asgiref.sync import async_to_sync from channels.auth import AuthMiddlewareStack from channels.consumer import get_channel_layer from channels.testing import WebsocketCommunicator from django.contrib.auth.models import AnonymousUser, Permission, User from django.test import Client, TestCase from django.utils import timezone from . import OpenKfet from .consumers import OpenKfetConsumer def ws_communicator(cls, path: str, headers=[]): return WebsocketCommunicator(AuthMiddlewareStack(cls.as_asgi()), path, headers) class OpenKfetTest(TestCase): """OpenKfet object unit-tests suite.""" def setUp(self): self.kfet_open = OpenKfet( cache_prefix="test_kfetopen_%s" % random.randrange(2**20) ) self.addCleanup(self.kfet_open.clear_cache) def test_defaults(self): """Default values.""" self.assertFalse(self.kfet_open.raw_open) self.assertIsNone(self.kfet_open.last_update) self.assertFalse(self.kfet_open.force_close) self.assertFalse(self.kfet_open.is_open) def test_raw_open(self): """Get and set raw_open; last_update is renewed.""" for raw_open in [True, False]: prev_update = self.kfet_open.last_update self.kfet_open.raw_open = raw_open self.assertEqual(raw_open, self.kfet_open.raw_open) self.assertNotEqual(prev_update, self.kfet_open.last_update) def test_force_close(self): """Get and set force_close.""" for force_close in [True, False]: self.kfet_open.force_close = force_close self.assertEqual(force_close, self.kfet_open.force_close) def test_is_open(self): """If force_close is disabled, is_open is raw_open.""" self.kfet_open.force_close = False for raw_open in [True, False]: self.kfet_open.raw_open = raw_open self.assertEqual(raw_open, self.kfet_open.is_open) def test_is_open_force_close(self): """If force_close is enabled, is_open is False.""" self.kfet_open.force_close = True for raw_open in [True, False]: self.kfet_open.raw_open = raw_open self.assertFalse(self.kfet_open.is_open) def test_status(self): # (raw_open, force_close, expected status, expected admin) cases = [ (False, False, OpenKfet.CLOSED, OpenKfet.CLOSED), (False, True, OpenKfet.CLOSED, OpenKfet.CLOSED), (True, False, OpenKfet.OPENED, OpenKfet.OPENED), (True, True, OpenKfet.CLOSED, OpenKfet.FAKE_CLOSED), ] for raw_open, force_close, exp_stat, exp_adm_stat in cases: self.kfet_open.raw_open = raw_open self.kfet_open.force_close = force_close self.assertEqual(exp_stat, self.kfet_open.status()) self.assertEqual(exp_adm_stat, self.kfet_open.admin_status()) def test_status_unknown(self): self.kfet_open.raw_open = True self.kfet_open._last_update = timezone.now() - timedelta(days=30) self.assertEqual(OpenKfet.UNKNOWN, self.kfet_open.status()) def test_export_user(self): """Export is limited for an anonymous user.""" export = self.kfet_open.export(AnonymousUser()) self.assertSetEqual(set(["status", "type"]), set(export)) def test_export_team(self): """Export all values for a team member.""" user = User.objects.create_user("team", "", "team") is_team = Permission.objects.get( codename="is_team", content_type__app_label="kfet" ) user.user_permissions.add(is_team) export = self.kfet_open.export(user) self.assertSetEqual( set(["status", "admin_status", "force_close", "type"]), set(export) ) async def test_send_ws(self): channel_layer = get_channel_layer() base_channel = await channel_layer.new_channel() team_channel = await channel_layer.new_channel() await channel_layer.group_add("kfet.open.base", base_channel) await channel_layer.group_add("kfet.open.team", team_channel) await self.kfet_open.send_ws() base = await channel_layer.receive(base_channel) self.assertSetEqual(set(["status", "type"]), set(base)) team = await channel_layer.receive(team_channel) self.assertSetEqual( set(["status", "admin_status", "force_close", "type"]), set(team) ) class OpenKfetViewsTest(TestCase): """OpenKfet views unit-tests suite.""" def setUp(self): # Need this (and here) because of '.login' in setUp patcher_messages = mock.patch("gestioncof.signals.messages") patcher_messages.start() self.addCleanup(patcher_messages.stop) # get some permissions perms = { "kfet.is_team": Permission.objects.get( codename="is_team", content_type__app_label="kfet" ), "kfet.can_force_close": Permission.objects.get( codename="can_force_close", content_type__app_label="kfet" ), } # authenticated user and its client self.u = User.objects.create_user("user", "", "user") self.c = Client() self.c.login(username="user", password="user") # team user and its clients self.t = User.objects.create_user("team", "", "team") self.t.user_permissions.add(perms["kfet.is_team"]) self.c_t = Client() self.c_t.login(username="team", password="team") # admin user and its client self.a = User.objects.create_user("admin", "", "admin") self.a.user_permissions.add( perms["kfet.is_team"], perms["kfet.can_force_close"] ) self.c_a = Client() self.c_a.login(username="admin", password="admin") self.kfet_open = OpenKfet( cache_prefix="test_kfetopen_%s" % random.randrange(2**20) ) self.addCleanup(self.kfet_open.clear_cache) views_patcher = mock.patch("kfet.open.views.kfet_open", self.kfet_open) views_patcher.start() self.addCleanup(views_patcher.stop) def test_door(self): """Edit raw_status.""" for sent, expected in [(1, True), (0, False)]: resp = Client().post( "/k-fet/open/raw_open", {"raw_open": sent, "token": "plop"} ) self.assertEqual(200, resp.status_code) self.assertEqual(expected, self.kfet_open.raw_open) def test_force_close(self): """Edit force_close.""" for sent, expected in [(1, True), (0, False)]: resp = self.c_a.post("/k-fet/open/force_close", {"force_close": sent}) self.assertEqual(200, resp.status_code) self.assertEqual(expected, self.kfet_open.force_close) def test_force_close_forbidden(self): """Can't edit force_close without kfet.can_force_close permission.""" clients = [Client(), self.c, self.c_t] for client in clients: resp = client.post("/k-fet/open/force_close", {"force_close": 0}) self.assertEqual(403, resp.status_code) class OpenKfetConsumerTest(TestCase): """OpenKfet consumer unit-tests suite.""" @classmethod def setUpTestData(cls): t = User.objects.create_user("team", "", "team") is_team = Permission.objects.get( codename="is_team", content_type__app_label="kfet" ) t.user_permissions.add(is_team) cls.team_user = t async def test_standard_user(self): """Lambda user is added to kfet.open.base group.""" # setup anonymous client c = ws_communicator(OpenKfetConsumer, "/ws/k-fet/open") connected, _ = await c.connect() self.assertTrue(connected) # initialization data is replied on connection message = await c.receive_json_from() self.assertIsNotNone(message) # client belongs to the 'kfet.open' group... channel_layer = get_channel_layer() await channel_layer.group_send( "kfet.open.base", {"test": "plop", "type": "open.status"} ) message = await c.receive_json_from() self.assertEqual(message, {"test": "plop"}) # ...but not to the 'kfet.open.admin' one await channel_layer.group_send( "kfet.open.team", {"test": "plop", "type": "open.status"} ) self.assertTrue(await c.receive_nothing()) async def test_team_user(self): """Team user is added to kfet.open.team group.""" # On simule l'appartenance de l'user à la team kfet car l'utilisation de # tests async avec postgres fait tout planter si on modifie la db dans un # des sous tests. with mock.patch("gestioncof.signals.messages"), mock.patch( "kfet.open.consumers.kfet_is_team", return_value=True ): c = ws_communicator(OpenKfetConsumer, "/ws/k-fet/open") connected, _ = await c.connect() channel_layer = get_channel_layer() self.assertTrue(connected) # initialization data is replied on connection message = await c.receive_json_from() self.assertIsNotNone(message) # client belongs to the 'kfet.open.team' group... await channel_layer.group_send( "kfet.open.team", {"test": "plop", "type": "open.status"} ) message = await c.receive_json_from() self.assertEqual(message, {"test": "plop"}) # ...but not to the 'kfet.open' one await channel_layer.group_send( "kfet.open.base", {"test": "plop", "type": "open.status"} ) self.assertTrue(await c.receive_nothing()) class OpenKfetScenarioTest(TestCase): """OpenKfet functionnal tests suite.""" @classmethod def setUpTestData(cls): # root user cls.r = User.objects.create_superuser("team", "", "team") # anonymous client (for views) cls.c = Client() # root client cls.r_c = Client() with mock.patch("gestioncof.signals.messages"): cls.r_c.login(username="team", password="team") def setUp(self): # Create channels to listen to messages channel_layer = get_channel_layer() self.channel = async_to_sync(channel_layer.new_channel)() self.team_channel = async_to_sync(channel_layer.new_channel)() async_to_sync(channel_layer.group_add)("kfet.open.base", self.channel) async_to_sync(channel_layer.group_add)("kfet.open.team", self.team_channel) self.receive_msg = lambda c: async_to_sync(channel_layer.receive)(c) self.kfet_open = OpenKfet( cache_prefix="test_kfetopen_%s" % random.randrange(2**20) ) self.addCleanup(self.kfet_open.clear_cache) async def ws_connect(self, ws_communicator): c, _ = await ws_communicator.connect() self.assertTrue(c) return await ws_communicator.receive_json_from() def test_scenario_1(self): """Clients connect, door opens, enable force close.""" # door sent "I'm open!" self.c.post("/k-fet/open/raw_open", {"raw_open": True, "token": "plop"}) # anonymous user agree msg = self.receive_msg(self.channel) self.assertEqual(OpenKfet.OPENED, msg["status"]) # root user too msg = self.receive_msg(self.team_channel) self.assertEqual(OpenKfet.OPENED, msg["status"]) self.assertEqual(OpenKfet.OPENED, msg["admin_status"]) # admin says "no it's closed" self.r_c.post("/k-fet/open/force_close", {"force_close": True}) # so anonymous user see it's closed msg = self.receive_msg(self.channel) self.assertEqual(OpenKfet.CLOSED, msg["status"]) # root user too msg = self.receive_msg(self.team_channel) self.assertEqual(OpenKfet.CLOSED, msg["status"]) # but root knows things self.assertEqual(OpenKfet.FAKE_CLOSED, msg["admin_status"]) self.assertTrue(msg["force_close"]) def test_scenario_2(self): """Starting falsely closed, clients connect, disable force close.""" self.kfet_open.raw_open = True self.kfet_open.force_close = True async_to_sync(OpenKfet().send_ws)() msg = self.receive_msg(self.channel) self.assertEqual(OpenKfet.CLOSED, msg["status"]) msg = self.receive_msg(self.team_channel) self.assertEqual(OpenKfet.CLOSED, msg["status"]) self.assertEqual(OpenKfet.FAKE_CLOSED, msg["admin_status"]) self.assertTrue(msg["force_close"]) self.r_c.post("/k-fet/open/force_close", {"force_close": False}) msg = self.receive_msg(self.channel) self.assertEqual(OpenKfet.OPENED, msg["status"]) msg = self.receive_msg(self.team_channel) self.assertEqual(OpenKfet.OPENED, msg["status"]) self.assertEqual(OpenKfet.OPENED, msg["admin_status"]) self.assertFalse(msg["force_close"]) async def test_scenario_3(self): """Clients connect.""" # anonymous client (for websockets) self.c_ws = ws_communicator(OpenKfetConsumer, "/ws/k-fet/open") # test for anonymous user msg = await self.ws_connect(self.c_ws) self.assertSetEqual(set(["status"]), set(msg)) # test for root user with mock.patch( "kfet.open.consumers.kfet_is_team", return_value=True ), mock.patch("kfet.open.open.kfet_is_team", return_value=True): self.r_c_ws = ws_communicator(OpenKfetConsumer, "/ws/k-fet/open") msg = await self.ws_connect(self.r_c_ws) self.assertSetEqual( set(["status", "admin_status", "force_close"]), set(msg) )