macadmin 0.0.4
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +163 -0
- data/Rakefile +23 -0
- data/ext/macadmin/password/crypto.c +89 -0
- data/ext/macadmin/password/extconf.rb +3 -0
- data/lib/macadmin.rb +22 -0
- data/lib/macadmin/common.rb +80 -0
- data/lib/macadmin/dslocal.rb +252 -0
- data/lib/macadmin/dslocal/computer.rb +22 -0
- data/lib/macadmin/dslocal/computergroup.rb +19 -0
- data/lib/macadmin/dslocal/dslocalnode.rb +281 -0
- data/lib/macadmin/dslocal/group.rb +82 -0
- data/lib/macadmin/dslocal/user.rb +113 -0
- data/lib/macadmin/mcx.rb +227 -0
- data/lib/macadmin/password.rb +72 -0
- data/lib/macadmin/shadowhash.rb +297 -0
- data/lib/macadmin/version.rb +8 -0
- data/macadmin.gemspec +35 -0
- data/spec/common_spec.rb +49 -0
- data/spec/computer_spec.rb +133 -0
- data/spec/computergroup_spec.rb +274 -0
- data/spec/dslocal_spec.rb +179 -0
- data/spec/dslocalnode_spec.rb +343 -0
- data/spec/group_spec.rb +275 -0
- data/spec/macadmin_spec.rb +13 -0
- data/spec/mcx_spec.rb +145 -0
- data/spec/password_spec.rb +40 -0
- data/spec/shadowhash_spec.rb +309 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/user_spec.rb +248 -0
- metadata +218 -0
@@ -0,0 +1,297 @@
|
|
1
|
+
module MacAdmin
|
2
|
+
|
3
|
+
# Custom error
|
4
|
+
class ShadowHashError < StandardError
|
5
|
+
UNSUPPORTED_OBJECT_ERR = 'Unsupported object: cannot store ShadowHashData'
|
6
|
+
end
|
7
|
+
|
8
|
+
# ShadowHash (super class)
|
9
|
+
# - common methods for password sub-classes
|
10
|
+
class ShadowHash
|
11
|
+
|
12
|
+
include MacAdmin::Password
|
13
|
+
|
14
|
+
attr_reader :label
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
# Reads the password data from the user record
|
19
|
+
# - returns an appropriate ShadowHash object
|
20
|
+
def create_from_user_record(user)
|
21
|
+
if user['ShadowHashData']
|
22
|
+
password = read_shadowhashdata(user['ShadowHashData'])
|
23
|
+
if password[SaltedSHA512::LABEL]
|
24
|
+
return SaltedSHA512.create_from_shadowhashdata(password)
|
25
|
+
else
|
26
|
+
return SaltedSHA512PBKDF2.create_from_shadowhashdata(password)
|
27
|
+
end
|
28
|
+
else
|
29
|
+
if guid = user['generateduid']
|
30
|
+
return SaltedSHA1.create_from_shadowhash_file(guid)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns Hash
|
37
|
+
# - key: label, value: password data
|
38
|
+
def read_shadowhashdata(data)
|
39
|
+
plist = CFPropertyList::List.new(:data => data[0].to_s)
|
40
|
+
CFPropertyList.native_types(plist.value)
|
41
|
+
end
|
42
|
+
|
43
|
+
end # end self
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
# Legacy ShadowHashs
|
48
|
+
# - password management for Mac OS X 10.6 and below
|
49
|
+
# - passwords are managed in separate files in /var/db/shadow/hash
|
50
|
+
class SaltedSHA1 < ShadowHash
|
51
|
+
|
52
|
+
SHADOWHASH_STORE = '/private/var/db/shadow/hash'
|
53
|
+
|
54
|
+
attr_accessor :hash
|
55
|
+
|
56
|
+
class << self
|
57
|
+
|
58
|
+
def create_from_shadowhash_file(guid)
|
59
|
+
file = "#{SHADOWHASH_STORE}/#{guid}"
|
60
|
+
if File.exists? file
|
61
|
+
hash = read_from_shadowhash_file(file)
|
62
|
+
return nil unless hash
|
63
|
+
self.new(hash)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def read_from_shadowhash_file(file)
|
68
|
+
content = File.readlines(file).first
|
69
|
+
content[168,48]
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
# Initializes a SaltedSHA512 ShadowHash object from string
|
75
|
+
# - string param should be a hex string, 24 bytes
|
76
|
+
def initialize(string)
|
77
|
+
@hash = validate(string)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Validates the string param
|
81
|
+
# - ensure the string param is hex string 24 bytes long
|
82
|
+
def validate(string)
|
83
|
+
error = "Invalid: arg must be hexadecimal string (24 bytes)"
|
84
|
+
raise ArgumentError.new(error) unless string =~ /([A-F0-9]{2}){24}/
|
85
|
+
string
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return a String representation of the ShadowHash data
|
89
|
+
def password
|
90
|
+
@hash.to_s
|
91
|
+
end
|
92
|
+
|
93
|
+
# Return the ShadowHash as a Salted SHA1 String
|
94
|
+
def data
|
95
|
+
@data ||= @hash.to_s
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# Pseudo callback
|
101
|
+
# - this method does not modify User objects
|
102
|
+
# - SHA1 passwords are not stored as part of the User object
|
103
|
+
# - They're stored in separate files on disk
|
104
|
+
def store(sender)
|
105
|
+
raise ShadowHashError.new(ShadowHashError::UNSUPPORTED_OBJECT_ERR) unless sender.is_a? MacAdmin::User
|
106
|
+
@data = @hash.to_s
|
107
|
+
end
|
108
|
+
|
109
|
+
# Write password to file
|
110
|
+
# - success based on the length of the file, must be 1240 bytes
|
111
|
+
# - returns boolean
|
112
|
+
def write_to_shadowhash_file(sender)
|
113
|
+
raise ShadowHashError.new(ShadowHashError::UNSUPPORTED_OBJECT_ERR) unless sender.is_a? MacAdmin::User
|
114
|
+
path = "#{SHADOWHASH_STORE}/#{sender[:generateduid].first}"
|
115
|
+
file = File.open(path, mode='w+')
|
116
|
+
content = file.read
|
117
|
+
content = "0" * 1240 if content.length < 1240
|
118
|
+
file.rewind
|
119
|
+
content[168...(168 + 48)] = @hash.to_s
|
120
|
+
file.write content
|
121
|
+
file.close
|
122
|
+
File.size(path) == 1240
|
123
|
+
end
|
124
|
+
alias :to_file :write_to_shadowhash_file
|
125
|
+
|
126
|
+
# Remove the ShadowHash file associated with the sender
|
127
|
+
# - returns boolean
|
128
|
+
def remove_shadowhash_file(sender)
|
129
|
+
raise ShadowHashError.new(ShadowHashError::UNSUPPORTED_OBJECT_ERR) unless sender.is_a? MacAdmin::User
|
130
|
+
path = "#{SHADOWHASH_STORE}/#{sender[:generateduid].first}"
|
131
|
+
FileUtils.rm path if File.exists? path
|
132
|
+
!File.exists? path
|
133
|
+
end
|
134
|
+
alias :rm_file :remove_shadowhash_file
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
# Lion ShadowHashs
|
139
|
+
# - Mac OS X 10.7 store passwords as Salted SHA512 hashes
|
140
|
+
# - hash is stored directly in the user's plist
|
141
|
+
class SaltedSHA512 < ShadowHash
|
142
|
+
|
143
|
+
LABEL = 'SALTED-SHA512'
|
144
|
+
|
145
|
+
attr_accessor :hash
|
146
|
+
|
147
|
+
class << self
|
148
|
+
|
149
|
+
# Constructs a SaltedSHA512 ShadowHash object from ShadowHashData
|
150
|
+
# - param is raw ShadowHashData object
|
151
|
+
def create_from_shadowhashdata(data)
|
152
|
+
value = data[SaltedSHA512::LABEL].to_s
|
153
|
+
hex = MacAdmin::Password.convert_to_hex(value)
|
154
|
+
self.new(hex)
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
# Initializes a SaltedSHA512 ShadowHash object from string
|
160
|
+
# - string param should be a hex string, 68 bytes
|
161
|
+
def initialize(string)
|
162
|
+
@label = LABEL
|
163
|
+
@hash = validate(string)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Validates the string param
|
167
|
+
# - ensure the string param is hex string 68 bytes long
|
168
|
+
def validate(string)
|
169
|
+
error = "Invalid: arg must be hexadecimal string (68 bytes)"
|
170
|
+
raise ArgumentError.new(error) unless string =~ /([a-f0-9]{2}){68}/
|
171
|
+
string
|
172
|
+
end
|
173
|
+
|
174
|
+
# Return the ShadowHash as a ShadowHashData object
|
175
|
+
# - Binary Plist
|
176
|
+
def data
|
177
|
+
@data ||= { @label => convert_to_blob(@hash) }.to_plist
|
178
|
+
end
|
179
|
+
|
180
|
+
# Return a Hash representation of the ShadowHash data
|
181
|
+
def password
|
182
|
+
{ @label => @hash }
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
# Pseudo callback for inserting a ShadowHashData object into the User object
|
188
|
+
def store(sender)
|
189
|
+
raise ShadowHashError.new(ShadowHashError::UNSUPPORTED_OBJECT_ERR) unless sender.is_a? MacAdmin::User
|
190
|
+
@data = { @label => convert_to_blob(@hash) }.to_plist
|
191
|
+
sender['ShadowHashData'] = [@data]
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
# Current ShadowHash Scheme
|
197
|
+
# - Mac OS X 10.8 and up store passwords as Salted SHA512-PBKDF2 hashes
|
198
|
+
# - hash is stored directly in the user's plist
|
199
|
+
class SaltedSHA512PBKDF2 < ShadowHash
|
200
|
+
|
201
|
+
LABEL = 'SALTED-SHA512-PBKDF2'
|
202
|
+
|
203
|
+
class << self
|
204
|
+
|
205
|
+
# Constructs a SaltedSHA512PBKDF2 ShadowHash object from ShadowHashData
|
206
|
+
# - param is raw ShadowHashData object
|
207
|
+
def create_from_shadowhashdata(data)
|
208
|
+
hash = data[SaltedSHA512PBKDF2::LABEL]
|
209
|
+
hash = hash.inject({}) do |memo, (key, value)|
|
210
|
+
if key.eql? 'iterations'
|
211
|
+
value = value.to_i
|
212
|
+
else
|
213
|
+
value = MacAdmin::Password.convert_to_hex(value)
|
214
|
+
end
|
215
|
+
memo[key.to_sym] = value
|
216
|
+
memo
|
217
|
+
end
|
218
|
+
self.new(hash)
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
# Initializes a SaltedSHA512PBKDF2 ShadowHash object from Hash or Array
|
224
|
+
# - if passing an array, you must order the elements: entropy, salt, iterations
|
225
|
+
# - pass a Hash with keys: entropy, salt, iterations
|
226
|
+
def initialize(args)
|
227
|
+
@label = LABEL
|
228
|
+
@hash = validate(args)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Validates the params
|
232
|
+
# - ensure that we have the required params
|
233
|
+
# - an Array that maps to required_keys structure
|
234
|
+
# - a Hash that contains the required keys
|
235
|
+
# - all values are qualified according to requirements
|
236
|
+
def validate(args)
|
237
|
+
error = nil
|
238
|
+
hash = nil
|
239
|
+
required_keys = [:entropy, :salt, :iterations]
|
240
|
+
if args.is_a? Array
|
241
|
+
hash = Hash[required_keys.zip(args)]
|
242
|
+
elsif args.is_a? Hash
|
243
|
+
hash = args
|
244
|
+
end
|
245
|
+
# validate hash
|
246
|
+
unless (hash.keys - required_keys).empty?
|
247
|
+
error = "Invalid: args must contain, #{required_keys.join(', ')}"
|
248
|
+
end
|
249
|
+
unless hash[:entropy] =~ /([a-f0-9]{2}){128}/
|
250
|
+
error = "Invalid: entropy must be hexadecimal string (128 bytes)"
|
251
|
+
end
|
252
|
+
unless hash[:salt] =~ /([a-f0-9]{2}){32}/
|
253
|
+
error = "Invalid: salt must be hexadecimal string (32 bytes)"
|
254
|
+
end
|
255
|
+
unless hash[:iterations] >= 0
|
256
|
+
error = "Invalid: entropy must positive integer"
|
257
|
+
end
|
258
|
+
raise ArgumentError.new(error) if error
|
259
|
+
hash
|
260
|
+
end
|
261
|
+
|
262
|
+
# Return the ShadowHash as a ShadowHashData object
|
263
|
+
# - Binary Plist
|
264
|
+
def data
|
265
|
+
@data ||= { @label => format(@hash) }.to_plist
|
266
|
+
end
|
267
|
+
|
268
|
+
# Return a Hash representation of the ShadowHash data
|
269
|
+
def password
|
270
|
+
{ @label => @hash }
|
271
|
+
end
|
272
|
+
|
273
|
+
private
|
274
|
+
|
275
|
+
# Pseudo callback for inserting a ShadowHashData object into the User object
|
276
|
+
def store(sender)
|
277
|
+
raise ShadowHashError.new(ShadowHashError::UNSUPPORTED_OBJECT_ERR) unless sender.is_a? MacAdmin::User
|
278
|
+
@data = { @label => format(@hash) }.to_plist
|
279
|
+
sender['ShadowHashData'] = [@data]
|
280
|
+
end
|
281
|
+
|
282
|
+
# Format the password data for ShadowHashData object compatibility
|
283
|
+
def format(hash)
|
284
|
+
hash.inject({}) do |memo, (key, value)|
|
285
|
+
if key.to_s.eql? 'iterations'
|
286
|
+
value = value.to_i
|
287
|
+
else
|
288
|
+
value = convert_to_blob value
|
289
|
+
end
|
290
|
+
memo[key.to_s] = value
|
291
|
+
memo
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
data/macadmin.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'macadmin/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "macadmin"
|
8
|
+
s.version = MacAdmin::VERSION
|
9
|
+
s.date = '2012-07-08'
|
10
|
+
s.authors = ["Brian Warsing"]
|
11
|
+
s.email = ['dayglojesus@gmail.com']
|
12
|
+
s.description = "Gem to assist in performing common systems administration tasks in Mac OS X"
|
13
|
+
s.summary = "Ruby Mac Systems Administration Library"
|
14
|
+
s.homepage = "http://github.com/dayglojesus/macadmin"
|
15
|
+
s.license = "MIT"
|
16
|
+
s.files = `git ls-files`.split($/)
|
17
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
19
|
+
s.require_paths = ["lib", "ext"]
|
20
|
+
s.extensions = Dir['ext/**/extconf.rb']
|
21
|
+
|
22
|
+
s.platform = Gem::Platform::RUBY
|
23
|
+
|
24
|
+
s.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
s.add_development_dependency "rake"
|
26
|
+
s.add_development_dependency "rake-compiler"
|
27
|
+
s.add_development_dependency "rspec"
|
28
|
+
s.add_development_dependency "CFPropertyList"
|
29
|
+
|
30
|
+
s.add_runtime_dependency "bundler", "~> 1.3"
|
31
|
+
s.add_runtime_dependency "rake"
|
32
|
+
s.add_runtime_dependency "rake-compiler"
|
33
|
+
s.add_runtime_dependency "CFPropertyList"
|
34
|
+
|
35
|
+
end
|
data/spec/common_spec.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MacAdmin::Common do
|
4
|
+
|
5
|
+
describe 'MAC_OS_X_PRODUCT_VERSION' do
|
6
|
+
it 'is a float greater than 10' do
|
7
|
+
version = MacAdmin::Common::MAC_OS_X_PRODUCT_VERSION
|
8
|
+
version.should > 10
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#load_plist' do
|
13
|
+
it 'should return an Hash object' do
|
14
|
+
file = '/System/Library/CoreServices/SystemVersion.plist'
|
15
|
+
MacAdmin::Common::load_plist(file).should be_an_instance_of Hash
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe MacAdmin::Common::UUID do
|
22
|
+
|
23
|
+
before :all do
|
24
|
+
@uuid = '897A6343-628F-4964-80F1-C86D0FFA3F91'
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#new' do
|
28
|
+
it 'should return a UUID string' do
|
29
|
+
UUID.new.should =~ /([A-Z0-9]{8})-([A-Z0-9]{4}-){3}([A-Z0-9]{12})/
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#match' do
|
34
|
+
it 'should match and return a UUID in _any_ string' do
|
35
|
+
UUID.match("com.apple.loginwindow.#{@uuid}.plist").should == @uuid
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#valid?' do
|
40
|
+
it 'should return true if handed a valid UUID string' do
|
41
|
+
UUID.valid?(@uuid).should == true
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should return false if handed a _bad_ UUID string' do
|
45
|
+
UUID.valid?(@uuid.chop).should == false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
describe MacAdmin::Computer do
|
5
|
+
|
6
|
+
before :all do
|
7
|
+
# Create a dslocal sandbox
|
8
|
+
@test_dir = "/private/tmp/macadmin_computer_test.#{rand(100000)}"
|
9
|
+
MacAdmin::DSLocalRecord.send(:remove_const, :DSLOCAL_ROOT)
|
10
|
+
MacAdmin::DSLocalRecord::DSLOCAL_ROOT = File.expand_path @test_dir
|
11
|
+
FileUtils.mkdir_p "#{@test_dir}/Default/computers"
|
12
|
+
|
13
|
+
# Create two computer record plists that we can load
|
14
|
+
planet_express = {
|
15
|
+
"en_address" => ["aa:aa:aa:aa:aa:aa"],
|
16
|
+
"realname" => ["Planet Express"],
|
17
|
+
"name" => ["planet-express"],
|
18
|
+
"generateduid" => ["00000000-0000-0000-0000-000000000001"],
|
19
|
+
}
|
20
|
+
|
21
|
+
[planet_express].each do |computer|
|
22
|
+
record = CFPropertyList::List.new
|
23
|
+
record.value = CFPropertyList.guess(computer)
|
24
|
+
record.save("#{@test_dir}/Default/computers/#{computer['name'].first}.plist", CFPropertyList::List::FORMAT_BINARY)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#new' do
|
30
|
+
|
31
|
+
context 'throws an ArgumentError when given fewer than 1 params' do
|
32
|
+
subject { Computer.new }
|
33
|
+
it { expect { subject }.to raise_error(ArgumentError) }
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when created from a parameter Hash' do
|
37
|
+
subject { Computer.new :name => 'nimbus', :en_address => 'dd:dd:dd:dd:dd:dd' }
|
38
|
+
it { should be_an_instance_of Computer }
|
39
|
+
it 'should have a valid generateduid attribute' do
|
40
|
+
(UUID.valid?(subject.generateduid.first)).should be_true
|
41
|
+
end
|
42
|
+
its (:name) { should eq ['nimbus'] }
|
43
|
+
its (:realname) { should eq ['nimbus'.capitalize] }
|
44
|
+
its (:en_address) { should eq ['dd:dd:dd:dd:dd:dd'] }
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when created from a just a name (String)' do
|
48
|
+
subject { Computer.new 'nimbus' }
|
49
|
+
it { should be_an_instance_of Computer }
|
50
|
+
it 'should have a valid generateduid attribute' do
|
51
|
+
(UUID.valid?(subject.generateduid.first)).should be_true
|
52
|
+
end
|
53
|
+
its (:name) { should eq ['nimbus'] }
|
54
|
+
its (:realname) { should eq ['nimbus'.capitalize] }
|
55
|
+
its (:en_address) { should eq [MacAdmin::Common.get_primary_mac_address] }
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#exists?' do
|
61
|
+
|
62
|
+
context "when the object is different from its associated file" do
|
63
|
+
subject { Computer.new :name => 'planet-express', :en_address => "ff:ff:ff:ff:ff:ff" }
|
64
|
+
it 'has an associated file' do
|
65
|
+
File.exists?(subject.file).should be_true
|
66
|
+
end
|
67
|
+
it 'returns false because file and object do not match' do
|
68
|
+
subject.exists?.should be_false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when ALL the object's attributes match its associated file" do
|
73
|
+
subject { Computer.new :name => 'planet-express', :en_address => "aa:aa:aa:aa:aa:aa" }
|
74
|
+
it 'has an associated file' do
|
75
|
+
File.exists?(subject.file).should be_true
|
76
|
+
end
|
77
|
+
it 'returns false because file and object do not match' do
|
78
|
+
subject.exists?.should be_true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when the object does not have an associated file on disk" do
|
83
|
+
subject { Computer.new :name => 'nimbus' }
|
84
|
+
it 'does not have an associated file' do
|
85
|
+
File.exists?(subject.file).should be_false
|
86
|
+
end
|
87
|
+
it 'should return false' do
|
88
|
+
subject.exists?.should be_false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#create' do
|
95
|
+
|
96
|
+
context "with NO path parameter (default)" do
|
97
|
+
subject { Computer.new :name => 'nimbus', :en_address => "bb:bb:bb:bb:bb:bb" }
|
98
|
+
it 'saves the record to disk on the derived path' do
|
99
|
+
subject.create.should be_true
|
100
|
+
File.exists?(subject.file).should be_true
|
101
|
+
end
|
102
|
+
after do
|
103
|
+
FileUtils.rm_rf subject.file
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context "with a path parameter" do
|
108
|
+
path = "/private/tmp/group-create-method-test.plist"
|
109
|
+
subject { Computer.new :name => 'nimbus', :en_address => "bb:bb:bb:bb:bb:bb" }
|
110
|
+
it 'saves the record to disk on the path specified' do
|
111
|
+
subject.create(path).should be_true
|
112
|
+
end
|
113
|
+
after do
|
114
|
+
FileUtils.rm_rf path
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
describe '#destroy' do
|
121
|
+
subject { Computer.new :name => 'nimbus', :en_address => "bb:bb:bb:bb:bb:bb" }
|
122
|
+
it 'removes the record on disk and returns true' do
|
123
|
+
subject.create
|
124
|
+
subject.destroy.should be_true
|
125
|
+
File.exists?(subject.file).should be_false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
after :all do
|
130
|
+
FileUtils.rm_rf @test_dir
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|