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. ...@@ -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) * 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 python -m 'parse_rest.tests'
tests.run_tests()
Usage Usage
......
...@@ -55,8 +55,7 @@ class ParseBase(object): ...@@ -55,8 +55,7 @@ class ParseBase(object):
command. command.
""" """
if batch: if batch:
ret = {"method": http_verb, ret = {"method": http_verb, "path": uri.split("parse.com", 1)[1]}
"path": uri.split("parse.com")[1]}
if kw: if kw:
ret["body"] = kw ret["body"] = kw
return ret return ret
...@@ -126,7 +125,9 @@ class ParseBatcher(ParseBase): ...@@ -126,7 +125,9 @@ class ParseBatcher(ParseBase):
Given a list of create, update or delete methods to call, call all Given a list of create, update or delete methods to call, call all
of them in a single batch operation. 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 # perform all the operations in one batch
responses = self.execute("", "POST", requests=queries) responses = self.execute("", "POST", requests=queries)
# perform the callbacks with the response data (updating the existing # perform the callbacks with the response data (updating the existing
...@@ -136,8 +137,8 @@ class ParseBatcher(ParseBase): ...@@ -136,8 +137,8 @@ class ParseBatcher(ParseBase):
def batch_save(self, objects): def batch_save(self, objects):
"""save a list of objects in one operation""" """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): def batch_delete(self, objects):
"""delete a list of objects in one operation""" """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 @@ ...@@ -13,8 +13,6 @@
import json import json
import collections import collections
import copy
import six
class QueryResourceDoesNotExist(Exception): class QueryResourceDoesNotExist(Exception):
...@@ -38,7 +36,7 @@ class QueryManager(object): ...@@ -38,7 +36,7 @@ class QueryManager(object):
return [klass(**it) for it in klass.GET(uri, **kw).get('results')] return [klass(**it) for it in klass.GET(uri, **kw).get('results')]
def _count(self, **kw): 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') return self.model_class.GET(self.model_class.ENDPOINT_ROOT, **kw).get('count')
def all(self): def all(self):
...@@ -54,22 +52,7 @@ class QueryManager(object): ...@@ -54,22 +52,7 @@ class QueryManager(object):
return self.filter(**kw).get() return self.filter(**kw).get()
class QuerysetMetaclass(type): class Queryset(object):
"""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)):
OPERATORS = [ OPERATORS = [
'lt', 'lte', 'gt', 'gte', 'ne', 'in', 'nin', 'exists', 'select', 'dontSelect', 'all', 'relatedTo' 'lt', 'lte', 'gt', 'gte', 'ne', 'in', 'nin', 'exists', 'select', 'dontSelect', 'all', 'relatedTo'
...@@ -99,7 +82,9 @@ class Queryset(six.with_metaclass(QuerysetMetaclass, object)): ...@@ -99,7 +82,9 @@ class Queryset(six.with_metaclass(QuerysetMetaclass, object)):
return iter(self._fetch()) return iter(self._fetch())
def __len__(self): 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): def __getitem__(self, key):
if isinstance(key, slice): if isinstance(key, slice):
...@@ -107,7 +92,7 @@ class Queryset(six.with_metaclass(QuerysetMetaclass, object)): ...@@ -107,7 +92,7 @@ class Queryset(six.with_metaclass(QuerysetMetaclass, object)):
return self._fetch()[key] return self._fetch()[key]
def _fetch(self, count=False): 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 len(self._result_cache) if count else self._result_cache
""" """
Return a list of objects matching query, or if count == True return Return a list of objects matching query, or if count == True return
...@@ -141,6 +126,14 @@ class Queryset(six.with_metaclass(QuerysetMetaclass, object)): ...@@ -141,6 +126,14 @@ class Queryset(six.with_metaclass(QuerysetMetaclass, object)):
raise ValueError("Cannot filter for a constraint after filtering for a specific value") raise ValueError("Cannot filter for a constraint after filtering for a specific value")
return self 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): def order_by(self, order, descending=False):
# add a minus sign before the order value if descending == True # add a minus sign before the order value if descending == True
self._options['order'] = descending and ('-' + order) or order self._options['order'] = descending and ('-' + order) or order
...@@ -151,7 +144,7 @@ class Queryset(six.with_metaclass(QuerysetMetaclass, object)): ...@@ -151,7 +144,7 @@ class Queryset(six.with_metaclass(QuerysetMetaclass, object)):
return self return self
def count(self): def count(self):
return len(self) return self._fetch(count=True)
def exists(self): def exists(self):
return bool(self) return bool(self)
......
...@@ -12,6 +12,7 @@ import subprocess ...@@ -12,6 +12,7 @@ import subprocess
import unittest import unittest
import datetime import datetime
import six import six
from itertools import chain
from parse_rest.core import ResourceRequestNotFound from parse_rest.core import ResourceRequestNotFound
from parse_rest.connection import register, ParseBatcher from parse_rest.connection import register, ParseBatcher
...@@ -72,23 +73,16 @@ class CollectedItem(Object): ...@@ -72,23 +73,16 @@ class CollectedItem(Object):
class TestObject(unittest.TestCase): class TestObject(unittest.TestCase):
def setUp(self): def setUp(self):
self.score = GameScore( self.score = GameScore(score=1337, player_name='John Doe', cheat_mode=False)
score=1337, player_name='John Doe', cheat_mode=False self.sao_paulo = City(name='São Paulo', location=GeoPoint(-23.5, -46.6167))
)
self.sao_paulo = City(
name='São Paulo', location=GeoPoint(-23.5, -46.6167)
)
def tearDown(self): def tearDown(self):
city_name = getattr(self.sao_paulo, 'name', None) city_name = getattr(self.sao_paulo, 'name', None)
game_score = getattr(self.score, 'score', None) game_score = getattr(self.score, 'score', None)
if city_name: if city_name:
for city in City.Query.filter(name=city_name): ParseBatcher().batch_delete(City.Query.filter(name=city_name))
city.delete()
if game_score: if game_score:
for score in GameScore.Query.filter(score=game_score): ParseBatcher().batch_delete(GameScore.Query.filter(score=game_score))
score.delete()
def testCanInitialize(self): def testCanInitialize(self):
self.assertEqual(self.score.score, 1337, 'Could not set score') self.assertEqual(self.score.score, 1337, 'Could not set score')
...@@ -149,8 +143,7 @@ class TestObject(unittest.TestCase): ...@@ -149,8 +143,7 @@ class TestObject(unittest.TestCase):
def testBatch(self): def testBatch(self):
"""test saving, updating and deleting objects in batches""" """test saving, updating and deleting objects in batches"""
scores = [GameScore(score=s, player_name='Jane', cheat_mode=False) scores = [GameScore(score=s, player_name='Jane', cheat_mode=False) for s in range(5)]
for s in range(5)]
batcher = ParseBatcher() batcher = ParseBatcher()
batcher.batch_save(scores) batcher.batch_save(scores)
self.assertEqual(GameScore.Query.filter(player_name='Jane').count(), 5, self.assertEqual(GameScore.Query.filter(player_name='Jane').count(), 5,
...@@ -208,22 +201,33 @@ class TestTypes(unittest.TestCase): ...@@ -208,22 +201,33 @@ class TestTypes(unittest.TestCase):
class TestQuery(unittest.TestCase): class TestQuery(unittest.TestCase):
"""Tests of an object's Queryset""" """Tests of an object's Queryset"""
def setUp(self):
@classmethod
def setUpClass(cls):
"""save a bunch of GameScore objects with varying scores""" """save a bunch of GameScore objects with varying scores"""
# first delete any that exist # first delete any that exist
for s in GameScore.Query.all(): ParseBatcher().batch_delete(GameScore.Query.all())
s.delete() ParseBatcher().batch_delete(Game.Query.all())
for g in Game.Query.all():
g.delete()
self.game = Game(title="Candyland") cls.game = Game(title="Candyland")
self.game.save() cls.game.save()
self.scores = [ cls.scores = [GameScore(score=s, player_name='John Doe', game=cls.game) for s in range(1, 6)]
GameScore(score=s, player_name='John Doe', game=self.game) ParseBatcher().batch_save(cls.scores)
for s in range(1, 6)]
for s in self.scores: @classmethod
s.save() 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): def testExists(self):
"""test the Queryset.exists() method""" """test the Queryset.exists() method"""
...@@ -258,66 +262,66 @@ class TestQuery(unittest.TestCase): ...@@ -258,66 +262,66 @@ class TestQuery(unittest.TestCase):
last_week = datetime.datetime.now() - datetime.timedelta(days=7) last_week = datetime.datetime.now() - datetime.timedelta(days=7)
score = GameScore(name='test', last_played=last_week) score = GameScore(name='test', last_played=last_week)
score.save() score.save()
self.assertTrue(GameScore.Query.filter(last_played=last_week).exists(), self.test_objects.append(score)
'Could not run query with dates') self.assertTrue(GameScore.Query.filter(last_played=last_week).exists(), 'Could not run query with dates')
def testComparisons(self): def testComparisons(self):
"""test comparison operators- gt, gte, lt, lte, ne""" """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.assertEqual(len(scores_gt_3), 2)
self.assertTrue(all([s.score > 3 for s in scores_gt_3])) 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.assertEqual(len(scores_gte_3), 3)
self.assertTrue(all([s.score >= 3 for s in scores_gt_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.assertEqual(len(scores_lt_4), 3)
self.assertTrue(all([s.score < 4 for s in scores_lt_4])) 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.assertEqual(len(scores_lte_4), 4)
self.assertTrue(all([s.score <= 4 for s in scores_lte_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.assertEqual(len(scores_ne_2), 4)
self.assertTrue(all([s.score != 2 for s in scores_ne_2])) self.assertTrue(all([s.score != 2 for s in scores_ne_2]))
# test chaining # 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(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') self.assertEqual(lt_4_gt_2[0].score, 3, 'chained lt+gt not working')
q = GameScore.Query.filter(score__gt=3, score__lt=3) q = GameScore.Query.filter(score__gt=3, score__lt=3)
self.assertFalse(q.exists(), "chained lt+gt not working") self.assertFalse(q.exists(), "chained lt+gt not working")
def testOptions(self): def testOrderBy(self):
"""test three options- order, limit, and skip""" """test three options- order, limit, and skip"""
scores_ordered = list(GameScore.Query.all().order_by("score")) scores_ordered = GameScore.Query.all().order_by("score")
self.assertEqual([s.score for s in scores_ordered], self.assertEqual([s.score for s in scores_ordered], [1, 2, 3, 4, 5])
[1, 2, 3, 4, 5])
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_ordered_desc = list(GameScore.Query.all().order_by("score", descending=True)) def testLimit(self):
self.assertEqual([s.score for s in scores_ordered_desc], q = GameScore.Query.all().limit(3)
[5, 4, 3, 2, 1]) self.assertEqual(len(q), 3)
scores_limit_3 = list(GameScore.Query.all().limit(3)) def testSkip(self):
self.assertTrue(len(scores_limit_3) == 3, "Limit did not return 3 items") q = GameScore.Query.all().skip(3)
self.assertEqual(len(q), 2)
scores_skip_3 = list(GameScore.Query.all().skip(3)) def testSelectRelated(self):
self.assertTrue(len(scores_skip_3) == 2, "Skip did not return 2 items") 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): def testCanCompareDateInequality(self):
today = datetime.datetime.today() today = datetime.datetime.today()
tomorrow = today + datetime.timedelta(days=1) tomorrow = today + datetime.timedelta(days=1)
self.assertTrue(GameScore.Query.filter(createdAt__lte=tomorrow).count() == 5, self.assertEqual(GameScore.Query.filter(createdAt__lte=tomorrow).count(), 5,
'Could not make inequality comparison with dates') '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()
class TestFunction(unittest.TestCase): class TestFunction(unittest.TestCase):
def setUp(self): def setUp(self):
...@@ -339,8 +343,7 @@ class TestFunction(unittest.TestCase): ...@@ -339,8 +343,7 @@ class TestFunction(unittest.TestCase):
os.chdir(original_dir) os.chdir(original_dir)
def tearDown(self): def tearDown(self):
for review in Review.Query.all(): ParseBatcher().batch_delete(Review.Query.all())
review.delete()
def test_simple_functions(self): def test_simple_functions(self):
"""test hello world and averageStars functions""" """test hello world and averageStars functions"""
...@@ -400,8 +403,8 @@ class TestUser(unittest.TestCase): ...@@ -400,8 +403,8 @@ class TestUser(unittest.TestCase):
def testCanSignUp(self): def testCanSignUp(self):
self._destroy_user() self._destroy_user()
user = User.signup(self.username, self.password) user = User.signup(self.username, self.password)
self.assertTrue(user is not None) self.assertIsNotNone(user)
self.assertTrue(user.username == self.username) self.assertEqual(user.username, self.username)
def testCanLogin(self): def testCanLogin(self):
self._get_user() # User should be created here. 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