Commit 8aaea7fd by Roman Krejcik

test improvements - added simple test for select related, optimized number of queries in tests

made skip and limit consistent with other query methods
parent c6106771
......@@ -52,10 +52,9 @@ in the app and may accidentally replace or change existing objects.
* install the [Parse CloudCode tool](https://www.parse.com/docs/cloud_code_guide)
You can then test the installation by running the following in a Python prompt:
You can then test the installation by running the following command:
from parse_rest import tests
tests.run_tests()
python -m 'parse_rest.tests'
Usage
......
......@@ -55,8 +55,7 @@ class ParseBase(object):
command.
"""
if batch:
ret = {"method": http_verb,
"path": uri.split("parse.com")[1]}
ret = {"method": http_verb, "path": uri.split("parse.com", 1)[1]}
if kw:
ret["body"] = kw
return ret
......@@ -126,7 +125,9 @@ class ParseBatcher(ParseBase):
Given a list of create, update or delete methods to call, call all
of them in a single batch operation.
"""
queries, callbacks = zip(*[m(batch=True) for m in methods])
#accepts also empty list (or generator) - it allows call batch directly
# with query result (eventually empty)
queries, callbacks = list(zip(*[m(batch=True) for m in methods])) or ([], [])
# perform all the operations in one batch
responses = self.execute("", "POST", requests=queries)
# perform the callbacks with the response data (updating the existing
......@@ -136,8 +137,8 @@ class ParseBatcher(ParseBase):
def batch_save(self, objects):
"""save a list of objects in one operation"""
self.batch([o.save for o in objects])
self.batch(o.save for o in objects)
def batch_delete(self, objects):
"""delete a list of objects in one operation"""
self.batch([o.delete for o in objects])
self.batch(o.delete for o in objects)
......@@ -13,8 +13,6 @@
import json
import collections
import copy
import six
class QueryResourceDoesNotExist(Exception):
......@@ -38,7 +36,7 @@ class QueryManager(object):
return [klass(**it) for it in klass.GET(uri, **kw).get('results')]
def _count(self, **kw):
kw.update({"count": 1, "limit": 0})
kw.update({"count": 1})
return self.model_class.GET(self.model_class.ENDPOINT_ROOT, **kw).get('count')
def all(self):
......@@ -54,22 +52,7 @@ class QueryManager(object):
return self.filter(**kw).get()
class QuerysetMetaclass(type):
"""metaclass to add the dynamically generated comparison functions"""
def __new__(mcs, name, bases, dct):
cls = super(QuerysetMetaclass, mcs).__new__(mcs, name, bases, dct)
for fname in ['limit', 'skip']:
def func(self, value, fname=fname):
s = copy.deepcopy(self)
s._options[fname] = int(value)
return s
setattr(cls, fname, func)
return cls
class Queryset(six.with_metaclass(QuerysetMetaclass, object)):
class Queryset(object):
OPERATORS = [
'lt', 'lte', 'gt', 'gte', 'ne', 'in', 'nin', 'exists', 'select', 'dontSelect', 'all', 'relatedTo'
......@@ -99,7 +82,9 @@ class Queryset(six.with_metaclass(QuerysetMetaclass, object)):
return iter(self._fetch())
def __len__(self):
return self._fetch(count=True)
#don't use count query for len operator
#count doesn't return real size of result in all cases (eg if query contains skip option)
return len(self._fetch())
def __getitem__(self, key):
if isinstance(key, slice):
......@@ -107,7 +92,7 @@ class Queryset(six.with_metaclass(QuerysetMetaclass, object)):
return self._fetch()[key]
def _fetch(self, count=False):
if self._result_cache:
if self._result_cache is not None:
return len(self._result_cache) if count else self._result_cache
"""
Return a list of objects matching query, or if count == True return
......@@ -141,6 +126,14 @@ class Queryset(six.with_metaclass(QuerysetMetaclass, object)):
raise ValueError("Cannot filter for a constraint after filtering for a specific value")
return self
def limit(self, value):
self._options['limit'] = int(value)
return self
def skip(self, value):
self._options['skip'] = int(value)
return self
def order_by(self, order, descending=False):
# add a minus sign before the order value if descending == True
self._options['order'] = descending and ('-' + order) or order
......@@ -151,7 +144,7 @@ class Queryset(six.with_metaclass(QuerysetMetaclass, object)):
return self
def count(self):
return len(self)
return self._fetch(count=True)
def exists(self):
return bool(self)
......
......@@ -12,6 +12,7 @@ import subprocess
import unittest
import datetime
import six
from itertools import chain
from parse_rest.core import ResourceRequestNotFound
from parse_rest.connection import register, ParseBatcher
......@@ -72,23 +73,16 @@ class CollectedItem(Object):
class TestObject(unittest.TestCase):
def setUp(self):
self.score = GameScore(
score=1337, player_name='John Doe', cheat_mode=False
)
self.sao_paulo = City(
name='São Paulo', location=GeoPoint(-23.5, -46.6167)
)
self.score = GameScore(score=1337, player_name='John Doe', cheat_mode=False)
self.sao_paulo = City(name='São Paulo', location=GeoPoint(-23.5, -46.6167))
def tearDown(self):
city_name = getattr(self.sao_paulo, 'name', None)
game_score = getattr(self.score, 'score', None)
if city_name:
for city in City.Query.filter(name=city_name):
city.delete()
ParseBatcher().batch_delete(City.Query.filter(name=city_name))
if game_score:
for score in GameScore.Query.filter(score=game_score):
score.delete()
ParseBatcher().batch_delete(GameScore.Query.filter(score=game_score))
def testCanInitialize(self):
self.assertEqual(self.score.score, 1337, 'Could not set score')
......@@ -149,8 +143,7 @@ class TestObject(unittest.TestCase):
def testBatch(self):
"""test saving, updating and deleting objects in batches"""
scores = [GameScore(score=s, player_name='Jane', cheat_mode=False)
for s in range(5)]
scores = [GameScore(score=s, player_name='Jane', cheat_mode=False) for s in range(5)]
batcher = ParseBatcher()
batcher.batch_save(scores)
self.assertEqual(GameScore.Query.filter(player_name='Jane').count(), 5,
......@@ -208,22 +201,33 @@ class TestTypes(unittest.TestCase):
class TestQuery(unittest.TestCase):
"""Tests of an object's Queryset"""
def setUp(self):
@classmethod
def setUpClass(cls):
"""save a bunch of GameScore objects with varying scores"""
# first delete any that exist
for s in GameScore.Query.all():
s.delete()
for g in Game.Query.all():
g.delete()
ParseBatcher().batch_delete(GameScore.Query.all())
ParseBatcher().batch_delete(Game.Query.all())
self.game = Game(title="Candyland")
self.game.save()
cls.game = Game(title="Candyland")
cls.game.save()
self.scores = [
GameScore(score=s, player_name='John Doe', game=self.game)
for s in range(1, 6)]
for s in self.scores:
s.save()
cls.scores = [GameScore(score=s, player_name='John Doe', game=cls.game) for s in range(1, 6)]
ParseBatcher().batch_save(cls.scores)
@classmethod
def tearDownClass(cls):
'''delete all GameScore and Game objects'''
ParseBatcher().batch_delete(chain(cls.scores, [cls.game]))
def setUp(self):
self.test_objects = []
def tearDown(self):
'''delete additional helper objects created in perticular tests'''
if self.test_objects:
ParseBatcher().batch_delete(self.test_objects)
self.test_objects = []
def testExists(self):
"""test the Queryset.exists() method"""
......@@ -258,65 +262,65 @@ class TestQuery(unittest.TestCase):
last_week = datetime.datetime.now() - datetime.timedelta(days=7)
score = GameScore(name='test', last_played=last_week)
score.save()
self.assertTrue(GameScore.Query.filter(last_played=last_week).exists(),
'Could not run query with dates')
self.test_objects.append(score)
self.assertTrue(GameScore.Query.filter(last_played=last_week).exists(), 'Could not run query with dates')
def testComparisons(self):
"""test comparison operators- gt, gte, lt, lte, ne"""
scores_gt_3 = list(GameScore.Query.filter(score__gt=3))
scores_gt_3 = GameScore.Query.filter(score__gt=3)
self.assertEqual(len(scores_gt_3), 2)
self.assertTrue(all([s.score > 3 for s in scores_gt_3]))
scores_gte_3 = list(GameScore.Query.filter(score__gte=3))
scores_gte_3 = GameScore.Query.filter(score__gte=3)
self.assertEqual(len(scores_gte_3), 3)
self.assertTrue(all([s.score >= 3 for s in scores_gt_3]))
scores_lt_4 = list(GameScore.Query.filter(score__lt=4))
scores_lt_4 = GameScore.Query.filter(score__lt=4)
self.assertEqual(len(scores_lt_4), 3)
self.assertTrue(all([s.score < 4 for s in scores_lt_4]))
scores_lte_4 = list(GameScore.Query.filter(score__lte=4))
scores_lte_4 = GameScore.Query.filter(score__lte=4)
self.assertEqual(len(scores_lte_4), 4)
self.assertTrue(all([s.score <= 4 for s in scores_lte_4]))
scores_ne_2 = list(GameScore.Query.filter(score__ne=2))
scores_ne_2 = GameScore.Query.filter(score__ne=2)
self.assertEqual(len(scores_ne_2), 4)
self.assertTrue(all([s.score != 2 for s in scores_ne_2]))
# test chaining
lt_4_gt_2 = list(GameScore.Query.filter(score__lt=4).filter(score__gt=2))
lt_4_gt_2 = GameScore.Query.filter(score__lt=4).filter(score__gt=2)
self.assertEqual(len(lt_4_gt_2), 1, 'chained lt+gt not working')
self.assertEqual(lt_4_gt_2[0].score, 3, 'chained lt+gt not working')
q = GameScore.Query.filter(score__gt=3, score__lt=3)
self.assertFalse(q.exists(), "chained lt+gt not working")
def testOptions(self):
def testOrderBy(self):
"""test three options- order, limit, and skip"""
scores_ordered = list(GameScore.Query.all().order_by("score"))
self.assertEqual([s.score for s in scores_ordered],
[1, 2, 3, 4, 5])
scores_ordered = GameScore.Query.all().order_by("score")
self.assertEqual([s.score for s in scores_ordered], [1, 2, 3, 4, 5])
scores_ordered_desc = list(GameScore.Query.all().order_by("score", descending=True))
self.assertEqual([s.score for s in scores_ordered_desc],
[5, 4, 3, 2, 1])
scores_ordered_desc = GameScore.Query.all().order_by("score", descending=True)
self.assertEqual([s.score for s in scores_ordered_desc], [5, 4, 3, 2, 1])
scores_limit_3 = list(GameScore.Query.all().limit(3))
self.assertTrue(len(scores_limit_3) == 3, "Limit did not return 3 items")
def testLimit(self):
q = GameScore.Query.all().limit(3)
self.assertEqual(len(q), 3)
scores_skip_3 = list(GameScore.Query.all().skip(3))
self.assertTrue(len(scores_skip_3) == 2, "Skip did not return 2 items")
def testSkip(self):
q = GameScore.Query.all().skip(3)
self.assertEqual(len(q), 2)
def testSelectRelated(self):
score = GameScore.Query.all().select_related('game').limit(1)[0]
self.assertTrue(score.game.objectId)
#nice to have - also check no more then one query is triggered
def testCanCompareDateInequality(self):
today = datetime.datetime.today()
tomorrow = today + datetime.timedelta(days=1)
self.assertTrue(GameScore.Query.filter(createdAt__lte=tomorrow).count() == 5,
'Could not make inequality comparison with dates')
def tearDown(self):
'''delete all GameScore and Game objects'''
for s in GameScore.Query.all():
s.delete()
self.game.delete()
self.assertEqual(GameScore.Query.filter(createdAt__lte=tomorrow).count(), 5,
'Could not make inequality comparison with dates')
class TestFunction(unittest.TestCase):
......@@ -339,8 +343,7 @@ class TestFunction(unittest.TestCase):
os.chdir(original_dir)
def tearDown(self):
for review in Review.Query.all():
review.delete()
ParseBatcher().batch_delete(Review.Query.all())
def test_simple_functions(self):
"""test hello world and averageStars functions"""
......@@ -400,8 +403,8 @@ class TestUser(unittest.TestCase):
def testCanSignUp(self):
self._destroy_user()
user = User.signup(self.username, self.password)
self.assertTrue(user is not None)
self.assertTrue(user.username == self.username)
self.assertIsNotNone(user)
self.assertEqual(user.username, self.username)
def testCanLogin(self):
self._get_user() # User should be created here.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment