diff --git a/qinling/services/periodics.py b/qinling/services/periodics.py index bff0bf38..cc4d4e53 100644 --- a/qinling/services/periodics.py +++ b/qinling/services/periodics.py @@ -57,8 +57,6 @@ def handle_function_service_expiration(ctx, engine_client, orchestrator): function_id={'in': expiry_ids} ) - LOG.info('Found %s total expiry function mappings', len(mappings)) - with db_api.transaction(): for m in mappings: LOG.info('Deleting service mapping for function %s', m.function_id) diff --git a/qinling/tests/unit/api/controllers/v1/test_function.py b/qinling/tests/unit/api/controllers/v1/test_function.py new file mode 100644 index 00000000..257f9c85 --- /dev/null +++ b/qinling/tests/unit/api/controllers/v1/test_function.py @@ -0,0 +1,120 @@ +# Copyright 2017 Catalyst IT Limited +# +# 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. + +import json +import tempfile + +import mock + +from qinling.tests.unit.api import base +from qinling.tests.unit import base as unit_base + +TEST_CASE_NAME = 'TestFunctionController' + + +class TestFunctionController(base.APITest): + def setUp(self): + super(TestFunctionController, self).setUp() + + # Insert a runtime record in db for each test case. The data will be + # removed automatically in tear down. + db_runtime = self.create_runtime(prefix=TEST_CASE_NAME) + self.runtime_id = db_runtime.id + + @mock.patch('qinling.storage.file_system.FileSystemStorage.store') + def test_post(self, mock_store): + class File(object): + def __init__(self, f): + self.file = f + + with tempfile.NamedTemporaryFile() as f: + body = { + 'name': self.rand_name('function', prefix=TEST_CASE_NAME), + 'code': json.dumps({"source": "package"}), + 'runtime_id': self.runtime_id, + } + resp = self.app.post( + '/v1/functions', + params=body, + upload_files=[('package', f.name, f.read())] + ) + + self.assertEqual(201, resp.status_int) + self.assertEqual(1, mock_store.call_count) + + body.update({'entry': 'main.main', 'code': {"source": "package"}}) + self._assertDictContainsSubset(resp.json, body) + + def test_get(self): + db_func = self.create_function( + runtime_id=self.runtime_id, prefix=TEST_CASE_NAME + ) + expected = { + 'id': db_func.id, + "code": {"source": "package"}, + "name": db_func.name, + 'entry': 'main.main', + "project_id": unit_base.DEFAULT_PROJECT_ID, + } + + resp = self.app.get('/v1/functions/%s' % db_func.id) + + self.assertEqual(200, resp.status_int) + self._assertDictContainsSubset(resp.json, expected) + + def test_get_all(self): + db_func = self.create_function( + runtime_id=self.runtime_id, prefix=TEST_CASE_NAME + ) + expected = { + 'id': db_func.id, + "code": json.dumps({"source": "package"}), + "name": db_func.name, + 'entry': 'main.main', + "project_id": unit_base.DEFAULT_PROJECT_ID, + } + + resp = self.app.get('/v1/functions') + + self.assertEqual(200, resp.status_int) + actual = self._assert_single_item( + resp.json['functions'], id=db_func.id + ) + self._assertDictContainsSubset(actual, expected) + + def test_put_name(self): + db_func = self.create_function( + runtime_id=self.runtime_id, prefix=TEST_CASE_NAME + ) + + resp = self.app.put_json( + '/v1/functions/%s' % db_func.id, {'name': 'new_name'} + ) + + self.assertEqual(200, resp.status_int) + self.assertEqual('new_name', resp.json['name']) + + @mock.patch('qinling.rpc.EngineClient.delete_function') + @mock.patch('qinling.storage.file_system.FileSystemStorage.delete') + def test_delete(self, mock_delete, mock_delete_func): + db_func = self.create_function( + runtime_id=self.runtime_id, prefix=TEST_CASE_NAME + ) + resp = self.app.delete('/v1/functions/%s' % db_func.id) + + self.assertEqual(204, resp.status_int) + mock_delete.assert_called_once_with( + unit_base.DEFAULT_PROJECT_ID, db_func.id + ) + mock_delete_func.assert_called_once_with(db_func.id) diff --git a/qinling/tests/unit/api/controllers/v1/test_job.py b/qinling/tests/unit/api/controllers/v1/test_job.py index 261073c7..55e17ddb 100644 --- a/qinling/tests/unit/api/controllers/v1/test_job.py +++ b/qinling/tests/unit/api/controllers/v1/test_job.py @@ -41,6 +41,17 @@ class TestJobController(base.APITest): self.assertEqual(201, resp.status_int) + def test_post_pattern(self): + body = { + 'name': self.rand_name('job', prefix='TestJobController'), + 'function_id': self.function_id, + 'pattern': '0 21 * * *', + 'count': 10 + } + resp = self.app.post_json('/v1/jobs', body) + + self.assertEqual(201, resp.status_int) + def test_delete(self): job_id = self.create_job( self.function_id, prefix='TestJobController', diff --git a/qinling/tests/unit/base.py b/qinling/tests/unit/base.py index 1dc4452f..c7103e33 100644 --- a/qinling/tests/unit/base.py +++ b/qinling/tests/unit/base.py @@ -55,16 +55,15 @@ class BaseTest(base.BaseTestCase): cfg.CONF.set_override(name, override, group) self.addCleanup(cfg.CONF.clear_override, name, group) - def _assertDictContainsSubset(self, parent, child, msg=None): + def _assertDictContainsSubset(self, parent, child): """Checks whether child dict is a superset of parent. assertDictContainsSubset() in standard Python 2.7 has been deprecated since Python 3.2 + + Refer to https://goo.gl/iABb5c """ - self.assertTrue( - set(child.items()).issubset(set(parent.items())), - msg=msg - ) + self.assertEqual(parent, dict(parent, **child)) def _assert_single_item(self, items, **props): return self._assert_multiple_items(items, 1, **props)[0]