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,343 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MacAdmin::DSLocalNode do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
require 'etc'
|
7
|
+
this_user = Etc.getpwnam(Etc.getlogin)
|
8
|
+
this_uid = this_user.uid
|
9
|
+
this_gid = this_user.gid
|
10
|
+
# Create a dslocal sandbox
|
11
|
+
@test_dir = "/private/tmp/macadmin_dslocalnode_test.#{rand(100000)}"
|
12
|
+
@child_dirs = ['aliases', 'computer_lists', 'computergroups', 'computers', 'config', 'groups', 'networks', 'users']
|
13
|
+
MacAdmin::DSLocalNode.send(:remove_const, :SANDBOX_FILE)
|
14
|
+
MacAdmin::DSLocalNode.send(:remove_const, :DSLOCAL_ROOT)
|
15
|
+
MacAdmin::DSLocalNode.send(:remove_const, :PREFERENCES)
|
16
|
+
MacAdmin::DSLocalNode.send(:remove_const, :PREFERENCES_LEGACY)
|
17
|
+
MacAdmin::DSLocalNode.send(:remove_const, :OWNER)
|
18
|
+
MacAdmin::DSLocalNode.send(:remove_const, :GROUP)
|
19
|
+
MacAdmin::DSLocalNode::DSLOCAL_ROOT = File.expand_path @test_dir
|
20
|
+
MacAdmin::DSLocalNode::SANDBOX_FILE = File.expand_path "#{@test_dir}/opendirectoryd.sb"
|
21
|
+
MacAdmin::DSLocalNode::PREFERENCES = File.expand_path "#{@test_dir}/config.plist"
|
22
|
+
MacAdmin::DSLocalNode::PREFERENCES_LEGACY = File.expand_path "#{@test_dir}/legacy.plist"
|
23
|
+
MacAdmin::DSLocalNode::OWNER = this_uid
|
24
|
+
MacAdmin::DSLocalNode::GROUP = this_gid
|
25
|
+
@root = "#{@test_dir}/Default"
|
26
|
+
FileUtils.mkdir_p @root
|
27
|
+
begin
|
28
|
+
FileUtils.mkdir_p @root unless File.exist? @root
|
29
|
+
FileUtils.chmod(0700, @root)
|
30
|
+
@child_dirs.each do |child|
|
31
|
+
FileUtils.mkdir_p("#{@root}/#{child}") unless File.exist?("#{@root}/#{child}")
|
32
|
+
FileUtils.chmod(0700, "#{@root}/#{child}")
|
33
|
+
end
|
34
|
+
FileUtils.chown_R this_uid, this_gid, @root
|
35
|
+
rescue Exception => e
|
36
|
+
p e.message
|
37
|
+
end
|
38
|
+
|
39
|
+
@config = <<-CONFIG
|
40
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
41
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
42
|
+
<plist version="1.0">
|
43
|
+
<dict>
|
44
|
+
<key>comment</key>
|
45
|
+
<string>Default search policy</string>
|
46
|
+
<key>enabled</key>
|
47
|
+
<true/>
|
48
|
+
<key>mappings</key>
|
49
|
+
<dict/>
|
50
|
+
<key>modules</key>
|
51
|
+
<dict>
|
52
|
+
<key>session</key>
|
53
|
+
<array>
|
54
|
+
<dict>
|
55
|
+
<key>module</key>
|
56
|
+
<string>search</string>
|
57
|
+
<key>options</key>
|
58
|
+
<dict>
|
59
|
+
<key>dsAttrTypeStandard:CSPSearchPath</key>
|
60
|
+
<array>
|
61
|
+
<string>/Local/Default</string>
|
62
|
+
</array>
|
63
|
+
<key>dsAttrTypeStandard:LSPSearchPath</key>
|
64
|
+
<array>
|
65
|
+
<string>/Local/Default</string>
|
66
|
+
</array>
|
67
|
+
<key>dsAttrTypeStandard:NSPSearchPath</key>
|
68
|
+
<array>
|
69
|
+
<string>/Local/Default</string>
|
70
|
+
</array>
|
71
|
+
<key>dsAttrTypeStandard:SearchPolicy</key>
|
72
|
+
<string>dsAttrTypeStandard:NSPSearchPath</string>
|
73
|
+
<key>notify_of_changes</key>
|
74
|
+
<true/>
|
75
|
+
<key>requiredNodes</key>
|
76
|
+
<array>
|
77
|
+
<string>/Local/Default</string>
|
78
|
+
</array>
|
79
|
+
</dict>
|
80
|
+
<key>uuid</key>
|
81
|
+
<string>A840FC81-A6CD-4665-899E-F8B52B1C6EC4</string>
|
82
|
+
</dict>
|
83
|
+
</array>
|
84
|
+
</dict>
|
85
|
+
<key>node name</key>
|
86
|
+
<string>/Search</string>
|
87
|
+
</dict>
|
88
|
+
</plist>
|
89
|
+
CONFIG
|
90
|
+
|
91
|
+
@legacy = <<-CONFIG
|
92
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
93
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
94
|
+
<plist version="1.0">
|
95
|
+
<dict>
|
96
|
+
<key>DHCP LDAP</key>
|
97
|
+
<dict>
|
98
|
+
<key>/Sets/2A7FEC83-9B78-4BD1-A4E3-29F685EFD97A</key>
|
99
|
+
<false/>
|
100
|
+
</dict>
|
101
|
+
<key>Search Node PlugIn Version</key>
|
102
|
+
<string>Search Node PlugIn Version 1.7</string>
|
103
|
+
<key>Search Policy</key>
|
104
|
+
<integer>1</integer>
|
105
|
+
</dict>
|
106
|
+
</plist>
|
107
|
+
CONFIG
|
108
|
+
|
109
|
+
@sandbox = <<-SANDBOX
|
110
|
+
;; Copyright (c) 2011 Apple Inc. All Rights reserved.
|
111
|
+
;;
|
112
|
+
;; WARNING: The sandbox rules in this file currently constitute
|
113
|
+
;; Apple System Private Interface and are subject to change at any time and
|
114
|
+
;; without notice.
|
115
|
+
;;
|
116
|
+
|
117
|
+
(version 1)
|
118
|
+
|
119
|
+
(allow default)
|
120
|
+
|
121
|
+
;; deny lookup to coreservicesd to avoid deadlocks
|
122
|
+
(deny mach-lookup
|
123
|
+
(global-name "com.apple.CoreServices.coreservicesd")
|
124
|
+
(with no-log)
|
125
|
+
(with no-callout)
|
126
|
+
)
|
127
|
+
|
128
|
+
;; we don't allow inbound or bound ports
|
129
|
+
(deny network-inbound (with no-callout))
|
130
|
+
(deny network-bind (with no-callout))
|
131
|
+
|
132
|
+
(allow network-inbound (local udp))
|
133
|
+
(allow network-bind (local ip))
|
134
|
+
|
135
|
+
;; deny all file writes except those explicity allowed
|
136
|
+
(deny file-write* (with no-callout))
|
137
|
+
|
138
|
+
(allow process-exec (literal "/usr/bin/nsupdate"))
|
139
|
+
|
140
|
+
;; allow slapconfig to be launched without sandbox due to denial of file-write
|
141
|
+
(allow process-exec (literal "/usr/sbin/slapconfig") (with no-sandbox))
|
142
|
+
|
143
|
+
;; 10735867
|
144
|
+
(allow process-exec (literal "/usr/sbin/kextcache") (with no-sandbox))
|
145
|
+
|
146
|
+
;; restrict where we write
|
147
|
+
(allow file-write*
|
148
|
+
;; key OpenDirectory files
|
149
|
+
(mount-relative-regex
|
150
|
+
;; our database and shadowhash files
|
151
|
+
;; we might be targeting '/' or some other volume
|
152
|
+
#"^(/private)?/var/db/dslocal/nodes/Default(/|$)"
|
153
|
+
#"^(/private)?/var/db/dslocal/nodes/Earth(/|$)"
|
154
|
+
#"^(/private)?/var/db/dslocal/nodes/MCX(/|$)"
|
155
|
+
#"^(/private)?/var/db/shadow(/|$)"
|
156
|
+
)
|
157
|
+
|
158
|
+
(regex
|
159
|
+
;; configuration files
|
160
|
+
;; we ignore DirectoryService files because they are handled by dspluginhelperd
|
161
|
+
#"^/Library/Preferences/OpenDirectory"
|
162
|
+
|
163
|
+
;; our log files
|
164
|
+
#"^(/private)?/var/log/opendirectoryd.log"
|
165
|
+
|
166
|
+
;; configuration files
|
167
|
+
#"^/Library/Preferences/SystemConfiguration/"
|
168
|
+
)
|
169
|
+
|
170
|
+
;; our SystemCache related files
|
171
|
+
(regex
|
172
|
+
#"^(/private)?/etc/memberd.conf" ;;; for unlinking
|
173
|
+
)
|
174
|
+
|
175
|
+
;; update system keychain
|
176
|
+
(regex
|
177
|
+
#"^/Library/Keychains/System.keychain"
|
178
|
+
#"^/Library/Keychains/\."
|
179
|
+
#"^(/private)?/var/db/mds/system/mds.lock$"
|
180
|
+
)
|
181
|
+
|
182
|
+
;; kerberos keytab updates
|
183
|
+
(regex
|
184
|
+
#"^(/private)?/etc/krb5.keytab$"
|
185
|
+
#"^(/private)?/tmp/krb5cc_"
|
186
|
+
)
|
187
|
+
|
188
|
+
;; additional required
|
189
|
+
(literal "/dev/dtracehelper")
|
190
|
+
(literal "/dev/null")
|
191
|
+
(literal "/dev/random")
|
192
|
+
|
193
|
+
;; for NIS support
|
194
|
+
(regex
|
195
|
+
#"^(/private)?/var/yp/"
|
196
|
+
#"^(/private)?/etc/defaultdomain$"
|
197
|
+
)
|
198
|
+
)
|
199
|
+
|
200
|
+
SANDBOX
|
201
|
+
|
202
|
+
File.open(MacAdmin::DSLocalNode::PREFERENCES, 'w') { |f| f.write(@config) }
|
203
|
+
File.open(MacAdmin::DSLocalNode::PREFERENCES_LEGACY, 'w') { |f| f.write(@legacy) }
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
before :each do
|
208
|
+
File.open(MacAdmin::DSLocalNode::SANDBOX_FILE, 'w') { |f| f.write(@sandbox) }
|
209
|
+
end
|
210
|
+
|
211
|
+
after :each do
|
212
|
+
if subject.name.eql? 'Earth'
|
213
|
+
FileUtils.rm_rf subject.root if File.exists? subject.root
|
214
|
+
FileUtils.rm_rf MacAdmin::DSLocalNode::SANDBOX_FILE if File.exists? MacAdmin::DSLocalNode::SANDBOX_FILE
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe '#new' do
|
219
|
+
|
220
|
+
context 'when intialized without arguments it returns the Default node' do
|
221
|
+
subject { DSLocalNode.new }
|
222
|
+
it { should be_an_instance_of DSLocalNode }
|
223
|
+
its (:name) { should eq 'Default' }
|
224
|
+
its (:label) { should eq '/Local/Default' }
|
225
|
+
its (:root) { should eq "#{MacAdmin::DSLocalNode::DSLOCAL_ROOT}/Default" }
|
226
|
+
end
|
227
|
+
|
228
|
+
context 'when intialized with a name (String)' do
|
229
|
+
subject { DSLocalNode.new 'Earth' }
|
230
|
+
it { should be_an_instance_of DSLocalNode }
|
231
|
+
its (:name) { should eq 'Earth' }
|
232
|
+
its (:label) { should eq '/Local/Earth' }
|
233
|
+
its (:root) { should eq "#{MacAdmin::DSLocalNode::DSLOCAL_ROOT}/Earth" }
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
describe '#exists?' do
|
239
|
+
|
240
|
+
context 'when the defined directory structure exists' do
|
241
|
+
subject { DSLocalNode.new }
|
242
|
+
it { subject.exists?.should be_true }
|
243
|
+
end
|
244
|
+
|
245
|
+
context 'when the defined directory structure does NOT exist' do
|
246
|
+
subject { DSLocalNode.new 'Earth' }
|
247
|
+
it { subject.exists?.should be_false }
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
describe '#active?' do
|
253
|
+
|
254
|
+
context "when the node is in the Directory Service's search path" do
|
255
|
+
subject { DSLocalNode.new }
|
256
|
+
it { subject.active?.should be_true }
|
257
|
+
end
|
258
|
+
|
259
|
+
context "when the node is NOT in the Directory Service's search path" do
|
260
|
+
subject { DSLocalNode.new 'Earth' }
|
261
|
+
it { subject.active?.should be_false }
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
describe '#create' do
|
267
|
+
|
268
|
+
subject { DSLocalNode.new 'Earth' }
|
269
|
+
it 'creates the defined directory structure' do
|
270
|
+
subject.create.should be_true
|
271
|
+
subject.exists?.should be_true
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
|
276
|
+
describe '#activate' do
|
277
|
+
|
278
|
+
subject { DSLocalNode.new 'Earth' }
|
279
|
+
it "add the node to Directory Service's search path" do
|
280
|
+
subject.activate.should be_true
|
281
|
+
subject.active?.should be_true
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
describe '#deactivate' do
|
287
|
+
|
288
|
+
subject { DSLocalNode.new 'Earth' }
|
289
|
+
it "removes the node from the Directory Service's search path" do
|
290
|
+
subject.activate
|
291
|
+
subject.deactivate.should be_true
|
292
|
+
subject.active?.should be_false
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
296
|
+
|
297
|
+
describe "#exists_and_active?" do
|
298
|
+
|
299
|
+
context "when the node exists and is active" do
|
300
|
+
subject { DSLocalNode.new }
|
301
|
+
it { subject.exists_and_active?.should be_true }
|
302
|
+
end
|
303
|
+
|
304
|
+
context "when the node exists but is NOT active" do
|
305
|
+
subject { DSLocalNode.new 'Earth' }
|
306
|
+
it 'should be_false' do
|
307
|
+
subject.create
|
308
|
+
subject.exists_and_active?.should be_false
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
context "when the node is active but does NOT exist" do
|
313
|
+
subject { DSLocalNode.new 'Earth' }
|
314
|
+
it 'should be_false' do
|
315
|
+
subject.activate
|
316
|
+
subject.exists_and_active?.should be_false
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
end
|
321
|
+
|
322
|
+
describe "#create_and_activate" do
|
323
|
+
subject { DSLocalNode.new 'Earth' }
|
324
|
+
it "should be_true" do
|
325
|
+
subject.create_and_activate
|
326
|
+
subject.exists_and_active?.should be_true
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
describe "#destroy_and_deactivate" do
|
331
|
+
subject { DSLocalNode.new 'Earth' }
|
332
|
+
it "should be_false" do
|
333
|
+
subject.create_and_activate
|
334
|
+
subject.destroy_and_deactivate
|
335
|
+
subject.exists_and_active?.should be_false
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
after :all do
|
340
|
+
FileUtils.rm_rf @test_dir
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
data/spec/group_spec.rb
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MacAdmin::Group do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
# Create a dslocal sandbox
|
7
|
+
@test_dir = "/private/tmp/macadmin_group_test.#{rand(100000)}"
|
8
|
+
MacAdmin::DSLocalRecord.send(:remove_const, :DSLOCAL_ROOT)
|
9
|
+
MacAdmin::DSLocalRecord::DSLOCAL_ROOT = File.expand_path @test_dir
|
10
|
+
FileUtils.mkdir_p "#{@test_dir}/Default/groups"
|
11
|
+
FileUtils.mkdir_p "#{@test_dir}/Default/users"
|
12
|
+
|
13
|
+
# Create two group record plists that we can load
|
14
|
+
foo = {
|
15
|
+
"gid" => ["501"],
|
16
|
+
"realname" => ["Foo"],
|
17
|
+
"name" => ["foo"],
|
18
|
+
"generateduid" => ["00000000-0000-0000-0000-000000000001"],
|
19
|
+
}
|
20
|
+
|
21
|
+
bar = {
|
22
|
+
"gid" => ["502"],
|
23
|
+
"realname" => ["Bar"],
|
24
|
+
"name" => ["bar"],
|
25
|
+
"generateduid" => ["00000000-0000-0000-0000-000000000002"],
|
26
|
+
}
|
27
|
+
|
28
|
+
[foo, bar].each do |group|
|
29
|
+
record = CFPropertyList::List.new
|
30
|
+
record.value = CFPropertyList.guess(group)
|
31
|
+
record.save("#{@test_dir}/Default/groups/#{group['name'].first}.plist", CFPropertyList::List::FORMAT_BINARY)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#new' do
|
36
|
+
|
37
|
+
context 'throws an ArgumentError when given fewer than 1 params' do
|
38
|
+
subject { Group.new }
|
39
|
+
it { expect { subject }.to raise_error(ArgumentError) }
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when created from a parameter Hash' do
|
43
|
+
subject { Group.new :name => 'baz', :gid => "666" }
|
44
|
+
it { should be_an_instance_of Group }
|
45
|
+
it 'should have a valid generateduid attribute' do
|
46
|
+
(UUID.valid?(subject.generateduid.first)).should be_true
|
47
|
+
end
|
48
|
+
its (:gid) { should eq ['666'] }
|
49
|
+
its (:name) { should eq ['baz'] }
|
50
|
+
its (:realname) { should eq ['baz'.capitalize] }
|
51
|
+
its (:users) { should eq [] }
|
52
|
+
its (:groupmembers) { should eq [] }
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when created from a just a name (String)' do
|
56
|
+
subject { Group.new 'baz' }
|
57
|
+
it { should be_an_instance_of Group }
|
58
|
+
it 'should have a valid generateduid attribute' do
|
59
|
+
(UUID.valid?(subject.generateduid.first)).should be_true
|
60
|
+
end
|
61
|
+
its (:gid) { should eq ['503'] } # 503 should be the next available GID
|
62
|
+
its (:name) { should eq ['baz'] }
|
63
|
+
its (:realname) { should eq ['baz'.capitalize] }
|
64
|
+
its (:users) { should eq [] }
|
65
|
+
its (:groupmembers) { should eq [] }
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#exists?' do
|
71
|
+
|
72
|
+
context "when the object is different from its associated file" do
|
73
|
+
subject { Group.new :name => 'foo', :gid => "503" }
|
74
|
+
it 'should return false' do
|
75
|
+
subject.exists?.should be_false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "when ALL the object's attributes match its associated file" do
|
80
|
+
subject { Group.new :name => 'foo', :gid => "501" }
|
81
|
+
it 'should return true' do
|
82
|
+
subject.exists?.should be_true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when the object does not have an associated file on disk" do
|
87
|
+
subject { Group.new :name => 'baz' }
|
88
|
+
it 'should return false' do
|
89
|
+
subject.exists?.should be_false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#create' do
|
96
|
+
|
97
|
+
context "with NO path parameter (default)" do
|
98
|
+
subject { Group.new :name => 'baz', :gid => "666" }
|
99
|
+
it 'saves the record to disk on the derived path' do
|
100
|
+
subject.create.should be_true
|
101
|
+
File.exists?(subject.file).should be_true
|
102
|
+
end
|
103
|
+
after do
|
104
|
+
FileUtils.rm_rf subject.file
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "with a path parameter" do
|
109
|
+
path = "/private/tmp/group-create-method-test.plist"
|
110
|
+
subject { Group.new :name => 'baz', :gid => "666" }
|
111
|
+
it 'saves the record to disk on the path specified' do
|
112
|
+
subject.create(path).should be_true
|
113
|
+
end
|
114
|
+
after do
|
115
|
+
FileUtils.rm_rf path
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#destroy' do
|
122
|
+
subject { Group.new :name => 'baz', :gid => "666" }
|
123
|
+
it 'removes the record on disk and returns true' do
|
124
|
+
subject.create
|
125
|
+
subject.destroy.should be_true
|
126
|
+
File.exists?(subject.file).should be_false
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '#users=' do
|
131
|
+
subject { Group.new 'foo' }
|
132
|
+
before { subject.users = ['foo', 'bar'] }
|
133
|
+
it 'replaces the membership array for users' do
|
134
|
+
subject['users'].should eq ['foo', 'bar']
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#users' do
|
139
|
+
subject { Group.new 'foo' }
|
140
|
+
before { subject.users = ['foo', 'bar'] }
|
141
|
+
it 'returns a membership array of users' do
|
142
|
+
subject.users.should eq ['foo', 'bar']
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe '#has_user?' do
|
147
|
+
subject { Group.new 'baz' }
|
148
|
+
|
149
|
+
context 'when membership array is non-existent' do
|
150
|
+
it 'should be false' do
|
151
|
+
subject.has_user?('bender').should be_false
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context 'when membership array is populated' do
|
156
|
+
before { subject.users = ['fry', 'bender'] }
|
157
|
+
it 'should be true if the user is a member of the group' do
|
158
|
+
subject.has_user?('fry').should be_true
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should be false if the user is NOT a member of the group' do
|
162
|
+
subject.has_user?('leela').should be_false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
describe '#add_user' do
|
169
|
+
subject { Group.new 'baz' }
|
170
|
+
|
171
|
+
context 'when the member does NOT exist' do
|
172
|
+
it 'should raise an error' do
|
173
|
+
expect { subject.add_user('invalid') }.to raise_error(StandardError)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context 'when the member exists' do
|
178
|
+
it "should add the user to the users array" do
|
179
|
+
fry = User.new 'fry'
|
180
|
+
fry.create
|
181
|
+
subject.add_user('fry').should be_nil
|
182
|
+
subject['users'].should eql ["fry"]
|
183
|
+
fry.destroy
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
describe '#rm_user' do
|
190
|
+
subject { Group.new 'foo' }
|
191
|
+
|
192
|
+
context 'when user does NOT exist' do
|
193
|
+
it 'should return nil' do
|
194
|
+
subject.users = ['fry', 'bender']
|
195
|
+
subject.rm_user('leela').should be_nil
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'when user does exist' do
|
200
|
+
it 'should remove the user from the membership array' do
|
201
|
+
subject.users = ['fry', 'bender']
|
202
|
+
subject.rm_user('fry').should eq 'fry'
|
203
|
+
subject.users.should eq ['bender']
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
describe '#has_groupmember?' do
|
210
|
+
subject { Group.new 'baz' }
|
211
|
+
|
212
|
+
context 'when membership array is non-existent' do
|
213
|
+
it 'should be false' do
|
214
|
+
subject.has_groupmember?('foo').should be_false
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context 'when membership array is populated' do
|
219
|
+
it 'should be true if the user is a member of the group' do
|
220
|
+
subject.groupmembers = ['00000000-0000-0000-0000-000000000001']
|
221
|
+
subject.has_groupmember?('foo').should be_true
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'should be false if the user is NOT a member of the group' do
|
225
|
+
subject.has_groupmember?('leela').should be_false
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
describe '#add_groupmember' do
|
232
|
+
subject { Group.new 'baz' }
|
233
|
+
|
234
|
+
context 'when the member does NOT exist' do
|
235
|
+
it 'should raise an error' do
|
236
|
+
expect { subject.add_groupmember('invalid') }.to raise_error(StandardError)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
context 'when the member exists' do
|
241
|
+
it "should add named group's GeneratedUID to the groupmembers array" do
|
242
|
+
subject.add_groupmember('foo').should be_nil
|
243
|
+
subject['groupmembers'].should eql ["00000000-0000-0000-0000-000000000001"]
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
describe '#rm_groupmember' do
|
250
|
+
subject { Group.new 'baz' }
|
251
|
+
|
252
|
+
context 'when the member does NOT exist' do
|
253
|
+
it 'should return nil' do
|
254
|
+
subject.rm_groupmember('invalid').should be_nil
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context 'when the member exists' do
|
259
|
+
it 'should remove the member from the array' do
|
260
|
+
['foo', 'bar'].each do |member|
|
261
|
+
subject.add_groupmember member
|
262
|
+
end
|
263
|
+
subject.rm_groupmember('foo').should eq "00000000-0000-0000-0000-000000000001"
|
264
|
+
subject.groupmembers.should eq ["00000000-0000-0000-0000-000000000002"]
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
after :all do
|
272
|
+
FileUtils.rm_rf @test_dir
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|