wip?
This commit is contained in:
@@ -1,4 +1,21 @@
|
||||
---
|
||||
routes:
|
||||
quit: [api, web]
|
||||
join: [api, web]
|
||||
broadcast: [server, web]
|
||||
chat: [web]
|
||||
vault-contents: [server, web]
|
||||
player-death: [api, web]
|
||||
server-heartbeat: [api ]
|
||||
web-heartbeat: [web]
|
||||
player-kick: [server, web]
|
||||
player-message: [server, web]
|
||||
market-order: [market]
|
||||
block-break: [api]
|
||||
block-place: [api]
|
||||
player-murder: [api]
|
||||
weather: [api, web]
|
||||
test: [api, web, server]
|
||||
types:
|
||||
vault-slot:
|
||||
material: int
|
||||
@@ -9,6 +26,8 @@ types:
|
||||
name: !optional string
|
||||
durability: !optional int
|
||||
events:
|
||||
test:
|
||||
data: !optional string
|
||||
quit:
|
||||
player: string
|
||||
join:
|
||||
|
274
api/events.py
274
api/events.py
@@ -3,41 +3,109 @@ import os
|
||||
from django.core.cache import cache
|
||||
import time
|
||||
from minecraft.models import Server
|
||||
from json import loads, dumps, JSONEncoder, JSONDecoder
|
||||
from json import loads, dumps
|
||||
import beanstalkc
|
||||
from django.contrib.auth.models import User
|
||||
import yaml
|
||||
import django.dispatch
|
||||
|
||||
on_event = django.dispatch.Signal(providing_args=["event"])
|
||||
|
||||
def cache_heartbeat(sender, event, *args, **kwargs):
|
||||
#cache.set('caminus-server-heartbeat-%s'%(request.server.id), evt['payload'], 86400)
|
||||
pass
|
||||
|
||||
on_event.connect(cache_heartbeat, sender=intern('heartbeat'))
|
||||
|
||||
class Dispatcher(object):
|
||||
@classmethod
|
||||
def broadcastMessage(cls, message, *args):
|
||||
event = BroadcastEvent(message%args)
|
||||
ServerQueue.broadcast(event)
|
||||
WebQueue.broadcast(event)
|
||||
def routes(cls):
|
||||
types = cache.get('caminus-event-routes')
|
||||
types = None
|
||||
if types is None:
|
||||
typefile = os.path.sep.join(
|
||||
__file__.split(os.path.sep)[0:-1]+
|
||||
['event-types.yml',]
|
||||
)
|
||||
yaml.add_constructor(r'!optional', OptionalEventProperty)
|
||||
types = yaml.load(open(typefile, 'r'))['routes']
|
||||
cache.set('caminus-event-routes', types, 3600)
|
||||
return types
|
||||
|
||||
class EventEncoder(JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Event):
|
||||
return {'type': obj.type, 'properties': obj.properties}
|
||||
if isinstance(obj, beanstalkc.Job):
|
||||
return {'id': obj.id, 'event': loads(obj.body)}
|
||||
return super(EventEncoder, self).default(obj)
|
||||
@classmethod
|
||||
def dispatch(cls, event):
|
||||
routes = cls.routes()
|
||||
if event._type in routes:
|
||||
for route in routes[event._type]:
|
||||
if route == "api":
|
||||
on_event.send_robust(sender=intern(event._type), event=event)
|
||||
elif route == "web":
|
||||
WebQueue.route(event)
|
||||
elif route == "server":
|
||||
ServerQueue.route(event)
|
||||
elif route == "market":
|
||||
MarketQueue.route(event)
|
||||
else:
|
||||
raise KeyError, "Unknown route %s"%route
|
||||
else:
|
||||
raise KeyError, "Unable to find route for %s event"%(event._type)
|
||||
|
||||
@classmethod
|
||||
def broadcast(cls, msg, *args):
|
||||
cls.dispatch(Event('broadcast', message=msg%args))
|
||||
|
||||
@classmethod
|
||||
def user_message(cls, user, message, args):
|
||||
player = user.minecraftprofile.mc_username
|
||||
cls.player_message(player, message, *args)
|
||||
|
||||
@classmethod
|
||||
def player_message(cls, playername, message, *args):
|
||||
message = message % args
|
||||
cls.dispatch(Event('player-message', player=playername,
|
||||
message=message))
|
||||
|
||||
@classmethod
|
||||
def chat(cls, playername, message, *args):
|
||||
cls.dispatch(Event('chat', player=playername, message=message%args))
|
||||
|
||||
class Event(object):
|
||||
def __init__(self, type, *args, **kwargs):
|
||||
types = self.types()
|
||||
def __init__(self, type=None, *args, **kwargs):
|
||||
self._id = -1
|
||||
self._stamp = 0
|
||||
self._conn = None
|
||||
self._properties = {}
|
||||
types = self._types()
|
||||
if type in types:
|
||||
self.type = type
|
||||
self.properties = self.marshallType(kwargs, types[type], type)
|
||||
self._type = type
|
||||
self._properties = self._marshallType(kwargs, types[type], self._datatypes(), type)
|
||||
else:
|
||||
raise ValueError, "Unknown event type '%s'"%(type)
|
||||
|
||||
def _delete(self):
|
||||
self._conn.delete(self._id)
|
||||
|
||||
def _bury(self, priority=0):
|
||||
self._conn.bury(self._id, 0)
|
||||
|
||||
def __getattribute__(self, name):
|
||||
if name.startswith('_'):
|
||||
return super(Event, self).__getattribute__(name)
|
||||
return self._properties[name]
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name.startswith('_'):
|
||||
return super(Event, self).__setattr__(name, value)
|
||||
if name in self._properties:
|
||||
raise NotImplementedError, "Event properties are not modifyable"
|
||||
|
||||
@classmethod
|
||||
def marshallType(cls, data, typeDescription, paramPath=""):
|
||||
def _marshallType(cls, data, typeDescription, dataTypes, paramPath=""):
|
||||
assert(isinstance(dataTypes, dict))
|
||||
if data is None:
|
||||
return data
|
||||
if isinstance(typeDescription, OptionalEventProperty):
|
||||
return cls.marshallType(data, str(typeDescription), paramPath)
|
||||
return cls._marshallType(data, str(typeDescription), dataTypes, paramPath)
|
||||
try:
|
||||
if typeDescription == "string":
|
||||
return str(data)
|
||||
@@ -55,25 +123,30 @@ class Event(object):
|
||||
for key, elementType in typeDescription.iteritems():
|
||||
key = key.replace('-', '_')
|
||||
if key in data:
|
||||
ret[key] = cls.marshallType(data[key], elementType,
|
||||
if key.startswith("_"):
|
||||
raise ValueError, "Parameter '%s.%s' cannot start with '_'"%(key, paramPath)
|
||||
ret[key] = cls._marshallType(data[key], elementType, dataTypes,
|
||||
"%s.%s"%(paramPath, key))
|
||||
elif not isinstance(elementType, OptionalEventProperty):
|
||||
raise KeyError, "Missing parameter '%s.%s'"%(paramPath, key)
|
||||
else:
|
||||
ret[key] = None
|
||||
return ret
|
||||
if isinstance(typeDescription, list):
|
||||
ret = []
|
||||
i = 0
|
||||
for item in data:
|
||||
ret.append(cls.marshallType(item, typeDescription[0], "%s[%d]"%(paramPath, i)))
|
||||
ret.append(cls._marshallType(item, typeDescription[0], dataTypes, "%s[%d]"%(paramPath, i)))
|
||||
i += 1
|
||||
return ret
|
||||
if typeDescription in cls.datatypes():
|
||||
return cls.marshallType(data, cls.datatypes()[typeDescription], paramPath)
|
||||
if typeDescription in dataTypes:
|
||||
return cls._marshallType(data, dataTypes[typeDescription], dataTypes, paramPath)
|
||||
raise TypeError, "Unknown event data type '%s.%s'"%(paramPath, typeDescription)
|
||||
|
||||
@classmethod
|
||||
def types(cls):
|
||||
def _types(cls):
|
||||
types = cache.get('caminus-event-types')
|
||||
types = None
|
||||
if types is None:
|
||||
typefile = os.path.sep.join(
|
||||
__file__.split(os.path.sep)[0:-1]+
|
||||
@@ -85,7 +158,7 @@ class Event(object):
|
||||
return types
|
||||
|
||||
@classmethod
|
||||
def datatypes(cls):
|
||||
def _datatypes(cls):
|
||||
typefile = os.path.sep.join(
|
||||
__file__.split(os.path.sep)[0:-1]+
|
||||
['event-types.yml',]
|
||||
@@ -95,19 +168,57 @@ class Event(object):
|
||||
return types
|
||||
|
||||
def __repr__(self):
|
||||
return dumps(self, cls=EventEncoder)
|
||||
return "Event(%r)"%(self._properties)
|
||||
|
||||
def toJSON(self):
|
||||
return dumps(self, cls=EventEncoder)
|
||||
def __eq__(self, other):
|
||||
return self._properties == other._properties and self._id == other._id
|
||||
|
||||
@classmethod
|
||||
def fromJSON(cls, json):
|
||||
decoded = loads(json, cls=EventDecoder)
|
||||
print decoded
|
||||
evt = Event(json['type'], json['event']['payload'])
|
||||
def _fromBeanstalkJob(cls, job):
|
||||
assert(isinstance(job, beanstalkc.Job))
|
||||
id = job.jid
|
||||
evt = cls._fromJSON(job.body)
|
||||
evt._id = int(id)
|
||||
evt._conn = job.conn
|
||||
return evt
|
||||
|
||||
def _toDict(self):
|
||||
ret = {
|
||||
'_id': self._id,
|
||||
'_type': self._type,
|
||||
'_stamp': self._stamp,
|
||||
}
|
||||
ret.update(self._properties)
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def _fromDict(cls, data):
|
||||
assert(isinstance(data, dict))
|
||||
type = data['_type']
|
||||
properties = {}
|
||||
for prop in data:
|
||||
if prop.startswith('_'):
|
||||
continue
|
||||
properties[prop] = data[prop]
|
||||
evt = Event(type, **properties)
|
||||
if '_id' in data:
|
||||
evt._id = int(data['_id'])
|
||||
return evt
|
||||
|
||||
def _toJSON(self):
|
||||
return dumps(self._toDict())
|
||||
|
||||
@classmethod
|
||||
def _fromJSON(cls, json):
|
||||
assert(isinstance(json, str))
|
||||
return cls._fromDict(loads(json))
|
||||
|
||||
class OptionalEventProperty(str):
|
||||
def __new__(cls, loader, node):
|
||||
def __new__(cls, loader=None, node=None):
|
||||
if node is None:
|
||||
# Used with OptionalEventProperty("some-prop-name"), such as with pickling
|
||||
return str.__new__(cls, loader)
|
||||
# Used with yaml loader
|
||||
return str.__new__(cls, loader.construct_scalar(node))
|
||||
|
||||
class EventQueue(object):
|
||||
@@ -118,13 +229,13 @@ class EventQueue(object):
|
||||
self.queue.use(self.name)
|
||||
self.queue.watch(self.name)
|
||||
|
||||
@classmethod
|
||||
def route(self, evt):
|
||||
raise NotImplementedError
|
||||
|
||||
def sendEvent(self, evt):
|
||||
assert(isinstance(evt, Event))
|
||||
eventData = {}
|
||||
eventData['stamp'] = time.time()
|
||||
eventData['event'] = evt
|
||||
json = dumps(eventData, cls=EventEncoder)
|
||||
self.queue.put(json)
|
||||
id = self.queue.put(evt._toJSON())
|
||||
|
||||
def getEvents(self, timeout=30):
|
||||
ret = []
|
||||
@@ -132,7 +243,12 @@ class EventQueue(object):
|
||||
while job:
|
||||
ret.append(job)
|
||||
job = self.queue.reserve(timeout=0)
|
||||
return ret
|
||||
return map(lambda x: Event._fromBeanstalkJob(x), ret)
|
||||
|
||||
@classmethod
|
||||
def flushAll(cls):
|
||||
for q in cls.allQueues():
|
||||
q.flush()
|
||||
|
||||
def flush(self):
|
||||
job = self.queue.peek_ready()
|
||||
@@ -153,17 +269,22 @@ class EventQueue(object):
|
||||
ret.append(EventQueue(c))
|
||||
return ret
|
||||
|
||||
def deleteJob(self, id):
|
||||
self.queue.delete(id)
|
||||
|
||||
class MarketQueue(EventQueue):
|
||||
def __init__(self):
|
||||
super(MarketQueue, self).__init__('caminus-market')
|
||||
|
||||
@classmethod
|
||||
def route(cls, evt):
|
||||
cls().sendEvent(evt)
|
||||
|
||||
class ServerQueue(EventQueue):
|
||||
def __init__(self, server):
|
||||
super(ServerQueue, self).__init__('caminus-broadcast-%s'%server.id)
|
||||
|
||||
@classmethod
|
||||
def route(cls, evt):
|
||||
cls.broadcast(evt)
|
||||
|
||||
@classmethod
|
||||
def broadcast(cls, evt):
|
||||
for server in Server.objects.all():
|
||||
@@ -183,18 +304,23 @@ class WebQueue(EventQueue):
|
||||
for queueName, stamp in activeCache.iteritems():
|
||||
if time.time()-stamp > 120:
|
||||
expired.append(queueName)
|
||||
del activeCache[queueName]
|
||||
for e in expired:
|
||||
del activeCache[e]
|
||||
cache.set('minecraft-web-queues', activeCache, 3600)
|
||||
for e in expired:
|
||||
q = WebQueue(e)
|
||||
q.flush()
|
||||
|
||||
@classmethod
|
||||
def route(cls, event):
|
||||
cls.broadcast(event)
|
||||
|
||||
@classmethod
|
||||
def broadcast(cls, event):
|
||||
latest = cache.get('minecraft-web-events')
|
||||
if latest is None:
|
||||
latest = []
|
||||
latest.append(dumps(event, cls=EventEncoder))
|
||||
latest.append(dumps(event._toJSON()))
|
||||
while len(latest) > 10:
|
||||
latest.pop(0)
|
||||
cache.set('minecraft-web-events', latest, 86400)
|
||||
@@ -212,7 +338,11 @@ class WebQueue(EventQueue):
|
||||
ret.append(WebQueue(name))
|
||||
return ret
|
||||
|
||||
def getEvents(self):
|
||||
@staticmethod
|
||||
def clearActiveQueues():
|
||||
cache.set('minecraft-web-queues', None)
|
||||
|
||||
def getEvents(self, timeout=30):
|
||||
if self.id == "0":
|
||||
latestEvents = cache.get('minecraft-web-events')
|
||||
if latestEvents is None:
|
||||
@@ -222,59 +352,7 @@ class WebQueue(EventQueue):
|
||||
ret.append(loads(e))
|
||||
return ret
|
||||
else:
|
||||
ret = super(WebQueue, self).getEvents()
|
||||
ret = super(WebQueue, self).getEvents(timeout)
|
||||
for evt in ret:
|
||||
evt.delete()
|
||||
evt._delete()
|
||||
return ret
|
||||
|
||||
def server_broadcast(message, *args):
|
||||
message = message%args
|
||||
for server in Server.objects.all():
|
||||
event = Event('broadcast', message=message)
|
||||
queue = ServerQueue(server)
|
||||
queue.sendEvent(event)
|
||||
send_web_event(event)
|
||||
|
||||
def user_message(user, message, *args):
|
||||
player = user.minecraftprofile.mc_username
|
||||
player_message(player, message, *args)
|
||||
|
||||
def player_message(playername, message, *args):
|
||||
message = message%args
|
||||
for server in Server.objects.all():
|
||||
event = PlayerMessageEvent(playername, message)
|
||||
queue = ServerQueue(server)
|
||||
queue.sendEvent(event)
|
||||
|
||||
def send_web_event(event):
|
||||
latest = cache.get('minecraft-web-events')
|
||||
if latest is None:
|
||||
latest = []
|
||||
latest.append(dumps(event, cls=EventEncoder))
|
||||
while len(latest) > 10:
|
||||
latest.pop(0)
|
||||
cache.set('minecraft-web-events', latest, 86400);
|
||||
if settings.CAMINUS_USE_BEANSTALKD:
|
||||
queue = beanstalkc.Connection(host=settings.CAMINUS_BEANSTALKD_HOST,
|
||||
port = settings.CAMINUS_BEANSTALKD_PORT)
|
||||
json = dumps({'stamp': time.time(), 'event':event}, cls=EventEncoder)
|
||||
activeQueues = cache.get('minecraft-web-queues')
|
||||
#print "Active queues", repr(activeQueues)
|
||||
if activeQueues is None:
|
||||
activeQueues = {}
|
||||
for tube in activeQueues.iterkeys():
|
||||
queue.use('caminus-web-%s'%(tube))
|
||||
print "Web queue", tube
|
||||
pendingJob = queue.peek_ready()
|
||||
while pendingJob:
|
||||
pending = loads(pendingJob.body)
|
||||
if time.time()-pending['stamp'] > 30:
|
||||
pendingJob.delete()
|
||||
pendingJob = queue.peek_ready()
|
||||
else:
|
||||
pendingJob = None
|
||||
queue.put(json)
|
||||
|
||||
def chat(playername, message):
|
||||
evt = ChatEvent(playername, message)
|
||||
WebQueue.broadcast(evt)
|
||||
|
@@ -12,7 +12,7 @@ from urllib2 import urlopen
|
||||
import json
|
||||
from datetime import datetime
|
||||
from models import cachePlayerList
|
||||
from events import ServerQueue, WebQueue, Event, chat
|
||||
from events import ServerQueue, WebQueue, Event, Dispatcher
|
||||
from bounty.models import Bounty
|
||||
from vault.models import VaultSlot
|
||||
|
||||
@@ -107,24 +107,7 @@ class ServerEventHandler(BaseHandler):
|
||||
def update(self, request):
|
||||
events = json.loads(request.POST['events'])['events']
|
||||
for evt in events:
|
||||
if evt['type'] == 'chat':
|
||||
chat(evt['payload']['sender'], evt['payload']['message'])
|
||||
if evt['type'] == 'player-death':
|
||||
WebQueue.broadcast(Event('player-death', player=evt['payload']['player'],
|
||||
message=evt['payload']['message']))
|
||||
if evt['type'] == 'server-heartbeat':
|
||||
cache.set('caminus-server-heartbeat-%s'%(request.server.id), evt['payload'], 86400)
|
||||
WebQueue.broadcast(Event('web-heartbeat', server=request.server.id,
|
||||
heartbeat=evt['payload'],
|
||||
day_period=request.server.day_period(),
|
||||
time=request.server.current_time().second))
|
||||
if evt['type'] == 'player-murder':
|
||||
bounties = Bounty.objects.filter(target__mc_username=evt['payload']['player'])
|
||||
killer = MinecraftProfile.objects.get(mc_username=evt['payload']['killer'])
|
||||
for bounty in bounties:
|
||||
bounty.close(killer)
|
||||
if len(bounties) > 0:
|
||||
Dispatcher.broadcastMessage("The bounty on %s has been collected."%(evt['payload']['player']))
|
||||
Dispatcher.dispatch(Event.fromDict(evt)
|
||||
return {'result': 'success'}
|
||||
|
||||
class ChatHandler(BaseHandler):
|
||||
|
252
api/oldtests.py
Normal file
252
api/oldtests.py
Normal file
@@ -0,0 +1,252 @@
|
||||
from django.utils import unittest
|
||||
import json
|
||||
from django.test.client import Client
|
||||
from django.contrib.auth.models import User
|
||||
from minecraft.models import MinecraftProfile, Server, PlayerSession, MOTD, Ban
|
||||
from local.models import Quote
|
||||
import hashlib
|
||||
import events
|
||||
from django.conf import settings
|
||||
|
||||
class EventDispatcherTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
||||
events.EventQueue.flushAll()
|
||||
|
||||
def tearDown(self):
|
||||
self.server.delete()
|
||||
events.EventQueue.flushAll()
|
||||
|
||||
def testBroadcast(self):
|
||||
webQueue = events.WebQueue(1)
|
||||
serverQueue = events.ServerQueue(self.server)
|
||||
events.Dispatcher.dispatch(events.Event('broadcast', message="Test"))
|
||||
webEvents = webQueue.getEvents()
|
||||
serverEvents = serverQueue.getEvents()
|
||||
print webEvents
|
||||
self.assertEqual(len(webEvents), 1)
|
||||
self.assertEqual(len(serverEvents), 1)
|
||||
self.assertEqual(webEvents[0]._type, 'broadcast')
|
||||
self.assertEqual(serverEvents[0]._type, 'broadcast')
|
||||
|
||||
class EventQueueTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.queue = events.EventQueue("test")
|
||||
self.queue.flush()
|
||||
|
||||
def tearDown(self):
|
||||
self.queue.flush()
|
||||
|
||||
def testSendEvent(self):
|
||||
self.queue.sendEvent(events.Event('test'))
|
||||
|
||||
def testSendReceiveEvent(self):
|
||||
self.queue.sendEvent(events.Event('test'))
|
||||
self.assertEqual(len(self.queue.getEvents()), 1)
|
||||
|
||||
def testMarshallEvent(self):
|
||||
sentEvent = events.Event('test')
|
||||
self.queue.sendEvent(sentEvent)
|
||||
recvEvent = self.queue.getEvents()[0]
|
||||
self.assertEqual(type(sentEvent), type(recvEvent))
|
||||
self.assertEqual(sentEvent._type, recvEvent._type)
|
||||
self.assertNotEqual(sentEvent._id, recvEvent._id)
|
||||
|
||||
class EventMarshallTest(unittest.TestCase):
|
||||
def testMarshallToValidJSON(self):
|
||||
json.loads(events.Event('test')._toJSON())
|
||||
|
||||
def testReprToValidJSON(self):
|
||||
json.loads(repr(events.Event('test')))
|
||||
|
||||
def testLoadFromJSON(self):
|
||||
evt = events.Event('test', player='')
|
||||
jsonData = evt._toJSON()
|
||||
decodedEvent = events.Event._fromJSON(jsonData)
|
||||
|
||||
class ServerPingTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
|
||||
self.user.minecraftprofile.mc_username = "ValidUsername"
|
||||
self.user.minecraftprofile.save()
|
||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
||||
tokenHash = hashlib.sha1()
|
||||
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
|
||||
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
self.server.delete()
|
||||
|
||||
def testPing(self):
|
||||
resp = self.client.get('/api/server/whoami',
|
||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
resp = json.loads(resp.content)
|
||||
self.assertEqual(resp["api-version"], 2)
|
||||
self.assertTrue("api-version" in resp)
|
||||
|
||||
class ServerEventTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
|
||||
self.user.minecraftprofile.mc_username = "ValidUsername"
|
||||
self.user.minecraftprofile.save()
|
||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
||||
tokenHash = hashlib.sha1()
|
||||
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
|
||||
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
self.server.delete()
|
||||
|
||||
def testBroadcast(self):
|
||||
events.ServerQueue.broadcast(events.Event('broadcast', message="Test message"))
|
||||
response = json.loads(self.client.get('/api/server/events',
|
||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
|
||||
self.assertTrue(len(response['events']) > 0)
|
||||
response = json.loads(self.client.post('/api/server/events', {'job':response['events'][0]['id']},
|
||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
|
||||
self.assertEqual(response['result'], 'success')
|
||||
|
||||
def testUserMessage(self):
|
||||
events.user_message(self.user, "Test user message")
|
||||
response = json.loads(self.client.get('/api/server/events',
|
||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
|
||||
self.assertTrue(len(response['events']) > 0)
|
||||
response = json.loads(self.client.post('/api/server/events', {'job':response['events'][0]['id']},
|
||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
|
||||
self.assertEqual(response['result'], 'success')
|
||||
|
||||
class MOTDTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.server = Server.objects.create(hostname="localhost", secret="")
|
||||
|
||||
def tearDown(self):
|
||||
self.server.delete()
|
||||
|
||||
def testUnregisteredUser(self):
|
||||
response = json.loads(self.client.get('/api/motd/NewUser').content)
|
||||
self.assertIsInstance(response['motd'], list)
|
||||
|
||||
def testRandomMOTD(self):
|
||||
m = MOTD.objects.create(server=self.server, text="testMOTD")
|
||||
q = Quote.objects.create(text="testQUOTE")
|
||||
response = json.loads(self.client.get('/api/motd/NewUser').content)
|
||||
m.delete()
|
||||
q.delete()
|
||||
foundMOTD = False
|
||||
foundQuote = False
|
||||
for line in response['motd']:
|
||||
if line == "testMOTD":
|
||||
foundMOTD = True
|
||||
if line == '"testQUOTE"':
|
||||
foundQuote = True
|
||||
self.assertTrue(foundMOTD)
|
||||
self.assertTrue(foundQuote)
|
||||
|
||||
class SessionTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
|
||||
self.user.minecraftprofile.mc_username = "ValidUsername"
|
||||
self.user.minecraftprofile.save()
|
||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
||||
tokenHash = hashlib.sha1()
|
||||
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
|
||||
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
self.server.delete()
|
||||
|
||||
def testBannedStart(self):
|
||||
b = Ban.objects.create(player=self.user.minecraftprofile, banner=self.user, reason="test")
|
||||
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
b.delete()
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
session = json.loads(resp.content)
|
||||
self.assertFalse(session['success'])
|
||||
|
||||
def testBadUserStart(self):
|
||||
resp = self.client.post('/api/server/session/SomeUnknownUser/new', {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
session = json.loads(resp.content)
|
||||
self.assertFalse(session['success'])
|
||||
|
||||
def testInactiveStart(self):
|
||||
self.user.is_active = False
|
||||
self.user.save()
|
||||
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
self.user.is_active = True
|
||||
self.user.save()
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
session = json.loads(resp.content)
|
||||
self.assertFalse(session['success'])
|
||||
|
||||
def testSessionStart(self):
|
||||
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
session = json.loads(resp.content)
|
||||
self.assertTrue(session['success'])
|
||||
|
||||
def testSessionEnd(self):
|
||||
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
session = json.loads(resp.content)
|
||||
resp = self.client.get('/api/server/session/%s/close'%(self.user.minecraftprofile.mc_username), HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
class PollTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
def testAnonymousPoll(self):
|
||||
resp = self.client.get('/api/poll/0')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = json.loads(resp.content)
|
||||
self.assertTrue('info' in data)
|
||||
self.assertTrue('server' in data['info'])
|
||||
self.assertTrue('user' in data['info'])
|
||||
self.assertEqual(len(data['info']['user']), 0)
|
||||
|
||||
class EconomyTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
|
||||
self.user.minecraftprofile.mc_username = "ValidUsername"
|
||||
self.user.minecraftprofile.save()
|
||||
self.user.minecraftprofile.currencyaccount.balance=42
|
||||
self.user.minecraftprofile.currencyaccount.save()
|
||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
||||
tokenHash = hashlib.sha1()
|
||||
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
|
||||
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
self.server.delete()
|
||||
|
||||
def testBalanceQuery(self):
|
||||
resp = self.client.get('/api/server/economy/ValidUsername', HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['balance'], 42)
|
||||
|
||||
def testDeposit(self):
|
||||
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': 100}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['balance'], 142)
|
||||
self.assertTrue(data['success'])
|
||||
|
||||
def testWithdraw(self):
|
||||
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': -40}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['balance'], 2)
|
||||
self.assertTrue(data['success'])
|
||||
|
||||
def testOverdraw(self):
|
||||
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': -400}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
data = json.loads(resp.content)
|
||||
self.assertFalse(data['success'])
|
374
api/tests.py
374
api/tests.py
@@ -1,216 +1,216 @@
|
||||
from django.utils import unittest
|
||||
import json
|
||||
from django.test.client import Client
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.cache import cache
|
||||
from minecraft.models import MinecraftProfile, Server, PlayerSession, MOTD, Ban
|
||||
from local.models import Quote
|
||||
import hashlib
|
||||
import events
|
||||
from django.conf import settings
|
||||
import json
|
||||
|
||||
class EventTypeTest(unittest.TestCase):
|
||||
class QueueAssertMixin(object):
|
||||
def assertQueueLength(self, queue, count):
|
||||
self._queue_events = queue.getEvents(0)
|
||||
self.assertEqual(len(self._queue_events), count)
|
||||
|
||||
class EventTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
events.EventQueue.flushAll()
|
||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
events.EventQueue.flushAll()
|
||||
self.server.delete()
|
||||
|
||||
class ServerEventTest(EventTest, QueueAssertMixin):
|
||||
def testHeartbeatCache(self):
|
||||
oldBeat = cache.get('caminus-server-heartbeat-%s'%(self.server.id))
|
||||
self.assertIsNone(oldBeat)
|
||||
events.Dispatcher.dispatch(events.Event('server-heartbeat', port=self.server.port,
|
||||
name=self.server.hostname, worldTimes={'world': 0}))
|
||||
newBeat = cache.get('caminus-server-heartbeat-%s'%(self.server.id))
|
||||
self.assertIsNotNone(oldBeat)
|
||||
|
||||
class DispatcherTest(EventTest, QueueAssertMixin):
|
||||
def testDispatch(self):
|
||||
queue = events.ServerQueue(self.server)
|
||||
self.assertQueueLength(queue, 0)
|
||||
events.Dispatcher.dispatch(events.Event('test'))
|
||||
self.assertQueueLength(queue, 1)
|
||||
|
||||
def testBroadcast(self):
|
||||
queue = events.ServerQueue(self.server)
|
||||
self.assertQueueLength(queue, 0)
|
||||
events.Dispatcher.broadcast('test')
|
||||
self.assertQueueLength(queue, 1)
|
||||
|
||||
class WebQueueTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
events.EventQueue.flushAll()
|
||||
|
||||
def tearDown(self):
|
||||
events.EventQueue.flushAll()
|
||||
|
||||
def testBroadcast(self):
|
||||
queue = events.WebQueue(1)
|
||||
events.WebQueue.broadcast(events.Event('test'))
|
||||
webEvents = queue.getEvents()
|
||||
self.assertEqual(len(webEvents), 1)
|
||||
|
||||
def testHugeBroadcast(self):
|
||||
queues = []
|
||||
for i in range(0, 30):
|
||||
queues.append(events.WebQueue(i))
|
||||
events.WebQueue.broadcast(events.Event('test'))
|
||||
for queue in queues:
|
||||
eventList = queue.getEvents()
|
||||
self.assertEqual(len(eventList), 1)
|
||||
|
||||
def testActiveQueues(self):
|
||||
events.WebQueue.clearActiveQueues()
|
||||
self.assertEqual(len(events.WebQueue.activeQueues()), 0)
|
||||
queues = []
|
||||
for i in range(0, 30):
|
||||
queues.append(events.WebQueue(i))
|
||||
self.assertEqual(len(events.WebQueue.activeQueues()), len(queues))
|
||||
|
||||
def testSingleUseEvent(self):
|
||||
queue = events.WebQueue(1)
|
||||
queue.sendEvent(events.Event('test'))
|
||||
self.assertEqual(len(queue.getEvents(0)), 1)
|
||||
self.assertEqual(len(queue.getEvents(0)), 0)
|
||||
|
||||
class EventQueueTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.queue = events.EventQueue('test')
|
||||
events.EventQueue.flushAll()
|
||||
|
||||
def tearDown(self):
|
||||
events.EventQueue.flushAll()
|
||||
|
||||
def testSend(self):
|
||||
self.queue.sendEvent(events.Event('test'))
|
||||
|
||||
def testReceive(self):
|
||||
sentEvent = events.Event('test')
|
||||
self.queue.sendEvent(sentEvent)
|
||||
eventList = self.queue.getEvents()
|
||||
self.assertEqual(len(eventList), 1)
|
||||
self.assertEqual(type(eventList[0]), events.Event)
|
||||
self.assertNotEqual(eventList[0]._id, sentEvent._id)
|
||||
self.assertEqual(eventList[0]._type, sentEvent._type)
|
||||
self.assertNotEqual(eventList[0]._conn, sentEvent._conn)
|
||||
|
||||
def testFlush(self):
|
||||
evt = events.Event('test')
|
||||
for i in range(0, 300):
|
||||
self.queue.sendEvent(evt)
|
||||
self.assertEqual(len(self.queue.getEvents(0)), 300)
|
||||
self.queue.flush()
|
||||
self.assertEqual(len(self.queue.getEvents(0)), 0)
|
||||
|
||||
def testFlushAll(self):
|
||||
evt = events.Event('test')
|
||||
queues = []
|
||||
for i in range(0, 10):
|
||||
queues.append(events.EventQueue('test-%d'%(i)))
|
||||
for i in range(0, 10):
|
||||
for queue in queues:
|
||||
queue.sendEvent(evt)
|
||||
for queue in queues:
|
||||
self.assertEqual(len(queue.getEvents(0)), 10)
|
||||
events.EventQueue.flushAll()
|
||||
for queue in queues:
|
||||
self.assertEqual(len(queue.getEvents(0)), 0)
|
||||
|
||||
|
||||
class EventTest(unittest.TestCase):
|
||||
def testEqual(self):
|
||||
e = events.Event('test')
|
||||
e2 = events.Event('test')
|
||||
self.assertEqual(e, e2)
|
||||
|
||||
def testNewEvent(self):
|
||||
e = events.Event('test')
|
||||
self.assertEqual(e._id, -1)
|
||||
self.assertEqual(e._conn, None)
|
||||
self.assertDictEqual(e._properties, {'data': None})
|
||||
|
||||
def testValidTypes(self):
|
||||
e = events.Event('quit', player="TestPlayer")
|
||||
self.assertEqual(e.player, "TestPlayer")
|
||||
|
||||
def testInvalidType(self):
|
||||
e = events.Event('invalid')
|
||||
with self.assertRaises(ValueError):
|
||||
e = events.Event('invalid')
|
||||
|
||||
def testMissingArgs(self):
|
||||
e = events.Event('quit')
|
||||
with self.assertRaises(KeyError):
|
||||
e = events.Event('quit')
|
||||
|
||||
def testExtraArgs(self):
|
||||
e = events.Event('quit', player='TestPlayer', extra='extra')
|
||||
|
||||
class ServerPingTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
|
||||
self.user.minecraftprofile.mc_username = "ValidUsername"
|
||||
self.user.minecraftprofile.save()
|
||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
||||
tokenHash = hashlib.sha1()
|
||||
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
|
||||
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
|
||||
class EventMarshallTest(unittest.TestCase):
|
||||
def testToDict(self):
|
||||
self.assertDictEqual(events.Event('test')._toDict(), {
|
||||
'_id': -1,
|
||||
'_stamp': 0,
|
||||
'_type': 'test',
|
||||
'data': None,
|
||||
})
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
self.server.delete()
|
||||
def testJSONIdentity(self):
|
||||
evt = events.Event('test')
|
||||
self.assertEqual(events.Event._fromJSON(evt._toJSON()), evt)
|
||||
|
||||
def testPing(self):
|
||||
resp = self.client.get('/api/server/whoami',
|
||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
resp = json.loads(resp.content)
|
||||
self.assertEqual(resp["api-version"], 2)
|
||||
self.assertTrue("api-version" in resp)
|
||||
def testDictIdentity(self):
|
||||
evt = events.Event('test')
|
||||
self.assertEqual(events.Event._fromDict(evt._toDict()), evt)
|
||||
|
||||
if settings.CAMINUS_USE_BEANSTALKD:
|
||||
class ServerEventTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
|
||||
self.user.minecraftprofile.mc_username = "ValidUsername"
|
||||
self.user.minecraftprofile.save()
|
||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
||||
tokenHash = hashlib.sha1()
|
||||
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
|
||||
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
|
||||
def testEmptyType(self):
|
||||
desc = {}
|
||||
data = events.Event._marshallType({}, desc, events.Event._datatypes(), 'marshall-test')
|
||||
self.assertEqual(len(data), 0)
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
self.server.delete()
|
||||
def testSimpleType(self):
|
||||
desc = {'data': 'string'}
|
||||
data = events.Event._marshallType({'data': 'value'}, desc,
|
||||
events.Event._datatypes(), 'marshall-test')
|
||||
self.assertEqual(len(data), 1)
|
||||
self.assertTrue(isinstance(data['data'], str))
|
||||
self.assertEqual(data['data'], 'value')
|
||||
|
||||
def testBroadcast(self):
|
||||
events.server_broadcast("Test message")
|
||||
response = json.loads(self.client.get('/api/server/events',
|
||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
|
||||
self.assertTrue(len(response['events']) > 0)
|
||||
response = json.loads(self.client.post('/api/server/events', {'job':response['events'][0]['id']},
|
||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
|
||||
self.assertEqual(response['result'], 'success')
|
||||
def testTypeMismatch(self):
|
||||
desc = {'data': 'int'}
|
||||
with self.assertRaises(ValueError):
|
||||
events.Event._marshallType({'data': 'this is not an integer'}, desc,
|
||||
events.Event._datatypes(), 'marshall-test')
|
||||
|
||||
def testUserMessage(self):
|
||||
events.user_message(self.user, "Test user message")
|
||||
response = json.loads(self.client.get('/api/server/events',
|
||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
|
||||
self.assertTrue(len(response['events']) > 0)
|
||||
response = json.loads(self.client.post('/api/server/events', {'job':response['events'][0]['id']},
|
||||
HTTP_AUTHORIZATION='X-Caminus %s'%(self.token)).content)
|
||||
self.assertEqual(response['result'], 'success')
|
||||
def testComplexType(self):
|
||||
desc = {'data': 'complex-type'}
|
||||
typeDesc = {'complex-type': {'value': 'string'}}
|
||||
data = events.Event._marshallType({'data': {'value': 'test'}}, desc, typeDesc, 'marshall-test')
|
||||
self.assertDictEqual(data, {'data': {'value': 'test'}})
|
||||
|
||||
class MOTDTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.server = Server.objects.create(hostname="localhost", secret="")
|
||||
def testComplexTypeMismatch(self):
|
||||
desc = {'data': 'complex-type'}
|
||||
typeDesc = {'complex-type': {'value': 'string'}}
|
||||
with self.assertRaises(KeyError):
|
||||
events.Event._marshallType({'data': 'this is just a string'}, desc,
|
||||
typeDesc, 'marshall-test')
|
||||
|
||||
def tearDown(self):
|
||||
self.server.delete()
|
||||
def testMissingOptionalAttribute(self):
|
||||
desc = {'data': events.OptionalEventProperty('string')}
|
||||
data = events.Event._marshallType({}, desc, events.Event._datatypes(),
|
||||
'marshall-test')
|
||||
self.assertDictEqual(data, {'data': None})
|
||||
|
||||
def testUnregisteredUser(self):
|
||||
response = json.loads(self.client.get('/api/motd/NewUser').content)
|
||||
self.assertIsInstance(response['motd'], list)
|
||||
def testOptionalAttribute(self):
|
||||
desc = {'data': events.OptionalEventProperty('string')}
|
||||
data = events.Event._marshallType({'data': 'value'}, desc,
|
||||
events.Event._datatypes(),
|
||||
'marshall-test')
|
||||
self.assertDictEqual(data, {'data': 'value'})
|
||||
|
||||
def testRandomMOTD(self):
|
||||
m = MOTD.objects.create(server=self.server, text="testMOTD")
|
||||
q = Quote.objects.create(text="testQUOTE")
|
||||
response = json.loads(self.client.get('/api/motd/NewUser').content)
|
||||
m.delete()
|
||||
q.delete()
|
||||
foundMOTD = False
|
||||
foundQuote = False
|
||||
for line in response['motd']:
|
||||
if line == "testMOTD":
|
||||
foundMOTD = True
|
||||
if line == '"testQUOTE"':
|
||||
foundQuote = True
|
||||
self.assertTrue(foundMOTD)
|
||||
self.assertTrue(foundQuote)
|
||||
|
||||
class SessionTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
|
||||
self.user.minecraftprofile.mc_username = "ValidUsername"
|
||||
self.user.minecraftprofile.save()
|
||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
||||
tokenHash = hashlib.sha1()
|
||||
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
|
||||
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
self.server.delete()
|
||||
|
||||
def testBannedStart(self):
|
||||
b = Ban.objects.create(player=self.user.minecraftprofile, banner=self.user, reason="test")
|
||||
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
b.delete()
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
session = json.loads(resp.content)
|
||||
self.assertFalse(session['success'])
|
||||
|
||||
def testBadUserStart(self):
|
||||
resp = self.client.post('/api/server/session/SomeUnknownUser/new', {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
session = json.loads(resp.content)
|
||||
self.assertFalse(session['success'])
|
||||
|
||||
def testInactiveStart(self):
|
||||
self.user.is_active = False
|
||||
self.user.save()
|
||||
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
self.user.is_active = True
|
||||
self.user.save()
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
session = json.loads(resp.content)
|
||||
self.assertFalse(session['success'])
|
||||
|
||||
def testSessionStart(self):
|
||||
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
session = json.loads(resp.content)
|
||||
self.assertTrue(session['success'])
|
||||
|
||||
def testSessionEnd(self):
|
||||
resp = self.client.post('/api/server/session/%s/new'%(self.user.minecraftprofile.mc_username), {'ip': '127.0.0.1'}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
session = json.loads(resp.content)
|
||||
resp = self.client.get('/api/server/session/%s/close'%(self.user.minecraftprofile.mc_username), HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
class PollTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
def testAnonymousPoll(self):
|
||||
resp = self.client.get('/api/poll/0')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = json.loads(resp.content)
|
||||
self.assertTrue('server-info' in data)
|
||||
self.assertTrue('user-info' in data)
|
||||
self.assertEqual(len(data['user-info']), 0)
|
||||
|
||||
class EconomyTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.user = User.objects.create_user('ValidUsername', 'test@example.com')
|
||||
self.user.minecraftprofile.mc_username = "ValidUsername"
|
||||
self.user.minecraftprofile.save()
|
||||
self.user.minecraftprofile.currencyaccount.balance=42
|
||||
self.user.minecraftprofile.currencyaccount.save()
|
||||
self.server = Server.objects.create(hostname='localhost', secret='secret')
|
||||
tokenHash = hashlib.sha1()
|
||||
tokenHash.update("%s%s%s"%('localhost', 0, 'secret'))
|
||||
self.token = "%s$%s$%s"%('localhost', 0, tokenHash.hexdigest())
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
self.server.delete()
|
||||
|
||||
def testBalanceQuery(self):
|
||||
resp = self.client.get('/api/server/economy/ValidUsername', HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['balance'], 42)
|
||||
|
||||
def testDeposit(self):
|
||||
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': 100}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['balance'], 142)
|
||||
self.assertTrue(data['success'])
|
||||
|
||||
def testWithdraw(self):
|
||||
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': -40}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
data = json.loads(resp.content)
|
||||
self.assertEqual(data['balance'], 2)
|
||||
self.assertTrue(data['success'])
|
||||
|
||||
def testOverdraw(self):
|
||||
resp = self.client.put('/api/server/economy/ValidUsername', {'delta': -400}, HTTP_AUTHORIZATION="X-Caminus %s"%(self.token))
|
||||
data = json.loads(resp.content)
|
||||
self.assertFalse(data['success'])
|
||||
def testMissingOptionalComplextType(self):
|
||||
desc = {'data': events.OptionalEventProperty('complex-type')}
|
||||
typeDesc = {'complex-type': {'value': 'string'}}
|
||||
data = events.Event._marshallType({}, desc, typeDesc, 'marshall-test')
|
||||
self.assertDictEqual(data, {'data': None})
|
||||
|
@@ -1,7 +1,7 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
import badges.api
|
||||
from caminus.api.events import user_message
|
||||
from caminus.api.events import Dispatcher
|
||||
from notification import models as notification
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
@@ -58,7 +58,7 @@ class Award(models.Model):
|
||||
super(Award, self).save(*args, **kwargs)
|
||||
badges.api.badge_awarded.send_robust(sender=intern(str(self.badge.slug)), award=self)
|
||||
notification.send([self.user], "badge_awarded", {"award": self, 'notice_description': self.badge, 'notice_url': reverse('user_profile')})
|
||||
user_message(self.user, "You have been awarded the '%s' badge."%self.badge)
|
||||
Dispatcher.user_message(self.user, "You have been awarded the '%s' badge."%self.badge)
|
||||
|
||||
def __unicode__(self):
|
||||
return "%s for %s"%(self.badge.__unicode__(), self.user.__unicode__())
|
||||
|
@@ -18,3 +18,13 @@ class Bounty(models.Model):
|
||||
self.save()
|
||||
killer.currencyaccount.balance = F('balance') + self.price
|
||||
killer.currencyaccount.save()
|
||||
|
||||
def handle_murder(sender, event, *args, **kwargs):
|
||||
bounties = Bounty.objects.filter(target__mc_username=evt['payload']['player'])
|
||||
killer = MinecraftProfile.objects.get(mc_username=evt['payload']['killer'])
|
||||
for bounty in bounties:
|
||||
bounty.close(killer)
|
||||
if len(bounties) > 0:
|
||||
Dispatcher.broadcast("The bounty on %s has been collected."%(evt['payload']['player']))
|
||||
|
||||
events.on_event.connect(handle_murder, sender=intern('player-murder'))
|
||||
|
83
bulkops.py
Normal file
83
bulkops.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# Bulk insert/update DB operations for the Django ORM. Useful when
|
||||
# inserting/updating lots of objects where the bottleneck is overhead
|
||||
# in talking to the database. Instead of doing this
|
||||
#
|
||||
# for x in seq:
|
||||
# o = SomeObject()
|
||||
# o.foo = x
|
||||
# o.save()
|
||||
#
|
||||
# or equivalently this
|
||||
#
|
||||
# for x in seq:
|
||||
# SomeObject.objects.create(foo=x)
|
||||
#
|
||||
# do this
|
||||
#
|
||||
# l = []
|
||||
# for x in seq:
|
||||
# o = SomeObject()
|
||||
# o.foo = x
|
||||
# l.append(o)
|
||||
# insert_many(l)
|
||||
#
|
||||
# Note that these operations are really simple. They won't work with
|
||||
# many-to-many relationships, and you may have to divide really big
|
||||
# lists into smaller chunks before sending them through.
|
||||
#
|
||||
# History
|
||||
# 2010-12-10: quote column names, reported by Beres Botond.
|
||||
|
||||
def insert_many(objects, using="default"):
|
||||
"""Insert list of Django objects in one SQL query. Objects must be
|
||||
of the same Django model. Note that save is not called and signals
|
||||
on the model are not raised."""
|
||||
if not objects:
|
||||
return
|
||||
|
||||
import django.db.models
|
||||
from django.db import connections
|
||||
con = connections[using]
|
||||
|
||||
model = objects[0].__class__
|
||||
fields = [f for f in model._meta.fields if not isinstance(f, django.db.models.AutoField)]
|
||||
parameters = []
|
||||
for o in objects:
|
||||
parameters.append(tuple(f.get_db_prep_save(f.pre_save(o, True), connection=con) for f in fields))
|
||||
|
||||
table = model._meta.db_table
|
||||
column_names = ",".join(con.ops.quote_name(f.column) for f in fields)
|
||||
placeholders = ",".join(("%s",) * len(fields))
|
||||
con.cursor().executemany(
|
||||
"insert into %s (%s) values (%s)" % (table, column_names, placeholders),
|
||||
parameters)
|
||||
|
||||
def update_many(objects, fields=[], using="default"):
|
||||
"""Update list of Django objects in one SQL query, optionally only
|
||||
overwrite the given fields (as names, e.g. fields=["foo"]).
|
||||
Objects must be of the same Django model. Note that save is not
|
||||
called and signals on the model are not raised."""
|
||||
if not objects:
|
||||
return
|
||||
|
||||
import django.db.models
|
||||
from django.db import connections
|
||||
con = connections[using]
|
||||
|
||||
names = fields
|
||||
meta = objects[0]._meta
|
||||
fields = [f for f in meta.fields if not isinstance(f, django.db.models.AutoField) and (not names or f.name in names)]
|
||||
|
||||
if not fields:
|
||||
raise ValueError("No fields to update, field names are %s." % names)
|
||||
|
||||
fields_with_pk = fields + [meta.pk]
|
||||
parameters = []
|
||||
for o in objects:
|
||||
parameters.append(tuple(f.get_db_prep_save(f.pre_save(o, True), connection=con) for f in fields_with_pk))
|
||||
|
||||
table = meta.db_table
|
||||
assignments = ",".join(("%s=%%s"% con.ops.quote_name(f.column)) for f in fields)
|
||||
con.cursor().executemany(
|
||||
"update %s set %s where %s=%%s" % (table, assignments, con.ops.quote_name(meta.pk.column)),
|
||||
parameters)
|
@@ -9,7 +9,7 @@ from django.template import RequestContext
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from notification import models as notification
|
||||
from api.events import server_broadcast, user_message
|
||||
from api.events import Dispatcher
|
||||
|
||||
def index(request):
|
||||
forums = models.Forum.objects.filter(parent=None)
|
||||
@@ -45,7 +45,7 @@ def reply(request, topicID=None):
|
||||
if reply.parent.user != request.user:
|
||||
notification.send([reply.parent.user], "forum_reply", {"reply": reply, 'notice_url': reverse('forums.views.post', kwargs={'id':reply.id}), 'notice_description': reply.topic().title})
|
||||
messages.info(request, "Reply successful")
|
||||
user_message(reply.parent.user, "%s replied to your post in '%s'",
|
||||
Dispatcher.user_message(reply.parent.user, "%s replied to your post in '%s'",
|
||||
request.user, reply.topic().title)
|
||||
return HttpResponseRedirect(reverse('forums.views.post', kwargs={"id":reply.id}))
|
||||
return render_to_response('forums/reply.html', {"parent":parentPost, "form":form}, context_instance = RequestContext(request))
|
||||
@@ -84,7 +84,7 @@ def newTopic(request, forumID=None):
|
||||
topic.rootPost = reply
|
||||
topic.save()
|
||||
messages.info(request, "Posting successful")
|
||||
server_broadcast("New forum topic: %s", topic.title)
|
||||
Dispatcher.broadcast("New forum topic: %s", topic.title)
|
||||
return HttpResponseRedirect(reverse('forums.views.post', kwargs={'id': reply.id}))
|
||||
return render_to_response('forums/newTopic.html', {"forum":parentForum, "replyForm":replyForm, "topicForm": topicForm}, context_instance = RequestContext(request))
|
||||
|
||||
|
@@ -12,7 +12,7 @@ from django.contrib.auth import authenticate, login
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
import forms
|
||||
import models
|
||||
from api.events import user_message
|
||||
from api.events import Dispatcher
|
||||
from forums.models import Forum
|
||||
from minecraft.forms import ProfileForm
|
||||
from minecraft.models import MinecraftProfile
|
||||
@@ -80,7 +80,7 @@ def register(request):
|
||||
profile.save()
|
||||
user = authenticate(username=userForm.cleaned_data['username'], password=userForm.cleaned_data['password'])
|
||||
notification.send_now([invite.creator], "invite_accepted", {"new_user": user})
|
||||
user_message(invite.creator, "%s has accepted your invite."%(user.username))
|
||||
Dispatcher.user_message(invite.creator, "%s has accepted your invite."%(user.username))
|
||||
login(request, user)
|
||||
del request.session['profile-invite']
|
||||
return HttpResponseRedirect(reverse('welcome'))
|
||||
|
@@ -8,9 +8,3 @@ Replace this with more appropriate tests for your application.
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.assertEqual(1 + 1, 2)
|
||||
|
@@ -1,6 +1,9 @@
|
||||
import models
|
||||
|
||||
def server_info(request):
|
||||
period = models.Server.objects.all()[0].day_period()
|
||||
try:
|
||||
period = models.Server.objects.all()[0].day_period()
|
||||
except IndexError, e:
|
||||
period = "daytime"
|
||||
return {'minecraft_servers': models.Server.objects.all(),
|
||||
'first_day_period': period}
|
||||
|
@@ -178,5 +178,6 @@ import api.events
|
||||
def kick_banned_user(sender, instance, created, **kwargs):
|
||||
player = instance.player
|
||||
if player.isBanned():
|
||||
api.events.ServerQueue.broadcast(api.events.KickEvent(player.mc_username, instance.reason))
|
||||
api.events.ServerQueue.broadcast(api.events.Event('player-kick',
|
||||
player=player.mc_username, message=instance.reason))
|
||||
post_save.connect(kick_banned_user, sender=Ban)
|
||||
|
@@ -8,7 +8,7 @@ from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from notification import models as notification
|
||||
from django.contrib import messages
|
||||
from api.events import user_message
|
||||
from api.events import Dispatcher
|
||||
|
||||
@login_required
|
||||
def create(request):
|
||||
@@ -24,7 +24,7 @@ def create(request):
|
||||
adminUsers = User.objects.filter(is_staff=True)
|
||||
notification.send(adminUsers, "petition_opened", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id})
|
||||
for user in adminUsers:
|
||||
user_message(user, "%s has opened a petition."%(request.user))
|
||||
Dispatcher.user_message(user, "%s has opened a petition."%(request.user))
|
||||
messages.info(request, "Petition created.")
|
||||
return HttpResponseRedirect(reverse('petition.views.view', kwargs={"id":petition.id}))
|
||||
return render_to_response('petition/create.html', {'form':form}, context_instance = RequestContext(request))
|
||||
@@ -59,7 +59,7 @@ def comment(request, id):
|
||||
comment.save()
|
||||
adminUsers = User.objects.filter(is_staff=True)
|
||||
for user in adminUsers:
|
||||
user_message(user, "%s has opened a petition."%(request.user))
|
||||
Dispatcher.user_message(user, "%s has opened a petition."%(request.user))
|
||||
notification.send(adminUsers, "petition_commented", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id, 'comment': comment})
|
||||
if comment.author != petition.author:
|
||||
notification.send([petition.author], "petition_commented", {"petition": petition, 'notice_url': reverse('petition.views.view', kwargs={'id':petition.id}),'notice_description': petition.id, 'comment': comment})
|
||||
|
@@ -4,9 +4,10 @@ if [ ! -f virtualenv/bin/activate ];then
|
||||
fi
|
||||
source virtualenv/bin/activate
|
||||
pip install -r pip-requirements
|
||||
coverage erase
|
||||
coverage run ./manage.py test $@
|
||||
ret=$?
|
||||
if [ $ret -eq 0 ];then
|
||||
coverage report -m --include=\* --omit=\*/migrations/\*,settings.py,local_settings.py,manage.py
|
||||
coverage report -m --include=\* --omit=\*/migrations/\*,settings.py,local_settings.py,manage.py,bulkops.py
|
||||
fi
|
||||
exit $ret
|
||||
|
@@ -96,7 +96,6 @@ TEMPLATE_LOADERS = (
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'caminus.devtools.PrintExceptionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
|
@@ -1,9 +1,10 @@
|
||||
from django.db import models
|
||||
from minecraft.models import MinecraftProfile
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
from api.events import Event, ServerQueue, send_web_event
|
||||
from api.events import Event, ServerQueue, WebQueue
|
||||
from minecraft.models import Item
|
||||
from django.conf import settings
|
||||
from caminus.bulkops import insert_many
|
||||
|
||||
class VaultError(Exception):
|
||||
pass
|
||||
@@ -86,7 +87,7 @@ def send_vault_update(sender, instance, created, *args, **kwargs):
|
||||
]
|
||||
evt = Event('vault-contents', player=instance.player.mc_username, items=slots)
|
||||
ServerQueue.broadcast(evt)
|
||||
send_web_event(evt)
|
||||
WebQueue.broadcast(evt)
|
||||
|
||||
post_save.connect(send_vault_update, sender=VaultSlot, dispatch_uid='derp')
|
||||
|
||||
@@ -96,8 +97,9 @@ def ensure_initial_slots(sender, instance, created, *args, **kwargs):
|
||||
neededPositions = range(0, settings.CAMINUS_VAULT_SLOTS)
|
||||
for s in slots:
|
||||
neededPositions.remove(s.position)
|
||||
bulk = []
|
||||
for pos in neededPositions:
|
||||
VaultSlot.objects.create(player=instance, position=pos)
|
||||
bulk.append(VaultSlot(player=instance, position=pos))
|
||||
insert_many(bulk)
|
||||
|
||||
post_save.connect(ensure_initial_slots, sender=MinecraftProfile)
|
||||
post_delete.connect(ensure_initial_slots, sender=VaultSlot, dispatch_uid='derp')
|
||||
|
Reference in New Issue
Block a user