/spec/requests/api/members_spec.rb
Ruby | 363 lines | 288 code | 69 blank | 6 comment | 8 complexity | b7f6f622d502c3a7567819668b3b408f MD5 | raw file
1require 'spec_helper'
2
3describe API::Members, api: true do
4 include ApiHelpers
5
6 let(:master) { create(:user) }
7 let(:developer) { create(:user) }
8 let(:access_requester) { create(:user) }
9 let(:stranger) { create(:user) }
10
11 let(:project) do
12 project = create(:project, :public, creator_id: master.id, namespace: master.namespace)
13 project.team << [developer, :developer]
14 project.team << [master, :master]
15 project.request_access(access_requester)
16 project
17 end
18
19 let!(:group) do
20 group = create(:group, :public)
21 group.add_developer(developer)
22 group.add_owner(master)
23 group.request_access(access_requester)
24 group
25 end
26
27 shared_examples 'GET /:sources/:id/members' do |source_type|
28 context "with :sources == #{source_type.pluralize}" do
29 it_behaves_like 'a 404 response when source is private' do
30 let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members", stranger) }
31 end
32
33 %i[master developer access_requester stranger].each do |type|
34 context "when authenticated as a #{type}" do
35 it 'returns 200' do
36 user = public_send(type)
37 get api("/#{source_type.pluralize}/#{source.id}/members", user)
38
39 expect(response).to have_http_status(200)
40 expect(json_response.size).to eq(2)
41 expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
42 end
43 end
44 end
45
46 it 'does not return invitees' do
47 create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil)
48
49 get api("/#{source_type.pluralize}/#{source.id}/members", developer)
50
51 expect(response).to have_http_status(200)
52 expect(json_response.size).to eq(2)
53 expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
54 end
55
56 it 'finds members with query string' do
57 get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username
58
59 expect(response).to have_http_status(200)
60 expect(json_response.count).to eq(1)
61 expect(json_response.first['username']).to eq(master.username)
62 end
63 end
64 end
65
66 shared_examples 'GET /:sources/:id/members/:user_id' do |source_type|
67 context "with :sources == #{source_type.pluralize}" do
68 it_behaves_like 'a 404 response when source is private' do
69 let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) }
70 end
71
72 context 'when authenticated as a non-member' do
73 %i[access_requester stranger].each do |type|
74 context "as a #{type}" do
75 it 'returns 200' do
76 user = public_send(type)
77 get api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user)
78
79 expect(response).to have_http_status(200)
80 # User attributes
81 expect(json_response['id']).to eq(developer.id)
82 expect(json_response['name']).to eq(developer.name)
83 expect(json_response['username']).to eq(developer.username)
84 expect(json_response['state']).to eq(developer.state)
85 expect(json_response['avatar_url']).to eq(developer.avatar_url)
86 expect(json_response['web_url']).to eq(Gitlab::Routing.url_helpers.user_url(developer))
87
88 # Member attributes
89 expect(json_response['access_level']).to eq(Member::DEVELOPER)
90 end
91 end
92 end
93 end
94 end
95 end
96
97 shared_examples 'POST /:sources/:id/members' do |source_type|
98 context "with :sources == #{source_type.pluralize}" do
99 it_behaves_like 'a 404 response when source is private' do
100 let(:route) do
101 post api("/#{source_type.pluralize}/#{source.id}/members", stranger),
102 user_id: access_requester.id, access_level: Member::MASTER
103 end
104 end
105
106 context 'when authenticated as a non-member or member with insufficient rights' do
107 %i[access_requester stranger developer].each do |type|
108 context "as a #{type}" do
109 it 'returns 403' do
110 user = public_send(type)
111 post api("/#{source_type.pluralize}/#{source.id}/members", user),
112 user_id: access_requester.id, access_level: Member::MASTER
113
114 expect(response).to have_http_status(403)
115 end
116 end
117 end
118 end
119
120 context 'when authenticated as a master/owner' do
121 context 'and new member is already a requester' do
122 it 'transforms the requester into a proper member' do
123 expect do
124 post api("/#{source_type.pluralize}/#{source.id}/members", master),
125 user_id: access_requester.id, access_level: Member::MASTER
126
127 expect(response).to have_http_status(201)
128 end.to change { source.members.count }.by(1)
129 expect(source.requesters.count).to eq(0)
130 expect(json_response['id']).to eq(access_requester.id)
131 expect(json_response['access_level']).to eq(Member::MASTER)
132 end
133 end
134
135 it 'creates a new member' do
136 expect do
137 post api("/#{source_type.pluralize}/#{source.id}/members", master),
138 user_id: stranger.id, access_level: Member::DEVELOPER, expires_at: '2016-08-05'
139
140 expect(response).to have_http_status(201)
141 end.to change { source.members.count }.by(1)
142 expect(json_response['id']).to eq(stranger.id)
143 expect(json_response['access_level']).to eq(Member::DEVELOPER)
144 expect(json_response['expires_at']).to eq('2016-08-05')
145 end
146 end
147
148 it "returns #{source_type == 'project' ? 201 : 409} if member already exists" do
149 post api("/#{source_type.pluralize}/#{source.id}/members", master),
150 user_id: master.id, access_level: Member::MASTER
151
152 expect(response).to have_http_status(source_type == 'project' ? 201 : 409)
153 end
154
155 it 'returns 400 when user_id is not given' do
156 post api("/#{source_type.pluralize}/#{source.id}/members", master),
157 access_level: Member::MASTER
158
159 expect(response).to have_http_status(400)
160 end
161
162 it 'returns 400 when access_level is not given' do
163 post api("/#{source_type.pluralize}/#{source.id}/members", master),
164 user_id: stranger.id
165
166 expect(response).to have_http_status(400)
167 end
168
169 it 'returns 422 when access_level is not valid' do
170 post api("/#{source_type.pluralize}/#{source.id}/members", master),
171 user_id: stranger.id, access_level: 1234
172
173 expect(response).to have_http_status(422)
174 end
175 end
176 end
177
178 ## EE specific
179 shared_examples 'POST /projects/:id/members with the project group membership locked' do
180 context 'project in a group' do
181 it 'returns a 405 method not allowed error when group membership lock is enabled' do
182 group_with_membership_locked = create(:group, membership_lock: true)
183 project = create(:project, group: group_with_membership_locked)
184 project.group.add_owner(master)
185
186 post api("/projects/#{project.id}/members", master),
187 user_id: developer.id, access_level: Member::MASTER
188
189 expect(response.status).to eq 405
190 end
191 end
192 end
193 ## EE specific
194
195 shared_examples 'PUT /:sources/:id/members/:user_id' do |source_type|
196 context "with :sources == #{source_type.pluralize}" do
197 it_behaves_like 'a 404 response when source is private' do
198 let(:route) do
199 put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger),
200 access_level: Member::MASTER
201 end
202 end
203
204 context 'when authenticated as a non-member or member with insufficient rights' do
205 %i[access_requester stranger developer].each do |type|
206 context "as a #{type}" do
207 it 'returns 403' do
208 user = public_send(type)
209 put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user),
210 access_level: Member::MASTER
211
212 expect(response).to have_http_status(403)
213 end
214 end
215 end
216 end
217
218 context 'when authenticated as a master/owner' do
219 it 'updates the member' do
220 put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master),
221 access_level: Member::MASTER, expires_at: '2016-08-05'
222
223 expect(response).to have_http_status(200)
224 expect(json_response['id']).to eq(developer.id)
225 expect(json_response['access_level']).to eq(Member::MASTER)
226 expect(json_response['expires_at']).to eq('2016-08-05')
227 end
228 end
229
230 it 'returns 409 if member does not exist' do
231 put api("/#{source_type.pluralize}/#{source.id}/members/123", master),
232 access_level: Member::MASTER
233
234 expect(response).to have_http_status(404)
235 end
236
237 it 'returns 400 when access_level is not given' do
238 put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master)
239
240 expect(response).to have_http_status(400)
241 end
242
243 it 'returns 422 when access level is not valid' do
244 put api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master),
245 access_level: 1234
246
247 expect(response).to have_http_status(422)
248 end
249 end
250 end
251
252 shared_examples 'DELETE /:sources/:id/members/:user_id' do |source_type|
253 context "with :sources == #{source_type.pluralize}" do
254 it_behaves_like 'a 404 response when source is private' do
255 let(:route) { delete api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", stranger) }
256 end
257
258 context 'when authenticated as a non-member or member with insufficient rights' do
259 %i[access_requester stranger].each do |type|
260 context "as a #{type}" do
261 it 'returns 403' do
262 user = public_send(type)
263 delete api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", user)
264
265 expect(response).to have_http_status(403)
266 end
267 end
268 end
269 end
270
271 context 'when authenticated as a member and deleting themself' do
272 it 'deletes the member' do
273 expect do
274 delete api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", developer)
275
276 expect(response).to have_http_status(200)
277 end.to change { source.members.count }.by(-1)
278 end
279 end
280
281 context 'when authenticated as a master/owner' do
282 context 'and member is a requester' do
283 it "returns #{source_type == 'project' ? 200 : 404}" do
284 expect do
285 delete api("/#{source_type.pluralize}/#{source.id}/members/#{access_requester.id}", master)
286
287 expect(response).to have_http_status(source_type == 'project' ? 200 : 404)
288 end.not_to change { source.requesters.count }
289 end
290 end
291
292 it 'deletes the member' do
293 expect do
294 delete api("/#{source_type.pluralize}/#{source.id}/members/#{developer.id}", master)
295
296 expect(response).to have_http_status(200)
297 end.to change { source.members.count }.by(-1)
298 end
299 end
300
301 it "returns #{source_type == 'project' ? 200 : 404} if member does not exist" do
302 delete api("/#{source_type.pluralize}/#{source.id}/members/123", master)
303
304 expect(response).to have_http_status(source_type == 'project' ? 200 : 404)
305 end
306 end
307 end
308
309 it_behaves_like 'GET /:sources/:id/members', 'project' do
310 let(:source) { project }
311 end
312
313 it_behaves_like 'GET /:sources/:id/members', 'group' do
314 let(:source) { group }
315 end
316
317 it_behaves_like 'GET /:sources/:id/members/:user_id', 'project' do
318 let(:source) { project }
319 end
320
321 it_behaves_like 'GET /:sources/:id/members/:user_id', 'group' do
322 let(:source) { group }
323 end
324
325 it_behaves_like 'POST /:sources/:id/members', 'project' do
326 let(:source) { project }
327 end
328
329 ## EE specific
330 it_behaves_like 'POST /projects/:id/members with the project group membership locked'
331 ## EE specific
332
333 it_behaves_like 'POST /:sources/:id/members', 'group' do
334 let(:source) { group }
335 end
336
337 it_behaves_like 'PUT /:sources/:id/members/:user_id', 'project' do
338 let(:source) { project }
339 end
340
341 it_behaves_like 'PUT /:sources/:id/members/:user_id', 'group' do
342 let(:source) { group }
343 end
344
345 it_behaves_like 'DELETE /:sources/:id/members/:user_id', 'project' do
346 let(:source) { project }
347 end
348
349 it_behaves_like 'DELETE /:sources/:id/members/:user_id', 'group' do
350 let(:source) { group }
351 end
352
353 context 'Adding owner to project' do
354 it 'returns 403' do
355 expect do
356 post api("/projects/#{project.id}/members", master),
357 user_id: stranger.id, access_level: Member::OWNER
358
359 expect(response).to have_http_status(422)
360 end.to change { project.members.count }.by(0)
361 end
362 end
363end