From ed0136e6da1d6d69a442fd50434069100f213f83 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Wed, 6 Apr 2016 13:07:42 -0400 Subject: [PATCH 1/8] Use a referrer to test the salesforce role. --- app/config/test.default.py | 2 +- app/tests/test_project.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/config/test.default.py b/app/config/test.default.py index 81e4fb6..6c28d8e 100644 --- a/app/config/test.default.py +++ b/app/config/test.default.py @@ -23,7 +23,7 @@ APP_CACHE_EXPIRY = 60 * 60 # One hour. # A client with the project_post_salesforce role. SALESFORCE_KEY = '$SALESFORCE_KEY' -SALESFORCE_SECRET = '$SALESFORCE_SECRET' +SALESFORCE_REFERRER = '$SALESFORCE_REFERRER' # Auth GOOGLE_AUTH_HEADER = 'x-blocpower-google-token' diff --git a/app/tests/test_project.py b/app/tests/test_project.py index fbe0a62..c9afe37 100644 --- a/app/tests/test_project.py +++ b/app/tests/test_project.py @@ -204,8 +204,7 @@ class TestSalesForceProject(UnprotectedRestTestCase): return { services.config['headers']['app_key']: \ self.app.config['SALESFORCE_KEY'], - services.config['headers']['app_secret']: \ - self.app.config['SALESFORCE_SECRET']} + 'referer': self.app.config['SALESFORCE_REFERRER']} def test_post_sf_client(self): """Tests /project/?form=salesforce POST with a new client.""" -- GitLab From 64bb568e5ae5bcd83629683745f918f02c68d572 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Wed, 6 Apr 2016 14:02:57 -0400 Subject: [PATCH 2/8] Add document slot model. --- app/models/document.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 app/models/document.py diff --git a/app/models/document.py b/app/models/document.py new file mode 100644 index 0000000..34d1f14 --- /dev/null +++ b/app/models/document.py @@ -0,0 +1,16 @@ +"""Models for handling associated documents.""" +from app.lib.database import db +from app.models.base import Model, Tracked +from app.models.columns import GUID + + +class DocumentSlot(Model, Tracked, db.Model): + """A m2m relationship between the project model and documents (provided by + the document service). + """ + project_id = db.Column( + db.Integer, db.ForeignKey('project.id'), nullable=False) + document_uuid = db.Column(GUID(), nullable=False) + + # How the document fits into the project. + role = db.Column(db.Unicode(length=255), nullable=False) -- GitLab From ce399eaf5e16c744005f3d8b6ec2a9115d78c80c Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Wed, 6 Apr 2016 14:03:10 -0400 Subject: [PATCH 3/8] Add document slot model to the test environment. --- app/tests/environment.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/tests/environment.py b/app/tests/environment.py index 892eccf..1674896 100644 --- a/app/tests/environment.py +++ b/app/tests/environment.py @@ -16,6 +16,7 @@ from app.models.project import Project from app.models.client import Client from app.models.contact import Contact, ContactMethod, ProjectContact from app.models.note import Note +from app.models.document import DocumentSlot class Environment(object): @@ -115,3 +116,11 @@ class Environment(object): title='Test', content='This is a test note.', poster_name='test')) + + @property + def document_slot(self): + """A document slot.""" + return self.add(DocumentSlot( + project_id=self.project.id, + document_uuid=uuid.uuid4(), + role='test')) -- GitLab From ea1fd67b662a573030db5edfb9f29d4fb0f003a4 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Wed, 6 Apr 2016 14:03:19 -0400 Subject: [PATCH 4/8] Add a document slot form. --- app/forms/document.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 app/forms/document.py diff --git a/app/forms/document.py b/app/forms/document.py new file mode 100644 index 0000000..5223348 --- /dev/null +++ b/app/forms/document.py @@ -0,0 +1,13 @@ +"""Forms for handling associated documents.""" +import wtforms as wtf +from app.forms import fields +from app.forms.base import Form + + +class DocumentSlotForm(Form): + """A form for the m2m relationship between documents and projects.""" + project_id = wtf.IntegerField(validators=[wtf.validators.Required()]) + document_uuid = fields.UUID(validators=[wtf.validators.Required()]) + role = wtf.StringField(validators=[ + wtf.validators.Length(max=255), + wtf.validators.Required()]) -- GitLab From 6552e74823390ad95e705f53fba9a5ff0279760c Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Wed, 6 Apr 2016 14:03:29 -0400 Subject: [PATCH 5/8] Add a document slot controller. --- app/controllers/document.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 app/controllers/document.py diff --git a/app/controllers/document.py b/app/controllers/document.py new file mode 100644 index 0000000..1c87f0b --- /dev/null +++ b/app/controllers/document.py @@ -0,0 +1,19 @@ +"""Controllers for managing documents.""" +from app.controllers.base import RestController +from app.models.document import DocumentSlot +from app.forms.document import DocumentSlotForm + + +class DocumentSlotController(RestController): + """A controller for managing the m2m relationship between documents and + projects. + """ + Model = DocumentSlot + constant_fields = ['project_id', 'document_uuid', 'role'] + filters = { + 'project_id': lambda d: DocumentSlot.project_id == d['project_id'] + } + + def get_form(self, filter_data): + """Return the document slot form.""" + return DocumentSlotForm -- GitLab From c3c22e9e31b7d1224068c118e65822e078e980fa Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Wed, 6 Apr 2016 14:03:45 -0400 Subject: [PATCH 6/8] Add a document slot view. --- app/views/document.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 app/views/document.py diff --git a/app/views/document.py b/app/views/document.py new file mode 100644 index 0000000..5eb8be6 --- /dev/null +++ b/app/views/document.py @@ -0,0 +1,12 @@ +"""Views for working with documents.""" +from app.views.base import RestView +from app.controllers.document import DocumentSlotController + + +class DocumentSlotView(RestView): + """A view for the m2m relationship between document and project.""" + route_base = '/project/document/' + + def get_controller(self): + """Return an instance of the document slot controller.""" + return DocumentSlotController() -- GitLab From 6b3c352cef3c1fcad910ccf5dd6e6d3768533a14 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Wed, 6 Apr 2016 14:03:56 -0400 Subject: [PATCH 7/8] Register the document slot view. --- app/views/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/__init__.py b/app/views/__init__.py index 63e29d5..c0f64ed 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -1,5 +1,5 @@ """Flask-classy views for the flask application.""" -from app.views import project, client, contact, place, note +from app.views import project, client, contact, place, note, document def register(app): @@ -20,3 +20,5 @@ def register(app): place.AddressView.register(app) note.NoteView.register(app) + + document.DocumentSlotView.register(app) -- GitLab From 9539d2f25e25d149c03d4753df005df52b70a933 Mon Sep 17 00:00:00 2001 From: astex <0astex@gmail.com> Date: Wed, 6 Apr 2016 14:04:09 -0400 Subject: [PATCH 8/8] Add unit tests for the document slot view. --- app/tests/test_document.py | 132 +++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 app/tests/test_document.py diff --git a/app/tests/test_document.py b/app/tests/test_document.py new file mode 100644 index 0000000..c40d8a0 --- /dev/null +++ b/app/tests/test_document.py @@ -0,0 +1,132 @@ +"""Unit tests for working with documents.""" +import uuid +from sqlalchemy import and_ +from app.lib.database import db +from app.tests.base import RestTestCase +from app.models.document import DocumentSlot + + +class TestDocumentSlot(RestTestCase): + """Tests the /project/document/ endpoints.""" + url = '/project/document/' + Model = DocumentSlot + + def test_index(self): + """Tests /project/document/ GET.""" + model = self.env.document_slot + self._test_index() + + def test_index_project_id(self): + """Tests /project/document/?project_id=... GET.""" + # Add a model that will be in the response. + project = self.env.project + model = self.env.add(DocumentSlot( + project_id=project.id, + document_uuid=uuid.uuid4(), + role='test')) + # Add a model that will not be in the response. + _ = self.env.document_slot + + response_data = self._test_index( + filter_data={'project_id': project.id}) + self.assertEqual(len(response_data), 1) + data = response_data[0] + self.assertIn('id', data) + self.assertEqual(data['id'], model.id) + + def test_get(self): + """Tests /project/document/ GET.""" + model = self.env.document_slot + self._test_get(model.id) + + def test_post(self): + """Tests /project/document/ POST.""" + data = { + 'project_id': self.env.project.id, + 'document_uuid': str(uuid.uuid4()), + 'role': 'test'} + response_data = self._test_post(data) + self.assertTrue( + db.session.query(DocumentSlot)\ + .filter(and_(*[ + getattr(DocumentSlot, k) == v for k, v in data.items() + ]))\ + .first()) + + def test_post_no_project_id(self): + """Tests /project/document/ POST with no project id. It should 400.""" + data = { + 'document_uuid': str(uuid.uuid4()), + 'role': 'test'} + response = self.post(self.url, data=data) + self.assertEqual(response.status_code, 400) + + def test_post_bad_project_id(self): + """Tests /project/document/ POST with a bad project id. It should 400. + """ + data = { + 'project_id': 1, + 'document_uuid': str(uuid.uuid4()), + 'role': 'test'} + response = self.post(self.url, data=data) + self.assertEqual(response.status_code, 400) + + def test_post_no_document_uuid(self): + """Tests /project/document/ POST with no document uuid. It should 400. + """ + data = { + 'project_id': self.env.project.id, + 'role': 'test'} + response = self.post(self.url, data=data) + self.assertEqual(response.status_code, 400) + + def test_post_bad_document_uuid(self): + """Tests /project/document/ POST with a bad document uuid. It should + 400. + """ + data = { + 'project_id': self.env.project.id, + 'document_uuid': 'foo', + 'role': 'test'} + response = self.post(self.url, data=data) + self.assertEqual(response.status_code, 400) + + def test_post_no_role(self): + """Tests /project/document/ POST with no role. It should 400.""" + data = { + 'project_id': self.env.project.id, + 'document_uuid': str(uuid.uuid4())} + response = self.post(self.url, data=data) + self.assertEqual(response.status_code, 400) + + def test_put_project_id(self): + """Tests /project/document/ PUT with a project id. It should 400. + """ + model = self.env.document_slot + data = model.get_dictionary() + data.update({'project_id': self.env.project.id}) + response = self.put(self.url + str(model.id), data=data) + self.assertEqual(response.status_code, 400) + + def test_put_document_uuid(self): + """Tests /project/document/ PUT with a document uuid. It should + 400. + """ + model = self.env.document_slot + data = model.get_dictionary() + data.update({'document_uuid': str(uuid.uuid4())}) + response = self.put(self.url + str(model.id), data=data) + self.assertEqual(response.status_code, 400) + + def test_put_role(self): + """Tests /project/document/ PUT with a role. It should 400.""" + model = self.env.document_slot + data = model.get_dictionary() + data.update({'role': 'foo'}) + response = self.put(self.url + str(model.id), data=data) + self.assertEqual(response.status_code, 400) + + def test_delete(self): + """Tests /project/document/ DELETE.""" + model = self.env.document_slot + self._test_delete(model.id) -- GitLab