2015-10-16 20:11:19 +00:00
|
|
|
from tests import BaseTestCase
|
2018-10-09 13:38:06 +00:00
|
|
|
|
2016-11-30 14:52:30 +00:00
|
|
|
from redash.models import db
|
2018-10-09 13:38:06 +00:00
|
|
|
from redash.utils import json_dumps
|
2019-07-21 06:21:45 +00:00
|
|
|
from redash.handlers.query_results import error_messages
|
2015-10-16 20:11:19 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TestQueryResultsCacheHeaders(BaseTestCase):
|
|
|
|
def test_uses_cache_headers_for_specific_result(self):
|
2015-12-01 13:44:08 +00:00
|
|
|
query_result = self.factory.create_query_result()
|
|
|
|
query = self.factory.create_query(latest_query_data=query_result)
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/results/{}.json'.format(query.id, query_result.id))
|
|
|
|
self.assertIn('Cache-Control', rv.headers)
|
2015-10-16 20:11:19 +00:00
|
|
|
|
|
|
|
def test_doesnt_use_cache_headers_for_non_specific_result(self):
|
2015-12-01 13:44:08 +00:00
|
|
|
query_result = self.factory.create_query_result()
|
|
|
|
query = self.factory.create_query(latest_query_data=query_result)
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/results.json'.format(query.id))
|
|
|
|
self.assertNotIn('Cache-Control', rv.headers)
|
2015-10-16 20:11:19 +00:00
|
|
|
|
2016-06-14 07:41:01 +00:00
|
|
|
def test_returns_404_if_no_cached_result_found(self):
|
|
|
|
query = self.factory.create_query(latest_query_data=None)
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/results.json'.format(query.id))
|
|
|
|
self.assertEqual(404, rv.status_code)
|
|
|
|
|
2016-02-22 08:40:46 +00:00
|
|
|
|
2016-03-09 13:15:03 +00:00
|
|
|
class TestQueryResultListAPI(BaseTestCase):
|
2016-02-22 08:40:46 +00:00
|
|
|
def test_get_existing_result(self):
|
|
|
|
query_result = self.factory.create_query_result()
|
|
|
|
query = self.factory.create_query()
|
|
|
|
|
|
|
|
rv = self.make_request('post', '/api/query_results',
|
|
|
|
data={'data_source_id': self.factory.data_source.id,
|
2016-11-30 14:52:30 +00:00
|
|
|
'query': query.query_text})
|
2016-02-22 08:40:46 +00:00
|
|
|
self.assertEquals(rv.status_code, 200)
|
|
|
|
self.assertEquals(query_result.id, rv.json['query_result']['id'])
|
|
|
|
|
|
|
|
def test_execute_new_query(self):
|
|
|
|
query_result = self.factory.create_query_result()
|
|
|
|
query = self.factory.create_query()
|
|
|
|
|
|
|
|
rv = self.make_request('post', '/api/query_results',
|
|
|
|
data={'data_source_id': self.factory.data_source.id,
|
2016-11-30 14:52:30 +00:00
|
|
|
'query': query.query_text,
|
2016-02-22 08:40:46 +00:00
|
|
|
'max_age': 0})
|
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
|
|
|
self.assertNotIn('query_result', rv.json)
|
|
|
|
self.assertIn('job', rv.json)
|
|
|
|
|
|
|
|
def test_execute_query_without_access(self):
|
2016-11-30 14:52:30 +00:00
|
|
|
group = self.factory.create_group()
|
|
|
|
db.session.commit()
|
|
|
|
user = self.factory.create_user(group_ids=[group.id])
|
2016-02-22 08:40:46 +00:00
|
|
|
query = self.factory.create_query()
|
|
|
|
|
|
|
|
rv = self.make_request('post', '/api/query_results',
|
|
|
|
data={'data_source_id': self.factory.data_source.id,
|
2016-11-30 14:52:30 +00:00
|
|
|
'query': query.query_text,
|
2016-02-22 08:40:46 +00:00
|
|
|
'max_age': 0},
|
|
|
|
user=user)
|
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 403)
|
|
|
|
self.assertIn('job', rv.json)
|
|
|
|
|
|
|
|
def test_execute_query_with_params(self):
|
|
|
|
query = "SELECT {{param}}"
|
|
|
|
|
|
|
|
rv = self.make_request('post', '/api/query_results',
|
|
|
|
data={'data_source_id': self.factory.data_source.id,
|
|
|
|
'query': query,
|
|
|
|
'max_age': 0})
|
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 400)
|
|
|
|
self.assertIn('job', rv.json)
|
|
|
|
|
2018-11-19 08:50:00 +00:00
|
|
|
rv = self.make_request('post', '/api/query_results',
|
|
|
|
data={'data_source_id': self.factory.data_source.id,
|
|
|
|
'query': query,
|
|
|
|
'parameters': {'param': 1},
|
|
|
|
'max_age': 0})
|
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
|
|
|
self.assertIn('job', rv.json)
|
|
|
|
|
2016-02-22 08:40:46 +00:00
|
|
|
rv = self.make_request('post', '/api/query_results?p_param=1',
|
|
|
|
data={'data_source_id': self.factory.data_source.id,
|
|
|
|
'query': query,
|
|
|
|
'max_age': 0})
|
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
|
|
|
self.assertIn('job', rv.json)
|
2016-03-09 13:15:03 +00:00
|
|
|
|
2016-05-30 15:30:05 +00:00
|
|
|
def test_execute_on_paused_data_source(self):
|
|
|
|
self.factory.data_source.pause()
|
|
|
|
|
|
|
|
rv = self.make_request('post', '/api/query_results',
|
|
|
|
data={'data_source_id': self.factory.data_source.id,
|
|
|
|
'query': 'SELECT 1',
|
|
|
|
'max_age': 0})
|
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 400)
|
|
|
|
self.assertNotIn('query_result', rv.json)
|
|
|
|
self.assertIn('job', rv.json)
|
|
|
|
|
2019-07-15 12:09:30 +00:00
|
|
|
def test_execute_without_data_source(self):
|
|
|
|
rv = self.make_request('post', '/api/query_results',
|
|
|
|
data={'query': 'SELECT 1',
|
|
|
|
'max_age': 0})
|
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 401)
|
2019-07-21 06:21:45 +00:00
|
|
|
self.assertDictEqual(rv.json, error_messages['select_data_source'][0])
|
2019-07-15 12:09:30 +00:00
|
|
|
|
2016-03-09 13:15:03 +00:00
|
|
|
|
|
|
|
class TestQueryResultAPI(BaseTestCase):
|
|
|
|
def test_has_no_access_to_data_source(self):
|
|
|
|
ds = self.factory.create_data_source(group=self.factory.create_group())
|
|
|
|
query_result = self.factory.create_query_result(data_source=ds)
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/query_results/{}'.format(query_result.id))
|
|
|
|
self.assertEquals(rv.status_code, 403)
|
|
|
|
|
|
|
|
def test_has_view_only_access_to_data_source(self):
|
|
|
|
ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=True)
|
|
|
|
query_result = self.factory.create_query_result(data_source=ds)
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/query_results/{}'.format(query_result.id))
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
|
|
|
|
|
|
|
def test_has_full_access_to_data_source(self):
|
|
|
|
ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=False)
|
|
|
|
query_result = self.factory.create_query_result(data_source=ds)
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/query_results/{}'.format(query_result.id))
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
2016-06-14 07:41:01 +00:00
|
|
|
|
2019-01-23 09:10:04 +00:00
|
|
|
def test_execute_new_query(self):
|
|
|
|
query = self.factory.create_query()
|
|
|
|
|
|
|
|
rv = self.make_request('post', '/api/queries/{}/results'.format(query.id), data={'parameters': {}})
|
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
|
|
|
self.assertIn('job', rv.json)
|
2019-07-07 11:22:08 +00:00
|
|
|
|
2019-07-21 06:21:45 +00:00
|
|
|
def test_execute_but_has_no_access_to_data_source(self):
|
|
|
|
ds = self.factory.create_data_source(group=self.factory.create_group())
|
|
|
|
query = self.factory.create_query(data_source=ds)
|
|
|
|
|
|
|
|
rv = self.make_request('post', '/api/queries/{}/results'.format(query.id))
|
|
|
|
self.assertEquals(rv.status_code, 403)
|
|
|
|
self.assertDictEqual(rv.json, error_messages['no_permission'][0])
|
|
|
|
|
2019-07-07 11:22:08 +00:00
|
|
|
def test_execute_with_no_parameter_values(self):
|
|
|
|
query = self.factory.create_query()
|
|
|
|
|
|
|
|
rv = self.make_request('post', '/api/queries/{}/results'.format(query.id))
|
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
|
|
|
self.assertIn('job', rv.json)
|
2019-01-23 09:10:04 +00:00
|
|
|
|
2019-02-26 18:55:01 +00:00
|
|
|
def test_prevents_execution_of_unsafe_queries_on_view_only_data_sources(self):
|
|
|
|
ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=True)
|
|
|
|
query = self.factory.create_query(data_source=ds, options={"parameters": [{"name": "foo", "type": "text"}]})
|
|
|
|
|
|
|
|
rv = self.make_request('post', '/api/queries/{}/results'.format(query.id), data={"parameters": {}})
|
|
|
|
self.assertEquals(rv.status_code, 403)
|
2019-07-21 06:21:45 +00:00
|
|
|
self.assertDictEqual(rv.json, error_messages['unsafe_on_view_only'][0])
|
2019-02-26 18:55:01 +00:00
|
|
|
|
|
|
|
def test_allows_execution_of_safe_queries_on_view_only_data_sources(self):
|
|
|
|
ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=True)
|
|
|
|
query = self.factory.create_query(data_source=ds, options={"parameters": [{"name": "foo", "type": "number"}]})
|
|
|
|
|
|
|
|
rv = self.make_request('post', '/api/queries/{}/results'.format(query.id), data={"parameters": {}})
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
|
|
|
|
2019-04-02 08:45:38 +00:00
|
|
|
def test_prevents_execution_of_unsafe_queries_using_api_key(self):
|
|
|
|
ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=True)
|
|
|
|
query = self.factory.create_query(data_source=ds, options={"parameters": [{"name": "foo", "type": "text"}]})
|
|
|
|
|
|
|
|
data = {'parameters': {'foo': 'bar'}}
|
|
|
|
rv = self.make_request('post', '/api/queries/{}/results?api_key={}'.format(query.id, query.api_key), data=data)
|
|
|
|
self.assertEquals(rv.status_code, 403)
|
2019-07-21 06:21:45 +00:00
|
|
|
self.assertDictEqual(rv.json, error_messages['unsafe_when_shared'][0])
|
2019-04-02 08:45:38 +00:00
|
|
|
|
2017-11-01 11:51:09 +00:00
|
|
|
def test_access_with_query_api_key(self):
|
|
|
|
ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=False)
|
|
|
|
query = self.factory.create_query()
|
|
|
|
query_result = self.factory.create_query_result(data_source=ds, query_text=query.query_text)
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/results/{}.json?api_key={}'.format(query.id, query_result.id, query.api_key), user=False)
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
|
|
|
|
|
|
|
def test_access_with_query_api_key_without_query_result_id(self):
|
|
|
|
ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=False)
|
|
|
|
query = self.factory.create_query()
|
|
|
|
query_result = self.factory.create_query_result(data_source=ds, query_text=query.query_text, query_hash=query.query_hash)
|
|
|
|
query.latest_query_data = query_result
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/results.json?api_key={}'.format(query.id, query.api_key), user=False)
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
2018-09-16 06:43:44 +00:00
|
|
|
|
2017-11-01 11:51:09 +00:00
|
|
|
def test_query_api_key_and_different_query_result(self):
|
|
|
|
ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=False)
|
|
|
|
query = self.factory.create_query(query_text="SELECT 8")
|
|
|
|
query_result2 = self.factory.create_query_result(data_source=ds, query_hash='something-different')
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/results/{}.json?api_key={}'.format(query.id, query_result2.id, query.api_key), user=False)
|
|
|
|
self.assertEquals(rv.status_code, 404)
|
|
|
|
|
|
|
|
def test_signed_in_user_and_different_query_result(self):
|
|
|
|
ds2 = self.factory.create_data_source(group=self.factory.org.admin_group, view_only=False)
|
|
|
|
query = self.factory.create_query(query_text="SELECT 8")
|
|
|
|
query_result2 = self.factory.create_query_result(data_source=ds2, query_hash='something-different')
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/results/{}.json'.format(query.id, query_result2.id))
|
|
|
|
self.assertEquals(rv.status_code, 403)
|
|
|
|
|
2016-07-11 08:51:52 +00:00
|
|
|
|
2019-02-10 11:10:39 +00:00
|
|
|
class TestQueryResultDropdownResource(BaseTestCase):
|
|
|
|
def test_checks_for_access_to_the_query(self):
|
|
|
|
ds2 = self.factory.create_data_source(group=self.factory.org.admin_group, view_only=False)
|
|
|
|
query = self.factory.create_query(data_source=ds2)
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/dropdown'.format(query.id))
|
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 403)
|
|
|
|
|
2019-05-12 09:48:01 +00:00
|
|
|
|
2019-04-02 08:45:38 +00:00
|
|
|
class TestQueryDropdownsResource(BaseTestCase):
|
2019-05-12 09:48:01 +00:00
|
|
|
def test_prevents_access_if_unassociated_and_doesnt_have_access(self):
|
2019-04-02 08:45:38 +00:00
|
|
|
query = self.factory.create_query()
|
2019-05-12 09:48:01 +00:00
|
|
|
ds2 = self.factory.create_data_source(group=self.factory.org.admin_group, view_only=False)
|
|
|
|
unrelated_dropdown_query = self.factory.create_query(data_source=ds2)
|
|
|
|
|
|
|
|
# unrelated_dropdown_query has not been associated with query
|
|
|
|
# user does not have direct access to unrelated_dropdown_query
|
2019-04-02 08:45:38 +00:00
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/dropdowns/{}'.format(query.id, unrelated_dropdown_query.id))
|
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 403)
|
|
|
|
|
2019-05-12 09:48:01 +00:00
|
|
|
def test_allows_access_if_unassociated_but_user_has_access(self):
|
2019-03-20 07:16:10 +00:00
|
|
|
query = self.factory.create_query()
|
2019-05-12 09:48:01 +00:00
|
|
|
|
|
|
|
query_result = self.factory.create_query_result()
|
|
|
|
data = {
|
|
|
|
'rows': [],
|
|
|
|
'columns': [{'name': 'whatever'}]
|
|
|
|
}
|
|
|
|
query_result = self.factory.create_query_result(data=json_dumps(data))
|
|
|
|
unrelated_dropdown_query = self.factory.create_query(latest_query_data=query_result)
|
|
|
|
|
|
|
|
# unrelated_dropdown_query has not been associated with query
|
|
|
|
# user has direct access to unrelated_dropdown_query
|
2019-03-20 07:16:10 +00:00
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/dropdowns/{}'.format(query.id, unrelated_dropdown_query.id))
|
|
|
|
|
2019-05-12 09:48:01 +00:00
|
|
|
self.assertEquals(rv.status_code, 200)
|
2019-03-20 07:16:10 +00:00
|
|
|
|
2019-05-12 09:48:01 +00:00
|
|
|
def test_allows_access_if_associated_and_has_access_to_parent(self):
|
2019-03-20 07:16:10 +00:00
|
|
|
query_result = self.factory.create_query_result()
|
|
|
|
data = {
|
|
|
|
'rows': [],
|
|
|
|
'columns': [{'name': 'whatever'}]
|
|
|
|
}
|
|
|
|
query_result = self.factory.create_query_result(data=json_dumps(data))
|
|
|
|
dropdown_query = self.factory.create_query(latest_query_data=query_result)
|
|
|
|
|
|
|
|
options = {
|
|
|
|
'parameters': [{
|
|
|
|
'type': 'query',
|
|
|
|
'queryId': dropdown_query.id
|
|
|
|
}]
|
|
|
|
}
|
|
|
|
query = self.factory.create_query(options=options)
|
|
|
|
|
2019-05-12 09:48:01 +00:00
|
|
|
# dropdown_query has been associated with query
|
|
|
|
# user has access to query
|
|
|
|
|
2019-03-20 07:16:10 +00:00
|
|
|
rv = self.make_request('get', '/api/queries/{}/dropdowns/{}'.format(query.id, dropdown_query.id))
|
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
|
|
|
|
2019-05-12 09:48:01 +00:00
|
|
|
def test_prevents_access_if_associated_and_doesnt_have_access_to_parent(self):
|
|
|
|
ds2 = self.factory.create_data_source(group=self.factory.org.admin_group, view_only=False)
|
|
|
|
dropdown_query = self.factory.create_query(data_source=ds2)
|
2019-03-20 07:16:10 +00:00
|
|
|
options = {
|
|
|
|
'parameters': [{
|
|
|
|
'type': 'query',
|
2019-05-12 09:48:01 +00:00
|
|
|
'queryId': dropdown_query.id
|
2019-03-20 07:16:10 +00:00
|
|
|
}]
|
|
|
|
}
|
2019-05-12 09:48:01 +00:00
|
|
|
query = self.factory.create_query(data_source=ds2, options=options)
|
2019-03-20 07:16:10 +00:00
|
|
|
|
2019-05-12 09:48:01 +00:00
|
|
|
# dropdown_query has been associated with query
|
|
|
|
# user doesnt have access to either query
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/dropdowns/{}'.format(query.id, dropdown_query.id))
|
2019-03-20 07:16:10 +00:00
|
|
|
|
|
|
|
self.assertEquals(rv.status_code, 403)
|
2019-02-10 11:10:39 +00:00
|
|
|
|
2019-04-02 08:45:38 +00:00
|
|
|
|
2016-07-11 08:51:52 +00:00
|
|
|
class TestQueryResultExcelResponse(BaseTestCase):
|
|
|
|
def test_renders_excel_file(self):
|
|
|
|
query = self.factory.create_query()
|
|
|
|
query_result = self.factory.create_query_result()
|
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/results/{}.xlsx'.format(query.id, query_result.id), is_json=False)
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
|
|
|
|
|
|
|
def test_renders_excel_file_when_rows_have_missing_columns(self):
|
|
|
|
query = self.factory.create_query()
|
2018-10-09 13:38:06 +00:00
|
|
|
data = {
|
|
|
|
'rows': [
|
|
|
|
{'test': 1},
|
|
|
|
{'test': 2, 'test2': 3},
|
|
|
|
],
|
|
|
|
'columns': [
|
|
|
|
{'name': 'test'},
|
|
|
|
{'name': 'test2'},
|
|
|
|
],
|
|
|
|
}
|
|
|
|
query_result = self.factory.create_query_result(data=json_dumps(data))
|
2016-07-11 08:51:52 +00:00
|
|
|
|
|
|
|
rv = self.make_request('get', '/api/queries/{}/results/{}.xlsx'.format(query.id, query_result.id), is_json=False)
|
|
|
|
self.assertEquals(rv.status_code, 200)
|
|
|
|
|