ronin-db-activerecord 0.1.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.github/workflows/ruby.yml +31 -0
  4. data/.gitignore +13 -0
  5. data/.rspec +1 -0
  6. data/.ruby-version +1 -0
  7. data/.yardopts +1 -0
  8. data/COPYING.txt +165 -0
  9. data/ChangeLog.md +39 -0
  10. data/Gemfile +27 -0
  11. data/README.md +143 -0
  12. data/Rakefile +72 -0
  13. data/db/migrate/0001_create_ronin_ip_address_mac_addresses_table.rb +43 -0
  14. data/db/migrate/0002_create_ronin_vulnerabilities_table.rb +61 -0
  15. data/db/migrate/0003_create_ronin_url_schemes_table.rb +32 -0
  16. data/db/migrate/0004_create_ronin_url_query_param_names_table.rb +32 -0
  17. data/db/migrate/0005_create_ronin_user_names_table.rb +33 -0
  18. data/db/migrate/0006_create_ronin_software_vendors_table.rb +32 -0
  19. data/db/migrate/0007_create_ronin_advisories_table.rb +42 -0
  20. data/db/migrate/0008_create_ronin_host_name_ip_addresses_table.rb +43 -0
  21. data/db/migrate/0009_create_ronin_host_names_table.rb +34 -0
  22. data/db/migrate/0010_create_ronin_arches_table.rb +37 -0
  23. data/db/migrate/0011_create_ronin_email_addresses_table.rb +44 -0
  24. data/db/migrate/0012_create_ronin_oses_table.rb +36 -0
  25. data/db/migrate/0013_create_ronin_organizations_table.rb +31 -0
  26. data/db/migrate/0014_create_ronin_ip_addresses_table.rb +35 -0
  27. data/db/migrate/0015_create_ronin_os_guesses_table.rb +40 -0
  28. data/db/migrate/0016_create_ronin_url_query_params_table.rb +42 -0
  29. data/db/migrate/0017_create_ronin_passwords_table.rb +32 -0
  30. data/db/migrate/0018_create_ronin_open_ports_table.rb +46 -0
  31. data/db/migrate/0019_create_ronin_urls_table.rb +50 -0
  32. data/db/migrate/0020_create_ronin_softwares_table.rb +39 -0
  33. data/db/migrate/0021_create_ronin_mac_addresses_table.rb +33 -0
  34. data/db/migrate/0022_create_ronin_countries_table.rb +34 -0
  35. data/db/migrate/0023_create_ronin_services_table.rb +32 -0
  36. data/db/migrate/0024_create_ronin_credentials_table.rb +44 -0
  37. data/db/migrate/0025_create_ronin_ports_table.rb +33 -0
  38. data/db/migrate/0026_create_ronin_asns_table.rb +44 -0
  39. data/db/migrate/0027_create_ronin_http_query_param_names_table.rb +32 -0
  40. data/db/migrate/0028_create_ronin_http_query_params_table.rb +42 -0
  41. data/db/migrate/0029_create_ronin_http_header_names_table.rb +31 -0
  42. data/db/migrate/0030_create_ronin_http_request_headers_table.rb +41 -0
  43. data/db/migrate/0031_create_ronin_http_response_headers_table.rb +41 -0
  44. data/db/migrate/0032_create_ronin_http_requests_table.rb +41 -0
  45. data/db/migrate/0033_create_ronin_http_responses_table.rb +36 -0
  46. data/db/migrate/0034_create_ronin_service_credentials_table.rb +41 -0
  47. data/db/migrate/0035_create_ronin_web_credentials_table.rb +41 -0
  48. data/gemspec.yml +28 -0
  49. data/lib/ronin/db/address.rb +105 -0
  50. data/lib/ronin/db/advisory.rb +169 -0
  51. data/lib/ronin/db/arch.rb +160 -0
  52. data/lib/ronin/db/asn.rb +212 -0
  53. data/lib/ronin/db/credential.rb +248 -0
  54. data/lib/ronin/db/email_address.rb +225 -0
  55. data/lib/ronin/db/host_name.rb +224 -0
  56. data/lib/ronin/db/host_name_ip_address.rb +65 -0
  57. data/lib/ronin/db/http_header_name.rb +75 -0
  58. data/lib/ronin/db/http_query_param.rb +79 -0
  59. data/lib/ronin/db/http_query_param_name.rb +76 -0
  60. data/lib/ronin/db/http_request.rb +120 -0
  61. data/lib/ronin/db/http_request_header.rb +78 -0
  62. data/lib/ronin/db/http_response.rb +91 -0
  63. data/lib/ronin/db/http_response_header.rb +78 -0
  64. data/lib/ronin/db/ip_address.rb +351 -0
  65. data/lib/ronin/db/ip_address_mac_address.rb +62 -0
  66. data/lib/ronin/db/mac_address.rb +91 -0
  67. data/lib/ronin/db/migrations.rb +137 -0
  68. data/lib/ronin/db/model/has_name.rb +102 -0
  69. data/lib/ronin/db/model/has_unique_name.rb +82 -0
  70. data/lib/ronin/db/model/importable.rb +85 -0
  71. data/lib/ronin/db/model/last_scanned_at.rb +48 -0
  72. data/lib/ronin/db/model.rb +37 -0
  73. data/lib/ronin/db/models.rb +108 -0
  74. data/lib/ronin/db/open_port.rb +148 -0
  75. data/lib/ronin/db/organization.rb +50 -0
  76. data/lib/ronin/db/os.rb +183 -0
  77. data/lib/ronin/db/os_guess.rb +67 -0
  78. data/lib/ronin/db/password.rb +167 -0
  79. data/lib/ronin/db/port.rb +123 -0
  80. data/lib/ronin/db/root.rb +28 -0
  81. data/lib/ronin/db/schema_migration.rb +34 -0
  82. data/lib/ronin/db/service.rb +48 -0
  83. data/lib/ronin/db/service_credential.rb +66 -0
  84. data/lib/ronin/db/software.rb +85 -0
  85. data/lib/ronin/db/software_vendor.rb +42 -0
  86. data/lib/ronin/db/url.rb +497 -0
  87. data/lib/ronin/db/url_query_param.rb +79 -0
  88. data/lib/ronin/db/url_query_param_name.rb +76 -0
  89. data/lib/ronin/db/url_scheme.rb +80 -0
  90. data/lib/ronin/db/user_name.rb +96 -0
  91. data/lib/ronin/db/vulnerability.rb +81 -0
  92. data/lib/ronin/db/web_credential.rb +69 -0
  93. data/ronin-db-activerecord.gemspec +61 -0
  94. data/spec/advisory_spec.rb +277 -0
  95. data/spec/arch_spec.rb +228 -0
  96. data/spec/asn_spec.rb +504 -0
  97. data/spec/credential_spec.rb +362 -0
  98. data/spec/email_address_spec.rb +372 -0
  99. data/spec/host_name_ip_address_spec.rb +8 -0
  100. data/spec/host_name_spec.rb +207 -0
  101. data/spec/http_header_name_spec.rb +25 -0
  102. data/spec/http_query_param_name_spec.rb +25 -0
  103. data/spec/http_query_param_spec.rb +104 -0
  104. data/spec/http_request_header_spec.rb +72 -0
  105. data/spec/http_request_spec.rb +168 -0
  106. data/spec/http_response_header_spec.rb +74 -0
  107. data/spec/http_response_spec.rb +103 -0
  108. data/spec/ip_address_mac_addresses_spec.rb +8 -0
  109. data/spec/ip_address_spec.rb +386 -0
  110. data/spec/mac_address_spec.rb +67 -0
  111. data/spec/migrations_spec.rb +122 -0
  112. data/spec/model/has_name_spec.rb +65 -0
  113. data/spec/model/has_unique_name_spec.rb +61 -0
  114. data/spec/model/importable_spec.rb +105 -0
  115. data/spec/models_spec.rb +60 -0
  116. data/spec/open_port_spec.rb +87 -0
  117. data/spec/organization_spec.rb +10 -0
  118. data/spec/os_guess_spec.rb +43 -0
  119. data/spec/os_spec.rb +114 -0
  120. data/spec/password_spec.rb +81 -0
  121. data/spec/port_spec.rb +102 -0
  122. data/spec/schema_migration_spec.rb +8 -0
  123. data/spec/service_credential_spec.rb +43 -0
  124. data/spec/service_spec.rb +39 -0
  125. data/spec/software_spec.rb +76 -0
  126. data/spec/software_vendor_spec.rb +33 -0
  127. data/spec/spec_helper.rb +13 -0
  128. data/spec/url_query_param_name_spec.rb +25 -0
  129. data/spec/url_query_param_spec.rb +110 -0
  130. data/spec/url_scheme_spec.rb +39 -0
  131. data/spec/url_spec.rb +951 -0
  132. data/spec/user_name_spec.rb +54 -0
  133. data/spec/vulnerability_spec.rb +8 -0
  134. data/spec/web_credential_spec.rb +72 -0
  135. metadata +266 -0
@@ -0,0 +1,386 @@
1
+ require 'spec_helper'
2
+
3
+ require 'ronin/db/ip_address'
4
+
5
+ describe Ronin::DB::IPAddress do
6
+ it "must use the 'ronin_ip_addresses' table" do
7
+ expect(described_class.table_name).to eq('ronin_ip_addresses')
8
+ end
9
+
10
+ let(:address) { '127.0.0.1' }
11
+
12
+ let(:ipv4_address) { '93.184.216.34' }
13
+ let(:ipv6_address) { '2606:2800:220:1:248:1893:25c8:1946' }
14
+
15
+ subject { described_class.new(address: address) }
16
+
17
+ describe "validations" do
18
+ describe "address" do
19
+ it "must require an address" do
20
+ ip_address = described_class.new
21
+ expect(ip_address).to_not be_valid
22
+
23
+ ip_address = described_class.new(address: address)
24
+ expect(ip_address).to be_valid
25
+ end
26
+
27
+ it "must accept IPv4 addresses" do
28
+ ip_address = described_class.new(address: ipv4_address)
29
+ expect(ip_address).to be_valid
30
+ end
31
+
32
+ it "must accept IPv6 addresses" do
33
+ ip_address = described_class.new(address: ipv6_address)
34
+ expect(ip_address).to be_valid
35
+ end
36
+
37
+ it "must not accept non-IP addresses" do
38
+ ip_address = described_class.new(address: '0')
39
+ expect(ip_address).to_not be_valid
40
+ expect(ip_address.errors[:address]).to eq(
41
+ ["Must be a valid IP address"]
42
+ )
43
+ end
44
+ end
45
+ end
46
+
47
+ describe ".v4" do
48
+ subject { described_class }
49
+
50
+ before do
51
+ subject.create(address: ipv4_address)
52
+ subject.create(address: ipv6_address)
53
+ end
54
+
55
+ it "must query all #{described_class} with IPv4 addresses" do
56
+ ip_addresses = subject.v4
57
+ expect(ip_addresses.count).to eq(1)
58
+
59
+ ip_address = ip_addresses.first
60
+ expect(ip_address).to be_kind_of(described_class)
61
+ expect(ip_address.address).to eq(ipv4_address)
62
+ end
63
+
64
+ after do
65
+ subject.destroy_all
66
+ end
67
+ end
68
+
69
+ describe ".v6" do
70
+ subject { described_class }
71
+
72
+ before do
73
+ subject.create(address: ipv4_address)
74
+ subject.create(address: ipv6_address)
75
+ end
76
+
77
+ it "must query all #{described_class} with IPv6 addresses" do
78
+ ip_addresses = subject.v6
79
+ expect(ip_addresses.count).to eq(1)
80
+
81
+ ip_address = ip_addresses.first
82
+ expect(ip_address).to be_kind_of(described_class)
83
+ expect(ip_address.address).to eq(ipv6_address)
84
+ end
85
+
86
+ after do
87
+ subject.destroy_all
88
+ end
89
+ end
90
+
91
+ describe ".between" do
92
+ subject { described_class }
93
+
94
+ let(:address1) { '4.1.1.1' }
95
+ let(:address2) { '4.2.2.2' }
96
+ let(:address3) { '4.3.3.3' }
97
+
98
+ before do
99
+ described_class.create(address: '4.0.0.0')
100
+ described_class.create(address: address1)
101
+ described_class.create(address: address2)
102
+ described_class.create(address: address3)
103
+ described_class.create(address: '4.4.4.4')
104
+ end
105
+
106
+ it "must query all IP addresses that are in between the first and last IP address" do
107
+ ip_addresses = subject.between(address1,address3)
108
+
109
+ expect(ip_addresses.length).to eq(3)
110
+ expect(ip_addresses).to all(be_kind_of(described_class))
111
+ expect(ip_addresses[0].address).to eq(address1)
112
+ expect(ip_addresses[1].address).to eq(address2)
113
+ expect(ip_addresses[2].address).to eq(address3)
114
+ end
115
+
116
+ after { described_class.destroy_all }
117
+ end
118
+
119
+ describe ".in_range" do
120
+ subject { described_class }
121
+
122
+ let(:address1) { '4.1.1.1' }
123
+ let(:address2) { '4.2.2.2' }
124
+ let(:address3) { '4.3.3.3' }
125
+ let(:range) { address1..address3 }
126
+
127
+ before do
128
+ described_class.create(address: '4.0.0.0')
129
+ described_class.create(address: address1)
130
+ described_class.create(address: address2)
131
+ described_class.create(address: address3)
132
+ described_class.create(address: '4.4.4.4')
133
+ end
134
+
135
+ it "must query all IP addresses that are in between the first and last IP address" do
136
+ ip_addresses = subject.in_range(range)
137
+
138
+ expect(ip_addresses.length).to eq(3)
139
+ expect(ip_addresses).to all(be_kind_of(described_class))
140
+ expect(ip_addresses[0].address).to eq(address1)
141
+ expect(ip_addresses[1].address).to eq(address2)
142
+ expect(ip_addresses[2].address).to eq(address3)
143
+ end
144
+
145
+ after { described_class.destroy_all }
146
+ end
147
+
148
+ describe ".with_mac_address" do
149
+ subject { described_class }
150
+
151
+ let(:mac_address) { '00:01:02:03:04:05' }
152
+
153
+ before do
154
+ ip_address = subject.create(address: address)
155
+ ip_address.mac_addresses.create(address: mac_address)
156
+ end
157
+
158
+ it "must query all #{described_class} with the associated MAC address" do
159
+ ip_addresses = subject.with_mac_address(mac_address)
160
+ expect(ip_addresses.count).to eq(1)
161
+
162
+ ip_address = ip_addresses.first
163
+ expect(ip_address.mac_addresses.first.address).to eq(mac_address)
164
+ end
165
+
166
+ after do
167
+ Ronin::DB::IPAddressMACAddress.destroy_all
168
+ Ronin::DB::IPAddress.destroy_all
169
+ Ronin::DB::MACAddress.destroy_all
170
+ end
171
+ end
172
+
173
+ describe ".with_host_name" do
174
+ subject { described_class }
175
+
176
+ let(:host) { 'example.com' }
177
+
178
+ before do
179
+ ip_address = subject.create(address: address)
180
+ ip_address.host_names.create(name: host)
181
+ end
182
+
183
+ it "must query all #{described_class} with the associated host name" do
184
+ ip_addresses = subject.with_host_name(host)
185
+ expect(ip_addresses.count).to eq(1)
186
+
187
+ ip_address = ip_addresses.first
188
+ expect(ip_address.host_names.first.name).to eq(host)
189
+ end
190
+
191
+ after do
192
+ Ronin::DB::HostNameIPAddress.destroy_all
193
+ Ronin::DB::IPAddress.destroy_all
194
+ Ronin::DB::HostName.destroy_all
195
+ end
196
+ end
197
+
198
+ describe ".with_port_number" do
199
+ subject { described_class }
200
+
201
+ let(:port) { 443 }
202
+
203
+ before do
204
+ ip_address = subject.create(address: address)
205
+ ip_address.ports.create(protocol: :tcp, number: 80)
206
+ ip_address.ports.create(protocol: :tcp, number: port)
207
+ ip_address.ports.create(protocol: :tcp, number: 8080)
208
+ end
209
+
210
+ it "must query all #{described_class} with the associated port number" do
211
+ ip_addresses = subject.with_port_number(port)
212
+ expect(ip_addresses.count).to eq(1)
213
+
214
+ ip_address = ip_addresses.first
215
+ expect(ip_address.ports[1].number).to eq(port)
216
+ end
217
+
218
+ after do
219
+ Ronin::DB::OpenPort.destroy_all
220
+ Ronin::DB::IPAddress.destroy_all
221
+ Ronin::DB::Port.destroy_all
222
+ end
223
+ end
224
+
225
+ describe ".lookup" do
226
+ before do
227
+ described_class.create(address: '1.2.4.5')
228
+ described_class.create(address: address)
229
+ described_class.create(address: '6.7.8.9')
230
+ end
231
+
232
+ it "must query the #{described_class} with the matching IP address" do
233
+ ip_address = described_class.lookup(address)
234
+
235
+ expect(ip_address).to be_kind_of(described_class)
236
+ expect(ip_address.address).to eq(address)
237
+ end
238
+
239
+ after { described_class.destroy_all }
240
+ end
241
+
242
+ describe ".import" do
243
+ subject { described_class.import(address) }
244
+
245
+ it "must parse and import the IP address and set #address" do
246
+ expect(subject).to be_kind_of(described_class)
247
+ expect(subject.id).to_not be(nil)
248
+ expect(subject.address).to eq(address)
249
+ end
250
+
251
+ after { described_class.destroy_all }
252
+ end
253
+
254
+ describe "#ipaddr" do
255
+ it "must automatically parse #address and return an IPAddr" do
256
+ expect(subject.ipaddr).to be_kind_of(IPAddr)
257
+ expect(subject.ipaddr.to_s).to eq(address)
258
+ end
259
+
260
+ context "when the address is an IPv4 address" do
261
+ let(:address) { ipv4_address }
262
+
263
+ it "must return an IPv4 IPAddr" do
264
+ expect(subject.ipaddr.ipv4?).to be(true)
265
+ end
266
+ end
267
+
268
+ context "when the address is an IPv6 address" do
269
+ let(:address) { ipv6_address }
270
+
271
+ it "must return an IPv6 IPAddr" do
272
+ expect(subject.ipaddr.ipv6?).to be(true)
273
+ end
274
+ end
275
+
276
+ context "when the address is not a valid IP address" do
277
+ let(:address) { '0' }
278
+
279
+ it "must return nil" do
280
+ expect(subject.ipaddr).to be(nil)
281
+ end
282
+ end
283
+ end
284
+
285
+ describe "#version" do
286
+ it "should only accept 4 or 6" do
287
+ ip_address = described_class.new(address: '1.1.1.1', version: 7)
288
+
289
+ expect(ip_address).not_to be_valid
290
+ end
291
+
292
+ context "with IPv4 address" do
293
+ subject { described_class.new(address: '127.0.0.1') }
294
+
295
+ it { expect(subject.version).to be == 4 }
296
+ end
297
+
298
+ context "with IPv6 address" do
299
+ subject { described_class.new(address: '::1') }
300
+
301
+ it { expect(subject.version).to be == 6 }
302
+ end
303
+ end
304
+
305
+ describe "#asn" do
306
+ let(:asn_version) { 4 }
307
+ let(:asn_range_start) { '4.0.0.0' }
308
+ let(:asn_range_end) { '4.7.168.255' }
309
+ let(:asn_number) { 3356 }
310
+ let(:asn_country_code) { 'US' }
311
+ let(:asn_name) { 'LEVEL3' }
312
+
313
+ before do
314
+ Ronin::DB::ASN.create(
315
+ version: 6,
316
+ range_start: '64:ff9b::1:0:0',
317
+ range_end: '100::ffff:ffff:ffff:ffff',
318
+ number: 0
319
+ )
320
+
321
+ Ronin::DB::ASN.create(
322
+ version: asn_version,
323
+ range_start: asn_range_start,
324
+ range_end: asn_range_end,
325
+ number: asn_number,
326
+ country_code: asn_country_code,
327
+ name: asn_name
328
+ )
329
+
330
+ Ronin::DB::ASN.create(
331
+ version: 6,
332
+ range_start: '::',
333
+ range_end: '::1',
334
+ number: 0
335
+ )
336
+ end
337
+
338
+ let(:version) { 4 }
339
+ let(:address) { '4.4.4.4' }
340
+
341
+ subject do
342
+ described_class.new(
343
+ version: version,
344
+ address: address
345
+ )
346
+ end
347
+
348
+ it "must lookup the ASN record containing the IP address" do
349
+ asn = subject.asn
350
+
351
+ expect(asn).to be_kind_of(Ronin::DB::ASN)
352
+ expect(asn.version).to eq(version)
353
+ expect(asn.range_start).to eq('4.0.0.0')
354
+ expect(asn.range_end).to eq('4.7.168.255')
355
+ end
356
+
357
+ after do
358
+ Ronin::DB::ASN.destroy_all
359
+ end
360
+ end
361
+
362
+ describe "#recent_mac_address" do
363
+ end
364
+
365
+ describe "#recent_host_name" do
366
+ end
367
+
368
+ describe "#recent_os_guess" do
369
+ end
370
+
371
+ describe "#save" do
372
+ subject { described_class.create(address: address) }
373
+
374
+ it "must set hton" do
375
+ expect(subject.hton).to eq(subject.ipaddr.hton)
376
+ end
377
+
378
+ after { subject.destroy }
379
+ end
380
+
381
+ describe "#to_ip" do
382
+ end
383
+
384
+ describe "#to_i" do
385
+ end
386
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+ require 'ronin/db/mac_address'
3
+
4
+ describe Ronin::DB::MACAddress do
5
+ it "must use the 'ronin_mac_addresses' table" do
6
+ expect(described_class.table_name).to eq('ronin_mac_addresses')
7
+ end
8
+
9
+ let(:address) { '00:01:02:03:04:05' }
10
+
11
+ subject { described_class.new(address: address) }
12
+
13
+ describe "validations" do
14
+ describe "address" do
15
+ it "should require an address" do
16
+ mac_address = described_class.new
17
+ expect(mac_address).not_to be_valid
18
+ expect(mac_address.errors[:address]).to include(
19
+ "can't be blank"
20
+ )
21
+
22
+ mac_address = described_class.new(address: address)
23
+ expect(mac_address).to be_valid
24
+ end
25
+ end
26
+ end
27
+
28
+ describe ".lookup" do
29
+ before do
30
+ described_class.create(address: '11:12:13:14:15:16')
31
+ described_class.create(address: address)
32
+ described_class.create(address: '21:22:23:24:25:26')
33
+ end
34
+
35
+ it "must query the #{described_class} with the matching MAC address" do
36
+ ip_address = described_class.lookup(address)
37
+
38
+ expect(ip_address).to be_kind_of(described_class)
39
+ expect(ip_address.address).to eq(address)
40
+ end
41
+
42
+ after { described_class.destroy_all }
43
+ end
44
+
45
+ describe ".import" do
46
+ subject { described_class.import(address) }
47
+
48
+ it "must parse and import the MAC address and set #address" do
49
+ expect(subject).to be_kind_of(described_class)
50
+ expect(subject.id).to_not be(nil)
51
+ expect(subject.address).to eq(address)
52
+ end
53
+
54
+ after { described_class.destroy_all }
55
+ end
56
+
57
+ describe "#recent_ip_address" do
58
+ end
59
+
60
+ describe "#to_i" do
61
+ let(:integer) { 0x000102030405 }
62
+
63
+ it "should convert the MAC Address to an Integer" do
64
+ expect(subject.to_i).to eq(integer)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+ require 'ronin/db/migrations'
3
+
4
+ describe Ronin::DB::Migrations do
5
+ let(:migration_context) { subject.send(:context) }
6
+
7
+ describe ".current_version" do
8
+ it "must call .current_version on the MigrationContext object" do
9
+ expect(migration_context).to receive(:current_version)
10
+
11
+ subject.current_version
12
+ end
13
+ end
14
+
15
+ describe ".needs_migration?" do
16
+ it "must call .needs_migration? on the MigrationContext object" do
17
+ expect(migration_context).to receive(:needs_migration?)
18
+
19
+ subject.needs_migration?
20
+ end
21
+ end
22
+
23
+ describe ".migrate" do
24
+ context "when called with no arguments" do
25
+ it "must call .migrate(nil) on the MigrationContext object" do
26
+ expect(migration_context).to receive(:migrate).with(nil)
27
+
28
+ subject.migrate
29
+ end
30
+ end
31
+
32
+ context "when given a target version" do
33
+ let(:target_version) { 42 }
34
+
35
+ it "must call .migrate(target_version) on the MigrationContext object" do
36
+ expect(migration_context).to receive(:migrate).with(target_version)
37
+
38
+ subject.migrate(target_version)
39
+ end
40
+ end
41
+ end
42
+
43
+ describe ".up" do
44
+ context "when called with no arguments" do
45
+ it "must call .up(nil) on the MigrationContext object" do
46
+ expect(migration_context).to receive(:up).with(nil)
47
+
48
+ subject.up
49
+ end
50
+ end
51
+
52
+ context "when given a target version" do
53
+ let(:target_version) { 42 }
54
+
55
+ it "must call .up(target_version) on the MigrationContext object" do
56
+ expect(migration_context).to receive(:up).with(target_version)
57
+
58
+ subject.up(target_version)
59
+ end
60
+ end
61
+ end
62
+
63
+ describe ".down" do
64
+ context "when called with no arguments" do
65
+ it "must call .down(nil) on the MigrationContext object" do
66
+ expect(migration_context).to receive(:down).with(nil)
67
+
68
+ subject.down
69
+ end
70
+ end
71
+
72
+ context "when given a target version" do
73
+ let(:target_version) { 42 }
74
+
75
+ it "must call .down(target_version) on the MigrationContext object" do
76
+ expect(migration_context).to receive(:down).with(target_version)
77
+
78
+ subject.down(target_version)
79
+ end
80
+ end
81
+ end
82
+
83
+ describe ".rollback" do
84
+ context "when called with no arguments" do
85
+ it "must call .rollback(1) on the MigrationContext object" do
86
+ expect(migration_context).to receive(:rollback).with(1)
87
+
88
+ subject.rollback
89
+ end
90
+ end
91
+
92
+ context "when given a target version" do
93
+ let(:steps) { 2 }
94
+
95
+ it "must call .rollback(steps) on the MigrationContext object" do
96
+ expect(migration_context).to receive(:rollback).with(steps)
97
+
98
+ subject.rollback(steps)
99
+ end
100
+ end
101
+ end
102
+
103
+ describe ".foreward" do
104
+ context "when called with no arguments" do
105
+ it "must call .foreward(1) on the MigrationContext object" do
106
+ expect(migration_context).to receive(:foreward).with(1)
107
+
108
+ subject.foreward
109
+ end
110
+ end
111
+
112
+ context "when given a target version" do
113
+ let(:steps) { 2 }
114
+
115
+ it "must call .foreward(steps) on the MigrationContext object" do
116
+ expect(migration_context).to receive(:foreward).with(steps)
117
+
118
+ subject.foreward(steps)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require 'ronin/db/model/has_name'
3
+
4
+ describe Ronin::DB::Model::HasName do
5
+ class TestModelHasName < ActiveRecord::Base
6
+ include Ronin::DB::Model::HasName
7
+
8
+ self.table_name = 'test_model_has_name'
9
+ end
10
+
11
+ let(:model) { TestModelHasName }
12
+
13
+ before(:all) do
14
+ ActiveRecord::Base.connection.create_table :test_model_has_name do |t|
15
+ t.string :name
16
+ end
17
+ end
18
+
19
+ describe ".included" do
20
+ subject { model }
21
+
22
+ it "should include Ronin::DB::Model" do
23
+ expect(subject.ancestors).to include(Ronin::DB::Model)
24
+ end
25
+
26
+ it "should define a name attribute" do
27
+ expect(subject.new).to respond_to(:name)
28
+ expect(subject.new).to respond_to(:name=)
29
+ end
30
+ end
31
+
32
+ describe "validations" do
33
+ subject { model }
34
+
35
+ it "should require a name" do
36
+ resource = subject.new
37
+ expect(resource).not_to be_valid
38
+
39
+ resource.name = 'foo'
40
+ expect(resource).to be_valid
41
+ end
42
+ end
43
+
44
+ describe ".named" do
45
+ subject { model }
46
+
47
+ let(:name1) { 'foo1' }
48
+ let(:name2) { 'foo2' }
49
+
50
+ before do
51
+ subject.create(name: name1)
52
+ subject.create(name: name2)
53
+ end
54
+
55
+ it "should be able to find resources with similar names" do
56
+ resources = subject.named('foo')
57
+
58
+ expect(resources.length).to eq(2)
59
+ expect(resources[0].name).to be == name1
60
+ expect(resources[1].name).to be == name2
61
+ end
62
+
63
+ after { subject.destroy_all }
64
+ end
65
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+ require 'ronin/db/model/has_unique_name'
3
+
4
+ describe Ronin::DB::Model::HasUniqueName do
5
+ class TestModelHasUniqueName < ActiveRecord::Base
6
+ include Ronin::DB::Model::HasUniqueName
7
+
8
+ self.table_name = 'test_model_has_unique_name'
9
+ end
10
+
11
+ let(:model) { TestModelHasUniqueName }
12
+
13
+ before(:all) do
14
+ ActiveRecord::Base.connection.create_table :test_model_has_unique_name do |t|
15
+ t.string :name
16
+ end
17
+ end
18
+
19
+ describe ".included" do
20
+ subject { model }
21
+
22
+ it "should include Ronin::DB::Model" do
23
+ expect(subject.ancestors).to include(Ronin::DB::Model)
24
+ end
25
+
26
+ it "should include Ronin::DB::Model::HasName::InstanceMethods" do
27
+ expect(subject.ancestors).to include(Ronin::DB::Model::HasName::InstanceMethods)
28
+ end
29
+
30
+ it "should define a name attribute" do
31
+ expect(subject.new).to respond_to(:name)
32
+ expect(subject.new).to respond_to(:name=)
33
+ end
34
+ end
35
+
36
+ describe "validations" do
37
+ subject { model }
38
+
39
+ it "should require a name" do
40
+ record = subject.new
41
+ expect(record).to_not be_valid
42
+
43
+ record.name = 'foo'
44
+ expect(record).to be_valid
45
+ end
46
+
47
+ context "when the given name already exists in the table" do
48
+ let(:name) { 'foo' }
49
+
50
+ before { model.create(name: name) }
51
+
52
+ it "must require a unique name" do
53
+ record = subject.new(name: name)
54
+
55
+ expect(record).to_not be_valid
56
+ end
57
+
58
+ after { model.destroy_all }
59
+ end
60
+ end
61
+ end