From 0ecb171f941e63bf79b42225f2f301d82ff22551 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Wed, 23 Mar 2016 12:00:18 -0400 Subject: [PATCH 1/8] Add coverage module. --- .gitignore | 4 +++- requirements.txt | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c16b943..13a62c5 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ config.json # PyCharm .idea ->>>>>>> d818a0e1981417ebc795923ddc504dc124f62a16 + +# Nosetests Coverage +.coverage diff --git a/requirements.txt b/requirements.txt index 2f33974..0d6a6fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ botocore==1.3.28 cement==2.4.0 cffi==1.5.2 colorama==0.3.3 +coverage==4.0.3 docker-py==1.1.0 dockerpty==0.3.4 docopt==0.6.2 @@ -18,6 +19,7 @@ Jinja2==2.8 jmespath==0.9.0 MarkupSafe==0.23 needs==1.0.9 +nose==1.3.7 oauth2client==2.0.1 parse==1.6.6 pathspec==0.3.3 -- GitLab From df72e9124a9598241dd37b2070daf58d59e52f7d Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Wed, 23 Mar 2016 12:03:00 -0400 Subject: [PATCH 2/8] Add a test to check that views 404 when given a bad id. --- app/tests/test_project.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/tests/test_project.py b/app/tests/test_project.py index 282fba1..1af533d 100644 --- a/app/tests/test_project.py +++ b/app/tests/test_project.py @@ -21,6 +21,11 @@ class TestProject(RestTestCase): model = self.env.project self._test_get(model.id) + def test_get_bad_id(self): + """Tests /project/ GET with a bad id. It should 404.""" + response = self.get(self.url + str(1)) + self.assertTrue(response.status_code, 404) + def test_post(self): """Tests /project/ POST. -- GitLab From 8e2b55181c696fcab3206ecd5fde62499374e4ba Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Wed, 23 Mar 2016 17:06:03 -0400 Subject: [PATCH 3/8] Add support for embedding clients in project post requests from salesforce. --- app/controllers/project.py | 31 ++++++++++++++++++++- app/tests/test_project.py | 56 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/app/controllers/project.py b/app/controllers/project.py index f79edea..b7558dd 100644 --- a/app/controllers/project.py +++ b/app/controllers/project.py @@ -1,8 +1,14 @@ """Controllers for managing top-level projects.""" from werkzeug.exceptions import BadRequest + from app.lib.database import db from app.controllers.base import RestController + +from app.models.client import Client from app.models.project import Project, ProjectStateChange + +from app.controllers.client import ClientController + from app.forms.project import ProjectForm @@ -22,9 +28,32 @@ class ProjectController(RestController): self.commit() def post(self, data, filter_data): - """Logs a model state change after saving a new model.""" + """Post a new model. + + This logs a model state change after saving a new model. It + includes support for salesforce, which dumps all submodels (e.g + client, contacts, ...) in addition to the model itself. + """ + # A project has to have a client, so create or modify the client model + # before posting the project when performing a bulk upload. + if filter_data.get('form') == 'salesforce': + try: + client_data = data['client'] + except KeyError: + raise BadRequest( + 'SalesForce projects require an embedded client.') + client = db.session.query(Client)\ + .filter(Client.sales_force_id == client_data['sales_force_id'])\ + .first() + if client: + client = ClientController().put(client.id, client_data, {}) + else: + client = ClientController().post(client_data, {}) + data.update({'client_id': client.id}) + model = super(ProjectController, self).post(data, filter_data) self.log_state_change(model) + return model def put(self, id_, data, filter_data): diff --git a/app/tests/test_project.py b/app/tests/test_project.py index 329841b..eac4b29 100644 --- a/app/tests/test_project.py +++ b/app/tests/test_project.py @@ -3,6 +3,7 @@ from sqlalchemy import and_ from app.lib.database import db from app.tests.base import RestTestCase +from app.models.client import Client from app.models.project import Project, ProjectStateChange @@ -53,6 +54,61 @@ class TestProject(RestTestCase): ProjectStateChange.state == data['state']))\ .first()) + def test_post_sf_client(self): + """Tests /project/?form=salesforce POST with a new client.""" + client_data = {'sales_force_id': 'test', 'name': 'test'} + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': client_data} + response_data = self._test_post(data, {'form': 'salesforce'}) + # Check that a client was posted with the new data. + self.assertTrue( + db.session.query(Client)\ + .join(Project, Project.client_id == Client.id) + .filter(and_( + Project.id == response_data['id'], + Client.name == client_data['name'], + Client.sales_force_id == client_data['sales_force_id']))\ + .first()) + + def test_post_sf_change_client(self): + """Tests /project/?form=salesforce POST with a modified client.""" + client = self.env.client + client_data = client.get_dictionary() + client_data.update({'name': 'foo'}) + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': client_data} + response_data = self._test_post(data, {'form': 'salesforce'}) + # Check that the client was modified as expected. + self.assertTrue( + db.session.query(Client)\ + .join(Project, Project.client_id == Client.id) + .filter(and_( + Project.id == response_data['id'], + Client.id == client.id, + Client.name == client_data['name']))\ + .first()) + + def test_post_sf_no_client(self): + """Tests /project/?form=salesforce POST with no client. This should + 400. + """ + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending' + } + response = self.post( + self.url, + data=data, + query_string={'form': 'salesforce'}) + self.assertEqual(response.status_code, 400) + def test_post_missing_name(self): """Tests /project/ POST with a missing name.""" response = self.post(self.url, -- GitLab From f14f7788b9b4a21f70d0baf7e0caba49eda17a48 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Wed, 23 Mar 2016 19:21:56 -0400 Subject: [PATCH 4/8] Add support for embedded contacts in the /project/?form=salesforce POST endpoint. --- app/controllers/project.py | 49 ++++++++++++++++++++++- app/tests/test_project.py | 80 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 125 insertions(+), 4 deletions(-) diff --git a/app/controllers/project.py b/app/controllers/project.py index b7558dd..76261bf 100644 --- a/app/controllers/project.py +++ b/app/controllers/project.py @@ -1,13 +1,16 @@ """Controllers for managing top-level projects.""" +from sqlalchemy import and_ from werkzeug.exceptions import BadRequest from app.lib.database import db from app.controllers.base import RestController from app.models.client import Client +from app.models.contact import Contact, ProjectContact from app.models.project import Project, ProjectStateChange from app.controllers.client import ClientController +from app.controllers.contact import ContactController, ProjectContactController from app.forms.project import ProjectForm @@ -33,6 +36,13 @@ class ProjectController(RestController): This logs a model state change after saving a new model. It includes support for salesforce, which dumps all submodels (e.g client, contacts, ...) in addition to the model itself. + + Behavior with the embedded models is a little odd. Rather than + validating everything up front, we validate as we go. Except for + the project itself, an object will successfully save if all + subsequent saves succeeded. For example, this means that a client + may be created even if the request 400s due to a problem with the + project itself. """ # A project has to have a client, so create or modify the client model # before posting the project when performing a bulk upload. @@ -52,8 +62,45 @@ class ProjectController(RestController): data.update({'client_id': client.id}) model = super(ProjectController, self).post(data, filter_data) - self.log_state_change(model) + if filter_data.get('form') == 'salesforce': + try: + try: + contacts_data = data['contacts'] + except KeyError: + raise BadRequest( + 'SalesForce projects require embedded contacts.') + + for contact_data in contacts_data: + # Create the contact if it does not exist. + contact = db.session.query(Contact)\ + .filter(Contact.sales_force_id == \ + contact_data['sales_force_id'])\ + .first() + if contact: + contact = ContactController().put( + contact.id, contact_data, {}) + else: + contact = ContactController().post(contact_data, {}) + + # Associate the contact with the project. + project_contact = db.session.query(ProjectContact)\ + .filter(and_( + ProjectContact.project_id == model.id, + ProjectContact.contact_id == contact.id))\ + .first() + if not project_contact: + ProjectContactController().post( + {'project_id': model.id, 'contact_id': contact.id}, + {}) + + except BadRequest as e: + # Do not keep the project if contacts failed to upload. + db.session.delete(model) + self.commit() + raise e + + self.log_state_change(model) return model def put(self, id_, data, filter_data): diff --git a/app/tests/test_project.py b/app/tests/test_project.py index eac4b29..a6fc987 100644 --- a/app/tests/test_project.py +++ b/app/tests/test_project.py @@ -4,6 +4,7 @@ from sqlalchemy import and_ from app.lib.database import db from app.tests.base import RestTestCase from app.models.client import Client +from app.models.contact import Contact, ProjectContact from app.models.project import Project, ProjectStateChange @@ -61,7 +62,8 @@ class TestProject(RestTestCase): 'sales_force_id': 'test', 'name': 'test', 'state': 'pending', - 'client': client_data} + 'client': client_data, + 'contacts': []} response_data = self._test_post(data, {'form': 'salesforce'}) # Check that a client was posted with the new data. self.assertTrue( @@ -82,7 +84,8 @@ class TestProject(RestTestCase): 'sales_force_id': 'test', 'name': 'test', 'state': 'pending', - 'client': client_data} + 'client': client_data, + 'contacts': []} response_data = self._test_post(data, {'form': 'salesforce'}) # Check that the client was modified as expected. self.assertTrue( @@ -101,13 +104,84 @@ class TestProject(RestTestCase): data = { 'sales_force_id': 'test', 'name': 'test', - 'state': 'pending' + 'state': 'pending', + 'contacts': [] + } + response = self.post( + self.url, + data=data, + query_string={'form': 'salesforce'}) + self.assertEqual(response.status_code, 400) + + # There are additional ways that posting with a client could fail, but + # those are already tested in the /client/ endpoint, so there's no need + # to test them here. + + def test_post_sf_new_contacts(self): + """Tests /project/?form=salesforce POST with a new contact.""" + contact_data = {'sales_force_id': 'test', 'name': 'test'} + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': self.env.client.get_dictionary(), + 'contacts': [contact_data] + } + response_data = self._test_post(data, {'form': 'salesforce'}) + self.assertTrue( + db.session.query(Contact)\ + .join(ProjectContact, ProjectContact.contact_id == Contact.id)\ + .join(Project, ProjectContact.project_id == Project.id)\ + .filter(and_( + Project.id == response_data['id'], + Contact.sales_force_id == contact_data['sales_force_id'], + Contact.name == contact_data['name']))\ + .first()) + + def test_post_sf_change_contacts(self): + """Tests /project/?form=salesforce POST with a modified contact.""" + contact = self.env.contact + contact_data = contact.get_dictionary() + contact_data.update({'name': 'foo'}) + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': self.env.client.get_dictionary(), + 'contacts': [contact_data] + } + response_data = self._test_post(data, {'form': 'salesforce'}) + self.assertTrue( + db.session.query(Contact)\ + .join(ProjectContact, ProjectContact.contact_id == Contact.id)\ + .join(Project, ProjectContact.project_id == Project.id)\ + .filter(and_( + Project.id == response_data['id'], + Contact.id == contact.id, + Contact.sales_force_id == contact_data['sales_force_id'], + Contact.name == contact_data['name']))\ + .first()) + + def test_post_sf_no_contacts(self): + """Tests /project/?form=salesforce POST with no contacts. It should + 400. + """ + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': self.env.client.get_dictionary() } response = self.post( self.url, data=data, query_string={'form': 'salesforce'}) self.assertEqual(response.status_code, 400) + self.assertFalse(db.session.query(Project).first()) + + # There are additional ways that posting with a contact could fail, but + # those are already tested in the /contact/ and /contact/project/ + # endpoints, so there's no need to test them here. def test_post_missing_name(self): """Tests /project/ POST with a missing name.""" -- GitLab From ed19156defaf6ba800c1a6ea39414a1f348a7bfa Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 11:36:03 -0400 Subject: [PATCH 5/8] Add support for embedded contact methods in the /project/?form=salesforce POST endpoint. --- app/controllers/project.py | 36 +++++++++++- app/tests/test_project.py | 116 +++++++++++++++++++++++++++++++++---- 2 files changed, 139 insertions(+), 13 deletions(-) diff --git a/app/controllers/project.py b/app/controllers/project.py index 76261bf..ed43f90 100644 --- a/app/controllers/project.py +++ b/app/controllers/project.py @@ -6,11 +6,12 @@ from app.lib.database import db from app.controllers.base import RestController from app.models.client import Client -from app.models.contact import Contact, ProjectContact +from app.models.contact import Contact, ContactMethod, ProjectContact from app.models.project import Project, ProjectStateChange from app.controllers.client import ClientController -from app.controllers.contact import ContactController, ProjectContactController +from app.controllers.contact import ( + ContactController, ContactMethodController, ProjectContactController) from app.forms.project import ProjectForm @@ -67,6 +68,7 @@ class ProjectController(RestController): try: try: contacts_data = data['contacts'] + contact_methods_data = data['contact_methods'] except KeyError: raise BadRequest( 'SalesForce projects require embedded contacts.') @@ -94,6 +96,36 @@ class ProjectController(RestController): {'project_id': model.id, 'contact_id': contact.id}, {}) + for contact_method_data in contact_methods_data: + # Grab the contact by salesforce id. + contact = db.session.query(Contact)\ + .join(ProjectContact, + ProjectContact.contact_id == Contact.id)\ + .join(Project, Project.id == ProjectContact.project_id)\ + .filter(and_( + Project.id == model.id, + Contact.sales_force_id == \ + contact_method_data['contact_sales_force_id']))\ + .first() + if not contact: + raise BadRequest( + 'Contact method references a contact that is not ' + + 'associated with the project.') + + contact_method = db.session.query(ContactMethod)\ + .filter(and_( + ContactMethod.contact_id == contact.id, + ContactMethod.method == \ + contact_method_data['method'], + ContactMethod.value == \ + contact_method_data['value']))\ + .first() + if not contact_method: + contact_method_data.update({'contact_id': contact.id}) + contact_method = ContactMethodController().post( + contact_method_data, + {}) + except BadRequest as e: # Do not keep the project if contacts failed to upload. db.session.delete(model) diff --git a/app/tests/test_project.py b/app/tests/test_project.py index a6fc987..7f5f62c 100644 --- a/app/tests/test_project.py +++ b/app/tests/test_project.py @@ -4,7 +4,7 @@ from sqlalchemy import and_ from app.lib.database import db from app.tests.base import RestTestCase from app.models.client import Client -from app.models.contact import Contact, ProjectContact +from app.models.contact import Contact, ContactMethod, ProjectContact from app.models.project import Project, ProjectStateChange @@ -63,7 +63,8 @@ class TestProject(RestTestCase): 'name': 'test', 'state': 'pending', 'client': client_data, - 'contacts': []} + 'contacts': [], + 'contact_methods': []} response_data = self._test_post(data, {'form': 'salesforce'}) # Check that a client was posted with the new data. self.assertTrue( @@ -85,7 +86,8 @@ class TestProject(RestTestCase): 'name': 'test', 'state': 'pending', 'client': client_data, - 'contacts': []} + 'contacts': [], + 'contact_methods': []} response_data = self._test_post(data, {'form': 'salesforce'}) # Check that the client was modified as expected. self.assertTrue( @@ -105,8 +107,8 @@ class TestProject(RestTestCase): 'sales_force_id': 'test', 'name': 'test', 'state': 'pending', - 'contacts': [] - } + 'contacts': [], + 'contact_methods': []} response = self.post( self.url, data=data, @@ -125,8 +127,8 @@ class TestProject(RestTestCase): 'name': 'test', 'state': 'pending', 'client': self.env.client.get_dictionary(), - 'contacts': [contact_data] - } + 'contacts': [contact_data], + 'contact_methods': []} response_data = self._test_post(data, {'form': 'salesforce'}) self.assertTrue( db.session.query(Contact)\ @@ -148,8 +150,8 @@ class TestProject(RestTestCase): 'name': 'test', 'state': 'pending', 'client': self.env.client.get_dictionary(), - 'contacts': [contact_data] - } + 'contacts': [contact_data], + 'contact_methods': []} response_data = self._test_post(data, {'form': 'salesforce'}) self.assertTrue( db.session.query(Contact)\ @@ -170,8 +172,8 @@ class TestProject(RestTestCase): 'sales_force_id': 'test', 'name': 'test', 'state': 'pending', - 'client': self.env.client.get_dictionary() - } + 'client': self.env.client.get_dictionary(), + 'contact_methods': []} response = self.post( self.url, data=data, @@ -183,6 +185,98 @@ class TestProject(RestTestCase): # those are already tested in the /contact/ and /contact/project/ # endpoints, so there's no need to test them here. + def test_post_sf_contact_methods(self): + """Tests /project/?form=salesforce POST with a contact method.""" + contact_data = { + 'sales_force_id': 'test', + 'name': 'test'} + contact_method_data = { + 'contact_sales_force_id': contact_data['sales_force_id'], + 'method': 'email', + 'value': 'test@blocpower.org'} + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': self.env.client.get_dictionary(), + 'contacts': [contact_data], + 'contact_methods': [contact_method_data]} + response_data = self._test_post(data, {'form': 'salesforce'}) + self.assertTrue( + db.session.query(ContactMethod)\ + .join(Contact, ContactMethod.contact_id == Contact.id)\ + .join(ProjectContact, ProjectContact.contact_id == Contact.id)\ + .join(Project, ProjectContact.project_id == Project.id)\ + .filter(and_( + Project.id == response_data['id'], + ContactMethod.method == contact_method_data['method'], + ContactMethod.value == contact_method_data['value']))\ + .first()) + + def test_post_sf_contact_methods_bad_sf_id(self): + """Tests /project/?form=salesforce POST with a contact method with a + bad contact_sales_force_id. It should 400. + """ + contact_method_data = { + 'contact_sales_force_id': 'test', + 'method': 'email', + 'value': 'test@blocpower.org'} + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': self.env.client.get_dictionary(), + 'contacts': [], + 'contact_methods': [contact_method_data]} + response = self.post( + self.url, + data=data, + query_string={'form': 'salesforce'}) + self.assertEqual(response.status_code, 400) + + def test_post_sf_contact_methods_unassociated_sf_id(self): + """Tests /project/?form=salesforce POST with a contact method with a + contact_sales_force_id that is not associated with the project. It + should 400. + """ + contact = self.env.contact + contact_method_data = { + 'contact_sales_force_id': contact.sales_force_id, + 'method': 'email', + 'value': 'test@blocpower.org'} + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': self.env.client.get_dictionary(), + 'contacts': [], + 'contact_methods': [contact_method_data]} + response = self.post( + self.url, + data=data, + query_string={'form': 'salesforce'}) + self.assertEqual(response.status_code, 400) + + def test_post_sf_no_contact_methods(self): + """Tests /project/?form=salesforce POST with no contact methods. It + should 400. + """ + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': self.env.client.get_dictionary(), + 'contacts': []} + response = self.post( + self.url, + data=data, + query_string={'form': 'salesforce'}) + self.assertEqual(response.status_code, 400) + + # There are additional ways that posting with a contact method could fail, + # but those are already tested in the /contact/method/ endpoint, so + # there's no need to test them here. + def test_post_missing_name(self): """Tests /project/ POST with a missing name.""" response = self.post(self.url, -- GitLab From 3ff3cfad4092849769cb9081086292fc7af5f8de Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 15:42:33 -0400 Subject: [PATCH 6/8] Add support for embedding places and addresses in the /project/?form=salesforce POST endpoint. --- app/controllers/project.py | 47 +++++++++-- app/models/place.py | 4 +- app/tests/test_project.py | 158 +++++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 6 deletions(-) diff --git a/app/controllers/project.py b/app/controllers/project.py index f69f893..c9827ae 100644 --- a/app/controllers/project.py +++ b/app/controllers/project.py @@ -7,11 +7,13 @@ from app.controllers.base import RestController from app.models.client import Client from app.models.contact import Contact, ContactMethod, ProjectContact +from app.models.place import Place, Address from app.models.project import Project, ProjectStateChange from app.controllers.client import ClientController from app.controllers.contact import ( ContactController, ContactMethodController, ProjectContactController) +from app.controllers.place import PlaceController, AddressController from app.forms.project import ProjectForm @@ -45,23 +47,58 @@ class ProjectController(RestController): may be created even if the request 400s due to a problem with the project itself. """ - # A project has to have a client, so create or modify the client model + # A project has to have a client and a place, so we create these models # before posting the project when performing a bulk upload. if filter_data.get('form') == 'salesforce': try: client_data = data['client'] + place_data = data['place'] + address_data = data['address'] except KeyError: raise BadRequest( - 'SalesForce projects require an embedded client.') - client = db.session.query(Client)\ - .filter(Client.sales_force_id == client_data['sales_force_id'])\ - .first() + 'SalesForce projects require an embedded client, place, ' + + 'and address.') + + # Create/modify the client. + try: + client = db.session.query(Client)\ + .filter( + Client.sales_force_id == client_data['sales_force_id'])\ + .first() + except KeyError: + raise BadRequest( + 'When embedding a client in a salesforce project, please ' + + 'include its salesforce id.') if client: client = ClientController().put(client.id, client_data, {}) else: client = ClientController().post(client_data, {}) data.update({'client_id': client.id}) + # Create/modify the place and address. + try: + place = db.session.query(Place)\ + .filter( + Place.sales_force_id == place_data['sales_force_id'])\ + .first() + except KeyError: + raise BadRequest( + 'When embedding a place in a salesforce project, please ' + + 'include its salesforce id.') + + address = place.address if place else None + if address: + address = AddressController().put(address.id, address_data, {}) + else: + address = AddressController().post(address_data, {}) + place_data.update({'address_id': address.id}) + + if place: + place = PlaceController().put(place.id, place_data, {}) + else: + place = PlaceController().post(place_data, {}) + data.update({'place_id': place.id}) + model = super(ProjectController, self).post(data, filter_data) if filter_data.get('form') == 'salesforce': diff --git a/app/models/place.py b/app/models/place.py index b12d124..55e8bed 100644 --- a/app/models/place.py +++ b/app/models/place.py @@ -1,4 +1,4 @@ -from geoalchemy2 import Geometry +from sqlalchemy.orm import relationship from app.lib.database import db from app.lib import geography @@ -11,6 +11,8 @@ class Place(Model, Tracked, SalesForce, db.Model): address_id = db.Column( db.Integer, db.ForeignKey('address.id'), nullable=False) + address = relationship('Address', uselist=False) + class Address(Model, Tracked, db.Model): """The Address class""" diff --git a/app/tests/test_project.py b/app/tests/test_project.py index a19f1a1..fb3172b 100644 --- a/app/tests/test_project.py +++ b/app/tests/test_project.py @@ -5,6 +5,7 @@ from app.lib.database import db from app.tests.base import RestTestCase from app.models.client import Client from app.models.contact import Contact, ContactMethod, ProjectContact +from app.models.place import Place, Address from app.models.project import Project, ProjectStateChange @@ -59,11 +60,14 @@ class TestProject(RestTestCase): def test_post_sf_client(self): """Tests /project/?form=salesforce POST with a new client.""" client_data = {'sales_force_id': 'test', 'name': 'test'} + place = self.env.place data = { 'sales_force_id': 'test', 'name': 'test', 'state': 'pending', 'client': client_data, + 'place': place.get_dictionary(), + 'address': place.address.get_dictionary(), 'contacts': [], 'contact_methods': []} response_data = self._test_post(data, {'form': 'salesforce'}) @@ -82,11 +86,14 @@ class TestProject(RestTestCase): client = self.env.client client_data = client.get_dictionary() client_data.update({'name': 'foo'}) + place = self.env.place data = { 'sales_force_id': 'test', 'name': 'test', 'state': 'pending', 'client': client_data, + 'place': place.get_dictionary(), + 'address': place.address.get_dictionary(), 'contacts': [], 'contact_methods': []} response_data = self._test_post(data, {'form': 'salesforce'}) @@ -104,10 +111,13 @@ class TestProject(RestTestCase): """Tests /project/?form=salesforce POST with no client. This should 400. """ + place = self.env.place data = { 'sales_force_id': 'test', 'name': 'test', 'state': 'pending', + 'place': place.get_dictionary(), + 'address': place.address.get_dictionary(), 'contacts': [], 'contact_methods': []} response = self.post( @@ -120,14 +130,144 @@ class TestProject(RestTestCase): # those are already tested in the /client/ endpoint, so there's no need # to test them here. + def test_post_sf_new_place(self): + """Tests /project/?form=salesforce POST with a new place and address. + """ + place_data = {'sales_force_id': 'test', 'name': 'test'} + address_data = { + 'street_address': '15 MetroTech Center', + 'unit_number': 'Suite 1900', + 'city': 'Brooklyn', + 'county': 'Kings', + 'state': 'NY', + 'country': 'United States of America', + 'postal_code': '11210'} + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': self.env.client.get_dictionary(), + 'place': place_data, + 'address': address_data, + 'contacts': [], + 'contact_methods': []} + response_data = self._test_post(data, {'form': 'salesforce'}) + + filters = [Project.id == response_data['id']] + filters += [ + getattr(Place, k) == place_data.get(k) + for k in place_data.keys()] + filters += [ + getattr(Address, k) == address_data.get(k) + for k in address_data.keys()] + + self.assertTrue( + db.session.query(Address)\ + .join(Place, Place.address_id == Address.id)\ + .join(Project, Project.place_id == Place.id)\ + .filter(and_(*filters))\ + .first()) + + def test_post_sf_change_place(self): + """Tests /project/?form=salesforce POST with a modified place and + address. + """ + place = self.env.place + place_data = place.get_dictionary() + new_place_data = {'name': 'foo'} + place_data.update(new_place_data) + address_data = place.address.get_dictionary() + new_address_data = { + 'street_address': '111 N Fake St', + 'unit_number': '', + 'city': 'Raleigh', + 'county': 'Wake', + 'state': 'NC', + 'postal_code': '27615'} + address_data.update(new_address_data) + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': self.env.client.get_dictionary(), + 'place': place_data, + 'address': address_data, + 'contacts': [], + 'contact_methods': []} + response_data = self._test_post(data, {'form': 'salesforce'}) + + filters = [ + Project.id == response_data['id'], + Place.id == place.id, + Address.id == place.address.id] + filters += [ + getattr(Place, k) == place_data.get(k) + for k in new_place_data.keys()] + filters += [ + getattr(Address, k) == address_data.get(k) + for k in new_address_data.keys()] + + self.assertTrue( + db.session.query(Address)\ + .join(Place, Place.address_id == Address.id)\ + .join(Project, Project.place_id == Place.id)\ + .filter(and_(*filters))\ + .first()) + + def test_post_sf_no_place(self): + """Tests /project/?form=salesforce POST with no place. It should 400. + """ + address_data = { + 'street_address': '15 MetroTech Center', + 'unit_number': 'Suite 1900', + 'city': 'Brooklyn', + 'county': 'Kings', + 'state': 'NY', + 'country': 'United States of America', + 'postal_code': '11210'} + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': self.env.client.get_dictionary(), + 'address': address_data, + 'contacts': [], + 'contact_methods': []} + response = self.post( + self.url, + data=data, + query_string={'form': 'salesforce'}) + self.assertEqual(response.status_code, 400) + + def test_post_sf_no_address(self): + """Tests /project/?form=salesforce POST with no address. It should 400. + """ + place_data = {'sales_force_id': 'test', 'name': 'test'} + data = { + 'sales_force_id': 'test', + 'name': 'test', + 'state': 'pending', + 'client': self.env.client.get_dictionary(), + 'place': place_data, + 'contacts': [], + 'contact_methods': []} + response = self.post( + self.url, + data=data, + query_string={'form': 'salesforce'}) + self.assertEqual(response.status_code, 400) + def test_post_sf_new_contacts(self): """Tests /project/?form=salesforce POST with a new contact.""" + place = self.env.place contact_data = {'sales_force_id': 'test', 'name': 'test'} data = { 'sales_force_id': 'test', 'name': 'test', 'state': 'pending', 'client': self.env.client.get_dictionary(), + 'place': place.get_dictionary(), + 'address': place.address.get_dictionary(), 'contacts': [contact_data], 'contact_methods': []} response_data = self._test_post(data, {'form': 'salesforce'}) @@ -143,6 +283,7 @@ class TestProject(RestTestCase): def test_post_sf_change_contacts(self): """Tests /project/?form=salesforce POST with a modified contact.""" + place = self.env.place contact = self.env.contact contact_data = contact.get_dictionary() contact_data.update({'name': 'foo'}) @@ -151,6 +292,8 @@ class TestProject(RestTestCase): 'name': 'test', 'state': 'pending', 'client': self.env.client.get_dictionary(), + 'place': place.get_dictionary(), + 'address': place.address.get_dictionary(), 'contacts': [contact_data], 'contact_methods': []} response_data = self._test_post(data, {'form': 'salesforce'}) @@ -169,11 +312,14 @@ class TestProject(RestTestCase): """Tests /project/?form=salesforce POST with no contacts. It should 400. """ + place = self.env.place data = { 'sales_force_id': 'test', 'name': 'test', 'state': 'pending', 'client': self.env.client.get_dictionary(), + 'place': place.get_dictionary(), + 'address': place.address.get_dictionary(), 'contact_methods': []} response = self.post( self.url, @@ -188,6 +334,7 @@ class TestProject(RestTestCase): def test_post_sf_contact_methods(self): """Tests /project/?form=salesforce POST with a contact method.""" + place = self.env.place contact_data = { 'sales_force_id': 'test', 'name': 'test'} @@ -200,6 +347,8 @@ class TestProject(RestTestCase): 'name': 'test', 'state': 'pending', 'client': self.env.client.get_dictionary(), + 'place': place.get_dictionary(), + 'address': place.address.get_dictionary(), 'contacts': [contact_data], 'contact_methods': [contact_method_data]} response_data = self._test_post(data, {'form': 'salesforce'}) @@ -218,6 +367,7 @@ class TestProject(RestTestCase): """Tests /project/?form=salesforce POST with a contact method with a bad contact_sales_force_id. It should 400. """ + place = self.env.place contact_method_data = { 'contact_sales_force_id': 'test', 'method': 'email', @@ -227,6 +377,8 @@ class TestProject(RestTestCase): 'name': 'test', 'state': 'pending', 'client': self.env.client.get_dictionary(), + 'place': place.get_dictionary(), + 'address': place.address.get_dictionary(), 'contacts': [], 'contact_methods': [contact_method_data]} response = self.post( @@ -240,6 +392,7 @@ class TestProject(RestTestCase): contact_sales_force_id that is not associated with the project. It should 400. """ + place = self.env.place contact = self.env.contact contact_method_data = { 'contact_sales_force_id': contact.sales_force_id, @@ -250,6 +403,8 @@ class TestProject(RestTestCase): 'name': 'test', 'state': 'pending', 'client': self.env.client.get_dictionary(), + 'place': place.get_dictionary(), + 'address': place.address.get_dictionary(), 'contacts': [], 'contact_methods': [contact_method_data]} response = self.post( @@ -262,11 +417,14 @@ class TestProject(RestTestCase): """Tests /project/?form=salesforce POST with no contact methods. It should 400. """ + place = self.env.place data = { 'sales_force_id': 'test', 'name': 'test', 'state': 'pending', 'client': self.env.client.get_dictionary(), + 'place': place.get_dictionary(), + 'address': place.address.get_dictionary(), 'contacts': []} response = self.post( self.url, -- GitLab From bf2b212c47315bac29212a97819edc80357554ef Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 16:48:29 -0400 Subject: [PATCH 7/8] Remove deprecated unit_number field from the project tests. --- app/tests/test_project.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/tests/test_project.py b/app/tests/test_project.py index fb3172b..09d4e52 100644 --- a/app/tests/test_project.py +++ b/app/tests/test_project.py @@ -136,7 +136,6 @@ class TestProject(RestTestCase): place_data = {'sales_force_id': 'test', 'name': 'test'} address_data = { 'street_address': '15 MetroTech Center', - 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', 'state': 'NY', @@ -179,7 +178,6 @@ class TestProject(RestTestCase): address_data = place.address.get_dictionary() new_address_data = { 'street_address': '111 N Fake St', - 'unit_number': '', 'city': 'Raleigh', 'county': 'Wake', 'state': 'NC', @@ -219,7 +217,6 @@ class TestProject(RestTestCase): """ address_data = { 'street_address': '15 MetroTech Center', - 'unit_number': 'Suite 1900', 'city': 'Brooklyn', 'county': 'Kings', 'state': 'NY', -- GitLab From 9c3ed9d66ae24dc890901fd88176de069b70eb53 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Thu, 24 Mar 2016 16:50:40 -0400 Subject: [PATCH 8/8] Add clarifying comment on missing address/place unit tests in the project test case. --- app/tests/test_project.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/tests/test_project.py b/app/tests/test_project.py index 09d4e52..d6240d4 100644 --- a/app/tests/test_project.py +++ b/app/tests/test_project.py @@ -254,6 +254,10 @@ class TestProject(RestTestCase): query_string={'form': 'salesforce'}) self.assertEqual(response.status_code, 400) + # There are additional ways that posting with a place and address could + # fail, but those are already tested in the /address/ and /place/ + # endpoints, so there's no need to test them here. + def test_post_sf_new_contacts(self): """Tests /project/?form=salesforce POST with a new contact.""" place = self.env.place -- GitLab