Statistics
| Branch: | Tag: | Revision:

root / snf-astakos-app / astakos / im / tests / projects.py @ ff5edb80

History | View | Annotate | Download (33.2 kB)

1
# Copyright 2011-2014 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from astakos.im.tests.common import *
35

    
36

    
37
NotFound = type('NotFound', (), {})
38

    
39

    
40
def find(f, seq):
41
    for item in seq:
42
        if f(item):
43
            return item
44
    return NotFound
45

    
46

    
47
class ProjectAPITest(TestCase):
48

    
49
    def setUp(self):
50
        self.client = Client()
51
        component1 = Component.objects.create(name="comp1")
52
        register.add_service(component1, "service1", "type1", [])
53
        # custom service resources
54
        resource11 = {"name": "service1.resource11",
55
                      "desc": "resource11 desc",
56
                      "service_type": "type1",
57
                      "service_origin": "service1",
58
                      "ui_visible": True}
59
        r, _ = register.add_resource(resource11)
60
        register.update_base_default(r, 100)
61
        resource12 = {"name": "service1.resource12",
62
                      "desc": "resource11 desc",
63
                      "service_type": "type1",
64
                      "service_origin": "service1",
65
                      "unit": "bytes"}
66
        r, _ = register.add_resource(resource12)
67
        register.update_base_default(r, 1024)
68

    
69
        # create user
70
        self.user1 = get_local_user("test@grnet.gr")
71
        self.user2 = get_local_user("test2@grnet.gr")
72
        self.user2.uuid = "uuid2"
73
        self.user2.save()
74
        self.user3 = get_local_user("test3@grnet.gr")
75

    
76
        astakos = Component.objects.create(name="astakos")
77
        register.add_service(astakos, "astakos_account", "account", [])
78
        # create another service
79
        pending_app = {"name": "astakos.pending_app",
80
                       "desc": "pend app desc",
81
                       "service_type": "account",
82
                       "service_origin": "astakos_account",
83
                       "ui_visible": False,
84
                       "api_visible": False}
85
        r, _ = register.add_resource(pending_app)
86
        register.update_base_default(r, 3)
87
        request = {"resources": {r.name: {"member_capacity": 3,
88
                                          "project_capacity": 3}}}
89
        functions.modify_projects_in_bulk(Q(is_base=True), request)
90

    
91
    def create(self, app, headers):
92
        dump = json.dumps(app)
93
        r = self.client.post(reverse("api_projects"), dump,
94
                             content_type="application/json", **headers)
95
        body = json.loads(r.content)
96
        return r.status_code, body
97

    
98
    def modify(self, app, project_id, headers):
99
        dump = json.dumps(app)
100
        kwargs = {"project_id": project_id}
101
        r = self.client.put(reverse("api_project", kwargs=kwargs), dump,
102
                             content_type="application/json", **headers)
103
        body = json.loads(r.content)
104
        return r.status_code, body
105

    
106
    def project_action(self, project_id, action, app_id=None, headers=None):
107
        action_data = {"reason": ""}
108
        if app_id is not None:
109
            action_data["app_id"] = app_id
110
        action = json.dumps({action: action_data})
111
        r = self.client.post(reverse("api_project_action",
112
                                     kwargs={"project_id": project_id}),
113
                             action, content_type="application/json",
114
                             **headers)
115
        return r.status_code
116

    
117
    def memb_action(self, memb_id, action, headers):
118
        action = json.dumps({action: "reason"})
119
        r = self.client.post(reverse("api_membership_action",
120
                                     kwargs={"memb_id": memb_id}), action,
121
                             content_type="application/json", **headers)
122
        return r.status_code
123

    
124
    def join(self, project_id, headers):
125
        action = {"join": {"project": project_id}}
126
        req = json.dumps(action)
127
        r = self.client.post(reverse("api_memberships"), req,
128
                             content_type="application/json", **headers)
129
        body = json.loads(r.content)
130
        return r.status_code, body
131

    
132
    def enroll(self, project_id, user, headers):
133
        action = {
134
            "enroll": {
135
                "project": project_id,
136
                "user": user.email,
137
            }
138
        }
139
        req = json.dumps(action)
140
        r = self.client.post(reverse("api_memberships"), req,
141
                             content_type="application/json", **headers)
142
        body = json.loads(r.content)
143
        return r.status_code, body
144

    
145
    @im_settings(PROJECT_ADMINS=["uuid2"])
146
    def test_projects(self):
147
        client = self.client
148
        h_owner = {"HTTP_X_AUTH_TOKEN": self.user1.auth_token}
149
        h_admin = {"HTTP_X_AUTH_TOKEN": self.user2.auth_token}
150
        h_plain = {"HTTP_X_AUTH_TOKEN": self.user3.auth_token}
151
        r = client.get(reverse("api_project", kwargs={"project_id": 1}))
152
        self.assertEqual(r.status_code, 401)
153

    
154
        r = client.get(reverse("api_project", kwargs={"project_id": 1}),
155
                       **h_owner)
156
        self.assertEqual(r.status_code, 404)
157
        r = client.get(reverse("api_membership", kwargs={"memb_id": 100}),
158
                       **h_owner)
159
        self.assertEqual(r.status_code, 404)
160

    
161
        status = self.memb_action(1, "accept", h_admin)
162
        self.assertEqual(status, 409)
163

    
164
        app1 = {"name": "test.pr",
165
                "end_date": "2013-5-5T20:20:20Z",
166
                "join_policy": "auto",
167
                "max_members": 5,
168
                "resources": {"service1.resource11": {
169
                    "project_capacity": 1024,
170
                    "member_capacity": 512}}
171
                }
172

    
173
        status, body = self.modify(app1, 100, h_owner)
174
        self.assertEqual(status, 404)
175

    
176
        # Create
177
        status, body = self.create(app1, h_owner)
178
        self.assertEqual(status, 201)
179
        project_id = body["id"]
180
        app_id = body["application"]
181

    
182
        # Get project
183
        r = client.get(reverse("api_project",
184
                               kwargs={"project_id": project_id}),
185
                       **h_owner)
186
        self.assertEqual(r.status_code, 200)
187
        body = json.loads(r.content)
188
        self.assertEqual(body["id"], project_id)
189
        self.assertEqual(body["last_application"]["id"], app_id)
190
        self.assertEqual(body["last_application"]["state"], "pending")
191
        self.assertEqual(body["state"], "uninitialized")
192
        self.assertEqual(body["owner"], self.user1.uuid)
193

    
194
        # Approve forbidden
195
        status = self.project_action(project_id, "approve", app_id=app_id,
196
                                     headers=h_owner)
197
        self.assertEqual(status, 403)
198

    
199
        # Create another with the same name
200
        status, body = self.create(app1, h_owner)
201
        self.assertEqual(status, 201)
202
        project2_id = body["id"]
203
        project2_app_id = body["application"]
204

    
205
        # Create yet another, with different name
206
        app_p3 = copy.deepcopy(app1)
207
        app_p3["name"] = "new.pr"
208
        status, body = self.create(app_p3, h_owner)
209
        self.assertEqual(status, 201)
210
        project3_id = body["id"]
211
        project3_app_id = body["application"]
212

    
213
        # No more pending allowed
214
        status, body = self.create(app_p3, h_owner)
215
        self.assertEqual(status, 409)
216

    
217
        # Cancel
218
        status = self.project_action(project3_id, "cancel",
219
                                     app_id=project3_app_id, headers=h_owner)
220
        self.assertEqual(status, 200)
221

    
222
        # Get project
223
        r = client.get(reverse("api_project",
224
                               kwargs={"project_id": project3_id}),
225
                       **h_owner)
226
        body = json.loads(r.content)
227
        self.assertEqual(body["state"], "deleted")
228

    
229
        # Modify of uninitialized failed
230
        app2 = {"name": "test.pr",
231
                "start_date": "2013-5-5T20:20:20Z",
232
                "end_date": "2013-7-5T20:20:20Z",
233
                "join_policy": "moderated",
234
                "leave_policy": "auto",
235
                "max_members": 3,
236
                "resources": {"service1.resource11": {
237
                    "project_capacity": 1024,
238
                    "member_capacity": 1024}}
239
                }
240
        status, body = self.modify(app2, project_id, h_owner)
241
        self.assertEqual(status, 409)
242

    
243
        # Create the project again
244
        status, body = self.create(app2, h_owner)
245
        self.assertEqual(status, 201)
246
        project_id = body["id"]
247
        app_id = body["application"]
248

    
249
        # Dismiss failed
250
        status = self.project_action(project_id, "dismiss", app_id,
251
                                     headers=h_owner)
252
        self.assertEqual(status, 409)
253

    
254
        # Deny
255
        status = self.project_action(project_id, "deny", app_id,
256
                                     headers=h_admin)
257
        self.assertEqual(status, 200)
258

    
259
        # Get project
260
        r = client.get(reverse("api_project",
261
                               kwargs={"project_id": project_id}),
262
                       **h_owner)
263
        body = json.loads(r.content)
264
        self.assertEqual(body["last_application"]["id"], app_id)
265
        self.assertEqual(body["last_application"]["state"], "denied")
266
        self.assertEqual(body["state"], "uninitialized")
267

    
268
        # Dismiss
269
        status = self.project_action(project_id, "dismiss", app_id,
270
                                     headers=h_owner)
271
        self.assertEqual(status, 200)
272

    
273
        # Get project
274
        r = client.get(reverse("api_project",
275
                               kwargs={"project_id": project_id}),
276
                       **h_owner)
277
        body = json.loads(r.content)
278
        self.assertEqual(body["last_application"]["id"], app_id)
279
        self.assertEqual(body["last_application"]["state"], "dismissed")
280
        self.assertEqual(body["state"], "deleted")
281

    
282
        # Create the project again
283
        status, body = self.create(app2, h_owner)
284
        self.assertEqual(status, 201)
285
        project_id = body["id"]
286
        app_id = body["application"]
287

    
288
        # Approve
289
        status = self.project_action(project_id, "approve", app_id,
290
                                     headers=h_admin)
291
        self.assertEqual(status, 200)
292

    
293
        # Check memberships
294
        r = client.get(reverse("api_memberships"), **h_plain)
295
        body = json.loads(r.content)
296
        self.assertEqual(len(body), 1)
297

    
298
        # Enroll
299
        status, body = self.enroll(project_id, self.user3, h_owner)
300
        self.assertEqual(status, 200)
301
        m_plain_id = body["id"]
302

    
303
        # Get project
304
        r = client.get(reverse("api_project",
305
                               kwargs={"project_id": project_id}),
306
                       **h_owner)
307
        body = json.loads(r.content)
308
        # Join
309
        status, body = self.join(project_id, h_owner)
310
        self.assertEqual(status, 200)
311
        memb_id = body["id"]
312

    
313
        # Check memberships
314
        r = client.get(reverse("api_memberships"), **h_plain)
315
        body = json.loads(r.content)
316
        self.assertEqual(len(body), 2)
317
        m = find(lambda m: m["project"] == project_id, body)
318
        self.assertNotEqual(m, NotFound)
319
        self.assertEqual(m["user"], self.user3.uuid)
320
        self.assertEqual(m["state"], "accepted")
321

    
322
        r = client.get(reverse("api_memberships"), **h_owner)
323
        body = json.loads(r.content)
324
        self.assertEqual(len(body), 3)
325

    
326
        # Check membership
327
        r = client.get(reverse("api_membership", kwargs={"memb_id": memb_id}),
328
                       **h_admin)
329
        m = json.loads(r.content)
330
        self.assertEqual(m["user"], self.user1.uuid)
331
        self.assertEqual(m["state"], "requested")
332
        self.assertEqual(sorted(m["allowed_actions"]),
333
                         ["accept", "cancel", "reject"])
334

    
335
        r = client.get(reverse("api_membership", kwargs={"memb_id": memb_id}),
336
                       **h_plain)
337
        self.assertEqual(r.status_code, 403)
338

    
339
        status = self.memb_action(memb_id, "leave", h_admin)
340
        self.assertEqual(status, 409)
341

    
342
        status = self.memb_action(memb_id, "cancel", h_owner)
343
        self.assertEqual(status, 200)
344

    
345
        status, body = self.join(project_id, h_owner)
346
        self.assertEqual(status, 200)
347
        self.assertEqual(memb_id, body["id"])
348

    
349
        status = self.memb_action(memb_id, "reject", h_owner)
350
        self.assertEqual(status, 200)
351

    
352
        status, body = self.join(project_id, h_owner)
353
        self.assertEqual(status, 200)
354
        self.assertEqual(memb_id, body["id"])
355

    
356
        status = self.memb_action(memb_id, "accept", h_owner)
357
        self.assertEqual(status, 200)
358

    
359
        # Enroll fails, already in
360
        status, body = self.enroll(project_id, self.user1, h_owner)
361
        self.assertEqual(status, 409)
362

    
363
        # Remove member
364
        status = self.memb_action(memb_id, "remove", h_owner)
365
        self.assertEqual(status, 200)
366

    
367
        # Enroll a removed member
368
        status, body = self.enroll(project_id, self.user1, h_owner)
369
        self.assertEqual(status, 200)
370

    
371
        # Remove member
372
        status = self.memb_action(memb_id, "remove", h_owner)
373
        self.assertEqual(status, 200)
374

    
375
        # Re-join
376
        status, body = self.join(project_id, h_owner)
377
        self.assertEqual(status, 200)
378
        self.assertEqual(memb_id, body["id"])
379

    
380
        # Enroll a requested member
381
        status, body = self.enroll(project_id, self.user1, h_owner)
382
        self.assertEqual(status, 200)
383

    
384
        # Enroll fails, already in
385
        status, body = self.enroll(project_id, self.user1, h_owner)
386
        self.assertEqual(status, 409)
387

    
388
        # Enroll fails, project does not exist
389
        status, body = self.enroll(-1, self.user1, h_owner)
390
        self.assertEqual(status, 409)
391

    
392
        # Get projects
393
        ## Simple user mode
394
        r = client.get(reverse("api_projects"), **h_plain)
395
        body = json.loads(r.content)
396
        self.assertEqual(len(body), 2)
397
        p = body[0]
398
        with assertRaises(KeyError):
399
            p["pending_application"]
400

    
401
        ## Owner mode
402
        filters = {"state": "active"}
403
        r = client.get(reverse("api_projects"), filters, **h_owner)
404
        body = json.loads(r.content)
405
        self.assertEqual(len(body), 2)
406

    
407
        filters = {"state": "deleted"}
408
        r = client.get(reverse("api_projects"), filters, **h_owner)
409
        body = json.loads(r.content)
410
        self.assertEqual(len(body), 2)
411

    
412
        filters = {"state": "uninitialized"}
413
        r = client.get(reverse("api_projects"), filters, **h_owner)
414
        body = json.loads(r.content)
415
        self.assertEqual(len(body), 2)
416

    
417
        filters = {"name": "test.pr"}
418
        r = client.get(reverse("api_projects"), filters, **h_owner)
419
        body = json.loads(r.content)
420
        self.assertEqual(len(body), 4)
421

    
422
        filters = {"mode": "member"}
423
        r = client.get(reverse("api_projects"), filters, **h_owner)
424
        body = json.loads(r.content)
425
        self.assertEqual(len(body), 2)
426

    
427
        # Leave failed
428
        status = self.memb_action(m_plain_id, "leave", h_owner)
429
        self.assertEqual(status, 403)
430

    
431
        # Leave
432
        status = self.memb_action(m_plain_id, "leave", h_plain)
433
        self.assertEqual(status, 200)
434

    
435
        # Suspend failed
436
        status = self.project_action(project_id, "suspend", headers=h_owner)
437
        self.assertEqual(status, 403)
438

    
439
        # Unsuspend failed
440
        status = self.project_action(project_id, "unsuspend", headers=h_admin)
441
        self.assertEqual(status, 409)
442

    
443
        # Suspend
444
        status = self.project_action(project_id, "suspend", headers=h_admin)
445
        self.assertEqual(status, 200)
446

    
447
        # Cannot view project
448
        r = client.get(reverse("api_project",
449
                               kwargs={"project_id": project_id}), **h_plain)
450
        self.assertEqual(r.status_code, 403)
451

    
452
        # Unsuspend
453
        status = self.project_action(project_id, "unsuspend", headers=h_admin)
454
        self.assertEqual(status, 200)
455

    
456
        # Cannot approve, project with same name exists
457
        status = self.project_action(project2_id, "approve", project2_app_id,
458
                                     headers=h_admin)
459
        self.assertEqual(status, 409)
460

    
461
        # Terminate
462
        status = self.project_action(project_id, "terminate", headers=h_admin)
463
        self.assertEqual(status, 200)
464

    
465
        # Join failed
466
        status, _ = self.join(project_id, h_admin)
467
        self.assertEqual(status, 409)
468

    
469
        # Can approve now
470
        status = self.project_action(project2_id, "approve", project2_app_id,
471
                                     headers=h_admin)
472
        self.assertEqual(status, 200)
473

    
474
        # Join new project
475
        status, body = self.join(project2_id, h_plain)
476
        self.assertEqual(status, 200)
477
        m_project2 = body["id"]
478

    
479
        # Get memberships of project
480
        filters = {"project": project2_id}
481
        r = client.get(reverse("api_memberships"), filters, **h_owner)
482
        body = json.loads(r.content)
483
        self.assertEqual(len(body), 1)
484
        self.assertEqual(body[0]["id"], m_project2)
485

    
486
        # Remove member
487
        status = self.memb_action(m_project2, "remove", h_owner)
488
        self.assertEqual(status, 200)
489

    
490
        # Reinstate failed
491
        status = self.project_action(project_id, "reinstate", headers=h_admin)
492
        self.assertEqual(status, 409)
493

    
494
        # Rename
495
        app2_renamed = copy.deepcopy(app2)
496
        app2_renamed["name"] = "new.name"
497
        status, body = self.modify(app2_renamed, project_id, h_owner)
498
        self.assertEqual(status, 201)
499
        app2_renamed_id = body["application"]
500

    
501
        # Get project
502
        r = client.get(reverse("api_project",
503
                               kwargs={"project_id": project_id}), **h_owner)
504
        body = json.loads(r.content)
505
        self.assertEqual(body["last_application"]["id"], app2_renamed_id)
506
        self.assertEqual(body["state"], "terminated")
507
        assertIn("deactivation_date", body)
508
        self.assertEqual(body["last_application"]["state"], "pending")
509
        self.assertEqual(body["last_application"]["name"], "new.name")
510
        status = self.project_action(project_id, "approve", app2_renamed_id,
511
                                     headers=h_admin)
512
        self.assertEqual(r.status_code, 200)
513

    
514
        # Change homepage
515
        status, body = self.modify({"homepage": "new.page"},
516
                                   project_id, h_owner)
517
        self.assertEqual(status, 201)
518

    
519
        r = client.get(reverse("api_project",
520
                               kwargs={"project_id": project_id}), **h_owner)
521
        body = json.loads(r.content)
522
        self.assertEqual(body["homepage"], "")
523
        self.assertEqual(body["last_application"]["homepage"], "new.page")
524
        homepage_app = body["last_application"]["id"]
525
        status = self.project_action(project_id, "approve", homepage_app,
526
                                     headers=h_admin)
527
        self.assertEqual(r.status_code, 200)
528
        r = client.get(reverse("api_project",
529
                               kwargs={"project_id": project_id}), **h_owner)
530
        body = json.loads(r.content)
531
        self.assertEqual(body["homepage"], "new.page")
532

    
533
        # Bad requests
534
        r = client.head(reverse("api_projects"), **h_admin)
535
        self.assertEqual(r.status_code, 405)
536
        self.assertTrue('Allow' in r)
537

    
538
        r = client.head(reverse("api_project",
539
                                kwargs={"project_id": 1}), **h_admin)
540
        self.assertEqual(r.status_code, 405)
541
        self.assertTrue('Allow' in r)
542

    
543
        r = client.head(reverse("api_memberships"), **h_admin)
544
        self.assertEqual(r.status_code, 405)
545
        self.assertTrue('Allow' in r)
546

    
547
        status = self.project_action(1, "nonex", headers=h_owner)
548
        self.assertEqual(status, 400)
549

    
550
        action = json.dumps({"suspend": "", "unsuspend": ""})
551
        r = client.post(reverse("api_project_action",
552
                                kwargs={"project_id": 1}),
553
                        action, content_type="application/json", **h_owner)
554
        self.assertEqual(r.status_code, 400)
555

    
556
        ap = {"owner": "nonex",
557
              "join_policy": "nonex",
558
              "leave_policy": "nonex",
559
              "start_date": "nonex",
560
              "homepage": {},
561
              "max_members": -3,
562
              "resources": [],
563
              }
564

    
565
        status, body = self.create(ap, h_owner)
566
        self.assertEqual(status, 400)
567
        self.assertEqual(body["badRequest"]["message"], "User does not exist.")
568

    
569
        ap["owner"] = self.user1.uuid
570
        status, body = self.create(ap, h_owner)
571
        self.assertEqual(status, 400)
572

    
573
        ap["name"] = "some.name"
574
        status, body = self.create(ap, h_owner)
575
        self.assertEqual(status, 400)
576

    
577
        ap["join_policy"] = "auto"
578
        status, body = self.create(ap, h_owner)
579
        self.assertEqual(status, 400)
580

    
581
        ap["leave_policy"] = "closed"
582
        status, body = self.create(ap, h_owner)
583
        self.assertEqual(status, 400)
584

    
585
        ap["start_date"] = "2013-01-01T0:0Z"
586
        status, body = self.create(ap, h_owner)
587
        self.assertEqual(status, 400)
588

    
589
        ap["end_date"] = "2014-01-01T0:0Z"
590
        status, body = self.create(ap, h_owner)
591
        self.assertEqual(status, 400)
592

    
593
        ap["max_members"] = 0
594
        status, body = self.create(ap, h_owner)
595
        self.assertEqual(status, 400)
596

    
597
        ap["homepage"] = "a.stri.ng"
598
        status, body = self.create(ap, h_owner)
599
        self.assertEqual(status, 400)
600

    
601
        ap["resources"] = {42: 42}
602
        status, body = self.create(ap, h_owner)
603
        self.assertEqual(status, 400)
604

    
605
        ap["resources"] = {"service1.resource11": {
606
                "member_capacity": 512}}
607
        status, body = self.create(ap, h_owner)
608
        self.assertEqual(status, 400)
609

    
610
        ap["resources"] = {"service1.resource11": {"member_capacity": 512,
611
                                                   "project_capacity": 1024}}
612
        status, body = self.create(ap, h_owner)
613
        self.assertEqual(status, 201)
614

    
615
        ap["name"] = "non_domain_name"
616
        status, body = self.create(ap, h_owner)
617
        self.assertEqual(status, 400)
618

    
619
        ap["name"] = "domain.name"
620

    
621
        filters = {"state": "nonex"}
622
        r = client.get(reverse("api_projects"), filters, **h_owner)
623
        self.assertEqual(r.status_code, 400)
624

    
625
        # directly modify a base project
626
        with assertRaises(functions.ProjectBadRequest):
627
            functions.modify_project(self.user1.uuid,
628
                                     {"description": "new description",
629
                                      "member_join_policy":
630
                                          functions.MODERATED_POLICY})
631
        functions.modify_project(self.user1.uuid,
632
                                 {"member_join_policy":
633
                                      functions.MODERATED_POLICY})
634
        r = client.get(reverse("api_project",
635
                               kwargs={"project_id": self.user1.uuid}),
636
                       **h_owner)
637
        body = json.loads(r.content)
638
        self.assertEqual(body["join_policy"], "moderated")
639

    
640

    
641
class TestProjects(TestCase):
642
    """
643
    Test projects.
644
    """
645
    def setUp(self):
646
        # astakos resources
647
        self.resource = Resource.objects.create(name="astakos.pending_app",
648
                                                uplimit=0,
649
                                                project_default=0,
650
                                                ui_visible=False,
651
                                                api_visible=False,
652
                                                service_type="astakos")
653

    
654
        # custom service resources
655
        self.resource = Resource.objects.create(name="service1.resource",
656
                                                uplimit=100,
657
                                                project_default=0,
658
                                                service_type="service1")
659
        self.admin = get_local_user("projects-admin@synnefo.org")
660
        self.admin.uuid = 'uuid1'
661
        self.admin.save()
662

    
663
        self.user = get_local_user("user@synnefo.org")
664
        self.member = get_local_user("member@synnefo.org")
665
        self.member2 = get_local_user("member2@synnefo.org")
666

    
667
        self.admin_client = get_user_client("projects-admin@synnefo.org")
668
        self.user_client = get_user_client("user@synnefo.org")
669
        self.member_client = get_user_client("member@synnefo.org")
670
        self.member2_client = get_user_client("member2@synnefo.org")
671

    
672
    def tearDown(self):
673
        Service.objects.all().delete()
674
        ProjectApplication.objects.all().delete()
675
        Project.objects.all().delete()
676
        AstakosUser.objects.all().delete()
677

    
678
    @im_settings(PROJECT_ADMINS=['uuid1'])
679
    def test_application_limit(self):
680
        # user cannot create a project
681
        r = self.user_client.get(reverse('project_add'), follow=True)
682
        self.assertRedirects(r, reverse('project_list'))
683
        self.assertContains(r, "You are not allowed to create a new project")
684

    
685
        # but admin can
686
        r = self.admin_client.get(reverse('project_add'), follow=True)
687
        self.assertRedirects(r, reverse('project_add'))
688

    
689
    @im_settings(PROJECT_ADMINS=['uuid1'])
690
    def test_ui_visible(self):
691
        dfrom = datetime.now()
692
        dto = datetime.now() + timedelta(days=30)
693

    
694
        # astakos.pending_app ui_visible flag is False
695
        # we shouldn't be able to create a project application using this
696
        # resource.
697
        application_data = {
698
            'name': 'project.synnefo.org',
699
            'homepage': 'https://www.synnefo.org',
700
            'start_date': dfrom.strftime("%Y-%m-%d"),
701
            'end_date': dto.strftime("%Y-%m-%d"),
702
            'member_join_policy': 2,
703
            'member_leave_policy': 1,
704
            'limit_on_members_number': 5,
705
            'service1.resource_m_uplimit': 100,
706
            'is_selected_service1.resource': "1",
707
            'astakos.pending_app_m_uplimit': 100,
708
            'is_selected_accounts': "1",
709
            'user': self.user.pk
710
        }
711
        form = forms.ProjectApplicationForm(data=application_data)
712
        # form is invalid
713
        self.assertEqual(form.is_valid(), False)
714

    
715
        del application_data['astakos.pending_app_m_uplimit']
716
        del application_data['is_selected_accounts']
717
        form = forms.ProjectApplicationForm(data=application_data)
718
        self.assertEqual(form.is_valid(), True)
719

    
720
    @im_settings(PROJECT_ADMINS=['uuid1'])
721
    def no_test_applications(self):
722
        # let user have 2 pending applications
723

    
724
        # TODO figure this out
725
        request = {"resources": {"astakos.pending_app":
726
                                     {"member_capacity": 2,
727
                                      "project_capacity": 2}}}
728
        functions.modify_project(self.user.uuid, request)
729

    
730
        r = self.user_client.get(reverse('project_add'), follow=True)
731
        self.assertRedirects(r, reverse('project_add'))
732

    
733
        # user fills the project application form
734
        post_url = reverse('project_add') + '?verify=1'
735
        dfrom = datetime.now()
736
        dto = datetime.now() + timedelta(days=30)
737
        application_data = {
738
            'name': 'project.synnefo.org',
739
            'homepage': 'https://www.synnefo.org',
740
            'start_date': dfrom.strftime("%Y-%m-%d"),
741
            'end_date': dto.strftime("%Y-%m-%d"),
742
            'member_join_policy': 2,
743
            'member_leave_policy': 1,
744
            'service1.resource_m_uplimit': 100,
745
            'is_selected_service1.resource': "1",
746
            'user': self.user.pk
747
        }
748
        r = self.user_client.post(post_url, data=application_data, follow=True)
749
        self.assertEqual(r.status_code, 200)
750
        self.assertEqual(r.context['form'].is_valid(), False)
751

    
752
        application_data['limit_on_members_number'] = 5
753
        r = self.user_client.post(post_url, data=application_data, follow=True)
754
        self.assertEqual(r.status_code, 200)
755
        self.assertEqual(r.context['form'].is_valid(), True)
756

    
757
        # confirm request
758
        post_url = reverse('project_add') + '?verify=0&edit=0'
759
        r = self.user_client.post(post_url, data=application_data, follow=True)
760
        self.assertContains(r, "The project application has been received")
761
        self.assertRedirects(r, reverse('project_list'))
762
        self.assertEqual(ProjectApplication.objects.count(), 1)
763
        app1 = ProjectApplication.objects.filter().order_by('pk')[0]
764
        app1_id = app1.pk
765
        project1_id = app1.chain_id
766

    
767
        # create another one
768
        application_data['name'] = 'project2.synnefo.org'
769
        r = self.user_client.post(post_url, data=application_data, follow=True)
770
        app2 = ProjectApplication.objects.filter().order_by('pk')[1]
771
        project2_id = app2.chain_id
772

    
773
        # no more applications (LIMIT is 2)
774
        r = self.user_client.get(reverse('project_add'), follow=True)
775
        self.assertRedirects(r, reverse('project_list'))
776
        self.assertContains(r, "You are not allowed to create a new project")
777

    
778
        # one project per application
779
        self.assertEqual(Project.objects.filter(is_base=False).count(), 2)
780

    
781
        # login
782
        self.admin_client.get(reverse("edit_profile"))
783
        # admin approves
784
        r = self.admin_client.post(reverse('project_app_approve',
785
                                           kwargs={'application_id': app1_id}),
786
                                   follow=True)
787
        self.assertEqual(r.status_code, 200)
788

    
789
        Q_ACTIVE = Project.o_state_q(Project.O_ACTIVE)
790
        self.assertEqual(Project.objects.filter(Q_ACTIVE).count(), 1)
791

    
792
        # login
793
        self.member_client.get(reverse("edit_profile"))
794
        # cannot join project2 (not approved yet)
795
        join_url = reverse("project_join", kwargs={'chain_id': project2_id})
796
        r = self.member_client.post(join_url, follow=True)
797

    
798
        # can join project1
799
        self.member_client.get(reverse("edit_profile"))
800
        join_url = reverse("project_join", kwargs={'chain_id': project1_id})
801
        r = self.member_client.post(join_url, follow=True)
802
        self.assertEqual(r.status_code, 200)
803

    
804
        memberships = ProjectMembership.objects.all()
805
        self.assertEqual(len(memberships), 1)
806
        memb_id = memberships[0].id
807

    
808
        reject_member_url = reverse('project_reject_member',
809
                                    kwargs={'memb_id': memb_id})
810
        accept_member_url = reverse('project_accept_member',
811
                                    kwargs={'memb_id': memb_id})
812

    
813
        # only project owner is allowed to reject
814
        r = self.member_client.post(reject_member_url, follow=True)
815
        self.assertContains(r, "You do not have the permissions")
816
        self.assertEqual(r.status_code, 200)
817

    
818
        # user (owns project) rejects membership
819
        r = self.user_client.post(reject_member_url, follow=True)
820
        self.assertEqual(ProjectMembership.objects.any_accepted().count(), 0)
821

    
822
        # user rejoins
823
        self.member_client.get(reverse("edit_profile"))
824
        join_url = reverse("project_join", kwargs={'chain_id': project1_id})
825
        r = self.member_client.post(join_url, follow=True)
826
        self.assertEqual(r.status_code, 200)
827
        self.assertEqual(ProjectMembership.objects.requested().count(), 1)
828

    
829
        # user (owns project) accepts membership
830
        r = self.user_client.post(accept_member_url, follow=True)
831
        self.assertEqual(ProjectMembership.objects.any_accepted().count(), 1)
832
        membership = ProjectMembership.objects.get()
833
        self.assertEqual(membership.state, ProjectMembership.ACCEPTED)
834

    
835
        user_quotas = quotas.get_users_quotas([self.member])
836
        resource = 'service1.resource'
837
        newlimit = user_quotas[self.member.uuid]['system'][resource]['limit']
838
        # 100 from initial uplimit + 100 from project
839
        self.assertEqual(newlimit, 200)
840

    
841
        remove_member_url = reverse('project_remove_member',
842
                                    kwargs={'memb_id': membership.id})
843
        r = self.user_client.post(remove_member_url, follow=True)
844
        self.assertEqual(r.status_code, 200)
845

    
846
        user_quotas = quotas.get_users_quotas([self.member])
847
        resource = 'service1.resource'
848
        newlimit = user_quotas[self.member.uuid]['system'][resource]['limit']
849
        # 200 - 100 from project
850
        self.assertEqual(newlimit, 100)
851

    
852
        # support email gets rendered in emails content
853
        for mail in get_mailbox('user@synnefo.org'):
854
            self.assertTrue(settings.CONTACT_EMAIL in
855
                            mail.message().as_string())