audit_ctrl_specs.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
  2. import _ from 'lodash';
  3. import {AuditLogCtrl} from 'app/features/dashboard/audit/audit_ctrl';
  4. import { versions, compare, restore } from 'test/mocks/audit-mocks';
  5. import config from 'app/core/config';
  6. describe('AuditLogCtrl', function() {
  7. var RESTORE_ID = 4;
  8. var ctx: any = {};
  9. var versionsResponse: any = versions();
  10. var restoreResponse: any = restore(7, RESTORE_ID);
  11. beforeEach(angularMocks.module('grafana.core'));
  12. beforeEach(angularMocks.module('grafana.services'));
  13. beforeEach(angularMocks.inject($rootScope => {
  14. ctx.scope = $rootScope.$new();
  15. }));
  16. var auditSrv;
  17. var $rootScope;
  18. beforeEach(function() {
  19. auditSrv = {
  20. getAuditLog: sinon.stub(),
  21. compareVersions: sinon.stub(),
  22. restoreDashboard: sinon.stub(),
  23. };
  24. $rootScope = {
  25. appEvent: sinon.spy(),
  26. onAppEvent: sinon.spy(),
  27. };
  28. });
  29. describe('when the audit log component is loaded', function() {
  30. var deferred;
  31. beforeEach(angularMocks.inject(($controller, $q) => {
  32. deferred = $q.defer();
  33. auditSrv.getAuditLog.returns(deferred.promise);
  34. ctx.ctrl = $controller(AuditLogCtrl, {
  35. auditSrv,
  36. $rootScope,
  37. $scope: ctx.scope,
  38. });
  39. }));
  40. it('should immediately attempt to fetch the audit log', function() {
  41. expect(auditSrv.getAuditLog.calledOnce).to.be(true);
  42. });
  43. describe('and the audit log is successfully fetched', function() {
  44. beforeEach(function() {
  45. deferred.resolve(versionsResponse);
  46. ctx.ctrl.$scope.$apply();
  47. });
  48. it('should reset the controller\'s state', function() {
  49. expect(ctx.ctrl.mode).to.be('list');
  50. expect(ctx.ctrl.delta).to.eql({ basic: '', html: '' });
  51. expect(ctx.ctrl.selected.length).to.be(0);
  52. expect(ctx.ctrl.selected).to.eql([]);
  53. expect(_.find(ctx.ctrl.revisions, rev => rev.checked)).to.be.undefined;
  54. });
  55. it('should indicate loading has finished', function() {
  56. expect(ctx.ctrl.loading).to.be(false);
  57. });
  58. it('should store the revisions sorted desc by version id', function() {
  59. expect(ctx.ctrl.revisions[0].version).to.be(4);
  60. expect(ctx.ctrl.revisions[1].version).to.be(3);
  61. expect(ctx.ctrl.revisions[2].version).to.be(2);
  62. expect(ctx.ctrl.revisions[3].version).to.be(1);
  63. });
  64. it('should add a checked property to each revision', function() {
  65. var actual = _.filter(ctx.ctrl.revisions, rev => rev.hasOwnProperty('checked'));
  66. expect(actual.length).to.be(4);
  67. });
  68. it('should set all checked properties to false on reset', function() {
  69. ctx.ctrl.revisions[0].checked = true;
  70. ctx.ctrl.revisions[2].checked = true;
  71. ctx.ctrl.selected = [0, 2];
  72. ctx.ctrl.reset();
  73. var actual = _.filter(ctx.ctrl.revisions, rev => !rev.checked);
  74. expect(actual.length).to.be(4);
  75. expect(ctx.ctrl.selected).to.eql([]);
  76. });
  77. it('should add a default message to versions without a message', function() {
  78. expect(ctx.ctrl.revisions[0].message).to.be('Dashboard saved');
  79. });
  80. it('should add a message to revisions restored from another version', function() {
  81. expect(ctx.ctrl.revisions[1].message).to.be('Restored from version 1');
  82. });
  83. it('should add a message to entries that overwrote version history', function() {
  84. expect(ctx.ctrl.revisions[2].message).to.be('Dashboard overwritten');
  85. });
  86. it('should add a message to the initial dashboard save', function() {
  87. expect(ctx.ctrl.revisions[3].message).to.be('Dashboard\'s initial save');
  88. });
  89. });
  90. describe('and fetching the audit log fails', function() {
  91. beforeEach(function() {
  92. deferred.reject(new Error('AuditLogError'));
  93. ctx.ctrl.$scope.$apply();
  94. });
  95. it('should reset the controller\'s state', function() {
  96. expect(ctx.ctrl.mode).to.be('list');
  97. expect(ctx.ctrl.delta).to.eql({ basic: '', html: '' });
  98. expect(ctx.ctrl.selected.length).to.be(0);
  99. expect(ctx.ctrl.selected).to.eql([]);
  100. expect(_.find(ctx.ctrl.revisions, rev => rev.checked)).to.be.undefined;
  101. });
  102. it('should indicate loading has finished', function() {
  103. expect(ctx.ctrl.loading).to.be(false);
  104. });
  105. it('should broadcast an event indicating the failure', function() {
  106. expect($rootScope.appEvent.calledOnce).to.be(true);
  107. expect($rootScope.appEvent.calledWith('alert-error')).to.be(true);
  108. });
  109. it('should have an empty revisions list', function() {
  110. expect(ctx.ctrl.revisions).to.eql([]);
  111. });
  112. });
  113. describe('should update the audit log when the dashboard is saved', function() {
  114. beforeEach(function() {
  115. ctx.ctrl.dashboard = { version: 3 };
  116. ctx.ctrl.resetFromSource = sinon.spy();
  117. });
  118. it('should listen for the `dashboard-saved` appEvent', function() {
  119. expect($rootScope.onAppEvent.calledOnce).to.be(true);
  120. expect($rootScope.onAppEvent.getCall(0).args[0]).to.be('dashboard-saved');
  121. });
  122. it('should call `onDashboardSaved` when the appEvent is received', function() {
  123. expect($rootScope.onAppEvent.getCall(0).args[1]).to.not.be(ctx.ctrl.onDashboardSaved);
  124. expect($rootScope.onAppEvent.getCall(0).args[1].toString).to.be(ctx.ctrl.onDashboardSaved.toString);
  125. });
  126. it('should emit an appEvent to hide the changelog', function() {
  127. ctx.ctrl.onDashboardSaved();
  128. expect($rootScope.appEvent.calledOnce).to.be(true);
  129. expect($rootScope.appEvent.getCall(0).args[0]).to.be('hide-dash-editor');
  130. });
  131. });
  132. });
  133. describe('when the user wants to compare two revisions', function() {
  134. var deferred;
  135. beforeEach(angularMocks.inject(($controller, $q) => {
  136. deferred = $q.defer();
  137. auditSrv.getAuditLog.returns($q.when(versionsResponse));
  138. auditSrv.compareVersions.returns(deferred.promise);
  139. ctx.ctrl = $controller(AuditLogCtrl, {
  140. auditSrv,
  141. $rootScope,
  142. $scope: ctx.scope,
  143. });
  144. ctx.ctrl.$scope.onDashboardSaved = sinon.spy();
  145. ctx.ctrl.$scope.$apply();
  146. }));
  147. it('should have already fetched the audit log', function() {
  148. expect(auditSrv.getAuditLog.calledOnce).to.be(true);
  149. expect(ctx.ctrl.revisions.length).to.be.above(0);
  150. });
  151. it('should check that two valid versions are selected', function() {
  152. // []
  153. expect(ctx.ctrl.isComparable()).to.be(false);
  154. // single value
  155. ctx.ctrl.selected = [4];
  156. expect(ctx.ctrl.isComparable()).to.be(false);
  157. // both values in range
  158. ctx.ctrl.selected = [4, 2];
  159. expect(ctx.ctrl.isComparable()).to.be(true);
  160. // values out of range
  161. ctx.ctrl.selected = [7, 4];
  162. expect(ctx.ctrl.isComparable()).to.be(false);
  163. });
  164. describe('and the basic diff is successfully fetched', function() {
  165. beforeEach(function() {
  166. deferred.resolve(compare('basic'));
  167. ctx.ctrl.selected = [3, 1];
  168. ctx.ctrl.getDiff('basic');
  169. ctx.ctrl.$scope.$apply();
  170. });
  171. it('should fetch the basic diff if two valid versions are selected', function() {
  172. expect(auditSrv.compareVersions.calledOnce).to.be(true);
  173. expect(ctx.ctrl.delta.basic).to.be('<div></div>');
  174. expect(ctx.ctrl.delta.html).to.be('');
  175. });
  176. it('should set the basic diff view as active', function() {
  177. expect(ctx.ctrl.mode).to.be('compare');
  178. expect(ctx.ctrl.diff).to.be('basic');
  179. });
  180. it('should indicate loading has finished', function() {
  181. expect(ctx.ctrl.loading).to.be(false);
  182. });
  183. });
  184. describe('and the json diff is successfully fetched', function() {
  185. beforeEach(function() {
  186. deferred.resolve(compare('html'));
  187. ctx.ctrl.selected = [3, 1];
  188. ctx.ctrl.getDiff('html');
  189. ctx.ctrl.$scope.$apply();
  190. });
  191. it('should fetch the json diff if two valid versions are selected', function() {
  192. expect(auditSrv.compareVersions.calledOnce).to.be(true);
  193. expect(ctx.ctrl.delta.basic).to.be('');
  194. expect(ctx.ctrl.delta.html).to.be('<pre><code></code></pre>');
  195. });
  196. it('should set the json diff view as active', function() {
  197. expect(ctx.ctrl.mode).to.be('compare');
  198. expect(ctx.ctrl.diff).to.be('html');
  199. });
  200. it('should indicate loading has finished', function() {
  201. expect(ctx.ctrl.loading).to.be(false);
  202. });
  203. });
  204. describe('and diffs have already been fetched', function() {
  205. beforeEach(function() {
  206. deferred.resolve(compare('basic'));
  207. ctx.ctrl.selected = [3, 1];
  208. ctx.ctrl.delta.basic = 'cached basic';
  209. ctx.ctrl.getDiff('basic');
  210. ctx.ctrl.$scope.$apply();
  211. });
  212. it('should use the cached diffs instead of fetching', function() {
  213. expect(auditSrv.compareVersions.calledOnce).to.be(false);
  214. expect(ctx.ctrl.delta.basic).to.be('cached basic');
  215. });
  216. it('should indicate loading has finished', function() {
  217. expect(ctx.ctrl.loading).to.be(false);
  218. });
  219. });
  220. describe('and fetching the diff fails', function() {
  221. beforeEach(function() {
  222. deferred.reject(new Error('DiffError'));
  223. ctx.ctrl.selected = [4, 2];
  224. ctx.ctrl.getDiff('basic');
  225. ctx.ctrl.$scope.$apply();
  226. });
  227. it('should fetch the diff if two valid versions are selected', function() {
  228. expect(auditSrv.compareVersions.calledOnce).to.be(true);
  229. });
  230. it('should return to the audit log view', function() {
  231. expect(ctx.ctrl.mode).to.be('list');
  232. });
  233. it('should indicate loading has finished', function() {
  234. expect(ctx.ctrl.loading).to.be(false);
  235. });
  236. it('should broadcast an event indicating the failure', function() {
  237. expect($rootScope.appEvent.calledOnce).to.be(true);
  238. expect($rootScope.appEvent.calledWith('alert-error')).to.be(true);
  239. });
  240. it('should have an empty delta/changeset', function() {
  241. expect(ctx.ctrl.delta).to.eql({ basic: '', html: '' });
  242. });
  243. });
  244. });
  245. describe('when the user wants to restore a revision', function() {
  246. var deferred;
  247. beforeEach(angularMocks.inject(($controller, $q) => {
  248. deferred = $q.defer();
  249. auditSrv.getAuditLog.returns($q.when(versionsResponse));
  250. auditSrv.restoreDashboard.returns(deferred.promise);
  251. ctx.ctrl = $controller(AuditLogCtrl, {
  252. auditSrv,
  253. contextSrv: { user: { name: 'Carlos' }},
  254. $rootScope,
  255. $scope: ctx.scope,
  256. });
  257. ctx.ctrl.$scope.setupDashboard = sinon.stub();
  258. ctx.ctrl.dashboard = { id: 1 };
  259. ctx.ctrl.restore();
  260. ctx.ctrl.$scope.$apply();
  261. }));
  262. it('should display a modal allowing the user to restore or cancel', function() {
  263. expect($rootScope.appEvent.calledOnce).to.be(true);
  264. expect($rootScope.appEvent.calledWith('confirm-modal')).to.be(true);
  265. });
  266. describe('from the diff view', function() {
  267. it('should return to the list view on restore', function() {
  268. ctx.ctrl.mode = 'compare';
  269. deferred.resolve(restoreResponse);
  270. ctx.ctrl.restoreConfirm(RESTORE_ID);
  271. ctx.ctrl.$scope.$apply();
  272. expect(ctx.ctrl.mode).to.be('list');
  273. });
  274. });
  275. describe('and restore is selected and successful', function() {
  276. beforeEach(function() {
  277. deferred.resolve(restoreResponse);
  278. ctx.ctrl.restoreConfirm(RESTORE_ID);
  279. ctx.ctrl.$scope.$apply();
  280. });
  281. it('should indicate loading has finished', function() {
  282. expect(ctx.ctrl.loading).to.be(false);
  283. });
  284. it('should add an entry for the restored revision to the audit log', function() {
  285. expect(ctx.ctrl.revisions.length).to.be(5);
  286. });
  287. describe('the restored revision', function() {
  288. var first;
  289. beforeEach(function() { first = ctx.ctrl.revisions[0]; });
  290. it('should have its `id` and `version` numbers incremented', function() {
  291. expect(first.id).to.be(5);
  292. expect(first.version).to.be(5);
  293. });
  294. it('should set `parentVersion` to the reverted version', function() {
  295. expect(first.parentVersion).to.be(RESTORE_ID);
  296. });
  297. it('should set `dashboardId` to the dashboard\'s id', function() {
  298. expect(first.dashboardId).to.be(1);
  299. });
  300. it('should set `created` to date to the current time', function() {
  301. expect(_.isDate(first.created)).to.be(true);
  302. });
  303. it('should set `createdBy` to the username of the user who reverted', function() {
  304. expect(first.createdBy).to.be('Carlos');
  305. });
  306. it('should set `message` to the user\'s commit message', function() {
  307. expect(first.message).to.be('Restored from version 4');
  308. });
  309. });
  310. it('should reset the controller\'s state', function() {
  311. expect(ctx.ctrl.mode).to.be('list');
  312. expect(ctx.ctrl.delta).to.eql({ basic: '', html: '' });
  313. expect(ctx.ctrl.selected.length).to.be(0);
  314. expect(ctx.ctrl.selected).to.eql([]);
  315. expect(_.find(ctx.ctrl.revisions, rev => rev.checked)).to.be.undefined;
  316. });
  317. it('should set the dashboard object to the response dashboard data', function() {
  318. expect(ctx.ctrl.dashboard).to.eql(restoreResponse.dashboard.dashboard);
  319. expect(ctx.ctrl.dashboard.meta).to.eql(restoreResponse.dashboard.meta);
  320. });
  321. it('should call setupDashboard to render new revision', function() {
  322. expect(ctx.ctrl.$scope.setupDashboard.calledOnce).to.be(true);
  323. expect(ctx.ctrl.$scope.setupDashboard.getCall(0).args[0]).to.eql(restoreResponse.dashboard);
  324. });
  325. });
  326. describe('and restore fails to fetch', function() {
  327. beforeEach(function() {
  328. deferred.reject(new Error('RestoreError'));
  329. ctx.ctrl.restoreConfirm(RESTORE_ID);
  330. ctx.ctrl.$scope.$apply();
  331. });
  332. it('should indicate loading has finished', function() {
  333. expect(ctx.ctrl.loading).to.be(false);
  334. });
  335. it('should broadcast an event indicating the failure', function() {
  336. expect($rootScope.appEvent.callCount).to.be(2);
  337. expect($rootScope.appEvent.getCall(0).calledWith('confirm-modal')).to.be(true);
  338. expect($rootScope.appEvent.getCall(1).args[0]).to.be('alert-error');
  339. expect($rootScope.appEvent.getCall(1).args[1][0]).to.be('There was an error restoring the dashboard');
  340. });
  341. // TODO: test state after failure i.e. do we hide the modal or keep it visible
  342. });
  343. });
  344. });