diff --git a/Dockerfile b/Dockerfile index c231f2a733..27af6b2a3a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,7 +30,7 @@ RUN apk update && apk add python py-pip python-dev py-setuptools gcc musl-dev jp ADD . /usr/src/evennia # install dependencies -RUN pip install -e /usr/src/evennia --index-url=http://pypi.python.org/simple/ --trusted-host pypi.python.org +RUN pip install -e /usr/src/evennia --trusted-host pypi.python.org # add the game source when rebuilding a new docker image from inside # a game dir diff --git a/evennia/accounts/tests.py b/evennia/accounts/tests.py new file mode 100644 index 0000000000..039a25601f --- /dev/null +++ b/evennia/accounts/tests.py @@ -0,0 +1,160 @@ +from mock import Mock +from random import randint +from unittest import TestCase + +from evennia.accounts.accounts import AccountSessionHandler +from evennia.accounts.accounts import DefaultAccount +from evennia.server.session import Session +from evennia.utils import create + +from django.conf import settings + + +class TestAccountSessionHandler(TestCase): + "Check AccountSessionHandler class" + + def setUp(self): + self.account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount) + self.handler = AccountSessionHandler(self.account) + + def test_get(self): + "Check get method" + self.assertEqual(self.handler.get(), []) + self.assertEqual(self.handler.get(100), []) + + import evennia.server.sessionhandler + + s1 = Session() + s1.logged_in = True + s1.uid = self.account.uid + evennia.server.sessionhandler.SESSIONS[s1.uid] = s1 + + s2 = Session() + s2.logged_in = True + s2.uid = self.account.uid + 1 + evennia.server.sessionhandler.SESSIONS[s2.uid] = s2 + + s3 = Session() + s3.logged_in = False + s3.uid = self.account.uid + 2 + evennia.server.sessionhandler.SESSIONS[s3.uid] = s3 + + self.assertEqual(self.handler.get(), [s1]) + self.assertEqual(self.handler.get(self.account.uid), [s1]) + self.assertEqual(self.handler.get(self.account.uid + 1), []) + + def test_all(self): + "Check all method" + self.assertEqual(self.handler.get(), self.handler.all()) + + def test_count(self): + "Check count method" + self.assertEqual(self.handler.count(), len(self.handler.get())) + +class TestDefaultAccount(TestCase): + "Check DefaultAccount class" + + def setUp(self): + self.s1 = Session() + self.s1.sessid = 0 + + def test_puppet_object_no_object(self): + "Check puppet_object method called with no object param" + + try: + DefaultAccount().puppet_object(self.s1, None) + self.fail("Expected error: 'Object not found'") + except RuntimeError as re: + self.assertEqual("Object not found", re.message) + + def test_puppet_object_no_session(self): + "Check puppet_object method called with no session param" + + try: + DefaultAccount().puppet_object(None, Mock()) + self.fail("Expected error: 'Session not found'") + except RuntimeError as re: + self.assertEqual("Session not found", re.message) + + def test_puppet_object_already_puppeting(self): + "Check puppet_object method called, already puppeting this" + + import evennia.server.sessionhandler + + account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount) + self.s1.uid = account.uid + evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 + + self.s1.logged_in = True + self.s1.data_out = Mock(return_value=None) + + obj = Mock() + self.s1.puppet = obj + account.puppet_object(self.s1, obj) + self.s1.data_out.assert_called_with(options=None, text="You are already puppeting this object.") + self.assertIsNone(obj.at_post_puppet.call_args) + + def test_puppet_object_no_permission(self): + "Check puppet_object method called, no permission" + + import evennia.server.sessionhandler + + account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount) + self.s1.uid = account.uid + evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 + + self.s1.puppet = None + self.s1.logged_in = True + self.s1.data_out = Mock(return_value=None) + + obj = Mock() + obj.access = Mock(return_value=False) + + account.puppet_object(self.s1, obj) + + self.assertTrue(self.s1.data_out.call_args[1]['text'].startswith("You don't have permission to puppet")) + self.assertIsNone(obj.at_post_puppet.call_args) + + def test_puppet_object_joining_other_session(self): + "Check puppet_object method called, joining other session" + + import evennia.server.sessionhandler + + account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount) + self.s1.uid = account.uid + evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 + + self.s1.puppet = None + self.s1.logged_in = True + self.s1.data_out = Mock(return_value=None) + + obj = Mock() + obj.access = Mock(return_value=True) + obj.account = account + + account.puppet_object(self.s1, obj) + # works because django.conf.settings.MULTISESSION_MODE is not in (1, 3) + self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("from another of your sessions.")) + self.assertTrue(obj.at_post_puppet.call_args[1] == {}) + + def test_puppet_object_already_puppeted(self): + "Check puppet_object method called, already puppeted" + + import evennia.server.sessionhandler + + account = create.create_account("TestAccount%s" % randint(0, 999999), email="test@test.com", password="testpassword", typeclass=DefaultAccount) + self.s1.uid = account.uid + evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 + + self.s1.puppet = None + self.s1.logged_in = True + self.s1.data_out = Mock(return_value=None) + + obj = Mock() + obj.access = Mock(return_value=True) + obj.account = Mock() + obj.at_post_puppet = Mock() + + account.puppet_object(self.s1, obj) + self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("is already puppeted by another Account.")) + self.assertIsNone(obj.at_post_puppet.call_args) diff --git a/evennia/commands/default/comms.py b/evennia/commands/default/comms.py index 53e047a2ac..37a4934d16 100644 --- a/evennia/commands/default/comms.py +++ b/evennia/commands/default/comms.py @@ -417,7 +417,7 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS): string = "You don't control this channel." self.msg(string) return - if account not in channel.db_subscriptions.all(): + if not channel.subscriptions.has(account): string = "Account %s is not connected to channel %s." % (account.key, channel.key) self.msg(string) return diff --git a/evennia/comms/admin.py b/evennia/comms/admin.py index 29a02507c1..52ed7993fd 100644 --- a/evennia/comms/admin.py +++ b/evennia/comms/admin.py @@ -58,7 +58,7 @@ class ChannelAdmin(admin.ModelAdmin): save_on_top = True list_select_related = True fieldsets = ( - (None, {'fields': (('db_key',), 'db_lock_storage', 'db_subscriptions')}), + (None, {'fields': (('db_key',), 'db_lock_storage', 'db_account_subscriptions', 'db_object_subscriptions')}), ) def subscriptions(self, obj): @@ -69,7 +69,7 @@ class ChannelAdmin(admin.ModelAdmin): obj (Channel): The channel to get subs from. """ - return ", ".join([str(sub) for sub in obj.db_subscriptions.all()]) + return ", ".join([str(sub) for sub in obj.subscriptions.all()]) def save_model(self, request, obj, form, change): """ diff --git a/evennia/contrib/tutorial_examples/bodyfunctions.py b/evennia/contrib/tutorial_examples/bodyfunctions.py index 406e8da643..2bf59427c2 100644 --- a/evennia/contrib/tutorial_examples/bodyfunctions.py +++ b/evennia/contrib/tutorial_examples/bodyfunctions.py @@ -31,10 +31,12 @@ class BodyFunctions(DefaultScript): This gets called every self.interval seconds. We make a random check here so as to only return 33% of the time. """ - if random.random() < 0.66: # no message this time return + self.send_random_message() + + def send_random_message(self): rand = random.random() # return a random message if rand < 0.1: diff --git a/evennia/contrib/tutorial_examples/tests.py b/evennia/contrib/tutorial_examples/tests.py new file mode 100644 index 0000000000..3ea3cfafce --- /dev/null +++ b/evennia/contrib/tutorial_examples/tests.py @@ -0,0 +1,69 @@ +from mock import Mock, patch + +from evennia.utils.test_resources import EvenniaTest + +from .bodyfunctions import BodyFunctions + +@patch("evennia.contrib.tutorial_examples.bodyfunctions.random") +class TestBodyFunctions(EvenniaTest): + script_typeclass = BodyFunctions + + def setUp(self): + super(TestBodyFunctions, self).setUp() + self.script.obj = self.char1 + + def tearDown(self): + super(TestBodyFunctions, self).tearDown() + # if we forget to stop the script, DirtyReactorAggregateError will be raised + self.script.stop() + + def test_at_repeat(self, mock_random): + """test that no message will be sent when below the 66% threshold""" + mock_random.random = Mock(return_value=0.5) + old_func = self.script.send_random_message + self.script.send_random_message = Mock() + self.script.at_repeat() + self.script.send_random_message.assert_not_called() + # test that random message will be sent + mock_random.random = Mock(return_value=0.7) + self.script.at_repeat() + self.script.send_random_message.assert_called() + self.script.send_random_message = old_func + + def test_send_random_message(self, mock_random): + """Test that correct message is sent for each random value""" + old_func = self.char1.msg + self.char1.msg = Mock() + # test each of the values + mock_random.random = Mock(return_value=0.05) + self.script.send_random_message() + self.char1.msg.assert_called_with("You tap your foot, looking around.") + mock_random.random = Mock(return_value=0.15) + self.script.send_random_message() + self.char1.msg.assert_called_with("You have an itch. Hard to reach too.") + mock_random.random = Mock(return_value=0.25) + self.script.send_random_message() + self.char1.msg.assert_called_with("You think you hear someone behind you. ... " + "but when you look there's noone there.") + mock_random.random = Mock(return_value=0.35) + self.script.send_random_message() + self.char1.msg.assert_called_with("You inspect your fingernails. Nothing to report.") + mock_random.random = Mock(return_value=0.45) + self.script.send_random_message() + self.char1.msg.assert_called_with("You cough discreetly into your hand.") + mock_random.random = Mock(return_value=0.55) + self.script.send_random_message() + self.char1.msg.assert_called_with("You scratch your head, looking around.") + mock_random.random = Mock(return_value=0.65) + self.script.send_random_message() + self.char1.msg.assert_called_with("You blink, forgetting what it was you were going to do.") + mock_random.random = Mock(return_value=0.75) + self.script.send_random_message() + self.char1.msg.assert_called_with("You feel lonely all of a sudden.") + mock_random.random = Mock(return_value=0.85) + self.script.send_random_message() + self.char1.msg.assert_called_with("You get a great idea. Of course you won't tell anyone.") + mock_random.random = Mock(return_value=0.95) + self.script.send_random_message() + self.char1.msg.assert_called_with("You suddenly realize how much you love Evennia!") + self.char1.msg = old_func diff --git a/evennia/server/profiling/tests.py b/evennia/server/profiling/tests.py new file mode 100644 index 0000000000..b3e9fba8d5 --- /dev/null +++ b/evennia/server/profiling/tests.py @@ -0,0 +1,93 @@ +from django.test import TestCase +from mock import Mock +from .dummyrunner_settings import (c_creates_button, c_creates_obj, c_digs, c_examines, c_help, c_idles, c_login, + c_login_nodig, c_logout, c_looks, c_moves, c_moves_n, c_moves_s, c_socialize) + + +class TestDummyrunnerSettings(TestCase): + def setUp(self): + self.client = Mock() + self.client.cid = 1 + self.client.counter = Mock(return_value=1) + self.client.gid = "20171025161153-1" + self.client.name = "Dummy-%s" % self.client.gid + self.client.password = "password-%s" % self.client.gid + self.client.start_room = "testing_room_start_%s" % self.client.gid + self.client.objs = [] + self.client.exits = [] + + def clear_client_lists(self): + self.client.objs = [] + self.client.exits = [] + + def test_c_login(self): + self.assertEqual(c_login(self.client), ('create %s %s' % (self.client.name, self.client.password), + 'connect %s %s' % (self.client.name, self.client.password), + '@dig %s' % self.client.start_room, + '@teleport %s' % self.client.start_room, + "@dig testing_room_1 = exit_1, exit_1")) + + def test_c_login_no_dig(self): + self.assertEqual(c_login_nodig(self.client), ('create %s %s' % (self.client.name, self.client.password), + 'connect %s %s' % (self.client.name, self.client.password))) + + def test_c_logout(self): + self.assertEqual(c_logout(self.client), "@quit") + + def perception_method_tests(self, func, verb, alone_suffix=""): + self.assertEqual(func(self.client), "%s%s" % (verb, alone_suffix)) + self.client.exits = ["exit1", "exit2"] + self.assertEqual(func(self.client), ["%s exit1" % verb, "%s exit2" % verb]) + self.client.objs = ["foo", "bar"] + self.assertEqual(func(self.client), ["%s foo" % verb, "%s bar" % verb]) + self.clear_client_lists() + + def test_c_looks(self): + self.perception_method_tests(c_looks, "look") + + def test_c_examines(self): + self.perception_method_tests(c_examines, "examine", " me") + + def test_idles(self): + self.assertEqual(c_idles(self.client), ('idle', 'idle')) + + def test_c_help(self): + self.assertEqual(c_help(self.client), ('help', 'help @teleport', 'help look', 'help @tunnel', 'help @dig')) + + def test_c_digs(self): + self.assertEqual(c_digs(self.client), ('@dig/tel testing_room_1 = exit_1, exit_1')) + self.assertEqual(self.client.exits, ['exit_1', 'exit_1']) + self.clear_client_lists() + + def test_c_creates_obj(self): + objname = "testing_obj_1" + self.assertEqual(c_creates_obj(self.client), ('@create %s' % objname, + '@desc %s = "this is a test object' % objname, + '@set %s/testattr = this is a test attribute value.' % objname, + '@set %s/testattr2 = this is a second test attribute.' % objname)) + self.assertEqual(self.client.objs, [objname]) + self.clear_client_lists() + + def test_c_creates_button(self): + objname = "testing_button_1" + typeclass_name = "contrib.tutorial_examples.red_button.RedButton" + self.assertEqual(c_creates_button(self.client), ('@create %s:%s' % (objname, typeclass_name), + '@desc %s = test red button!' % objname)) + self.assertEqual(self.client.objs, [objname]) + self.clear_client_lists() + + def test_c_socialize(self): + self.assertEqual(c_socialize(self.client), ('ooc Hello!', 'ooc Testing ...', 'ooc Testing ... times 2', + 'say Yo!', 'emote stands looking around.')) + + def test_c_moves(self): + self.assertEqual(c_moves(self.client), "look") + self.client.exits = ["south", "north"] + self.assertEqual(c_moves(self.client), ["south", "north"]) + self.clear_client_lists() + + def test_c_move_n(self): + self.assertEqual(c_moves_n(self.client), "north") + + def test_c_move_s(self): + self.assertEqual(c_moves_s(self.client), "south") diff --git a/evennia/server/tests.py b/evennia/server/tests.py index 7b39a2d7fd..e821d58583 100644 --- a/evennia/server/tests.py +++ b/evennia/server/tests.py @@ -15,11 +15,6 @@ Guidelines: used as test methods by the runner. Inside the test methods, special member methods assert*() are used to test the behaviour. """ - -import os -import sys -import glob - try: from django.utils.unittest import TestCase except ImportError: @@ -31,6 +26,8 @@ except ImportError: from django.test.runner import DiscoverRunner +from .deprecations import check_errors + class EvenniaTestSuiteRunner(DiscoverRunner): """ @@ -46,3 +43,37 @@ class EvenniaTestSuiteRunner(DiscoverRunner): import evennia evennia._init() return super(EvenniaTestSuiteRunner, self).build_suite(test_labels, extra_tests=extra_tests, **kwargs) + + +class MockSettings(object): + """ + Class for simulating django.conf.settings. Created with a single value, and then sets the required + WEBSERVER_ENABLED setting to True or False depending if we're testing WEBSERVER_PORTS. + """ + def __init__(self, setting, value=None): + setattr(self, setting, value) + if setting == "WEBSERVER_PORTS": + self.WEBSERVER_ENABLED = True + else: + self.WEBSERVER_ENABLED = False + + +class TestDeprecations(TestCase): + """ + Class for testing deprecations.check_errors. + """ + deprecated_settings = ("CMDSET_DEFAULT", "CMDSET_OOC", "BASE_COMM_TYPECLASS", "COMM_TYPECLASS_PATHS", + "CHARACTER_DEFAULT_HOME", "OBJECT_TYPECLASS_PATHS", "SCRIPT_TYPECLASS_PATHS", + "ACCOUNT_TYPECLASS_PATHS", "CHANNEL_TYPECLASS_PATHS", "SEARCH_MULTIMATCH_SEPARATOR", + "TIME_SEC_PER_MIN", "TIME_MIN_PER_HOUR", "TIME_HOUR_PER_DAY", "TIME_DAY_PER_WEEK", + "TIME_WEEK_PER_MONTH", "TIME_MONTH_PER_YEAR") + + def test_check_errors(self): + """ + All settings in deprecated_settings should raise a DeprecationWarning if they exist. WEBSERVER_PORTS + raises an error if the iterable value passed does not have a tuple as its first element. + """ + for setting in self.deprecated_settings: + self.assertRaises(DeprecationWarning, check_errors, MockSettings(setting)) + # test check for WEBSERVER_PORTS having correct value + self.assertRaises(DeprecationWarning, check_errors, MockSettings("WEBSERVER_PORTS", value=["not a tuple"])) diff --git a/evennia/utils/spawner.py b/evennia/utils/spawner.py index 6df11985f1..86b24e5e4f 100644 --- a/evennia/utils/spawner.py +++ b/evennia/utils/spawner.py @@ -192,12 +192,12 @@ def _batch_create_object(*objparams): # call all setup hooks on each object objparam = objparams[iobj] # setup - obj._createdict = {"permissions": objparam[1], + obj._createdict = {"permissions": make_iter(objparam[1]), "locks": objparam[2], - "aliases": objparam[3], + "aliases": make_iter(objparam[3]), "nattributes": objparam[4], "attributes": objparam[5], - "tags": objparam[6]} + "tags": make_iter(objparam[6])} # this triggers all hooks obj.save() # run eventual extra code diff --git a/evennia/web/utils/general_context.py b/evennia/web/utils/general_context.py index 2abd31afb9..27edd79c86 100644 --- a/evennia/web/utils/general_context.py +++ b/evennia/web/utils/general_context.py @@ -10,17 +10,25 @@ from django.conf import settings from evennia.utils.utils import get_evennia_version # Determine the site name and server version +def set_game_name_and_slogan(): + """ + Sets global variables GAME_NAME and GAME_SLOGAN which are used by + general_context. -try: - GAME_NAME = settings.SERVERNAME.strip() -except AttributeError: - GAME_NAME = "Evennia" -SERVER_VERSION = get_evennia_version() -try: - GAME_SLOGAN = settings.GAME_SLOGAN.strip() -except AttributeError: - GAME_SLOGAN = SERVER_VERSION - + Notes: + This function is used for unit testing the values of the globals. + """ + global GAME_NAME, GAME_SLOGAN, SERVER_VERSION + try: + GAME_NAME = settings.SERVERNAME.strip() + except AttributeError: + GAME_NAME = "Evennia" + SERVER_VERSION = get_evennia_version() + try: + GAME_SLOGAN = settings.GAME_SLOGAN.strip() + except AttributeError: + GAME_SLOGAN = SERVER_VERSION +set_game_name_and_slogan() # Setup lists of the most relevant apps so # the adminsite becomes more readable. @@ -32,13 +40,23 @@ CONNECTIONS = ['Irc'] WEBSITE = ['Flatpages', 'News', 'Sites'] + +def set_webclient_settings(): + """ + As with set_game_name_and_slogan above, this sets global variables pertaining + to webclient settings. + + Notes: + Used for unit testing. + """ + global WEBCLIENT_ENABLED, WEBSOCKET_CLIENT_ENABLED, WEBSOCKET_PORT, WEBSOCKET_URL + WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED + WEBSOCKET_CLIENT_ENABLED = settings.WEBSOCKET_CLIENT_ENABLED + WEBSOCKET_PORT = settings.WEBSOCKET_CLIENT_PORT + WEBSOCKET_URL = settings.WEBSOCKET_CLIENT_URL +set_webclient_settings() + # The main context processor function -WEBCLIENT_ENABLED = settings.WEBCLIENT_ENABLED -WEBSOCKET_CLIENT_ENABLED = settings.WEBSOCKET_CLIENT_ENABLED -WEBSOCKET_PORT = settings.WEBSOCKET_CLIENT_PORT -WEBSOCKET_URL = settings.WEBSOCKET_CLIENT_URL - - def general_context(request): """ Returns common Evennia-related context stuff, which diff --git a/evennia/web/utils/tests.py b/evennia/web/utils/tests.py new file mode 100644 index 0000000000..b2d42891ae --- /dev/null +++ b/evennia/web/utils/tests.py @@ -0,0 +1,59 @@ +from mock import Mock, patch + +from django.test import TestCase + +from . import general_context + + +class TestGeneralContext(TestCase): + maxDiff = None + + @patch('evennia.web.utils.general_context.GAME_NAME', "test_name") + @patch('evennia.web.utils.general_context.GAME_SLOGAN', "test_game_slogan") + @patch('evennia.web.utils.general_context.WEBSOCKET_CLIENT_ENABLED', "websocket_client_enabled_testvalue") + @patch('evennia.web.utils.general_context.WEBCLIENT_ENABLED', "webclient_enabled_testvalue") + @patch('evennia.web.utils.general_context.WEBSOCKET_PORT', "websocket_client_port_testvalue") + @patch('evennia.web.utils.general_context.WEBSOCKET_URL', "websocket_client_url_testvalue") + def test_general_context(self): + request = Mock() + self.assertEqual(general_context.general_context(request), { + 'game_name': "test_name", + 'game_slogan': "test_game_slogan", + 'evennia_userapps': ['Accounts'], + 'evennia_entityapps': ['Objects', 'Scripts', 'Comms', 'Help'], + 'evennia_setupapps': ['Permissions', 'Config'], + 'evennia_connectapps': ['Irc'], + 'evennia_websiteapps': ['Flatpages', 'News', 'Sites'], + "webclient_enabled": "webclient_enabled_testvalue", + "websocket_enabled": "websocket_client_enabled_testvalue", + "websocket_port": "websocket_client_port_testvalue", + "websocket_url": "websocket_client_url_testvalue" + }) + + # spec being an empty list will initially raise AttributeError in set_game_name_and_slogan to test defaults + @patch('evennia.web.utils.general_context.settings', spec=[]) + @patch('evennia.web.utils.general_context.get_evennia_version') + def test_set_game_name_and_slogan(self, mock_get_version, mock_settings): + mock_get_version.return_value = "version 1" + # test default/fallback values + general_context.set_game_name_and_slogan() + self.assertEqual(general_context.GAME_NAME, "Evennia") + self.assertEqual(general_context.GAME_SLOGAN, "version 1") + # test values when the settings are defined + mock_settings.SERVERNAME = "test_name" + mock_settings.GAME_SLOGAN = "test_game_slogan" + general_context.set_game_name_and_slogan() + self.assertEqual(general_context.GAME_NAME, "test_name") + self.assertEqual(general_context.GAME_SLOGAN, "test_game_slogan") + + @patch('evennia.web.utils.general_context.settings') + def test_set_webclient_settings(self, mock_settings): + mock_settings.WEBCLIENT_ENABLED = "webclient" + mock_settings.WEBSOCKET_CLIENT_URL = "websocket_url" + mock_settings.WEBSOCKET_CLIENT_ENABLED = "websocket_client" + mock_settings.WEBSOCKET_CLIENT_PORT = "websocket_port" + general_context.set_webclient_settings() + self.assertEqual(general_context.WEBCLIENT_ENABLED, "webclient") + self.assertEqual(general_context.WEBSOCKET_URL, "websocket_url") + self.assertEqual(general_context.WEBSOCKET_CLIENT_ENABLED, "websocket_client") + self.assertEqual(general_context.WEBSOCKET_PORT, "websocket_port")