diff --git a/Makefile b/Makefile index 19c29168..0fe93530 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ NAME=redash -VERSION=0.2 +VERSION=0.3 FULL_VERSION=$(VERSION).$(CIRCLE_BUILD_NUM) FILENAME=$(CIRCLE_ARTIFACTS)/$(NAME).$(FULL_VERSION).tar.gz diff --git a/redash/controllers.py b/redash/controllers.py index 4081c182..b132120a 100644 --- a/redash/controllers.py +++ b/redash/controllers.py @@ -94,7 +94,7 @@ class DashboardListAPI(BaseResource): return dashboards def post(self): - dashboard_properties = json.loads(self.request.body) + dashboard_properties = request.get_json(force=True) dashboard = models.Dashboard(name=dashboard_properties['name'], user=self.current_user, layout='[]') @@ -104,13 +104,18 @@ class DashboardListAPI(BaseResource): class DashboardAPI(BaseResource): def get(self, dashboard_slug=None): - # TODO: prefetching? - dashboard = models.Dashboard.get_by_slug(dashboard_slug) + # TODO: prefetching of widgets and queries? + try: + dashboard = models.Dashboard.get_by_slug(dashboard_slug) + except models.Dashboard.DoesNotExist: + abort(404) + return dashboard.to_dict(with_widgets=True) - def post(self, dashboard_id): - dashboard_properties = request.json - dashboard = models.Dashboard.get(models.Dashboard.id == dashboard_id) + def post(self, dashboard_slug): + # TODO: either convert all requests to use slugs or ids + dashboard_properties = request.get_json(force=True) + dashboard = models.Dashboard.get(models.Dashboard.id == dashboard_slug) dashboard.layout = dashboard_properties['layout'] dashboard.name = dashboard_properties['name'] dashboard.save() @@ -128,7 +133,7 @@ api.add_resource(DashboardAPI, '/api/dashboards/', endpoint='das class WidgetListAPI(BaseResource): def post(self): - widget_properties = request.json + widget_properties = request.get_json(force=True) widget_properties['options'] = json.dumps(widget_properties['options']) widget = models.Widget(**widget_properties) widget.save() diff --git a/redash/models.py b/redash/models.py index d1379676..d1a9d808 100644 --- a/redash/models.py +++ b/redash/models.py @@ -202,4 +202,4 @@ def create_db(create_tables, drop_tables): if create_tables: model.create_table() - db.close_db(None) + db.close_db(None) \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index 927ea298..6618283a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1 +1,56 @@ -from tests import controllers \ No newline at end of file +from unittest import TestCase +from redash import settings, db, app +import redash.models + +# TODO: this isn't pretty... :-) +settings.DATABASE_CONFIG = { + 'name': 'rd_test', + 'engine': 'peewee.PostgresqlDatabase', + 'threadlocals': True +} +app.config['DATABASE'] = settings.DATABASE_CONFIG +db.load_database() + + +def model_factory(model, **kwargs): + def factory(**properties): + kwargs.update(properties) + + return model(**kwargs) + + return factory + + +class ModelFactory(object): + def __init__(self, model, **kwargs): + self.model = model + self.kwargs = kwargs + + def _get_kwargs(self, override_kwargs): + kwargs = self.kwargs.copy() + kwargs.update(override_kwargs) + return kwargs + + def instance(self, **override_kwargs): + kwargs = self._get_kwargs(override_kwargs) + + return self.model(**kwargs) + + def create(self, **override_kwargs): + kwargs = self._get_kwargs(override_kwargs) + + return self.model.create(**kwargs) + +DashboardFactory = ModelFactory(redash.models.Dashboard, + name='test', user='test@everything.me', layout='[]') + +dashboard_factory = model_factory(redash.models.Dashboard, name='test', user='test@everything.me', layout='[]') + + +class BaseTestCase(TestCase): + def setUp(self): + redash.models.create_db(True, True) + + def tearDown(self): + db.close_db(None) + redash.models.create_db(False, True) \ No newline at end of file diff --git a/tests/controllers.py b/tests/controllers.py index 4fcf0864..f3c5d268 100644 --- a/tests/controllers.py +++ b/tests/controllers.py @@ -1,16 +1,31 @@ from contextlib import contextmanager -import unittest -from redash import app +import json +from tests import BaseTestCase, DashboardFactory +from redash import app, models @contextmanager -def authenticated_user(c): +def authenticated_user(c, user='test@example.com', name='John Test'): with c.session_transaction() as sess: - sess['openid'] = {'email': 'test@example.com', 'name': 'John Test'} + sess['openid'] = {'email': user, 'name': name} yield +def json_request(method, path, data=None): + if data: + response = method(path, data=json.dumps(data)) + else: + response = method(path) + + if response.data: + response.json = json.loads(response.data) + else: + response.json = None + + return response + + class AuthenticationTestMixin(): def test_redirects_when_not_authenticated(self): with app.test_client() as c: @@ -19,14 +34,13 @@ class AuthenticationTestMixin(): self.assertEquals(302, rv.status_code) def test_returns_content_when_authenticated(self): - with app.test_client() as c: - with authenticated_user(c): - for path in self.paths: - rv = c.get(path) - self.assertEquals(200, rv.status_code) + with app.test_client() as c, authenticated_user(c): + for path in self.paths: + rv = c.get(path) + self.assertEquals(200, rv.status_code) -class PingTest(unittest.TestCase): +class PingTest(BaseTestCase): def test_ping(self): with app.test_client() as c: rv = c.get('/ping') @@ -34,36 +48,83 @@ class PingTest(unittest.TestCase): self.assertEquals('PONG.', rv.data) -class IndexTest(unittest.TestCase, AuthenticationTestMixin): +class IndexTest(BaseTestCase, AuthenticationTestMixin): def setUp(self): self.paths = ['/', '/dashboard/example', '/queries/1', '/admin/status'] + super(IndexTest, self).setUp() -class StatusTest(unittest.TestCase, AuthenticationTestMixin): +class StatusTest(BaseTestCase, AuthenticationTestMixin): def setUp(self): self.paths = ['/status.json'] + super(StatusTest, self).setUp() -class DashboardAPITest(unittest.TestCase, AuthenticationTestMixin): +class DashboardAPITest(BaseTestCase, AuthenticationTestMixin): def setUp(self): self.paths = ['/api/dashboards'] + super(DashboardAPITest, self).setUp() + + def test_get_dashboard(self): + d1 = DashboardFactory.create() + with app.test_client() as c, authenticated_user(c): + rv = c.get('/api/dashboards/{0}'.format(d1.slug)) + self.assertEquals(rv.status_code, 200) + self.assertDictEqual(json.loads(rv.data), d1.to_dict(with_widgets=True)) + + def test_get_non_existint_dashbaord(self): + with app.test_client() as c, authenticated_user(c): + rv = c.get('/api/dashboards/not_existing') + self.assertEquals(rv.status_code, 404) + + def test_create_new_dashboard(self): + user_email = 'test@everything.me' + with app.test_client() as c, authenticated_user(c, user=user_email): + dashboard_name = 'Test Dashboard' + rv = json_request(c.post, '/api/dashboards', data={'name': dashboard_name}) + self.assertEquals(rv.status_code, 200) + self.assertEquals(rv.json['name'], 'Test Dashboard') + self.assertEquals(rv.json['user'], user_email) + self.assertEquals(rv.json['layout'], []) + + def test_update_dashboard(self): + d = DashboardFactory.create() + new_name = 'New Name' + with app.test_client() as c, authenticated_user(c): + rv = json_request(c.post, '/api/dashboards/{0}'.format(d.id), + data={'name': new_name, 'layout': '[]'}) + self.assertEquals(rv.status_code, 200) + self.assertEquals(rv.json['name'], new_name) + + def test_delete_dashbaord(self): + d = DashboardFactory.create() + with app.test_client() as c, authenticated_user(c): + rv = json_request(c.delete, '/api/dashboards/{0}'.format(d.slug)) + self.assertEquals(rv.status_code, 200) + + d = models.Dashboard.get_by_slug(d.slug) + self.assertTrue(d.is_archived) -class QueryAPITest(unittest.TestCase, AuthenticationTestMixin): +class QueryAPITest(BaseTestCase, AuthenticationTestMixin): def setUp(self): self.paths = ['/api/queries'] + super(QueryAPITest, self).setUp() -class QueryResultAPITest(unittest.TestCase, AuthenticationTestMixin): +class QueryResultAPITest(BaseTestCase, AuthenticationTestMixin): def setUp(self): self.paths = [] + super(QueryResultAPITest, self).setUp() -class JobAPITest(unittest.TestCase, AuthenticationTestMixin): +class JobAPITest(BaseTestCase, AuthenticationTestMixin): def setUp(self): self.paths = [] + super(JobAPITest, self).setUp() -class CsvQueryResultAPITest(unittest.TestCase, AuthenticationTestMixin): +class CsvQueryResultAPITest(BaseTestCase, AuthenticationTestMixin): def setUp(self): - self.paths = [] \ No newline at end of file + self.paths = [] + super(CsvQueryResultAPITest, self).setUp() \ No newline at end of file diff --git a/tests/models.py b/tests/models.py index c38839ed..2f00df63 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,31 +1,14 @@ -from unittest import TestCase -from redash import settings, db, app, models - -settings.DATABASE_CONFIG = { - 'name': 'rd_test', - 'engine': 'peewee.PostgresqlDatabase', - 'threadlocals': True -} -app.config['DATABASE'] = settings.DATABASE_CONFIG -db.load_database() +from tests import BaseTestCase, DashboardFactory -class DatabaseTestCase(TestCase): - def setUp(self): - models.create_db(True, True) - - def tearDown(self): - models.create_db(False, True) - - -class DashboardTest(DatabaseTestCase): +class DashboardTest(BaseTestCase): def test_appends_suffix_to_slug_when_duplicate(self): - d1 = models.Dashboard.create(name='test', user='arik', layout='') + d1 = DashboardFactory.create() self.assertEquals(d1.slug, 'test') - d2 = models.Dashboard.create(name='test', user='arik', layout='') + d2 = DashboardFactory.create() self.assertNotEquals(d1.slug, d2.slug) - d3 = models.Dashboard.create(name='test', user='arik', layout='') + d3 = DashboardFactory.create() self.assertNotEquals(d1.slug, d3.slug) self.assertNotEquals(d2.slug, d3.slug) \ No newline at end of file