summaryrefslogtreecommitdiffstats
path: root/appengine_django/tests
diff options
context:
space:
mode:
Diffstat (limited to 'appengine_django/tests')
-rw-r--r--appengine_django/tests/__init__.py56
-rwxr-xr-xappengine_django/tests/commands_test.py183
-rwxr-xr-xappengine_django/tests/core_test.py37
-rwxr-xr-xappengine_django/tests/db_test.py62
-rw-r--r--appengine_django/tests/memcache_test.py43
-rwxr-xr-xappengine_django/tests/model_test.py110
-rwxr-xr-xappengine_django/tests/serialization_test.py310
7 files changed, 801 insertions, 0 deletions
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<format><name>'. 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": """<?xml version="1.0" encoding="utf-8"?>\n"""
+ """<django-objects version="1.0">\n"""
+ """<entity kind="tests.modela" key="ahhnb29nbGUtYXBwL"""
+ """WVuZ2luZS1kamFuZ29yIgsSBk1vZGVsQiIGcGFyZW50DA"""
+ """sSBk1vZGVsQSIEdGVzdAw">\n """
+ """<key>tag:google-app-engine-django.gmail.com,"""
+ """2008-05-13:ModelA[ahhnb29nbGUtYXBwLWVuZ2luZS1kam"""
+ """FuZ29yIgsSBk1vZGVsQiIGcGFyZW50DAsSBk1vZGVsQSIEdGVzdAw"""
+ """]</key>\n <property name="description" """
+ """type="null"></property>\n</entity>\n</django-objects>"""
+ }
+
+ # 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()