metasploit_data_models 0.7.0 → 0.11.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.travis.yml +1 -0
- data/app/models/mdm/host.rb +352 -26
- data/app/models/mdm/loot.rb +72 -7
- data/app/models/mdm/{module_action.rb → module/action.rb} +3 -3
- data/app/models/mdm/{module_arch.rb → module/arch.rb} +3 -3
- data/app/models/mdm/{module_author.rb → module/author.rb} +3 -3
- data/app/models/mdm/module/detail.rb +280 -0
- data/app/models/mdm/{module_mixin.rb → module/mixin.rb} +3 -3
- data/app/models/mdm/{module_platform.rb → module/platform.rb} +3 -3
- data/app/models/mdm/module/ref.rb +48 -0
- data/app/models/mdm/{module_target.rb → module/target.rb} +3 -3
- data/app/models/mdm/note.rb +61 -6
- data/app/models/mdm/ref.rb +39 -1
- data/app/models/mdm/service.rb +85 -7
- data/app/models/mdm/session.rb +100 -6
- data/app/models/mdm/vuln.rb +104 -24
- data/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb +1 -17
- data/db/migrate/20130412154159_change_foreign_key_in_module_actions.rb +25 -0
- data/db/migrate/20130412171844_change_foreign_key_in_module_archs.rb +25 -0
- data/db/migrate/20130412173121_change_foreign_key_in_module_authors.rb +25 -0
- data/db/migrate/20130412173640_change_foreign_key_in_module_mixins.rb +25 -0
- data/db/migrate/20130412174254_change_foreign_key_in_module_platforms.rb +25 -0
- data/db/migrate/20130412174719_change_foreign_key_in_module_refs.rb +25 -0
- data/db/migrate/20130412175040_change_foreign_key_in_module_targets.rb +25 -0
- data/db/migrate/20130430151353_change_required_columns_to_null_false_in_hosts.rb +11 -0
- data/db/migrate/20130430162145_enforce_address_uniqueness_in_workspace_in_hosts.rb +23 -0
- data/lib/mdm/module.rb +4 -0
- data/lib/metasploit_data_models.rb +1 -0
- data/lib/metasploit_data_models/change_required_columns_to_null_false.rb +23 -0
- data/lib/metasploit_data_models/version.rb +1 -1
- data/spec/app/models/mdm/host_spec.rb +411 -0
- data/spec/app/models/mdm/host_tag_spec.rb +13 -0
- data/spec/app/models/mdm/{module_action_spec.rb → module/action_spec.rb} +6 -6
- data/spec/app/models/mdm/{module_arch_spec.rb → module/arch_spec.rb} +6 -6
- data/spec/app/models/mdm/{module_author_spec.rb → module/author_spec.rb} +6 -6
- data/spec/app/models/mdm/{module_detail_spec.rb → module/detail_spec.rb} +101 -11
- data/spec/app/models/mdm/{module_mixin_spec.rb → module/mixin_spec.rb} +6 -6
- data/spec/app/models/mdm/{module_platform_spec.rb → module/platform_spec.rb} +6 -6
- data/spec/app/models/mdm/module/ref_spec.rb +62 -0
- data/spec/app/models/mdm/{module_target_spec.rb → module/target_spec.rb} +6 -6
- data/spec/app/models/mdm/ref_spec.rb +62 -0
- data/spec/app/models/mdm/tag_spec.rb +13 -0
- data/spec/app/models/mdm/vuln_ref_spec.rb +13 -0
- data/spec/app/models/mdm/vuln_spec.rb +231 -0
- data/spec/dummy/db/schema.rb +20 -20
- data/spec/factories/mdm/host_tags.rb +9 -0
- data/spec/factories/mdm/hosts.rb +65 -0
- data/spec/factories/mdm/module/actions.rb +14 -0
- data/spec/factories/mdm/module/archs.rb +14 -0
- data/spec/factories/mdm/{module_authors.rb → module/authors.rb} +4 -4
- data/spec/factories/mdm/module/details.rb +66 -0
- data/spec/factories/mdm/module/mixins.rb +14 -0
- data/spec/factories/mdm/module/platforms.rb +14 -0
- data/spec/factories/mdm/module/refs.rb +14 -0
- data/spec/factories/mdm/{module_targets.rb → module/targets.rb} +3 -3
- data/spec/factories/mdm/refs.rb +9 -0
- data/spec/factories/mdm/tags.rb +14 -0
- data/spec/factories/mdm/vuln_refs.rb +4 -0
- data/spec/factories/mdm/vulns.rb +20 -0
- metadata +78 -45
- data/app/models/mdm/module_detail.rb +0 -59
- data/app/models/mdm/module_ref.rb +0 -24
- data/spec/app/models/mdm/module_ref_spec.rb +0 -38
- data/spec/factories/mdm/module_actions.rb +0 -14
- data/spec/factories/mdm/module_archs.rb +0 -14
- data/spec/factories/mdm/module_details.rb +0 -9
- data/spec/factories/mdm/module_mixins.rb +0 -14
- data/spec/factories/mdm/module_platforms.rb +0 -14
- data/spec/factories/mdm/module_refs.rb +0 -14
@@ -4,5 +4,5 @@ module MetasploitDataModels
|
|
4
4
|
# metasploit-framework/data/sql/migrate to db/migrate in this project, not all models have specs that verify the
|
5
5
|
# migrations (with have_db_column and have_db_index) and certain models may not be shared between metasploit-framework
|
6
6
|
# and pro, so models may be removed in the future. Because of the unstable API the version should remain below 1.0.0
|
7
|
-
VERSION = '0.
|
7
|
+
VERSION = '0.11.2'
|
8
8
|
end
|
@@ -0,0 +1,411 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mdm::Host do
|
4
|
+
subject(:host) do
|
5
|
+
FactoryGirl.build(:mdm_host)
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:architectures) do
|
9
|
+
[
|
10
|
+
'armbe',
|
11
|
+
'armle',
|
12
|
+
'cbea',
|
13
|
+
'cbea64',
|
14
|
+
'cmd',
|
15
|
+
'java',
|
16
|
+
'mips',
|
17
|
+
'mipsbe',
|
18
|
+
'mipsle',
|
19
|
+
'php',
|
20
|
+
'ppc',
|
21
|
+
'ppc64',
|
22
|
+
'ruby',
|
23
|
+
'sparc',
|
24
|
+
'tty',
|
25
|
+
'x64',
|
26
|
+
'x86',
|
27
|
+
'x86_64'
|
28
|
+
]
|
29
|
+
end
|
30
|
+
|
31
|
+
let(:operating_system_names) do
|
32
|
+
[
|
33
|
+
'FreeBSD',
|
34
|
+
'Linux',
|
35
|
+
'Mac OS X',
|
36
|
+
'Microsoft Windows',
|
37
|
+
'NetBSD',
|
38
|
+
'OpenBSD',
|
39
|
+
'Unknown',
|
40
|
+
'VMWare'
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
let(:states) do
|
45
|
+
[
|
46
|
+
'alive',
|
47
|
+
'down',
|
48
|
+
'unknown'
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'associations' do
|
53
|
+
it { should have_many(:creds).class_name('Mdm::Cred').through(:services) }
|
54
|
+
it { should have_many(:exploit_attempts).class_name('Mdm::ExploitAttempt').dependent(:destroy) }
|
55
|
+
it { should have_many(:exploited_hosts).class_name('Mdm::ExploitedHost').dependent(:destroy) }
|
56
|
+
it { should have_many(:host_details).class_name('Mdm::HostDetail').dependent(:destroy) }
|
57
|
+
it { should have_many(:hosts_tags).class_name('Mdm::HostTag') }
|
58
|
+
it { should have_many(:loots).class_name('Mdm::Loot').dependent(:destroy).order('loots.created_at DESC') }
|
59
|
+
|
60
|
+
context 'module_details' do
|
61
|
+
it { should have_many(:module_details).class_name('Mdm::Module::Detail').through(:module_refs) }
|
62
|
+
|
63
|
+
context 'with Mdm::Vulns' do
|
64
|
+
let!(:vulns) do
|
65
|
+
FactoryGirl.create_list(
|
66
|
+
:mdm_vuln,
|
67
|
+
2,
|
68
|
+
:host => host
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'with Mdm::Ref' do
|
73
|
+
let(:name) do
|
74
|
+
FactoryGirl.generate :mdm_ref_name
|
75
|
+
end
|
76
|
+
|
77
|
+
let!(:ref) do
|
78
|
+
FactoryGirl.create(:mdm_ref, :name => name)
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'with Mdm::VulnRefs' do
|
82
|
+
let!(:vuln_refs) do
|
83
|
+
vulns.collect { |vuln|
|
84
|
+
FactoryGirl.create(:mdm_vuln_ref, :ref => ref, :vuln => vuln)
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'with Mdm::Module::Detail' do
|
89
|
+
let!(:module_detail) do
|
90
|
+
FactoryGirl.create(
|
91
|
+
:mdm_module_detail
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'with Mdm::Module::Ref with same name as Mdm::Ref' do
|
96
|
+
let!(:module_ref) do
|
97
|
+
FactoryGirl.create(
|
98
|
+
:mdm_module_ref,
|
99
|
+
:detail => module_detail,
|
100
|
+
:name => name
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should list unique Mdm::Module::Detail' do
|
105
|
+
host.module_details.should =~ [module_detail]
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should have duplicate Mdm::Module::Details if collected through chain' do
|
109
|
+
vuln_refs = []
|
110
|
+
|
111
|
+
host.vulns.each do |vuln|
|
112
|
+
# @todo https://www.pivotaltracker.com/story/show/49004623
|
113
|
+
vuln_refs += vuln.vulns_refs
|
114
|
+
end
|
115
|
+
|
116
|
+
refs = []
|
117
|
+
|
118
|
+
vuln_refs.each do |vuln_ref|
|
119
|
+
refs << vuln_ref.ref
|
120
|
+
end
|
121
|
+
|
122
|
+
module_refs = []
|
123
|
+
|
124
|
+
refs.each do |ref|
|
125
|
+
module_refs += ref.module_refs
|
126
|
+
end
|
127
|
+
|
128
|
+
module_details = []
|
129
|
+
|
130
|
+
module_refs.each do |module_ref|
|
131
|
+
module_details << module_ref.detail
|
132
|
+
end
|
133
|
+
|
134
|
+
host.module_details.count.should < module_details.length
|
135
|
+
module_details.uniq.count.should == host.module_details.count
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
it { should have_many(:module_refs).class_name('Mdm::Module::Ref').through(:refs) }
|
145
|
+
it { should have_many(:notes).class_name('Mdm::Note').dependent(:delete_all).order('notes.created_at') }
|
146
|
+
it { should have_many(:refs).class_name('Mdm::Ref').through(:vuln_refs) }
|
147
|
+
it { should have_many(:services).class_name('Mdm::Service').dependent(:destroy).order('services.port, services.proto') }
|
148
|
+
it { should have_many(:service_notes).through(:services) }
|
149
|
+
it { should have_many(:sessions).class_name('Mdm::Session').dependent(:destroy).order('sessions.opened_at') }
|
150
|
+
it { should have_many(:tags).class_name('Mdm::Tag').through(:hosts_tags) }
|
151
|
+
it { should have_many(:vulns).class_name('Mdm::Vuln').dependent(:delete_all) }
|
152
|
+
it { should have_many(:vuln_refs).class_name('Mdm::VulnRef') }
|
153
|
+
it { should have_many(:web_sites).class_name('Mdm::WebSite').through(:services) }
|
154
|
+
it { should belong_to(:workspace).class_name('Mdm::Workspace') }
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'callbacks' do
|
158
|
+
context 'before destroy' do
|
159
|
+
context 'cleanup_tags' do
|
160
|
+
context 'with tags' do
|
161
|
+
let!(:tag) do
|
162
|
+
FactoryGirl.create(:mdm_tag)
|
163
|
+
end
|
164
|
+
|
165
|
+
let!(:host) do
|
166
|
+
FactoryGirl.create(:mdm_host)
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'with only this host' do
|
170
|
+
before(:each) do
|
171
|
+
FactoryGirl.create(
|
172
|
+
:mdm_host_tag,
|
173
|
+
:host => host,
|
174
|
+
:tag => tag
|
175
|
+
)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'should destroy the tags' do
|
179
|
+
expect {
|
180
|
+
host.destroy
|
181
|
+
}.to change(Mdm::Tag, :count).by(-1)
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'should destroy the host tags' do
|
185
|
+
expect {
|
186
|
+
host.destroy
|
187
|
+
}.to change(Mdm::HostTag, :count).by(-1)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context 'with additional hosts' do
|
192
|
+
let(:other_host) do
|
193
|
+
FactoryGirl.create(:mdm_host)
|
194
|
+
end
|
195
|
+
|
196
|
+
before(:each) do
|
197
|
+
FactoryGirl.create(:mdm_host_tag, :host => host, :tag => tag)
|
198
|
+
FactoryGirl.create(:mdm_host_tag, :host => other_host, :tag => tag)
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'should not destroy the tag' do
|
202
|
+
expect {
|
203
|
+
host.destroy
|
204
|
+
}.to_not change(Mdm::Tag, :count)
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'should destroy the host tags' do
|
208
|
+
expect {
|
209
|
+
host.destroy
|
210
|
+
}.to change(Mdm::HostTag, :count).by(-1)
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should not destroy the other host's tags" do
|
214
|
+
host.destroy
|
215
|
+
|
216
|
+
other_host.hosts_tags.count.should == 1
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
context 'CONSTANTS' do
|
225
|
+
context 'ARCHITECTURES' do
|
226
|
+
subject(:architectures) do
|
227
|
+
described_class::ARCHITECTURES
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'should be an Array<String>' do
|
231
|
+
architectures.should be_an Array
|
232
|
+
|
233
|
+
architectures.each do |architecture|
|
234
|
+
architecture.should be_a String
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'should include both endians of ARM' do
|
239
|
+
architectures.should include('armbe')
|
240
|
+
architectures.should include('armle')
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'should include 32-bit and 64-bit versions of Cell Broadband Engine Architecture' do
|
244
|
+
architectures.should include('cbea')
|
245
|
+
architectures.should include('cbea64')
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'should include cmd for command shell' do
|
249
|
+
architectures.should include('cmd')
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'should include java for Java Virtual Machine' do
|
253
|
+
architectures.should include('java')
|
254
|
+
end
|
255
|
+
|
256
|
+
it 'should include plain and endian-ware MIPS' do
|
257
|
+
architectures.should include('mips')
|
258
|
+
architectures.should include('mipsbe')
|
259
|
+
architectures.should include('mipsle')
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'should include php for PHP code' do
|
263
|
+
architectures.should include('php')
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'should include 32-bit and 64-bit PowerPC' do
|
267
|
+
architectures.should include('ppc')
|
268
|
+
architectures.should include('ppc64')
|
269
|
+
end
|
270
|
+
|
271
|
+
it 'should include ruby for Ruby code' do
|
272
|
+
architectures.should include('ruby')
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'should include sparc for Sparc' do
|
276
|
+
architectures.should include('sparc')
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'should include tty for Terminals' do
|
280
|
+
architectures.should include('tty')
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'should include 32-bit and 64-bit x86' do
|
284
|
+
architectures.should include('x64')
|
285
|
+
architectures.should include('x86')
|
286
|
+
architectures.should include('x86_64')
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
it 'should define OPERATING_SYSTEM_NAMES in any order' do
|
291
|
+
described_class::OPERATING_SYSTEM_NAMES.should =~ operating_system_names
|
292
|
+
end
|
293
|
+
|
294
|
+
context 'SEARCH_FIELDS' do
|
295
|
+
subject(:search_fields) do
|
296
|
+
described_class::SEARCH_FIELDS
|
297
|
+
end
|
298
|
+
|
299
|
+
it 'should be an Array<String>' do
|
300
|
+
search_fields.should be_an Array
|
301
|
+
|
302
|
+
search_fields.each { |search_field|
|
303
|
+
search_field.should be_a String
|
304
|
+
}
|
305
|
+
end
|
306
|
+
|
307
|
+
it 'should cast address to text' do
|
308
|
+
search_fields.should include('address::text')
|
309
|
+
end
|
310
|
+
|
311
|
+
it { should include('comments') }
|
312
|
+
it { should include('mac') }
|
313
|
+
it { should include('name') }
|
314
|
+
it { should include('os_flavor') }
|
315
|
+
it { should include('os_name') }
|
316
|
+
it { should include('os_sp') }
|
317
|
+
it { should include('purpose') }
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'should define STATES in any order' do
|
321
|
+
described_class::STATES.should =~ states
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
context 'database' do
|
326
|
+
context 'columns' do
|
327
|
+
it { should have_db_column(:address).of_type(:string).with_options(:null => false) }
|
328
|
+
it { should have_db_column(:arch).of_type(:string) }
|
329
|
+
it { should have_db_column(:comm).of_type(:string) }
|
330
|
+
it { should have_db_column(:comments).of_type(:text) }
|
331
|
+
it { should have_db_column(:info).of_type(:string).with_options(:limit => 2 ** 16) }
|
332
|
+
it { should have_db_column(:mac).of_type(:string) }
|
333
|
+
it { should have_db_column(:name).of_type(:string) }
|
334
|
+
it { should have_db_column(:os_flavor).of_type(:string) }
|
335
|
+
it { should have_db_column(:os_lang).of_type(:string) }
|
336
|
+
it { should have_db_column(:os_name).of_type(:string) }
|
337
|
+
it { should have_db_column(:os_sp).of_type(:string) }
|
338
|
+
it { should have_db_column(:purpose).of_type(:text) }
|
339
|
+
it { should have_db_column(:scope).of_type(:text) }
|
340
|
+
it { should have_db_column(:state).of_type(:string) }
|
341
|
+
it { should have_db_column(:virtual_host).of_type(:text) }
|
342
|
+
it { should have_db_column(:workspace_id).of_type(:integer).with_options(:null => false) }
|
343
|
+
|
344
|
+
context 'counter caches' do
|
345
|
+
it { should have_db_column(:exploit_attempt_count).of_type(:integer).with_options(:default => 0) }
|
346
|
+
it { should have_db_column(:host_detail_count).of_type(:integer).with_options(:default => 0) }
|
347
|
+
it { should have_db_column(:note_count).of_type(:integer).with_options(:default => 0) }
|
348
|
+
it { should have_db_column(:service_count).of_type(:integer).with_options(:default => 0) }
|
349
|
+
it { should have_db_column(:vuln_count).of_type(:integer).with_options(:default => 0) }
|
350
|
+
end
|
351
|
+
|
352
|
+
context 'timestamps' do
|
353
|
+
it { should have_db_column(:created_at).of_type(:datetime) }
|
354
|
+
it { should have_db_column(:updated_at).of_type(:datetime) }
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
context 'indices' do
|
359
|
+
it { should have_db_index([:workspace_id, :address]).unique(true) }
|
360
|
+
it { should have_db_index(:name) }
|
361
|
+
it { should have_db_index(:os_flavor) }
|
362
|
+
it { should have_db_index(:os_name) }
|
363
|
+
it { should have_db_index(:purpose) }
|
364
|
+
it { should have_db_index(:state) }
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
context 'factories' do
|
369
|
+
context 'full_mdm_host' do
|
370
|
+
subject(:full_mdm_host) do
|
371
|
+
FactoryGirl.build(:full_mdm_host)
|
372
|
+
end
|
373
|
+
|
374
|
+
it { should be_valid }
|
375
|
+
end
|
376
|
+
|
377
|
+
context 'mdm_host' do
|
378
|
+
subject(:mdm_host) do
|
379
|
+
FactoryGirl.build(:mdm_host)
|
380
|
+
end
|
381
|
+
|
382
|
+
it { should be_valid }
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
context 'validations' do
|
387
|
+
context 'address' do
|
388
|
+
it { should ensure_exclusion_of(:address).in_array(['127.0.0.1']) }
|
389
|
+
it { should validate_presence_of(:address) }
|
390
|
+
|
391
|
+
# can't use validate_uniqueness_of(:address).scoped_to(:workspace_id) because it will attempt to set workspace_id
|
392
|
+
# to `nil`, which will make the `:null => false` constraint on hosts.workspace_id to fail.
|
393
|
+
it 'should validate uniqueness of address scoped to workspace_id' do
|
394
|
+
address = '192.168.0.1'
|
395
|
+
|
396
|
+
workspace = FactoryGirl.create(:mdm_workspace)
|
397
|
+
FactoryGirl.create(:mdm_host, :address => address, :workspace => workspace)
|
398
|
+
|
399
|
+
duplicate_host = FactoryGirl.build(:mdm_host, :address => address, :workspace => workspace)
|
400
|
+
|
401
|
+
duplicate_host.should_not be_valid
|
402
|
+
duplicate_host.errors[:address].should include('has already been taken')
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
it { should ensure_inclusion_of(:arch).in_array(architectures).allow_nil }
|
407
|
+
it { should ensure_inclusion_of(:os_name).in_array(operating_system_names).allow_nil }
|
408
|
+
it { should ensure_inclusion_of(:state).in_array(states).allow_nil }
|
409
|
+
it { should validate_presence_of(:workspace) }
|
410
|
+
end
|
411
|
+
end
|
@@ -1,18 +1,18 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe Mdm::
|
3
|
+
describe Mdm::Module::Action do
|
4
4
|
context 'associations' do
|
5
|
-
it { should belong_to(:
|
5
|
+
it { should belong_to(:detail).class_name('Mdm::Module::Detail') }
|
6
6
|
end
|
7
7
|
|
8
8
|
context 'database' do
|
9
9
|
context 'columns' do
|
10
|
-
it { should have_db_column(:
|
10
|
+
it { should have_db_column(:detail_id).of_type(:integer) }
|
11
11
|
it { should have_db_column(:name).of_type(:text) }
|
12
12
|
end
|
13
13
|
|
14
14
|
context 'indices' do
|
15
|
-
it { should have_db_index(:
|
15
|
+
it { should have_db_index(:detail_id) }
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -27,12 +27,12 @@ describe Mdm::ModuleAction do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
context 'mass assignment security' do
|
30
|
-
it { should_not allow_mass_assignment_of(:
|
30
|
+
it { should_not allow_mass_assignment_of(:detail_id) }
|
31
31
|
it { should allow_mass_assignment_of(:name) }
|
32
32
|
end
|
33
33
|
|
34
34
|
context 'validations' do
|
35
|
-
it { should validate_presence_of(:
|
35
|
+
it { should validate_presence_of(:detail) }
|
36
36
|
it { should validate_presence_of(:name) }
|
37
37
|
end
|
38
38
|
end
|