mno-enterprise-api 3.3.3 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. data/app/controllers/mno_enterprise/jpi/v1/admin/impac/dashboards_controller.rb +105 -0
  3. data/app/controllers/mno_enterprise/jpi/v1/admin/impac/widgets_controller.rb +20 -8
  4. data/app/controllers/mno_enterprise/jpi/v1/admin/invoices_controller.rb +1 -1
  5. data/app/controllers/mno_enterprise/jpi/v1/admin/organizations_controller.rb +1 -140
  6. data/app/controllers/mno_enterprise/jpi/v1/admin/sub_tenants_controller.rb +64 -0
  7. data/app/controllers/mno_enterprise/jpi/v1/admin/users_controller.rb +30 -20
  8. data/app/views/mno_enterprise/jpi/v1/admin/impac/dashboards/_dashboard.json.jbuilder +12 -0
  9. data/app/views/mno_enterprise/jpi/v1/admin/impac/dashboards/index.json.jbuilder +1 -0
  10. data/app/views/mno_enterprise/jpi/v1/admin/impac/dashboards/show.json.jbuilder +1 -0
  11. data/app/views/mno_enterprise/jpi/v1/admin/impac/widgets/_widget.json.jbuilder +1 -0
  12. data/app/views/mno_enterprise/jpi/v1/admin/organizations/_organization.json.jbuilder +1 -1
  13. data/app/views/mno_enterprise/jpi/v1/admin/sub_tenants/_sub_tenant.json.jbuilder +1 -0
  14. data/app/views/mno_enterprise/jpi/v1/admin/sub_tenants/index.json.jbuilder +2 -0
  15. data/app/views/mno_enterprise/jpi/v1/admin/sub_tenants/show.json.jbuilder +12 -0
  16. data/app/views/mno_enterprise/jpi/v1/admin/users/_user.json.jbuilder +1 -1
  17. data/app/views/mno_enterprise/jpi/v1/admin/users/show.json.jbuilder +5 -5
  18. data/app/views/mno_enterprise/jpi/v1/current_users/show.json.jbuilder +4 -2
  19. data/app/views/mno_enterprise/jpi/v1/impac/widgets/_widget.json.jbuilder +1 -0
  20. data/app/views/mno_enterprise/jpi/v1/organizations/_organization.json.jbuilder +1 -1
  21. data/config/routes.rb +9 -1
  22. data/lib/mno_enterprise/concerns/controllers/jpi/v1/admin/organizations_controller.rb +159 -0
  23. data/lib/mno_enterprise/concerns/controllers/jpi/v1/impac/alerts_controller.rb +3 -6
  24. data/lib/mno_enterprise/concerns/controllers/jpi/v1/impac/dashboards_controller.rb +29 -38
  25. data/lib/mno_enterprise/concerns/controllers/jpi/v1/impac/kpis_controller.rb +7 -8
  26. data/lib/mno_enterprise/concerns/controllers/jpi/v1/impac/widgets_controller.rb +14 -2
  27. data/lib/mno_enterprise/concerns/controllers/jpi/v1/organizations_controller.rb +1 -1
  28. data/spec/controllers/mno_enterprise/jpi/v1/admin/impac/dashboard_controller_spec.rb +149 -0
  29. data/spec/controllers/mno_enterprise/jpi/v1/admin/impac/dashboard_templates_controller_spec.rb +151 -139
  30. data/spec/controllers/mno_enterprise/jpi/v1/admin/impac/kpis_controller_spec.rb +95 -69
  31. data/spec/controllers/mno_enterprise/jpi/v1/admin/impac/widgets_controller_spec.rb +169 -81
  32. data/spec/controllers/mno_enterprise/jpi/v1/admin/invoices_controller_spec.rb +1 -1
  33. data/spec/controllers/mno_enterprise/jpi/v1/admin/organizations_controller_spec.rb +2 -1
  34. data/spec/controllers/mno_enterprise/jpi/v1/admin/sub_tenants_controller_spec.rb +172 -0
  35. data/spec/controllers/mno_enterprise/jpi/v1/admin/users_controller_spec.rb +29 -12
  36. data/spec/controllers/mno_enterprise/jpi/v1/current_users_controller_spec.rb +4 -2
  37. data/spec/controllers/mno_enterprise/jpi/v1/impac/dashboards_controller_spec.rb +26 -15
  38. data/spec/controllers/mno_enterprise/jpi/v1/impac/kpis_controller_spec.rb +11 -7
  39. data/spec/routing/mno_enterprise/jpi/v1/admin/impac/dashboards_controller_routing_spec.rb +28 -0
  40. data/spec/routing/mno_enterprise/jpi/v1/impac/dashboards_controller_routing_spec.rb +4 -0
  41. metadata +85 -70
@@ -24,6 +24,8 @@ module MnoEnterprise::Concerns::Controllers::Jpi::V1::Impac::WidgetsController
24
24
  # -> POST /api/mnoe/v1/dashboards/:id/widgets
25
25
  def create
26
26
  if widgets
27
+ authorize! :create_impac_widgets, widgets.build(widget_create_params)
28
+
27
29
  if @widget = widgets.create(widget_create_params)
28
30
  MnoEnterprise::EventLogger.info('widget_create', current_user.id, 'Widget Creation', widget)
29
31
  @nocontent = true # no data fetch from Connec!
@@ -39,6 +41,8 @@ module MnoEnterprise::Concerns::Controllers::Jpi::V1::Impac::WidgetsController
39
41
  # PUT /mnoe/jpi/v1/impac/widgets/:id
40
42
  # -> PUT /api/mnoe/v1/widgets/:id
41
43
  def update
44
+ authorize! :update_impac_widgets, widget
45
+
42
46
  if widget.update(widget_update_params)
43
47
  MnoEnterprise::EventLogger.info('widget_update', current_user.id, 'Widget Update', widget, {widget_action: params[:widget]})
44
48
  @nocontent = !params['metadata']
@@ -51,6 +55,8 @@ module MnoEnterprise::Concerns::Controllers::Jpi::V1::Impac::WidgetsController
51
55
  # DELETE /mnoe/jpi/v1/impac/widgets/:id
52
56
  # -> DELETE /api/mnoe/v1/widgets/:id
53
57
  def destroy
58
+ authorize! :destroy_impac_widgets, widget
59
+
54
60
  if widget.destroy
55
61
  MnoEnterprise::EventLogger.info('widget_delete', current_user.id, 'Widget Deletion', widget)
56
62
  head status: :ok
@@ -69,13 +75,19 @@ module MnoEnterprise::Concerns::Controllers::Jpi::V1::Impac::WidgetsController
69
75
  @widget ||= MnoEnterprise::Impac::Widget.find(params[:id])
70
76
  end
71
77
 
78
+ def parent_dashboard
79
+ @parent_dashboard ||= MnoEnterprise::Impac::Dashboard.find(params[:dashboard_id])
80
+ end
81
+
72
82
  def widgets
73
- @widgets ||= MnoEnterprise::Impac::Dashboard.find(params[:dashboard_id]).widgets
83
+ @widgets ||= parent_dashboard.widgets
74
84
  end
75
85
 
76
86
  def widget_create_params
77
- params.require(:widget).permit(:endpoint, :name, :width).tap do |whitelisted|
87
+ permitted_attrs = [:endpoint, :name, :width, { layouts: [] }]
88
+ params.require(:widget).permit(*permitted_attrs).tap do |whitelisted|
78
89
  whitelisted[:settings] = params[:widget][:metadata] || {}
90
+ whitelisted[:settings][:organization_ids] ||= parent_dashboard.settings[:organization_ids]
79
91
  # TODO: remove when mnohub migrated to new model
80
92
  whitelisted[:widget_category] = params[:widget][:endpoint]
81
93
  end
@@ -200,7 +200,7 @@ module MnoEnterprise::Concerns::Controllers::Jpi::V1::OrganizationsController
200
200
  end
201
201
 
202
202
  def organization_permitted_update_params
203
- [:name, :soa_enabled, :industry, :size]
203
+ [:name, :soa_enabled, :industry, :size, :financial_year_end_month]
204
204
  end
205
205
 
206
206
  def organization_update_params
@@ -0,0 +1,149 @@
1
+ require 'rails_helper'
2
+ require 'mno_enterprise/testing_support/shared_contexts/jpi_v1_admin_controller'
3
+ require 'mno_enterprise/testing_support/shared_contexts/jpi_v1_admin_impac_controller'
4
+
5
+ module MnoEnterprise
6
+ describe Jpi::V1::Admin::Impac::DashboardsController, type: :controller do
7
+ include_context MnoEnterprise::Jpi::V1::Admin::BaseResourceController
8
+
9
+ include MnoEnterprise::TestingSupport::SharedContexts::JpiV1AdminImpacController
10
+ include_context 'MnoEnterprise::Jpi::V1::Admin::Impac'
11
+
12
+ describe 'GET #index' do
13
+ subject { get :index }
14
+
15
+ before do
16
+ api_stub_for(
17
+ get: '/dashboards',
18
+ params: { filter: { 'owner_type' => 'User', 'owner_id' => user.id } },
19
+ response: from_api([dashboard])
20
+ )
21
+ end
22
+
23
+ include_context "#{described_class}: dashboard dependencies stubs"
24
+
25
+ it_behaves_like "a jpi v1 admin action"
26
+
27
+ it 'returns a list of dashboards' do
28
+ subject
29
+ expect(JSON.parse(response.body)).to eq([hash_for_dashboard])
30
+ end
31
+ end
32
+
33
+ describe 'POST #create' do
34
+ subject { post :create, dashboard: dashboard_params }
35
+
36
+ let(:dashboard_params) do
37
+ {
38
+ name: dashboard.name,
39
+ currency: dashboard.currency,
40
+ organization_ids: [org.id]
41
+ }
42
+ end
43
+
44
+ before do
45
+ # TODO: stub params?
46
+ api_stub_for(
47
+ post: "/dashboards",
48
+ response: from_api(dashboard)
49
+ )
50
+ end
51
+
52
+ include_context "#{described_class}: dashboard dependencies stubs"
53
+
54
+ it_behaves_like "a jpi v1 admin action"
55
+
56
+ it 'returns a dashboard' do
57
+ subject
58
+ expect(JSON.parse(response.body)).to eq(hash_for_dashboard)
59
+ end
60
+
61
+ end
62
+
63
+ describe 'PUT #update' do
64
+ subject { put :update, id: dashboard.id, dashboard: dashboard_params }
65
+
66
+ let(:dashboard_params) do
67
+ {
68
+ name: dashboard.name,
69
+ currency: dashboard.currency,
70
+ organization_ids: [org.id]
71
+ }
72
+ end
73
+
74
+ before do
75
+ api_stub_for(
76
+ get: "/dashboards",
77
+ params: {
78
+ filter: { 'id' => dashboard.id, 'owner_id' => user.id, 'owner_type' => 'User' },
79
+ limit: 1
80
+ },
81
+ response: from_api([dashboard])
82
+ )
83
+ api_stub_for(
84
+ put: "/dashboards/#{dashboard.id}",
85
+ response: from_api(dashboard)
86
+ )
87
+ end
88
+
89
+ include_context "#{described_class}: dashboard dependencies stubs"
90
+
91
+ it_behaves_like "a jpi v1 admin action"
92
+
93
+ it 'returns a dashboard' do
94
+ subject
95
+ expect(JSON.parse(response.body)).to eq(hash_for_dashboard)
96
+ end
97
+ end
98
+
99
+ describe 'DELETE destroy' do
100
+ subject { delete :destroy, id: dashboard.id }
101
+
102
+ before do
103
+ api_stub_for(
104
+ get: "/dashboards",
105
+ params: {
106
+ filter: { 'id' => dashboard.id, 'owner_id' => user.id, 'owner_type' => 'User' },
107
+ limit: 1
108
+ },
109
+ response: from_api([dashboard])
110
+ )
111
+ api_stub_for(
112
+ delete: "/dashboards/#{dashboard.id}",
113
+ response: from_api(nil)
114
+ )
115
+ end
116
+
117
+ it_behaves_like "a jpi v1 admin action"
118
+ end
119
+
120
+ describe 'POST copy' do
121
+ subject { post :copy, id: template.id, dashboard: dashboard_params }
122
+
123
+ let(:template) { build(:impac_dashboard, dashboard_type: 'template') }
124
+ let(:dashboard_params) do
125
+ {
126
+ name: dashboard.name,
127
+ currency: dashboard.currency,
128
+ organization_ids: [org.id]
129
+ }
130
+ end
131
+
132
+ before do
133
+ api_stub_for(
134
+ get: "/dashboards/#{template.id}",
135
+ params: { filter: { 'dashboard_type' => 'template' } },
136
+ response: from_api(template)
137
+ )
138
+ api_stub_for(
139
+ post: "/dashboards/#{template.id}/copy",
140
+ response: from_api(dashboard)
141
+ )
142
+ end
143
+
144
+ include_context "#{described_class}: dashboard dependencies stubs"
145
+
146
+ it_behaves_like "a jpi v1 admin action"
147
+ end
148
+ end
149
+ end
@@ -1,75 +1,20 @@
1
1
  require 'rails_helper'
2
+ require 'mno_enterprise/testing_support/shared_contexts/jpi_v1_admin_controller'
3
+ require 'mno_enterprise/testing_support/shared_contexts/jpi_v1_admin_impac_controller'
2
4
 
3
5
  module MnoEnterprise
4
6
  describe Jpi::V1::Admin::Impac::DashboardTemplatesController, type: :controller do
5
- # include MnoEnterprise::TestingSupport::JpiV1TestHelper
6
- include MnoEnterprise::TestingSupport::SharedExamples::JpiV1Admin
7
- render_views
8
- routes { MnoEnterprise::Engine.routes }
9
- before { request.env["HTTP_ACCEPT"] = 'application/json' }
7
+ include_context MnoEnterprise::Jpi::V1::Admin::BaseResourceController
10
8
 
11
- RSpec.shared_context "#{described_class}: dashboard dependencies stubs" do
12
- before do
13
- api_stub_for(
14
- get: "/users/#{user.id}/organizations",
15
- response: from_api([org])
16
- )
17
- api_stub_for(
18
- get: "/dashboards/#{template.id}/widgets",
19
- response: from_api([widget])
20
- )
21
- api_stub_for(
22
- get: "/dashboards/#{template.id}/kpis",
23
- response: from_api([d_kpi])
24
- )
25
- api_stub_for(
26
- get: "/widgets/#{widget.id}/kpis",
27
- response: from_api([w_kpi])
28
- )
29
- end
9
+ include MnoEnterprise::TestingSupport::SharedContexts::JpiV1AdminImpacController
10
+ include_context 'MnoEnterprise::Jpi::V1::Admin::Impac' do
11
+ let(:dashboard) { template }
30
12
  end
31
13
 
32
- let(:user) { build(:user, :admin, :with_organizations) }
33
- let(:org) { build(:organization, users: [user]) }
34
- let(:metadata) { { hist_parameters: { from: '2015-01-01', to: '2015-03-31', period: 'MONTHLY' } } }
35
14
  let(:template) { build(:impac_dashboard, dashboard_type: 'template', organization_ids: [org.uid], currency: 'EUR', settings: metadata, owner_type: nil, owner_id: nil, published: true) }
36
- let(:widget) { build(:impac_widget, dashboard: template) }
37
- let(:d_kpi) { build(:impac_kpi, dashboard: template) }
38
- let(:w_kpi) { build(:impac_kpi, widget: widget) }
39
-
40
- def hash_for_kpi(kpi)
41
- {
42
- "id" => kpi.id,
43
- "element_watched" => kpi.element_watched,
44
- "endpoint" => kpi.endpoint
45
- }
46
- end
47
- let(:hash_for_widget) do
48
- {
49
- "id" => widget.id,
50
- "name" => widget.name,
51
- "endpoint" => widget.widget_category,
52
- "width" => widget.width,
53
- "kpis" => [hash_for_kpi(w_kpi)]
54
- }
55
- end
56
- let(:hash_for_template) do
57
- {
58
- "id" => template.id,
59
- "name" => template.name,
60
- "full_name" => template.full_name,
61
- "currency" => 'EUR',
62
- "metadata" => metadata.deep_stringify_keys,
63
- "data_sources" => [{ "id" => org.id, "uid" => org.uid, "label" => org.name}],
64
- "kpis" => [hash_for_kpi(d_kpi)],
65
- "widgets" => [hash_for_widget],
66
- "published" => true
67
- }
68
- end
69
15
 
70
- before do
71
- api_stub_for(get: "/users/#{user.id}", response: from_api(user))
72
- sign_in user
16
+ let(:hash_for_template) do
17
+ hash_for_dashboard.merge("published" => true)
73
18
  end
74
19
 
75
20
  describe '#index' do
@@ -94,26 +39,39 @@ module MnoEnterprise
94
39
 
95
40
  describe '#show' do
96
41
  subject { get :show, id: template.id }
97
-
98
- before do
99
- api_stub_for(
100
- get: "/dashboards/#{template.id}",
101
- params: { filter: { 'dashboard_type' => 'template' } },
102
- response: from_api(template)
103
- )
104
- end
105
- include_context "#{described_class}: dashboard dependencies stubs"
106
42
 
107
- it_behaves_like "a jpi v1 admin action"
108
-
109
- it 'returns a dashboard template' do
110
- subject
111
- expect(JSON.parse(response.body)).to eq(hash_for_template)
112
- end
113
-
114
- # api_stub should be modified to allow this case to be stubbed
115
- context 'when the template cannot be found' do
116
- xit 'spec to be described'
43
+ context 'when the template exists' do
44
+ before do
45
+ api_stub_for(
46
+ get: "/dashboards/#{template.id}",
47
+ params: { filter: { 'dashboard_type' => 'template' } },
48
+ response: from_api(template)
49
+ )
50
+ end
51
+ include_context "#{described_class}: dashboard dependencies stubs"
52
+
53
+ it_behaves_like "a jpi v1 admin action"
54
+
55
+ it 'returns a dashboard template' do
56
+ subject
57
+ expect(JSON.parse(response.body)).to eq(hash_for_template)
58
+ end
59
+ end
60
+
61
+ context 'when the template does not exist' do
62
+ before do
63
+ api_stub_for(
64
+ get: "/dashboards/#{template.id}",
65
+ params: { filter: { 'dashboard_type' => 'template' } },
66
+ code: 404
67
+ )
68
+ end
69
+
70
+ it 'returns an error message' do
71
+ subject
72
+ expect(response).to have_http_status(:not_found)
73
+ expect(JSON.parse(response.body)).to eq({ 'errors' => { 'message' => 'Dashboard template not found' } })
74
+ end
117
75
  end
118
76
  end
119
77
 
@@ -130,25 +88,38 @@ module MnoEnterprise
130
88
  end
131
89
 
132
90
  subject { post :create, dashboard: template_params }
133
-
134
- before do
135
- api_stub_for(
136
- post: "/dashboards",
137
- response: from_api(template)
138
- )
139
- end
140
- include_context "#{described_class}: dashboard dependencies stubs"
141
91
 
142
- it_behaves_like "a jpi v1 admin action"
92
+ context 'when the dashboard creation is successful' do
93
+ before do
94
+ api_stub_for(
95
+ post: "/dashboards",
96
+ response: from_api(template)
97
+ )
98
+ end
99
+ include_context "#{described_class}: dashboard dependencies stubs"
143
100
 
144
- it 'returns a dashboard template' do
145
- subject
146
- expect(JSON.parse(response.body)).to eq(hash_for_template)
101
+ it_behaves_like "a jpi v1 admin action"
102
+
103
+ it 'returns a dashboard template' do
104
+ subject
105
+ expect(JSON.parse(response.body)).to eq(hash_for_template)
106
+ end
147
107
  end
148
108
 
149
- # api_stub should be modified to allow this case to be stubbed
150
109
  context 'when the dashboard creation is unsuccessful' do
151
- xit 'spec to be described'
110
+ before do
111
+ api_stub_for(
112
+ post: "/dashboards",
113
+ code: 400,
114
+ response: { errors: [{ attribute: "name", value: "can't be blank" }] }
115
+ )
116
+ end
117
+
118
+ it 'returns an error message' do
119
+ subject
120
+ expect(response).to have_http_status(:bad_request)
121
+ expect(JSON.parse(response.body)).to eq({ 'errors' => { 'name' => ["can't be blank"] } })
122
+ end
152
123
  end
153
124
  end
154
125
 
@@ -166,59 +137,100 @@ module MnoEnterprise
166
137
  end
167
138
 
168
139
  subject { put :update, id: template.id, dashboard: template_params }
169
-
170
- before do
171
- api_stub_for(
172
- get: "/dashboards/#{template.id}",
173
- params: { filter: { 'dashboard_type' => 'template' } },
174
- response: from_api(template)
175
- )
176
- api_stub_for(
177
- put: "/dashboards/#{template.id}",
178
- response: from_api(template)
179
- )
180
- end
181
- include_context "#{described_class}: dashboard dependencies stubs"
182
-
183
- it_behaves_like "a jpi v1 admin action"
184
140
 
185
- it 'returns a dashboard template' do
186
- subject
187
- expect(JSON.parse(response.body)).to eq(hash_for_template)
141
+ context 'when the template exists' do
142
+ before do
143
+ api_stub_for(
144
+ get: "/dashboards/#{template.id}",
145
+ params: { filter: { 'dashboard_type' => 'template' } },
146
+ response: from_api(template)
147
+ )
148
+ api_stub_for(
149
+ put: "/dashboards/#{template.id}",
150
+ response: from_api(template)
151
+ )
152
+ end
153
+ include_context "#{described_class}: dashboard dependencies stubs"
154
+
155
+ it_behaves_like "a jpi v1 admin action"
156
+
157
+ it 'returns a dashboard template' do
158
+ subject
159
+ expect(JSON.parse(response.body)).to eq(hash_for_template)
160
+ end
161
+ end
162
+
163
+ context 'when the template does not exist' do
164
+ before do
165
+ api_stub_for(
166
+ get: "/dashboards/#{template.id}",
167
+ params: { filter: { 'dashboard_type' => 'template' } },
168
+ code: 404
169
+ )
170
+ end
171
+
172
+ it 'returns an error message' do
173
+ subject
174
+ expect(response).to have_http_status(:not_found)
175
+ expect(JSON.parse(response.body)).to eq({ 'errors' => { 'message' => 'Dashboard template not found' } })
176
+ end
188
177
  end
189
178
 
190
- # api_stub should be modified to allow these cases to be stubbed
191
- context 'when the template cannot be found' do
192
- xit 'spec to be described'
193
- end
194
179
  context 'when the dashboard update is unsuccessful' do
195
- xit 'spec to be described'
180
+ before do
181
+ api_stub_for(
182
+ get: "/dashboards/#{template.id}",
183
+ params: { filter: { 'dashboard_type' => 'template' } },
184
+ response: from_api(template)
185
+ )
186
+ api_stub_for(
187
+ put: "/dashboards/#{template.id}",
188
+ code: 400,
189
+ response: { errors: [{ attribute: "name", value: "can't be blank" }] }
190
+ )
191
+ end
192
+
193
+ it 'returns an error message' do
194
+ subject
195
+ expect(response).to have_http_status(:bad_request)
196
+ expect(JSON.parse(response.body)).to eq({ 'errors' => { 'name' => ["can't be blank"] } })
197
+ end
196
198
  end
197
199
  end
198
200
 
199
201
  describe '#destroy' do
200
202
  subject { delete :destroy, id: template.id }
201
203
 
202
- before do
203
- api_stub_for(
204
- get: "/dashboards/#{template.id}",
205
- params: { filter: { 'dashboard_type' => 'template' } },
206
- response: from_api(template)
207
- )
208
- api_stub_for(
209
- delete: "/dashboards/#{template.id}",
210
- response: from_api(nil)
211
- )
212
- end
213
-
214
- it_behaves_like "a jpi v1 admin action"
215
-
216
- # api_stub should be modified to allow these cases to be stubbed
217
- context 'when the template cannot be found' do
218
- xit 'spec to be described'
219
- end
220
- context 'when the dashboard destruction is unsuccessful' do
221
- xit 'spec to be described'
204
+ context 'when the template exists' do
205
+ before do
206
+ api_stub_for(
207
+ get: "/dashboards/#{template.id}",
208
+ params: { filter: { 'dashboard_type' => 'template' } },
209
+ response: from_api(template)
210
+ )
211
+ api_stub_for(
212
+ delete: "/dashboards/#{template.id}",
213
+ response: from_api(nil)
214
+ )
215
+ end
216
+
217
+ it_behaves_like "a jpi v1 admin action"
218
+ end
219
+
220
+ context 'when the template does not exist' do
221
+ before do
222
+ api_stub_for(
223
+ get: "/dashboards/#{template.id}",
224
+ params: { filter: { 'dashboard_type' => 'template' } },
225
+ code: 404
226
+ )
227
+ end
228
+
229
+ it 'returns an error message' do
230
+ subject
231
+ expect(response).to have_http_status(:not_found)
232
+ expect(JSON.parse(response.body)).to eq({ 'errors' => { 'message' => 'Dashboard template not found' } })
233
+ end
222
234
  end
223
235
  end
224
236
  end