Add unit tests 31/23231/1
authormaxbr <maxbr@mi.fu-berlin.de>
Mon, 17 Oct 2016 12:56:11 +0000 (14:56 +0200)
committermaxbr <maxbr@mi.fu-berlin.de>
Mon, 17 Oct 2016 12:56:11 +0000 (14:56 +0200)
JIRA: RELENG-12

This commit increases test statement coverage to 84%. It also fixes the bugs
that emerged while testing.

Change-Id: I696091f1a07f8b7647872c7cb15f4368a4690619
Signed-off-by: maxbr <maxbr@mi.fu-berlin.de>
25 files changed:
tools/pharos-dashboard/src/account/jira_util.py
tools/pharos-dashboard/src/account/models.py
tools/pharos-dashboard/src/account/tests/test_general.py
tools/pharos-dashboard/src/account/views.py
tools/pharos-dashboard/src/api/views.py
tools/pharos-dashboard/src/booking/models.py
tools/pharos-dashboard/src/booking/tests/test_models.py
tools/pharos-dashboard/src/booking/tests/test_views.py
tools/pharos-dashboard/src/booking/views.py
tools/pharos-dashboard/src/dashboard/models.py
tools/pharos-dashboard/src/dashboard/tasks.py
tools/pharos-dashboard/src/dashboard/templatetags/jira_filters.py
tools/pharos-dashboard/src/dashboard/tests/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/src/dashboard/tests/test_models.py [new file with mode: 0644]
tools/pharos-dashboard/src/dashboard/tests/test_views.py [new file with mode: 0644]
tools/pharos-dashboard/src/dashboard/urls.py
tools/pharos-dashboard/src/dashboard/views.py
tools/pharos-dashboard/src/jenkins/adapter.py
tools/pharos-dashboard/src/jenkins/models.py
tools/pharos-dashboard/src/jenkins/tasks.py
tools/pharos-dashboard/src/jenkins/tests.py
tools/pharos-dashboard/src/notification/models.py
tools/pharos-dashboard/src/notification/tasks.py
tools/pharos-dashboard/src/notification/tests.py [new file with mode: 0644]
tools/pharos-dashboard/src/templates/booking/booking_calendar.html

index c333f8c..fdb87f7 100644 (file)
@@ -12,11 +12,10 @@ import base64
 import os
 
 import oauth2 as oauth
+from django.conf import settings
 from jira import JIRA
 from tlslite.utils import keyfactory
 
-from django.conf import settings
-
 
 class SignatureMethod_RSA_SHA1(oauth.SignatureMethod):
     name = 'RSA-SHA1'
index d87ee18..c2e9902 100644 (file)
@@ -8,12 +8,9 @@
 ##############################################################################
 
 
-from django.db import models
-
 from django.contrib.auth.models import User
-from rest_framework.authtoken.models import Token
+from django.db import models
 
-from dashboard.models import Resource
 
 def upload_to(object, filename):
     return object.user.username + '/' + filename
index 72e7ea1..e8f483b 100644 (file)
@@ -48,3 +48,13 @@ class AccountMiddlewareTestCase(TestCase):
         self.user1profile.save()
         self.client.get(url)
         self.assertEqual(timezone.get_current_timezone_name(), 'Etc/Greenwich')
+
+        # if there is no profile for a user, it should be created
+        user2 = User.objects.create(username='user2')
+        user2.set_password('user2')
+        user2.save()
+        self.client.login(username='user2', password='user2')
+        self.client.get(url)
+        self.assertTrue(user2.userprofile)
+
+
index ac973f5..17fbdc3 100644 (file)
@@ -21,7 +21,6 @@ from django.contrib.auth.models import User
 from django.urls import reverse
 from django.utils.decorators import method_decorator
 from django.views.generic import RedirectView, TemplateView, UpdateView
-
 from jira import JIRA
 from rest_framework.authtoken.models import Token
 
@@ -58,9 +57,16 @@ class JiraLoginView(RedirectView):
         client.set_signature_method(SignatureMethod_RSA_SHA1())
 
         # Step 1. Get a request token from Jira.
-        resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST")
+        try:
+            resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST")
+        except Exception as e:
+            messages.add_message(self.request, messages.ERROR,
+                                 'Error: Connection to Jira failed. Please contact an Administrator')
+            return '/'
         if resp['status'] != '200':
-            raise Exception("Invalid response %s: %s" % (resp['status'], content))
+            messages.add_message(self.request, messages.ERROR,
+                                 'Error: Connection to Jira failed. Please contact an Administrator')
+            return '/'
 
         # Step 2. Store the request token in a session for later use.
         self.request.session['request_token'] = dict(urllib.parse.parse_qsl(content.decode()))
@@ -87,8 +93,15 @@ class JiraAuthenticatedView(RedirectView):
         client.set_signature_method(SignatureMethod_RSA_SHA1())
 
         # Step 2. Request the authorized access token from Jira.
-        resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST")
+        try:
+            resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST")
+        except Exception as e:
+            messages.add_message(self.request, messages.ERROR,
+                                 'Error: Connection to Jira failed. Please contact an Administrator')
+            return '/'
         if resp['status'] != '200':
+            messages.add_message(self.request, messages.ERROR,
+                                 'Error: Connection to Jira failed. Please contact an Administrator')
             return '/'
 
         access_token = dict(urllib.parse.parse_qsl(content.decode()))
@@ -128,6 +141,7 @@ class JiraAuthenticatedView(RedirectView):
         # redirect user to settings page to complete profile
         return url
 
+
 @method_decorator(login_required, name='dispatch')
 class UserListView(TemplateView):
     template_name = "account/user_list.html"
index 55de5c6..2595e5e 100644 (file)
@@ -8,16 +8,16 @@
 ##############################################################################
 
 
-from rest_framework import viewsets
-
 from django.contrib.auth.decorators import login_required
+from django.shortcuts import redirect
 from django.utils.decorators import method_decorator
+from django.views import View
+from rest_framework import viewsets
+from rest_framework.authtoken.models import Token
+
 from api.serializers import ResourceSerializer, ServerSerializer, BookingSerializer
 from booking.models import Booking
 from dashboard.models import Resource, Server
-from django.views import View
-from rest_framework.authtoken.models import Token
-from django.shortcuts import redirect
 
 
 class BookingViewSet(viewsets.ModelViewSet):
index 88ab558..0b3fa3b 100644 (file)
@@ -8,13 +8,14 @@
 ##############################################################################
 
 
+from django.conf import settings
 from django.contrib.auth.models import User
 from django.db import models
 from jira import JIRA
 from jira import JIRAError
 
 from dashboard.models import Resource
-from django.conf import settings
+
 
 class Installer(models.Model):
     id = models.AutoField(primary_key=True)
index 612b35c..b4cd113 100644 (file)
 
 from datetime import timedelta
 
-from django.contrib.auth.models import User, Permission
+from django.contrib.auth.models import Permission
 from django.test import TestCase
 from django.utils import timezone
 
-from account.models import UserProfile
-from booking.models import Booking
+from booking.models import *
 from dashboard.models import Resource
 from jenkins.models import JenkinsSlave
 
@@ -37,7 +36,10 @@ class BookingModelTestCase(TestCase):
 
         self.user1 = User.objects.get(pk=self.user1.id)
 
-    def test_start__end(self):
+        self.installer = Installer.objects.create(name='TestInstaller')
+        self.scenario = Scenario.objects.create(name='TestScenario')
+
+    def test_start_end(self):
         """
         if the start of a booking is greater or equal then the end, saving should raise a
         ValueException
@@ -88,20 +90,5 @@ class BookingModelTestCase(TestCase):
                                    user=self.user1, resource=self.res1))
         self.assertTrue(
             Booking.objects.create(start=start, end=end,
-                                   user=self.user1, resource=self.res2))
-
-    def test_authorization(self):
-        user = User.objects.create(username='user')
-        user.userprofile = UserProfile.objects.create(user=user)
-        self.assertRaises(PermissionError, Booking.objects.create, start=timezone.now(),
-                          end=timezone.now() + timedelta(days=1), resource=self.res1, user=user)
-        self.res1.owner = user
-        self.assertTrue(
-            Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1),
-                                   resource=self.res1, user=user))
-        self.res1.owner = self.owner
-        user.user_permissions.add(self.add_booking_perm)
-        user = User.objects.get(pk=user.id)
-        self.assertTrue(
-            Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1),
-                                   resource=self.res2, user=user))
+                                   user=self.user1, resource=self.res2, scenario=self.scenario,
+                                   installer=self.installer))
\ No newline at end of file
index e568c15..c1da013 100644 (file)
 
 from datetime import timedelta
 
-from django.contrib import auth
 from django.test import Client
-from django.utils import timezone
-from django.contrib.auth.models import Permission
 from django.test import TestCase
 from django.urls import reverse
+from django.utils import timezone
 from django.utils.encoding import force_text
 from registration.forms import User
 
@@ -37,9 +35,6 @@ class BookingViewTestCase(TestCase):
         self.user1profile = UserProfile.objects.create(user=self.user1)
         self.user1.save()
 
-        self.add_booking_perm = Permission.objects.get(codename='add_booking')
-        self.user1.user_permissions.add(self.add_booking_perm)
-
         self.user1 = User.objects.get(pk=self.user1.id)
 
 
@@ -78,4 +73,34 @@ class BookingViewTestCase(TestCase):
         self.assertIn('resource', response.context)
 
 
+    def test_booking_view(self):
+        start = timezone.now()
+        end = start + timedelta(weeks=1)
+        booking = Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1)
 
+        url = reverse('booking:detail', kwargs={'booking_id':0})
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 404)
+
+        url = reverse('booking:detail', kwargs={'booking_id':booking.id})
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed('booking/booking_detail.html')
+        self.assertIn('booking', response.context)
+
+    def test_booking_list_view(self):
+        start = timezone.now() - timedelta(weeks=2)
+        end = start + timedelta(weeks=1)
+        Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1)
+
+        url = reverse('booking:list')
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200)
+        self.assertTemplateUsed('booking/booking_list.html')
+        self.assertTrue(len(response.context['bookings']) == 0)
+
+        start = timezone.now()
+        end = start + timedelta(weeks=1)
+        Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1)
+        response = self.client.get(url)
+        self.assertTrue(len(response.context['bookings']) == 1)
\ No newline at end of file
index 413573d..6fdca0e 100644 (file)
@@ -8,8 +8,6 @@
 ##############################################################################
 
 
-from datetime import timedelta
-
 from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth.mixins import LoginRequiredMixin
@@ -45,7 +43,7 @@ def create_jira_ticket(user, booking):
     booking.save()
 
 
-class BookingFormView(LoginRequiredMixin, FormView):
+class BookingFormView(FormView):
     template_name = "booking/booking_calendar.html"
     form_class = BookingForm
 
@@ -63,6 +61,11 @@ class BookingFormView(LoginRequiredMixin, FormView):
         return reverse('booking:create', kwargs=self.kwargs)
 
     def form_valid(self, form):
+        if not self.request.user.is_authenticated:
+            messages.add_message(self.request, messages.ERROR,
+                                 'You need to be logged in to book a Pod.')
+            return super(BookingFormView, self).form_invalid(form)
+
         user = self.request.user
         booking = Booking(start=form.cleaned_data['start'],
                           end=form.cleaned_data['end'],
@@ -75,9 +78,6 @@ class BookingFormView(LoginRequiredMixin, FormView):
         except ValueError as err:
             messages.add_message(self.request, messages.ERROR, err)
             return super(BookingFormView, self).form_invalid(form)
-        except PermissionError as err:
-            messages.add_message(self.request, messages.ERROR, err)
-            return super(BookingFormView, self).form_invalid(form)
         try:
             if settings.CREATE_JIRA_TICKET:
                 create_jira_ticket(user, booking)
@@ -101,6 +101,7 @@ class BookingView(TemplateView):
         context.update({'title': title, 'booking': booking})
         return context
 
+
 class BookingListView(TemplateView):
     template_name = "booking/booking_list.html"
 
index e3f22e6..ec6fec7 100644 (file)
@@ -10,9 +10,9 @@
 
 from datetime import timedelta
 
-from django.utils import timezone
 from django.contrib.auth.models import User
 from django.db import models
+from django.utils import timezone
 
 from jenkins.models import JenkinsSlave
 
index 4c09bf9..c5ef505 100644 (file)
@@ -8,8 +8,9 @@
 ##############################################################################
 
 
-from celery import shared_task
 from datetime import timedelta
+
+from celery import shared_task
 from django.utils import timezone
 
 from jenkins.models import JenkinsStatistic
index 7020843..9a97c1d 100644 (file)
@@ -8,9 +8,8 @@
 ##############################################################################
 
 
-from django.template.defaultfilters import register
-
 from django.conf import settings
+from django.template.defaultfilters import register
 
 
 @register.filter
diff --git a/tools/pharos-dashboard/src/dashboard/tests/__init__.py b/tools/pharos-dashboard/src/dashboard/tests/__init__.py
new file mode 100644 (file)
index 0000000..b5914ce
--- /dev/null
@@ -0,0 +1,10 @@
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
diff --git a/tools/pharos-dashboard/src/dashboard/tests/test_models.py b/tools/pharos-dashboard/src/dashboard/tests/test_models.py
new file mode 100644 (file)
index 0000000..3a3aeab
--- /dev/null
@@ -0,0 +1,69 @@
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from datetime import timedelta
+from math import ceil, floor
+
+from django.test import TestCase
+from django.utils import timezone
+
+from booking.models import *
+from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
+
+
+class ResourceModelTestCase(TestCase):
+    def setUp(self):
+        self.slave = JenkinsSlave.objects.create(name='test', url='test')
+        self.owner = User.objects.create(username='owner')
+
+        self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x',
+                                            url='x', owner=self.owner)
+
+    def test_booking_utilization(self):
+        utilization = self.res1.get_booking_utilization(1)
+        self.assertTrue(utilization['booked_seconds'] == 0)
+        self.assertTrue(utilization['available_seconds'] == timedelta(weeks=1).total_seconds())
+
+        start = timezone.now() + timedelta(days=1)
+        end = start + timedelta(days=1)
+        booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1,
+                               user=self.owner)
+
+        utilization = self.res1.get_booking_utilization(1)
+        booked_seconds = timedelta(days=1).total_seconds()
+        self.assertEqual(utilization['booked_seconds'], booked_seconds)
+
+        utilization = self.res1.get_booking_utilization(-1)
+        self.assertEqual(utilization['booked_seconds'], 0)
+
+        booking.delete()
+        start = timezone.now() - timedelta(days=1)
+        end = start + timedelta(days=2)
+        booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1,
+                               user=self.owner)
+        booked_seconds = self.res1.get_booking_utilization(1)['booked_seconds']
+        # use ceil because a fraction of the booked time has already passed now
+        booked_seconds = ceil(booked_seconds)
+        self.assertEqual(booked_seconds, timedelta(days=1).total_seconds())
+
+        booking.delete()
+        start = timezone.now() + timedelta(days=6)
+        end = start + timedelta(days=2)
+        booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1,
+                               user=self.owner)
+        booked_seconds = self.res1.get_booking_utilization(1)['booked_seconds']
+        booked_seconds = floor(booked_seconds)
+        self.assertEqual(booked_seconds, timedelta(days=1).total_seconds())
+
+
+
+
+
diff --git a/tools/pharos-dashboard/src/dashboard/tests/test_views.py b/tools/pharos-dashboard/src/dashboard/tests/test_views.py
new file mode 100644 (file)
index 0000000..f5e17c2
--- /dev/null
@@ -0,0 +1,75 @@
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from django.test import TestCase
+from django.urls import reverse
+
+from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
+
+
+class DashboardViewTestCase(TestCase):
+    def setUp(self):
+        self.slave_active = JenkinsSlave.objects.create(name='slave_active', url='x', active=True)
+        self.slave_inactive = JenkinsSlave.objects.create(name='slave_inactive', url='x',
+                                                          active=False)
+        self.res_active = Resource.objects.create(name='res_active', slave=self.slave_active,
+                                                  description='x', url='x')
+        self.res_inactive = Resource.objects.create(name='res_inactive', slave=self.slave_inactive,
+                                                    description='x', url='x')
+
+    def test_booking_utilization_json(self):
+        url = reverse('dashboard:booking_utilization', kwargs={'resource_id': 0, 'weeks': 0})
+        self.assertEqual(self.client.get(url).status_code, 404)
+
+        url = reverse('dashboard:booking_utilization', kwargs={'resource_id': self.res_active.id,
+                                                               'weeks': 0})
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, 'data')
+
+    def test_jenkins_utilization_json(self):
+        url = reverse('dashboard:jenkins_utilization', kwargs={'resource_id': 0, 'weeks': 0})
+        self.assertEqual(self.client.get(url).status_code, 404)
+
+        url = reverse('dashboard:jenkins_utilization', kwargs={'resource_id': self.res_active.id,
+                                                               'weeks': 0})
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, 'data')
+
+    def test_jenkins_slaves_view(self):
+        url = reverse('dashboard:jenkins_slaves')
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200)
+        self.assertIn(self.slave_active, response.context['slaves'])
+        self.assertNotIn(self.slave_inactive, response.context['slaves'])
+
+    def test_ci_pods_view(self):
+        url = reverse('dashboard:ci_pods')
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(len(response.context['ci_pods']), 0)
+
+        self.slave_active.ci_slave = True
+        self.slave_inactive.ci_slave = True
+        self.slave_active.save()
+        self.slave_inactive.save()
+
+        response = self.client.get(url)
+        self.assertIn(self.res_active, response.context['ci_pods'])
+        self.assertNotIn(self.res_inactive, response.context['ci_pods'])
+
+    def test_dev_pods_view(self):
+        url = reverse('dashboard:dev_pods')
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(len(response.context['dev_pods']), 0)
+
index f04f5ca..609e5d6 100644 (file)
@@ -24,6 +24,7 @@ Including another URLconf
     2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
 """
 from django.conf.urls import url
+
 from dashboard.views import *
 
 urlpatterns = [
index 022a4af..1848844 100644 (file)
@@ -25,7 +25,7 @@ class JenkinsSlavesView(TemplateView):
     template_name = "dashboard/jenkins_slaves.html"
 
     def get_context_data(self, **kwargs):
-        slaves = JenkinsSlave.objects.all()
+        slaves = JenkinsSlave.objects.filter(active=True)
         context = super(JenkinsSlavesView, self).get_context_data(**kwargs)
         context.update({'title': "Jenkins Slaves", 'slaves': slaves})
         return context
@@ -35,7 +35,7 @@ class CIPodsView(TemplateView):
     template_name = "dashboard/ci_pods.html"
 
     def get_context_data(self, **kwargs):
-        ci_pods = Resource.objects.filter(slave__ci_slave=True)
+        ci_pods = Resource.objects.filter(slave__ci_slave=True, slave__active=True)
         context = super(CIPodsView, self).get_context_data(**kwargs)
         context.update({'title': "CI Pods", 'ci_pods': ci_pods})
         return context
@@ -45,7 +45,7 @@ class DevelopmentPodsView(TemplateView):
     template_name = "dashboard/dev_pods.html"
 
     def get_context_data(self, **kwargs):
-        resources = Resource.objects.filter(slave__dev_pod=True)
+        resources = Resource.objects.filter(slave__dev_pod=True, slave__active=True)
 
         bookings = Booking.objects.filter(start__lte=timezone.now())
         bookings = bookings.filter(end__gt=timezone.now())
@@ -55,7 +55,7 @@ class DevelopmentPodsView(TemplateView):
             booking_utilization = resource.get_booking_utilization(weeks=4)
             total = booking_utilization['booked_seconds'] + booking_utilization['available_seconds']
             try:
-               utilization_percentage =  "%d%%" % (float(booking_utilization['booked_seconds']) /
+                utilization_percentage = "%d%%" % (float(booking_utilization['booked_seconds']) /
                                                    total * 100)
             except (ValueError, ZeroDivisionError):
                 return ""
@@ -88,7 +88,7 @@ class LabOwnerView(TemplateView):
     template_name = "dashboard/resource_all.html"
 
     def get_context_data(self, **kwargs):
-        resources = Resource.objects.filter(slave__dev_pod=True)
+        resources = Resource.objects.filter(slave__dev_pod=True, slave__active=True)
         pods = []
         for resource in resources:
             utilization = resource.slave.get_utilization(timedelta(days=7))
index ff0508d..edf502f 100644 (file)
@@ -9,8 +9,8 @@
 
 
 import logging
-
 import re
+
 import requests
 from django.core.cache import cache
 
index 0875bba..8254ff3 100644 (file)
@@ -29,6 +29,8 @@ class JenkinsSlave(models.Model):
     last_job_installer = models.CharField(max_length=50, default='')
     last_job_result = models.CharField(max_length=30, default='')
 
+    active = models.BooleanField(default=False)
+
     def get_utilization(self, timedelta):
         """
         Return a dictionary containing the count of idle, online and offline measurements in the time from
index 7c03782..ea986c1 100644 (file)
@@ -10,6 +10,7 @@
 
 from celery import shared_task
 
+from dashboard.models import Resource
 from jenkins.models import JenkinsSlave, JenkinsStatistic
 from .adapter import *
 
@@ -20,14 +21,28 @@ def sync_jenkins():
 
 
 def update_jenkins_slaves():
+    JenkinsSlave.objects.all().update(active=False)
+
     jenkins_slaves = get_all_slaves()
     for slave in jenkins_slaves:
         jenkins_slave, created = JenkinsSlave.objects.get_or_create(name=slave['displayName'],
                                                                     url=get_slave_url(slave))
+        jenkins_slave.active = True
         jenkins_slave.ci_slave = is_ci_slave(slave['displayName'])
         jenkins_slave.dev_pod = is_dev_pod(slave['displayName'])
         jenkins_slave.status = get_slave_status(slave)
 
+        # if this is a new slave and a pod, check if there is a resource for it, create one if not
+        if  created and 'pod' in slave['displayName']:
+            # parse resource name from slave name
+            # naming example: orange-pod1, resource name: Orange POD 1
+            tokens = slave['displayName'].split('-')
+            name = tokens[0].capitalize() + ' POD '# company name
+            name += tokens[1][3:] # remove 'pod'
+            resource, created = Resource.objects.get_or_create(name=name)
+            resource.slave = jenkins_slave
+            resource.save()
+
         last_job = get_jenkins_job(jenkins_slave.name)
         if last_job is not None:
             last_job = parse_job(last_job)
index 4f350d2..3723cd3 100644 (file)
@@ -8,9 +8,11 @@
 ##############################################################################
 
 
+from datetime import timedelta
 from unittest import TestCase
 
 import jenkins.adapter as jenkins
+from jenkins.models import *
 
 
 # Tests that the data we get with the jenkinsadapter contains all the
@@ -28,12 +30,27 @@ class JenkinsAdapterTestCase(TestCase):
             self.assertTrue('idle' in slave)
             self.assertTrue('offline' in slave)
 
+    def test_get_slave(self):
+        slaves = jenkins.get_all_slaves()
+        self.assertEqual(slaves[0], jenkins.get_slave(slaves[0]['displayName']))
+        self.assertEqual({}, jenkins.get_slave('098f6bcd4621d373cade4e832627b4f6'))
+
     def test_get_ci_slaves(self):
         slaves = jenkins.get_ci_slaves()
         self.assertTrue(len(slaves) > 0)
         for slave in slaves:
             self.assertTrue('nodeName' in slave)
 
+    def test_get_jenkins_job(self):
+        slaves = jenkins.get_ci_slaves()
+        job = None
+        for slave in slaves:
+            job = jenkins.get_jenkins_job(slave['nodeName'])
+            if job is not None:
+                break
+        # We need to test at least one job
+        self.assertNotEqual(job, None)
+
     def test_get_all_jobs(self):
         jobs = jenkins.get_all_jobs()
         lastBuild = False
@@ -50,3 +67,63 @@ class JenkinsAdapterTestCase(TestCase):
                 self.assertTrue('timestamp' in job['lastBuild'])
                 self.assertTrue('builtOn' in job['lastBuild'])
         self.assertTrue(lastBuild)
+
+    def test_parse_job(self):
+        job = {
+            "displayName": "apex-deploy-baremetal-os-nosdn-fdio-noha-colorado",
+            "url": "https://build.opnfv.org/ci/job/apex-deploy-baremetal-os-nosdn-fdio-noha-colorado/",
+            "lastBuild": {
+                "building": False,
+                "fullDisplayName": "apex-deploy-baremetal-os-nosdn-fdio-noha-colorado #37",
+                "result": "SUCCESS",
+                "timestamp": 1476283629917,
+                "builtOn": "lf-pod1"
+            }
+        }
+
+        job = jenkins.parse_job(job)
+        self.assertEqual(job['scenario'], 'os-nosdn-fdio-noha')
+        self.assertEqual(job['installer'], 'apex')
+        self.assertEqual(job['branch'], 'colorado')
+        self.assertEqual(job['result'], 'SUCCESS')
+        self.assertEqual(job['building'], False)
+        self.assertEqual(job['url'],
+                         "https://build.opnfv.org/ci/job/apex-deploy-baremetal-os-nosdn-fdio-noha-colorado/")
+        self.assertEqual(job['name'],
+                         'apex-deploy-baremetal-os-nosdn-fdio-noha-colorado')
+
+    def test_get_slave_status(self):
+        slave = {
+            'offline': True,
+            'idle': False
+        }
+        self.assertEqual(jenkins.get_slave_status(slave), 'offline')
+        slave = {
+            'offline': False,
+            'idle': False
+        }
+        self.assertEqual(jenkins.get_slave_status(slave), 'online')
+        slave = {
+            'offline': False,
+            'idle': True
+        }
+        self.assertEqual(jenkins.get_slave_status(slave), 'online / idle')
+
+
+class JenkinsModelTestCase(TestCase):
+    def test_get_utilization(self):
+        jenkins_slave = JenkinsSlave.objects.create(name='test', status='offline', url='')
+        utilization = jenkins_slave.get_utilization(timedelta(weeks=1))
+        self.assertEqual(utilization['idle'], 0)
+        self.assertEqual(utilization['offline'], 0)
+        self.assertEqual(utilization['online'], 0)
+
+        for i in range(10):
+            JenkinsStatistic.objects.create(slave=jenkins_slave,
+                                            offline=True, idle=True,
+                                            online=True)
+
+        utilization = jenkins_slave.get_utilization(timedelta(weeks=1))
+        self.assertEqual(utilization['idle'], 10)
+        self.assertEqual(utilization['offline'], 10)
+        self.assertEqual(utilization['online'], 10)
index 2d19918..0ee275d 100644 (file)
@@ -26,7 +26,8 @@ class BookingNotification(models.Model):
         }
 
     def save(self, *args, **kwargs):
-        notifications = self.booking.bookingnotification_set.filter(type=self.type)
-        if notifications.count() > 1:
+        notifications = self.booking.bookingnotification_set.filter(type=self.type).exclude(
+            id=self.id)
+        if notifications.count() > 0:
             raise ValueError('Doubled Notification')
         return super(BookingNotification, self).save(*args, **kwargs)
\ No newline at end of file
index 61ab14a..4173433 100644 (file)
@@ -8,9 +8,9 @@
 ##############################################################################
 
 
-from celery import shared_task
 from datetime import timedelta
 
+from celery import shared_task
 from django.conf import settings
 from django.utils import timezone
 
diff --git a/tools/pharos-dashboard/src/notification/tests.py b/tools/pharos-dashboard/src/notification/tests.py
new file mode 100644 (file)
index 0000000..9df9aa6
--- /dev/null
@@ -0,0 +1,41 @@
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from datetime import timedelta
+from unittest import TestCase
+
+from django.contrib.auth.models import User
+from django.utils import timezone
+
+from booking.models import Booking
+from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
+from notification.models import *
+
+
+class JenkinsModelTestCase(TestCase):
+    def setUp(self):
+        self.slave = JenkinsSlave.objects.create(name='test1', url='test')
+        self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x',
+                                            url='x')
+        self.user1 = User.objects.create(username='user1')
+
+        start = timezone.now()
+        end = start + timedelta(days=1)
+        self.booking = Booking.objects.create(start=start, end=end, purpose='test',
+                                              resource=self.res1, user=self.user1)
+
+    def test_booking_notification(self):
+        BookingNotification.objects.create(type='test', booking=self.booking,
+                                           submit_time=timezone.now())
+
+        self.assertRaises(ValueError, BookingNotification.objects.create, type='test',
+                          booking=self.booking,
+                          submit_time=timezone.now())
index 34f425c..4644e85 100644 (file)
                 <i class="fa fa-edit fa-fw"></i>Booking
             </div>
             <div class="panel-body">
-                <div id="booking_form_div">
-                    {% bootstrap_form_errors form type='non_fields' %}
-                    <form method="post" action="" class="form" id="bookingform">
-                        {% csrf_token %}
+                {% if user.is_authenticated %}
+                    <div id="booking_form_div">
+                        {% bootstrap_form_errors form type='non_fields' %}
+                        <form method="post" action="" class="form" id="bookingform">
+                            {% csrf_token %}
 
-                        <div class='input-group' id='starttimepicker'>
-                            {% bootstrap_field form.start addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
-                        </div>
-                        <div class='input-group' id='endtimepicker'>
-                            {% bootstrap_field form.end addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
-                        </div>
-                        {% bootstrap_field form.purpose %}
-                        {% bootstrap_field form.installer %}
-                        {% bootstrap_field form.scenario %}
-                        {% buttons %}
-                            <button type="submit" class="btn btn btn-success">
-                                Book
-                            </button>
-                        {% endbuttons %}
-                    </form>
-                </div>
+                            <div class='input-group' id='starttimepicker'>
+                                {% bootstrap_field form.start addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
+                            </div>
+                            <div class='input-group' id='endtimepicker'>
+                                {% bootstrap_field form.end addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
+                            </div>
+                            {% bootstrap_field form.purpose %}
+                            {% bootstrap_field form.installer %}
+                            {% bootstrap_field form.scenario %}
+                            {% buttons %}
+                                <button type="submit" class="btn btn btn-success">
+                                    Book
+                                </button>
+                            {% endbuttons %}
+                        </form>
+                    </div>
+                {% else %}
+                    <p>Please
+                        <a href="{% url 'account:login' %}">
+                            login with Jira</a>
+                        to book this Pod</p>
+                {% endif %}
             </div>
         </div>
     </div>