From ae078728c8bc1c7d4412c4cb19b6802e9b7e8f74 Mon Sep 17 00:00:00 2001 From: bryan newbold Date: Sun, 14 Jun 2009 19:51:50 -0400 Subject: initial appengine setup; BROKEN --- appengine_django/tests/__init__.py | 56 +++++ appengine_django/tests/commands_test.py | 183 ++++++++++++++++ appengine_django/tests/core_test.py | 37 ++++ appengine_django/tests/db_test.py | 62 ++++++ appengine_django/tests/memcache_test.py | 43 ++++ appengine_django/tests/model_test.py | 110 ++++++++++ appengine_django/tests/serialization_test.py | 310 +++++++++++++++++++++++++++ 7 files changed, 801 insertions(+) create mode 100644 appengine_django/tests/__init__.py create mode 100755 appengine_django/tests/commands_test.py create mode 100755 appengine_django/tests/core_test.py create mode 100755 appengine_django/tests/db_test.py create mode 100644 appengine_django/tests/memcache_test.py create mode 100755 appengine_django/tests/model_test.py create mode 100755 appengine_django/tests/serialization_test.py (limited to 'appengine_django/tests') diff --git a/appengine_django/tests/__init__.py b/appengine_django/tests/__init__.py new file mode 100644 index 0000000..b511f58 --- /dev/null +++ b/appengine_django/tests/__init__.py @@ -0,0 +1,56 @@ +#!/usr/bin/python2.4 +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Loads all the _test.py files into the top level of the package. + +This file is a hack around the fact that Django expects the tests "module" to +be a single tests.py file and cannot handle a tests package inside an +application. + +All _test.py files inside this package are imported and any classes derived +from unittest.TestCase are then referenced from this file itself so that they +appear at the top level of the tests "module" that Django will import. +""" + + +import os +import re +import types +import unittest + +TEST_RE = r"^.*_test.py$" + +# Search through every file inside this package. +test_names = [] +test_dir = os.path.dirname( __file__) +for filename in os.listdir(test_dir): + if not re.match(TEST_RE, filename): + continue + # Import the test file and find all TestClass clases inside it. + test_module = __import__('appengine_django.tests.%s' % + filename[:-3], {}, {}, + filename[:-3]) + for name in dir(test_module): + item = getattr(test_module, name) + if not (isinstance(item, (type, types.ClassType)) and + issubclass(item, unittest.TestCase)): + continue + # Found a test, bring into the module namespace. + exec "%s = item" % name + test_names.append(name) + +# Hide everything other than the test cases from other modules. +__all__ = test_names diff --git a/appengine_django/tests/commands_test.py b/appengine_django/tests/commands_test.py new file mode 100755 index 0000000..a02ddbf --- /dev/null +++ b/appengine_django/tests/commands_test.py @@ -0,0 +1,183 @@ +#!/usr/bin/python2.4 +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Tests that the manage.py commands execute correctly. + +These tests only verify that the commands execute and exit with a success code. +They are intended to catch import exceptions and similar problems, it is left +up to tests in other modules to verify that the functionality of each command +works correctly. +""" + + +import os +import re +import signal +import subprocess +import tempfile +import time +import unittest + +from django.db.models import get_models + +from google.appengine.ext import db +from appengine_django.models import BaseModel +from appengine_django.models import ModelManager +from appengine_django.models import ModelOptions +from appengine_django.models import RegistrationTestModel + + +class CommandsTest(unittest.TestCase): + """Unit tests for the manage.py commands.""" + + # How many seconds to wait for a command to exit. + COMMAND_TIMEOUT = 10 + + def runCommand(self, command, args=None, int_after=None, input=None): + """Helper to run the specified command in a child process. + + Args: + command: The name of the command to run. + args: List of command arguments to run the command with. + int_after: If set to a positive integer, SIGINT will be sent to the + running child process after this many seconds to cause an exit. This + should be less than the COMMAND_TIMEOUT value (10 seconds). + input: A string to write to stdin when the command starts. stdin is + closed after the string is written. + + Returns: + rc: The integer return code of the process. + output: A string containing the childs output. + """ + if not args: + args = [] + start = time.time() + int_sent = False + fd = subprocess.PIPE + + child = subprocess.Popen(["./manage.py", command] + args, stdin=fd, + stdout=fd, stderr=fd, cwd=os.getcwdu()) + if input: + child.stdin.write(input) + child.stdin.close() + + while 1: + rc = child.poll() + if rc is not None: + # Child has exited. + break + elapsed = time.time() - start + if int_after and int_after > 0 and elapsed > int_after and not int_sent: + # Sent SIGINT as requested, give child time to exit cleanly. + os.kill(child.pid, signal.SIGINT) + start = time.time() + int_sent = True + continue + if elapsed < self.COMMAND_TIMEOUT: + continue + # Command is over time, kill and exit loop. + os.kill(child.pid, signal.SIGKILL) + time.sleep(2) # Give time for the signal to be received. + break + + # Return status and output. + return rc, child.stdout.read(), child.stderr.read() + + def assertCommandSucceeds(self, command, *args, **kwargs): + """Asserts that the specified command successfully completes. + + Args: + command: The name of the command to run. + All other arguments are passed directly through to the runCommand + routine. + + Raises: + This function does not return anything but will raise assertion errors if + the command does not exit successfully. + """ + rc, stdout, stderr = self.runCommand(command, *args, **kwargs) + fd, tempname = tempfile.mkstemp() + os.write(fd, stdout) + os.close(fd) + self.assertEquals(0, rc, + "%s did not return successfully (rc: %d): Output in %s" % + (command, rc, tempname)) + os.unlink(tempname) + + def getCommands(self): + """Returns a list of valid commands for manage.py. + + Args: + None + + Returns: + A list of valid commands for manage.py as read from manage.py's help + output. + """ + rc, stdout, stderr = self.runCommand("help") + parts = re.split("Available subcommands:", stderr) + if len(parts) < 2: + return [] + + return [t.strip() for t in parts[-1].split("\n") if t.strip()] + + def testDiffSettings(self): + """Tests the diffsettings command.""" + self.assertCommandSucceeds("diffsettings") + + def testDumpData(self): + """Tests the dumpdata command.""" + self.assertCommandSucceeds("dumpdata") + + def testFlush(self): + """Tests the flush command.""" + self.assertCommandSucceeds("flush") + + def testLoadData(self): + """Tests the loaddata command.""" + self.assertCommandSucceeds("loaddata") + + def testLoadData(self): + """Tests the loaddata command.""" + self.assertCommandSucceeds("loaddata") + + def testReset(self): + """Tests the reste command.""" + self.assertCommandSucceeds("reset", ["appengine_django"]) + + def testRunserver(self): + """Tests the runserver command.""" + self.assertCommandSucceeds("runserver", int_after=2.0) + + def testShell(self): + """Tests the shell command.""" + self.assertCommandSucceeds("shell", input="exit") + + def testUpdate(self): + """Tests that the update command exists. + + Cannot test that it works without mocking out parts of dev_appserver so for + now we just assume that if it is present it will work. + """ + cmd_list = self.getCommands() + self.assert_("update" in cmd_list) + + def testZipCommandListFiltersCorrectly(self): + """When running under a zipfile test that only valid commands are found.""" + cmd_list = self.getCommands() + self.assert_("__init__" not in cmd_list) + self.assert_("base" not in cmd_list) diff --git a/appengine_django/tests/core_test.py b/appengine_django/tests/core_test.py new file mode 100755 index 0000000..7454177 --- /dev/null +++ b/appengine_django/tests/core_test.py @@ -0,0 +1,37 @@ +#!/usr/bin/python2.4 +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests that the core module functionality is present and functioning.""" + + +import unittest + +from appengine_django import appid +from appengine_django import have_appserver + + +class AppengineDjangoTest(unittest.TestCase): + """Tests that the helper module has been correctly installed.""" + + def testAppidProvided(self): + """Tests that application ID and configuration has been loaded.""" + self.assert_(appid is not None) + + def testAppserverDetection(self): + """Tests that the appserver detection flag is present and correct.""" + # It seems highly unlikely that these tests would ever be run from within + # an appserver. + self.assertEqual(have_appserver, False) diff --git a/appengine_django/tests/db_test.py b/appengine_django/tests/db_test.py new file mode 100755 index 0000000..452e8f9 --- /dev/null +++ b/appengine_django/tests/db_test.py @@ -0,0 +1,62 @@ +#!/usr/bin/python2.4 +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests that the db module correctly initialises the API stubs.""" + + +import unittest + +from django.db import connection +from django.db.backends.appengine.base import DatabaseWrapper + +from appengine_django import appid +from appengine_django.db import base + + +class DatastoreTest(unittest.TestCase): + """Tests that the datastore stubs have been correctly setup.""" + + def testDjangoDBConnection(self): + """Tests that the Django DB connection is using our replacement.""" + self.assert_(isinstance(connection, DatabaseWrapper)) + + def testDjangoDBConnectionStubs(self): + """Tests that members required by Django are stubbed.""" + self.assert_(hasattr(connection, "features")) + self.assert_(hasattr(connection, "ops")) + + def testDjangoDBErrorClasses(self): + """Tests that the error classes required by Django are stubbed.""" + self.assert_(hasattr(base, "DatabaseError")) + self.assert_(hasattr(base, "IntegrityError")) + + def testDatastorePath(self): + """Tests that the datastore path contains the app name.""" + d_path, h_path = base.get_datastore_paths() + self.assertNotEqual(-1, d_path.find("django_%s" % appid)) + self.assertNotEqual(-1, h_path.find("django_%s" % appid)) + + def testTestInMemoryDatastorePath(self): + """Tests that the test datastore is using the in-memory datastore.""" + td_path, th_path = base.get_test_datastore_paths() + self.assert_(td_path is None) + self.assert_(th_path is None) + + def testTestFilesystemDatastorePath(self): + """Tests that the test datastore is on the filesystem when requested.""" + td_path, th_path = base.get_test_datastore_paths(False) + self.assertNotEqual(-1, td_path.find("testdatastore")) + self.assertNotEqual(-1, th_path.find("testdatastore")) diff --git a/appengine_django/tests/memcache_test.py b/appengine_django/tests/memcache_test.py new file mode 100644 index 0000000..4e5f02e --- /dev/null +++ b/appengine_django/tests/memcache_test.py @@ -0,0 +1,43 @@ +#!/usr/bin/python2.4 +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Ensures the App Engine memcache API works as Django's memcache backend.""" + +import unittest + +from django.core.cache import get_cache +from appengine_django import appid +from appengine_django import have_appserver + + +class AppengineMemcacheTest(unittest.TestCase): + """Tests that the memcache backend works.""" + + def setUp(self): + """Get the memcache cache module so it is available to tests.""" + self._cache = get_cache("memcached://") + + def testSimpleSetGet(self): + """Tests that a simple set/get operation through the cache works.""" + self._cache.set("test_key", "test_value") + self.assertEqual(self._cache.get("test_key"), "test_value") + + def testDelete(self): + """Tests that delete removes values from the cache.""" + self._cache.set("test_key", "test_value") + self.assertEqual(self._cache.has_key("test_key"), True) + self._cache.delete("test_key") + self.assertEqual(self._cache.has_key("test_key"), False) diff --git a/appengine_django/tests/model_test.py b/appengine_django/tests/model_test.py new file mode 100755 index 0000000..8611d8b --- /dev/null +++ b/appengine_django/tests/model_test.py @@ -0,0 +1,110 @@ +#!/usr/bin/python2.4 +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests that the combined appengine and Django models function correctly.""" + + +import unittest + +from django import VERSION +from django.db.models import get_models +from django import forms + +from google.appengine.ext.db import djangoforms +from google.appengine.ext import db + +from appengine_django.models import BaseModel +from appengine_django.models import ModelManager +from appengine_django.models import ModelOptions +from appengine_django.models import RegistrationTestModel + + +class TestModelWithProperties(BaseModel): + """Test model class for checking property -> Django field setup.""" + property1 = db.StringProperty() + property2 = db.IntegerProperty() + property3 = db.Reference() + + +class ModelTest(unittest.TestCase): + """Unit tests for the combined model class.""" + + def testModelRegisteredWithDjango(self): + """Tests that a combined model class has been registered with Django.""" + self.assert_(RegistrationTestModel in get_models()) + + def testDatastoreModelProperties(self): + """Tests that a combined model class still has datastore properties.""" + self.assertEqual(3, len(TestModelWithProperties.properties())) + + def testDjangoModelClass(self): + """Tests the parts of a model required by Django are correctly stubbed.""" + # Django requires model options to be found at ._meta. + self.assert_(isinstance(RegistrationTestModel._meta, ModelOptions)) + # Django requires a manager at .objects + self.assert_(isinstance(RegistrationTestModel.objects, ModelManager)) + # Django requires ._default_manager. + self.assert_(hasattr(RegistrationTestModel, "_default_manager")) + + def testDjangoModelFields(self): + """Tests that a combined model class has (faked) Django fields.""" + fields = TestModelWithProperties._meta.local_fields + self.assertEqual(3, len(fields)) + # Check each fake field has the minimal properties that Django needs. + for field in fields: + # The Django serialization code looks for rel to determine if the field + # is a relationship/reference to another model. + self.assert_(hasattr(field, "rel")) + # serialize is required to tell Django to serialize the field. + self.assertEqual(True, field.serialize) + if field.name == "property3": + # Extra checks for the Reference field. + # rel.field_name is used during serialization to find the field in the + # other model that this field is related to. This should always be + # 'key_name' for appengine models. + self.assertEqual("key_name", field.rel.field_name) + + def testDjangoModelOptionsStub(self): + """Tests that the options stub has the required properties by Django.""" + # Django requires object_name and app_label for serialization output. + self.assertEqual("RegistrationTestModel", + RegistrationTestModel._meta.object_name) + self.assertEqual("appengine_django", RegistrationTestModel._meta.app_label) + # The pk.name member is required during serialization for dealing with + # related fields. + self.assertEqual("key_name", RegistrationTestModel._meta.pk.name) + # The many_to_many method is called by Django in the serialization code to + # find m2m relationships. m2m is not supported by the datastore. + self.assertEqual([], RegistrationTestModel._meta.many_to_many) + + def testDjangoModelManagerStub(self): + """Tests that the manager stub acts as Django would expect.""" + # The serialization code calls model.objects.all() to retrieve all objects + # to serialize. + self.assertEqual([], list(RegistrationTestModel.objects.all())) + + def testDjangoModelPK(self): + """Tests that each model instance has a 'primary key' generated.""" + obj = RegistrationTestModel(key_name="test") + obj.put() + pk = obj._get_pk_val() + self.assert_(pk) + new_obj = RegistrationTestModel.get(pk) + self.assertEqual(obj.key(), new_obj.key()) + + def testModelFormPatched(self): + """Tests that the Django ModelForm is being successfully patched.""" + self.assertEqual(djangoforms.ModelForm, forms.ModelForm) diff --git a/appengine_django/tests/serialization_test.py b/appengine_django/tests/serialization_test.py new file mode 100755 index 0000000..39b92ea --- /dev/null +++ b/appengine_django/tests/serialization_test.py @@ -0,0 +1,310 @@ +#!/usr/bin/python2.4 +# +# Copyright 2008 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests that the serialization modules are functioning correctly. + +In particular, these tests verify that the modifications made to the standard +Django serialization modules function correctly and that the combined datastore +and Django models can be dumped and loaded to all of the provided formats. +""" + + +import os +import re +import unittest +from StringIO import StringIO + +from django.core import serializers + +from google.appengine.ext import db +from appengine_django.models import BaseModel + + +class ModelA(BaseModel): + description = db.StringProperty() + + +class ModelB(BaseModel): + description = db.StringProperty() + friend = db.Reference(ModelA) + + +class TestAllFormats(type): + + def __new__(cls, name, bases, attrs): + """Extends base test functions to be called for every serialisation format. + + Looks for functions matching 'run.*Test', where the wildcard in the middle + matches the desired test name and ensures that a test case is setup to call + that function once for every defined serialisation format. The test case + that is created will be called 'test'. Eg, for the function + 'runKeyedObjectTest' functions like 'testJsonKeyedObject' will be created. + """ + test_formats = serializers.get_serializer_formats() + test_formats.remove("python") # Python serializer is only used indirectly. + + for func_name in attrs.keys(): + m = re.match("^run(.*)Test$", func_name) + if not m: + continue + for format in test_formats: + test_name = "test%s%s" % (format.title(), m.group(1)) + test_func = eval("lambda self: getattr(self, \"%s\")(\"%s\")" % + (func_name, format)) + attrs[test_name] = test_func + + return super(TestAllFormats, cls).__new__(cls, name, bases, attrs) + + +class SerializationTest(unittest.TestCase): + """Unit tests for the serialization/deserialization functionality. + + Tests that every loaded serialization format can successfully dump and then + reload objects without the objects changing. + """ + __metaclass__ = TestAllFormats + + def compareObjects(self, orig, new, format="unknown"): + """Compares two objects to ensure they are identical. + + Args: + orig: The original object, must be an instance of db.Model. + new: The new object, must be an instance of db.Model. + format: The serialization format being tested, used to make error output + more helpful. + + Raises: + The function has no return value, but will raise assertion errors if the + objects do not match correctly. + """ + if orig.key().name(): + # Only compare object keys when the key is named. Key IDs are not static + # and will change between dump/load. If you want stable Keys they need to + # be named! + self.assertEqual(orig.key(), new.key(), + "keys not equal after %s serialization: %s != %s" % + (format, repr(orig.key()), repr(new.key()))) + + for key in orig.properties().keys(): + oval = getattr(orig, key) + nval = getattr(new, key) + if isinstance(orig.properties()[key], db.Reference): + # Need to compare object keys not the objects themselves. + oval = oval.key() + nval = nval.key() + self.assertEqual(oval, nval, "%s attribute differs after %s " + "serialization: %s != %s" % (key, format, oval, nval)) + + def doSerialisationTest(self, format, obj, rel_attr=None, obj_ref=None): + """Runs a serialization test on an object for the specified format. + + Args: + format: The name of the Django serialization class to use. + obj: The object to {,de}serialize, must be an instance of db.Model. + rel_attr: Name of the attribute of obj references another model. + obj_ref: The expected object reference, must be an instance of db.Model. + + Raises: + The function has no return value but raises assertion errors if the + object cannot be successfully serialized and then deserialized back to an + identical object. If rel_attr and obj_ref are specified the deserialized + object must also retain the references from the original object. + """ + serialised = serializers.serialize(format, [obj]) + # Try and get the object back from the serialized string. + result = list(serializers.deserialize(format, StringIO(serialised))) + self.assertEqual(1, len(result), + "%s serialization should create 1 object" % format) + result[0].save() # Must save back into the database to get a Key. + self.compareObjects(obj, result[0].object, format) + if rel_attr and obj_ref: + rel = getattr(result[0].object, rel_attr) + if callable(rel): + rel = rel() + self.compareObjects(rel, obj_ref, format) + + def doLookupDeserialisationReferenceTest(self, lookup_dict, format): + """Tests the Key reference is loaded OK for a format. + + Args: + lookup_dict: A dictionary indexed by format containing serialized strings + of the objects to load. + format: The format to extract from the dict and deserialize. + + Raises: + This function has no return value but raises assertion errors if the + string cannot be deserialized correctly or the resulting object does not + reference the object correctly. + """ + if format not in lookup_dict: + # Check not valid for this format. + return + obj = ModelA(description="test object", key_name="test") + obj.put() + s = lookup_dict[format] + result = list(serializers.deserialize(format, StringIO(s))) + self.assertEqual(1, len(result), "expected 1 object from %s" % format) + result[0].save() + self.compareObjects(obj, result[0].object.friend, format) + + def doModelKeyDeserialisationReferenceTest(self, lookup_dict, format): + """Tests a model with a key can be loaded OK for a format. + + Args: + lookup_dict: A dictionary indexed by format containing serialized strings + of the objects to load. + format: The format to extract from the dict and deserialize. + + Returns: + This function has no return value but raises assertion errors if the + string cannot be deserialized correctly or the resulting object is not an + instance of ModelA with a key named 'test'. + """ + if format not in lookup_dict: + # Check not valid for this format. + return + s = lookup_dict[format] + result = list(serializers.deserialize(format, StringIO(s))) + self.assertEqual(1, len(result), "expected 1 object from %s" % format) + result[0].save() + self.assert_(isinstance(result[0].object, ModelA)) + self.assertEqual("test", result[0].object.key().name()) + + # Lookup dicts for the above (doLookupDeserialisationReferenceTest) function. + SERIALIZED_WITH_KEY_AS_LIST = { + "json": """[{"pk": "agR0ZXN0chMLEgZNb2RlbEIiB21vZGVsYmkM", """ + """"model": "tests.modelb", "fields": {"description": "test", """ + """"friend": ["ModelA", "test"] }}]""", + "yaml": """- fields: {description: !!python/unicode 'test', friend: """ + """ [ModelA, test]}\n model: tests.modelb\n pk: """ + """ agR0ZXN0chMLEgZNb2RlbEEiB21vZGVsYWkM\n""" + } + SERIALIZED_WITH_KEY_REPR = { + "json": """[{"pk": "agR0ZXN0chMLEgZNb2RlbEIiB21vZGVsYmkM", """ + """"model": "tests.modelb", "fields": {"description": "test", """ + """"friend": "datastore_types.Key.from_path(""" + """'ModelA', 'test')" }}]""", + "yaml": """- fields: {description: !!python/unicode 'test', friend: """ + """\'datastore_types.Key.from_path("ModelA", "test")\'}\n """ + """model: tests.modelb\n pk: """ + """ agR0ZXN0chMLEgZNb2RlbEEiB21vZGVsYWkM\n""" + } + + # Lookup dict for the doModelKeyDeserialisationReferenceTest function. + MK_SERIALIZED_WITH_LIST = { + "json": """[{"pk": ["ModelA", "test"], "model": "tests.modela", """ + """"fields": {}}]""", + "yaml": """-\n fields: {description: null}\n model: tests.modela\n """ + """pk: [ModelA, test]\n""" + } + MK_SERIALIZED_WITH_KEY_REPR = { + "json": """[{"pk": "datastore_types.Key.from_path('ModelA', 'test')", """ + """"model": "tests.modela", "fields": {}}]""", + "yaml": """-\n fields: {description: null}\n model: tests.modela\n """ + """pk: \'datastore_types.Key.from_path("ModelA", "test")\'\n""" + } + MK_SERIALIZED_WITH_KEY_AS_TEXT = { + "json": """[{"pk": "test", "model": "tests.modela", "fields": {}}]""", + "yaml": """-\n fields: {description: null}\n model: tests.modela\n """ + """pk: test\n""" + } + + # Lookup dict for the function. + SERIALIZED_WITH_NON_EXISTANT_PARENT = { + "json": """[{"pk": "ahhnb29nbGUtYXBwLWVuZ2luZS1kamFuZ29yIgsSBk1vZG""" + """VsQiIGcGFyZW50DAsSBk1vZGVsQSIEdGVzdAw", """ + """"model": "tests.modela", "fields": """ + """{"description": null}}]""", + "yaml": """- fields: {description: null}\n """ + """model: tests.modela\n """ + """pk: ahhnb29nbGUtYXBwLWVuZ2luZS1kamFuZ29yIgsSBk1""" + """vZGVsQiIGcGFyZW50DAsSBk1vZGVsQSIEdGVzdAw\n""", + "xml": """\n""" + """\n""" + """\n """ + """tag:google-app-engine-django.gmail.com,""" + """2008-05-13:ModelA[ahhnb29nbGUtYXBwLWVuZ2luZS1kam""" + """FuZ29yIgsSBk1vZGVsQiIGcGFyZW50DAsSBk1vZGVsQSIEdGVzdAw""" + """]\n \n\n""" + } + + # The following functions are all expanded by the metaclass to be run once + # for every registered Django serialization module. + + def runKeyedObjectTest(self, format): + """Test serialization of a basic object with a named key.""" + obj = ModelA(description="test object", key_name="test") + obj.put() + self.doSerialisationTest(format, obj) + + def runObjectWithIdTest(self, format): + """Test serialization of a basic object with a numeric ID key.""" + obj = ModelA(description="test object") + obj.put() + self.doSerialisationTest(format, obj) + + def runObjectWithReferenceTest(self, format): + """Test serialization of an object that references another object.""" + obj = ModelA(description="test object", key_name="test") + obj.put() + obj2 = ModelB(description="friend object", friend=obj) + obj2.put() + self.doSerialisationTest(format, obj2, "friend", obj) + + def runObjectWithParentTest(self, format): + """Test serialization of an object that has a parent object reference.""" + obj = ModelA(description="parent object", key_name="parent") + obj.put() + obj2 = ModelA(description="child object", key_name="child", parent=obj) + obj2.put() + self.doSerialisationTest(format, obj2, "parent", obj) + + def runObjectWithNonExistantParentTest(self, format): + """Test deserialization of an object referencing a non-existant parent.""" + self.doModelKeyDeserialisationReferenceTest( + self.SERIALIZED_WITH_NON_EXISTANT_PARENT, format) + + def runCreateKeyReferenceFromListTest(self, format): + """Tests that a reference specified as a list in json/yaml can be loaded OK.""" + self.doLookupDeserialisationReferenceTest(self.SERIALIZED_WITH_KEY_AS_LIST, + format) + + def runCreateKeyReferenceFromReprTest(self, format): + """Tests that a reference specified as repr(Key) in can loaded OK.""" + self.doLookupDeserialisationReferenceTest(self.SERIALIZED_WITH_KEY_REPR, + format) + + def runCreateModelKeyFromListTest(self, format): + """Tests that a model key specified as a list can be loaded OK.""" + self.doModelKeyDeserialisationReferenceTest(self.MK_SERIALIZED_WITH_LIST, + format) + + def runCreateModelKeyFromReprTest(self, format): + """Tests that a model key specified as a repr(Key) can be loaded OK.""" + self.doModelKeyDeserialisationReferenceTest( + self.MK_SERIALIZED_WITH_KEY_REPR, format) + + def runCreateModelKeyFromTextTest(self, format): + """Tests that a reference specified as a plain key_name loads OK.""" + self.doModelKeyDeserialisationReferenceTest( + self.MK_SERIALIZED_WITH_KEY_AS_TEXT, format) + + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3