chart_api_tests.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. # Licensed to the Apache Software Foundation (ASF) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The ASF licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. """Unit tests for Superset"""
  18. import json
  19. from typing import List, Optional
  20. import prison
  21. from superset import db, security_manager
  22. from superset.connectors.connector_registry import ConnectorRegistry
  23. from superset.models.dashboard import Dashboard
  24. from superset.models.slice import Slice
  25. from .base_api_tests import ApiOwnersTestCaseMixin
  26. from .base_tests import SupersetTestCase
  27. class ChartApiTests(SupersetTestCase, ApiOwnersTestCaseMixin):
  28. resource_name = "chart"
  29. def __init__(self, *args, **kwargs):
  30. super(ChartApiTests, self).__init__(*args, **kwargs)
  31. def insert_chart(
  32. self,
  33. slice_name: str,
  34. owners: List[int],
  35. datasource_id: int,
  36. datasource_type: str = "table",
  37. description: Optional[str] = None,
  38. viz_type: Optional[str] = None,
  39. params: Optional[str] = None,
  40. cache_timeout: Optional[int] = None,
  41. ) -> Slice:
  42. obj_owners = list()
  43. for owner in owners:
  44. user = db.session.query(security_manager.user_model).get(owner)
  45. obj_owners.append(user)
  46. datasource = ConnectorRegistry.get_datasource(
  47. datasource_type, datasource_id, db.session
  48. )
  49. slice = Slice(
  50. slice_name=slice_name,
  51. datasource_id=datasource.id,
  52. datasource_name=datasource.name,
  53. datasource_type=datasource.type,
  54. owners=obj_owners,
  55. description=description,
  56. viz_type=viz_type,
  57. params=params,
  58. cache_timeout=cache_timeout,
  59. )
  60. db.session.add(slice)
  61. db.session.commit()
  62. return slice
  63. def test_delete_chart(self):
  64. """
  65. Chart API: Test delete
  66. """
  67. admin_id = self.get_user("admin").id
  68. chart_id = self.insert_chart("name", [admin_id], 1).id
  69. self.login(username="admin")
  70. uri = f"api/v1/chart/{chart_id}"
  71. rv = self.client.delete(uri)
  72. self.assertEqual(rv.status_code, 200)
  73. model = db.session.query(Slice).get(chart_id)
  74. self.assertEqual(model, None)
  75. def test_delete_not_found_chart(self):
  76. """
  77. Chart API: Test not found delete
  78. """
  79. self.login(username="admin")
  80. chart_id = 1000
  81. uri = f"api/v1/chart/{chart_id}"
  82. rv = self.client.delete(uri)
  83. self.assertEqual(rv.status_code, 404)
  84. def test_delete_chart_admin_not_owned(self):
  85. """
  86. Chart API: Test admin delete not owned
  87. """
  88. gamma_id = self.get_user("gamma").id
  89. chart_id = self.insert_chart("title", [gamma_id], 1).id
  90. self.login(username="admin")
  91. uri = f"api/v1/chart/{chart_id}"
  92. rv = self.client.delete(uri)
  93. self.assertEqual(rv.status_code, 200)
  94. model = db.session.query(Slice).get(chart_id)
  95. self.assertEqual(model, None)
  96. def test_delete_chart_not_owned(self):
  97. """
  98. Chart API: Test delete try not owned
  99. """
  100. user_alpha1 = self.create_user(
  101. "alpha1", "password", "Alpha", email="alpha1@superset.org"
  102. )
  103. user_alpha2 = self.create_user(
  104. "alpha2", "password", "Alpha", email="alpha2@superset.org"
  105. )
  106. chart = self.insert_chart("title", [user_alpha1.id], 1)
  107. self.login(username="alpha2", password="password")
  108. uri = f"api/v1/chart/{chart.id}"
  109. rv = self.client.delete(uri)
  110. self.assertEqual(rv.status_code, 403)
  111. db.session.delete(chart)
  112. db.session.delete(user_alpha1)
  113. db.session.delete(user_alpha2)
  114. db.session.commit()
  115. def test_create_chart(self):
  116. """
  117. Chart API: Test create chart
  118. """
  119. admin_id = self.get_user("admin").id
  120. chart_data = {
  121. "slice_name": "name1",
  122. "description": "description1",
  123. "owners": [admin_id],
  124. "viz_type": "viz_type1",
  125. "params": "1234",
  126. "cache_timeout": 1000,
  127. "datasource_id": 1,
  128. "datasource_type": "table",
  129. "dashboards": [1, 2],
  130. }
  131. self.login(username="admin")
  132. uri = f"api/v1/chart/"
  133. rv = self.client.post(uri, json=chart_data)
  134. self.assertEqual(rv.status_code, 201)
  135. data = json.loads(rv.data.decode("utf-8"))
  136. model = db.session.query(Slice).get(data.get("id"))
  137. db.session.delete(model)
  138. db.session.commit()
  139. def test_create_simple_chart(self):
  140. """
  141. Chart API: Test create simple chart
  142. """
  143. chart_data = {
  144. "slice_name": "title1",
  145. "datasource_id": 1,
  146. "datasource_type": "table",
  147. }
  148. self.login(username="admin")
  149. uri = f"api/v1/chart/"
  150. rv = self.client.post(uri, json=chart_data)
  151. self.assertEqual(rv.status_code, 201)
  152. data = json.loads(rv.data.decode("utf-8"))
  153. model = db.session.query(Slice).get(data.get("id"))
  154. db.session.delete(model)
  155. db.session.commit()
  156. def test_create_chart_validate_owners(self):
  157. """
  158. Chart API: Test create validate owners
  159. """
  160. chart_data = {
  161. "slice_name": "title1",
  162. "datasource_id": 1,
  163. "datasource_type": "table",
  164. "owners": [1000],
  165. }
  166. self.login(username="admin")
  167. uri = f"api/v1/chart/"
  168. rv = self.client.post(uri, json=chart_data)
  169. self.assertEqual(rv.status_code, 422)
  170. response = json.loads(rv.data.decode("utf-8"))
  171. expected_response = {"message": {"owners": {"0": ["User 1000 does not exist"]}}}
  172. self.assertEqual(response, expected_response)
  173. def test_create_chart_validate_params(self):
  174. """
  175. Chart API: Test create validate params json
  176. """
  177. chart_data = {
  178. "slice_name": "title1",
  179. "datasource_id": 1,
  180. "datasource_type": "table",
  181. "params": '{"A:"a"}',
  182. }
  183. self.login(username="admin")
  184. uri = f"api/v1/chart/"
  185. rv = self.client.post(uri, json=chart_data)
  186. self.assertEqual(rv.status_code, 422)
  187. def test_create_chart_validate_datasource(self):
  188. """
  189. Chart API: Test create validate datasource
  190. """
  191. self.login(username="admin")
  192. chart_data = {
  193. "slice_name": "title1",
  194. "datasource_id": 1,
  195. "datasource_type": "unknown",
  196. }
  197. uri = f"api/v1/chart/"
  198. rv = self.client.post(uri, json=chart_data)
  199. self.assertEqual(rv.status_code, 422)
  200. response = json.loads(rv.data.decode("utf-8"))
  201. self.assertEqual(
  202. response,
  203. {"message": {"_schema": ["Datasource [unknown].1 does not exist"]}},
  204. )
  205. chart_data = {
  206. "slice_name": "title1",
  207. "datasource_id": 0,
  208. "datasource_type": "table",
  209. }
  210. uri = f"api/v1/chart/"
  211. rv = self.client.post(uri, json=chart_data)
  212. self.assertEqual(rv.status_code, 422)
  213. response = json.loads(rv.data.decode("utf-8"))
  214. self.assertEqual(
  215. response, {"message": {"_schema": ["Datasource [table].0 does not exist"]}}
  216. )
  217. def test_update_chart(self):
  218. """
  219. Chart API: Test update
  220. """
  221. admin = self.get_user("admin")
  222. gamma = self.get_user("gamma")
  223. chart_id = self.insert_chart("title", [admin.id], 1).id
  224. chart_data = {
  225. "slice_name": "title1_changed",
  226. "description": "description1",
  227. "owners": [gamma.id],
  228. "viz_type": "viz_type1",
  229. "params": "{'a': 1}",
  230. "cache_timeout": 1000,
  231. "datasource_id": 1,
  232. "datasource_type": "table",
  233. "dashboards": [1],
  234. }
  235. self.login(username="admin")
  236. uri = f"api/v1/chart/{chart_id}"
  237. rv = self.client.put(uri, json=chart_data)
  238. self.assertEqual(rv.status_code, 200)
  239. model = db.session.query(Slice).get(chart_id)
  240. related_dashboard = db.session.query(Dashboard).get(1)
  241. self.assertEqual(model.slice_name, "title1_changed")
  242. self.assertEqual(model.description, "description1")
  243. self.assertIn(admin, model.owners)
  244. self.assertIn(gamma, model.owners)
  245. self.assertEqual(model.viz_type, "viz_type1")
  246. self.assertEqual(model.params, "{'a': 1}")
  247. self.assertEqual(model.cache_timeout, 1000)
  248. self.assertEqual(model.datasource_id, 1)
  249. self.assertEqual(model.datasource_type, "table")
  250. self.assertEqual(model.datasource_name, "birth_names")
  251. self.assertIn(related_dashboard, model.dashboards)
  252. db.session.delete(model)
  253. db.session.commit()
  254. def test_update_chart_new_owner(self):
  255. """
  256. Chart API: Test update set new owner to current user
  257. """
  258. gamma = self.get_user("gamma")
  259. admin = self.get_user("admin")
  260. chart_id = self.insert_chart("title", [gamma.id], 1).id
  261. chart_data = {"slice_name": "title1_changed"}
  262. self.login(username="admin")
  263. uri = f"api/v1/chart/{chart_id}"
  264. rv = self.client.put(uri, json=chart_data)
  265. self.assertEqual(rv.status_code, 200)
  266. model = db.session.query(Slice).get(chart_id)
  267. self.assertIn(admin, model.owners)
  268. db.session.delete(model)
  269. db.session.commit()
  270. def test_update_chart_not_owned(self):
  271. """
  272. Chart API: Test update not owned
  273. """
  274. user_alpha1 = self.create_user(
  275. "alpha1", "password", "Alpha", email="alpha1@superset.org"
  276. )
  277. user_alpha2 = self.create_user(
  278. "alpha2", "password", "Alpha", email="alpha2@superset.org"
  279. )
  280. chart = self.insert_chart("title", [user_alpha1.id], 1)
  281. self.login(username="alpha2", password="password")
  282. chart_data = {"slice_name": "title1_changed"}
  283. uri = f"api/v1/chart/{chart.id}"
  284. rv = self.client.put(uri, json=chart_data)
  285. self.assertEqual(rv.status_code, 403)
  286. db.session.delete(chart)
  287. db.session.delete(user_alpha1)
  288. db.session.delete(user_alpha2)
  289. db.session.commit()
  290. def test_update_chart_validate_datasource(self):
  291. """
  292. Chart API: Test update validate datasource
  293. """
  294. admin = self.get_user("admin")
  295. chart = self.insert_chart("title", [admin.id], 1)
  296. self.login(username="admin")
  297. chart_data = {"datasource_id": 1, "datasource_type": "unknown"}
  298. uri = f"api/v1/chart/{chart.id}"
  299. rv = self.client.put(uri, json=chart_data)
  300. self.assertEqual(rv.status_code, 422)
  301. response = json.loads(rv.data.decode("utf-8"))
  302. self.assertEqual(
  303. response,
  304. {"message": {"_schema": ["Datasource [unknown].1 does not exist"]}},
  305. )
  306. chart_data = {"datasource_id": 0, "datasource_type": "table"}
  307. uri = f"api/v1/chart/{chart.id}"
  308. rv = self.client.put(uri, json=chart_data)
  309. self.assertEqual(rv.status_code, 422)
  310. response = json.loads(rv.data.decode("utf-8"))
  311. self.assertEqual(
  312. response, {"message": {"_schema": ["Datasource [table].0 does not exist"]}}
  313. )
  314. db.session.delete(chart)
  315. db.session.commit()
  316. def test_update_chart_validate_owners(self):
  317. """
  318. Chart API: Test update validate owners
  319. """
  320. chart_data = {
  321. "slice_name": "title1",
  322. "datasource_id": 1,
  323. "datasource_type": "table",
  324. "owners": [1000],
  325. }
  326. self.login(username="admin")
  327. uri = f"api/v1/chart/"
  328. rv = self.client.post(uri, json=chart_data)
  329. self.assertEqual(rv.status_code, 422)
  330. response = json.loads(rv.data.decode("utf-8"))
  331. expected_response = {"message": {"owners": {"0": ["User 1000 does not exist"]}}}
  332. self.assertEqual(response, expected_response)
  333. def test_get_chart(self):
  334. """
  335. Chart API: Test get chart
  336. """
  337. admin = self.get_user("admin")
  338. chart = self.insert_chart("title", [admin.id], 1)
  339. self.login(username="admin")
  340. uri = f"api/v1/chart/{chart.id}"
  341. rv = self.client.get(uri)
  342. self.assertEqual(rv.status_code, 200)
  343. expected_result = {
  344. "cache_timeout": None,
  345. "dashboards": [],
  346. "description": None,
  347. "owners": [{"id": 1, "username": "admin"}],
  348. "params": None,
  349. "slice_name": "title",
  350. "viz_type": None,
  351. }
  352. data = json.loads(rv.data.decode("utf-8"))
  353. self.assertEqual(data["result"], expected_result)
  354. db.session.delete(chart)
  355. db.session.commit()
  356. def test_get_chart_not_found(self):
  357. """
  358. Chart API: Test get chart not found
  359. """
  360. chart_id = 1000
  361. self.login(username="admin")
  362. uri = f"api/v1/chart/{chart_id}"
  363. rv = self.client.get(uri)
  364. self.assertEqual(rv.status_code, 404)
  365. def test_get_chart_no_data_access(self):
  366. """
  367. Chart API: Test get chart without data access
  368. """
  369. self.login(username="gamma")
  370. chart_no_access = (
  371. db.session.query(Slice)
  372. .filter_by(slice_name="Girl Name Cloud")
  373. .one_or_none()
  374. )
  375. uri = f"api/v1/chart/{chart_no_access.id}"
  376. rv = self.client.get(uri)
  377. self.assertEqual(rv.status_code, 404)
  378. def test_get_charts(self):
  379. """
  380. Chart API: Test get charts
  381. """
  382. self.login(username="admin")
  383. uri = f"api/v1/chart/"
  384. rv = self.client.get(uri)
  385. self.assertEqual(rv.status_code, 200)
  386. data = json.loads(rv.data.decode("utf-8"))
  387. self.assertEqual(data["count"], 33)
  388. def test_get_charts_filter(self):
  389. """
  390. Chart API: Test get charts filter
  391. """
  392. self.login(username="admin")
  393. arguments = {"filters": [{"col": "slice_name", "opr": "sw", "value": "G"}]}
  394. uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
  395. rv = self.client.get(uri)
  396. self.assertEqual(rv.status_code, 200)
  397. data = json.loads(rv.data.decode("utf-8"))
  398. self.assertEqual(data["count"], 5)
  399. def test_get_charts_page(self):
  400. """
  401. Chart API: Test get charts filter
  402. """
  403. # Assuming we have 33 sample charts
  404. self.login(username="admin")
  405. arguments = {"page_size": 10, "page": 0}
  406. uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
  407. rv = self.client.get(uri)
  408. self.assertEqual(rv.status_code, 200)
  409. data = json.loads(rv.data.decode("utf-8"))
  410. self.assertEqual(len(data["result"]), 10)
  411. arguments = {"page_size": 10, "page": 3}
  412. uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
  413. rv = self.client.get(uri)
  414. self.assertEqual(rv.status_code, 200)
  415. data = json.loads(rv.data.decode("utf-8"))
  416. self.assertEqual(len(data["result"]), 3)
  417. def test_get_charts_no_data_access(self):
  418. """
  419. Chart API: Test get charts no data access
  420. """
  421. self.login(username="gamma")
  422. uri = f"api/v1/chart/"
  423. rv = self.client.get(uri)
  424. self.assertEqual(rv.status_code, 200)
  425. data = json.loads(rv.data.decode("utf-8"))
  426. self.assertEqual(data["count"], 0)